diff --git a/apps/builder/src/components/inputs/AutocompleteInput.tsx b/apps/builder/src/components/inputs/AutocompleteInput.tsx index 6c3093c09..ec0f96367 100644 --- a/apps/builder/src/components/inputs/AutocompleteInput.tsx +++ b/apps/builder/src/components/inputs/AutocompleteInput.tsx @@ -87,6 +87,7 @@ export const AutocompleteInput = ({ useOutsideClick({ ref: dropdownRef, handler: onClose, + isEnabled: isOpen, }) useEffect( @@ -110,7 +111,9 @@ export const AutocompleteInput = ({ inputRef.current?.focus() } - const handleKeyUp = (e: React.KeyboardEvent) => { + const updateFocusedDropdownItem = ( + e: React.KeyboardEvent + ) => { if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) { handleItemClick(filteredItems[keyboardFocusIndex])() return setKeyboardFocusIndex(undefined) @@ -179,7 +182,7 @@ export const AutocompleteInput = ({ onChange={(e) => changeValue(e.target.value)} onFocus={onOpen} onBlur={updateCarretPosition} - onKeyDown={handleKeyUp} + onKeyDown={updateFocusedDropdownItem} placeholder={placeholder} /> diff --git a/apps/builder/src/components/inputs/Select.tsx b/apps/builder/src/components/inputs/Select.tsx index 3e54009f9..195846234 100644 --- a/apps/builder/src/components/inputs/Select.tsx +++ b/apps/builder/src/components/inputs/Select.tsx @@ -61,10 +61,6 @@ export const Select = ({ ) ) - const closeDropwdown = () => { - onClose() - } - const [keyboardFocusIndex, setKeyboardFocusIndex] = useState< number | undefined >() @@ -85,12 +81,20 @@ export const Select = ({ : items ).slice(0, 50) + const closeDropdown = () => { + onClose() + setTimeout(() => { + setIsTouched(false) + }, dropdownCloseAnimationDuration) + } + useOutsideClick({ ref: dropdownRef, - handler: closeDropwdown, + handler: closeDropdown, + isEnabled: isOpen, }) - const handleInputChange = (e: ChangeEvent) => { + const updateInputValue = (e: ChangeEvent) => { if (!isOpen) onOpen() if (!isTouched) setIsTouched(true) setInputValue(e.target.value) @@ -101,10 +105,12 @@ export const Select = ({ setInputValue(getItemLabel(item)) onSelect?.(getItemValue(item), item) setKeyboardFocusIndex(undefined) - closeDropwdown() + closeDropdown() } - const handleKeyDown = (e: React.KeyboardEvent) => { + const updateFocusedDropdownItem = ( + e: React.KeyboardEvent + ) => { if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) { e.preventDefault() handleItemClick(filteredItems[keyboardFocusIndex])() @@ -138,13 +144,7 @@ export const Select = ({ setInputValue('') onSelect?.(undefined) setKeyboardFocusIndex(undefined) - closeDropwdown() - } - - const resetIsTouched = () => { - setTimeout(() => { - setIsTouched(false) - }, dropdownCloseAnimationDuration) + closeDropdown() } return ( @@ -183,10 +183,9 @@ export const Select = ({ placeholder={ !isTouched && inputValue !== '' ? undefined : placeholder } - onBlur={resetIsTouched} - onChange={handleInputChange} + onChange={updateInputValue} onFocus={onOpen} - onKeyDown={handleKeyDown} + onKeyDown={updateFocusedDropdownItem} pr={selectedItem ? 16 : undefined} /> diff --git a/apps/builder/src/components/inputs/VariableSearchInput.tsx b/apps/builder/src/components/inputs/VariableSearchInput.tsx index 14cd5fe72..b03fe736f 100644 --- a/apps/builder/src/components/inputs/VariableSearchInput.tsx +++ b/apps/builder/src/components/inputs/VariableSearchInput.tsx @@ -60,6 +60,7 @@ export const VariableSearchInput = ({ useOutsideClick({ ref: dropdownRef, handler: onClose, + isEnabled: isOpen, }) useEffect(() => { diff --git a/apps/builder/src/features/blocks/bubbles/image/components/ImageBubbleContent.tsx b/apps/builder/src/features/blocks/bubbles/image/components/ImageBubbleContent.tsx index fc51f90fe..74d292433 100644 --- a/apps/builder/src/features/blocks/bubbles/image/components/ImageBubbleContent.tsx +++ b/apps/builder/src/features/blocks/bubbles/image/components/ImageBubbleContent.tsx @@ -9,6 +9,7 @@ export const ImageBubbleContent = ({ block }: { block: ImageBubbleBlock }) => { ) : ( { - e.stopPropagation() - }, - textEditorRef.current - ) - const handleVariableSelected = (variable?: Variable) => { setIsVariableDropdownOpen(false) if (!rememberedSelection.current || !variable) return @@ -115,6 +102,7 @@ const TextBubbleEditorContent = ({ pos="relative" spacing={0} cursor="text" + className="prevent-group-drag" sx={{ '.slate-ToolbarButton-active': { color: useColorModeValue('blue.500', 'blue.300') + ' !important', diff --git a/apps/builder/src/features/graph/components/nodes/block/BlockNode.tsx b/apps/builder/src/features/graph/components/nodes/block/BlockNode.tsx index 0a8d45a81..38d71d6bc 100644 --- a/apps/builder/src/features/graph/components/nodes/block/BlockNode.tsx +++ b/apps/builder/src/features/graph/components/nodes/block/BlockNode.tsx @@ -204,6 +204,7 @@ export const BlockNode = ({ onClick={handleClick} data-testid={`block`} w="full" + className="prevent-group-drag" > setIsFocused(false), ref: groupRef, capture: true, + isEnabled: isFocused, }) // When the group is moved from external action (e.g. undo/redo), update the current coordinates @@ -154,8 +155,13 @@ const NonMemoizedDraggableGroupNode = ({ useDrag( ({ first, last, offset: [offsetX, offsetY], event, target }) => { event.stopPropagation() - if ((target as HTMLElement).classList.contains('prevent-group-drag')) + if ( + (target as HTMLElement) + .closest('.prevent-group-drag') + ?.classList.contains('prevent-group-drag') + ) return + if (first) { setIsFocused(true) setIsMouseDown(true) diff --git a/apps/builder/src/features/theme/components/chat/AvatarForm.tsx b/apps/builder/src/features/theme/components/chat/AvatarForm.tsx index 0f29507e7..0c4cd3b97 100644 --- a/apps/builder/src/features/theme/components/chat/AvatarForm.tsx +++ b/apps/builder/src/features/theme/components/chat/AvatarForm.tsx @@ -44,6 +44,7 @@ export const AvatarForm = ({ useOutsideClick({ ref: popoverContainerRef, handler: onClose, + isEnabled: isOpen, }) const isDefaultAvatar = !avatarProps?.url || avatarProps.url.includes('{{') diff --git a/apps/builder/src/features/variables/components/VariablesButton.tsx b/apps/builder/src/features/variables/components/VariablesButton.tsx index 0d4f49873..f1ac0041b 100644 --- a/apps/builder/src/features/variables/components/VariablesButton.tsx +++ b/apps/builder/src/features/variables/components/VariablesButton.tsx @@ -28,6 +28,7 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => { useOutsideClick({ ref: popoverRef, handler: onClose, + isEnabled: isOpen, }) return ( diff --git a/apps/builder/src/hooks/useOutsideClick.ts b/apps/builder/src/hooks/useOutsideClick.ts index c1a66042f..4631f9364 100644 --- a/apps/builder/src/hooks/useOutsideClick.ts +++ b/apps/builder/src/hooks/useOutsideClick.ts @@ -1,5 +1,4 @@ -import { useEventListener } from '@chakra-ui/react' -import { RefObject } from 'react' +import { RefObject, useEffect } from 'react' type Handler = (event: MouseEvent) => void @@ -7,22 +6,32 @@ type Props = { ref: RefObject handler: Handler capture?: boolean + isEnabled?: boolean } export const useOutsideClick = ({ ref, handler, capture, + isEnabled, }: Props): void => { - const triggerHandlerIfOutside = (event: MouseEvent) => { - const el = ref?.current - if (!el || el.contains(event.target as Node)) { - return + useEffect(() => { + if (isEnabled === false) return + const triggerHandlerIfOutside = (event: MouseEvent) => { + const el = ref?.current + if (!el || el.contains(event.target as Node)) { + return + } + handler(event) } - handler(event) - } - useEventListener('pointerdown', triggerHandlerIfOutside, null, { - capture, - }) + document.addEventListener('pointerdown', triggerHandlerIfOutside, { + capture, + }) + return () => { + document.removeEventListener('pointerdown', triggerHandlerIfOutside, { + capture, + }) + } + }, [capture, handler, isEnabled, ref]) }