diff --git a/apps/web/src/lib/components/chat/ChatWidget.svelte b/apps/web/src/lib/components/chat/ChatWidget.svelte index 01854d9..02ead38 100644 --- a/apps/web/src/lib/components/chat/ChatWidget.svelte +++ b/apps/web/src/lib/components/chat/ChatWidget.svelte @@ -44,6 +44,8 @@ let isDragging = $state(false); let dragStart = $state({ x: 0, y: 0 }); let isAnimating = $state(false); + let dragThreshold = $state(5); // Distância mínima em pixels para considerar arrastar + let hasMoved = $state(false); // Flag para verificar se houve movimento durante o arrastar // Tamanho da janela (redimensionável) const MIN_WIDTH = 300; @@ -76,6 +78,38 @@ let previousSize = $state({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }); let previousPosition = $state({ x: 0, y: 0 }); + // Dimensões da janela (reativo) + let windowDimensions = $state({ width: 0, height: 0 }); + + // Atualizar dimensões da janela + function updateWindowDimensions() { + if (typeof window !== 'undefined') { + windowDimensions = { + width: window.innerWidth, + height: window.innerHeight + }; + } + } + + // Inicializar e atualizar dimensões da janela + $effect(() => { + if (typeof window === 'undefined') return; + + updateWindowDimensions(); + + const handleResize = () => { + updateWindowDimensions(); + // Ajustar posição quando a janela redimensionar + ajustarPosicao(); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }); + // Salvar tamanho no localStorage function saveSize() { if (typeof window !== 'undefined') { @@ -119,15 +153,19 @@ newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resizeStart.width + deltaX)); } if (resizeDirection.includes('w')) { - newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resizeStart.width - deltaX)); - newX = position.x + (resizeStart.width - newWidth); + const calculatedWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resizeStart.width - deltaX)); + const widthDelta = resizeStart.width - calculatedWidth; + newWidth = calculatedWidth; + newX = position.x + widthDelta; } if (resizeDirection.includes('s')) { newHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, resizeStart.height + deltaY)); } if (resizeDirection.includes('n')) { - newHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, resizeStart.height - deltaY)); - newY = position.y + (resizeStart.height - newHeight); + const calculatedHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, resizeStart.height - deltaY)); + const heightDelta = resizeStart.height - calculatedHeight; + newHeight = calculatedHeight; + newY = position.y + heightDelta; } windowSize = { width: newWidth, height: newHeight }; @@ -337,9 +375,12 @@ previousPosition = { ...position }; // Maximizar completamente: usar toda a largura e altura da tela + const winWidth = windowDimensions.width || (typeof window !== 'undefined' ? window.innerWidth : DEFAULT_WIDTH); + const winHeight = windowDimensions.height || (typeof window !== 'undefined' ? window.innerHeight : DEFAULT_HEIGHT); + windowSize = { - width: window.innerWidth, - height: window.innerHeight + width: winWidth, + height: winHeight }; position = { x: 0, @@ -355,6 +396,7 @@ // Funcionalidade de arrastar function handleMouseDown(e: MouseEvent) { if (e.button !== 0) return; // Apenas botão esquerdo + hasMoved = false; isDragging = true; dragStart = { x: e.clientX - position.x, @@ -364,6 +406,20 @@ e.preventDefault(); } + // Handler específico para o botão flutuante (evita conflito com clique) + function handleButtonMouseDown(e: MouseEvent) { + if (e.button !== 0) return; + // Resetar flag de movimento + hasMoved = false; + isDragging = true; + dragStart = { + x: e.clientX - position.x, + y: e.clientY - position.y, + }; + document.body.classList.add('dragging'); + // Não prevenir default para permitir clique funcionar se não houver movimento + } + function handleMouseMove(e: MouseEvent) { if (isResizing) { handleResizeMove(e); @@ -375,15 +431,26 @@ const newX = e.clientX - dragStart.x; const newY = e.clientY - dragStart.y; + // Verificar se houve movimento significativo + const deltaX = Math.abs(newX - position.x); + const deltaY = Math.abs(newY - position.y); + if (deltaX > dragThreshold || deltaY > dragThreshold) { + hasMoved = true; + } + // Dimensões do widget const widgetWidth = isOpen && !isMinimized ? windowSize.width : 72; const widgetHeight = isOpen && !isMinimized ? windowSize.height : 72; + // Usar dimensões reativas da janela + const winWidth = windowDimensions.width || (typeof window !== 'undefined' ? window.innerWidth : 0); + const winHeight = windowDimensions.height || (typeof window !== 'undefined' ? window.innerHeight : 0); + // Limites da tela com margem de segurança const minX = -(widgetWidth - 100); // Permitir até 100px visíveis - const maxX = window.innerWidth - 100; // Manter 100px dentro da tela + const maxX = Math.max(0, winWidth - 100); // Manter 100px dentro da tela const minY = -(widgetHeight - 100); - const maxY = window.innerHeight - 100; + const maxY = Math.max(0, winHeight - 100); position = { x: Math.max(minX, Math.min(newX, maxX)), @@ -391,14 +458,26 @@ }; } - function handleMouseUp() { + function handleMouseUp(e?: MouseEvent) { + const hadMoved = hasMoved; + if (isDragging) { isDragging = false; + hasMoved = false; document.body.classList.remove('dragging'); + + // Se estava arrastando e houve movimento, prevenir clique + if (hadMoved && e) { + e.preventDefault(); + e.stopPropagation(); + } + // Garantir que está dentro dos limites ao soltar ajustarPosicao(); } handleResizeEnd(); + + return !hadMoved; // Retorna true se não houve movimento (permite clique) } function ajustarPosicao() { @@ -408,22 +487,32 @@ const widgetWidth = isOpen && !isMinimized ? windowSize.width : 72; const widgetHeight = isOpen && !isMinimized ? windowSize.height : 72; + // Usar dimensões reativas da janela + const winWidth = windowDimensions.width || (typeof window !== 'undefined' ? window.innerWidth : 0); + const winHeight = windowDimensions.height || (typeof window !== 'undefined' ? window.innerHeight : 0); + // Verificar se está fora dos limites let newX = position.x; let newY = position.y; - // Ajustar X - if (newX < -(widgetWidth - 100)) { - newX = -(widgetWidth - 100); - } else if (newX > window.innerWidth - 100) { - newX = window.innerWidth - 100; + // Ajustar X - garantir que pelo menos 100px fiquem visíveis + const minX = -(widgetWidth - 100); + const maxX = Math.max(0, winWidth - 100); + + if (newX < minX) { + newX = minX; + } else if (newX > maxX) { + newX = maxX; } - // Ajustar Y - if (newY < -(widgetHeight - 100)) { - newY = -(widgetHeight - 100); - } else if (newY > window.innerHeight - 100) { - newY = window.innerHeight - 100; + // Ajustar Y - garantir que pelo menos 100px fiquem visíveis + const minY = -(widgetHeight - 100); + const maxY = Math.max(0, winHeight - 100); + + if (newY < minY) { + newY = minY; + } else if (newY > maxY) { + newY = maxY; } position = { x: newX, y: newY }; @@ -433,15 +522,26 @@ }, 300); } - // Event listeners globais - if (typeof window !== 'undefined') { + // Event listeners globais com cleanup adequado + $effect(() => { + if (typeof window === 'undefined') return; + window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); - } + + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }); {#if !isOpen || isMinimized} + {@const winWidth = windowDimensions.width || (typeof window !== 'undefined' ? window.innerWidth : 0)} + {@const winHeight = windowDimensions.height || (typeof window !== 'undefined' ? window.innerHeight : 0)} + {@const bottomPos = position.y === 0 ? '1.5rem' : `${Math.max(0, winHeight - position.y - 72)}px`} + {@const rightPos = position.x === 0 ? '1.5rem' : `${Math.max(0, winWidth - position.x - 72)}px`}