@artsy/palette 46.4.0 → 46.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cjs/elements/AutocompleteInput/AutocompleteInput.js +3 -1
  2. package/dist/cjs/elements/AutocompleteInput/AutocompleteInput.js.map +1 -1
  3. package/dist/cjs/elements/BaseTabs/BaseTab.d.ts +2 -2
  4. package/dist/cjs/elements/Dropdown/Dropdown.d.ts +17 -3
  5. package/dist/cjs/elements/Dropdown/Dropdown.js +124 -174
  6. package/dist/cjs/elements/Dropdown/Dropdown.js.map +1 -1
  7. package/dist/cjs/elements/MultiSelect/MultiSelect.js +4 -2
  8. package/dist/cjs/elements/MultiSelect/MultiSelect.js.map +1 -1
  9. package/dist/cjs/elements/Pointer/Pointer.d.ts +21 -14
  10. package/dist/cjs/elements/Pointer/Pointer.js +65 -135
  11. package/dist/cjs/elements/Pointer/Pointer.js.map +1 -1
  12. package/dist/cjs/elements/Popover/Popover.js +12 -11
  13. package/dist/cjs/elements/Popover/Popover.js.map +1 -1
  14. package/dist/cjs/elements/SelectInput/SelectInput.js +3 -1
  15. package/dist/cjs/elements/SelectInput/SelectInput.js.map +1 -1
  16. package/dist/cjs/elements/Sup/Sup.d.ts +1 -1
  17. package/dist/cjs/elements/Tooltip/Tooltip.js +16 -17
  18. package/dist/cjs/elements/Tooltip/Tooltip.js.map +1 -1
  19. package/dist/cjs/utils/usePosition.d.ts +70 -37
  20. package/dist/cjs/utils/usePosition.js +110 -349
  21. package/dist/cjs/utils/usePosition.js.map +1 -1
  22. package/dist/esm/elements/AutocompleteInput/AutocompleteInput.js +3 -1
  23. package/dist/esm/elements/AutocompleteInput/AutocompleteInput.js.map +1 -1
  24. package/dist/esm/elements/Dropdown/Dropdown.js +124 -173
  25. package/dist/esm/elements/Dropdown/Dropdown.js.map +1 -1
  26. package/dist/esm/elements/MultiSelect/MultiSelect.js +4 -2
  27. package/dist/esm/elements/MultiSelect/MultiSelect.js.map +1 -1
  28. package/dist/esm/elements/Pointer/Pointer.js +66 -134
  29. package/dist/esm/elements/Pointer/Pointer.js.map +1 -1
  30. package/dist/esm/elements/Popover/Popover.js +13 -12
  31. package/dist/esm/elements/Popover/Popover.js.map +1 -1
  32. package/dist/esm/elements/SelectInput/SelectInput.js +3 -1
  33. package/dist/esm/elements/SelectInput/SelectInput.js.map +1 -1
  34. package/dist/esm/elements/Tooltip/Tooltip.js +17 -18
  35. package/dist/esm/elements/Tooltip/Tooltip.js.map +1 -1
  36. package/dist/esm/utils/usePosition.js +109 -343
  37. package/dist/esm/utils/usePosition.js.map +1 -1
  38. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AutocompleteInput.js","names":["CloseIcon","SearchIcon","composeRefs","themeGet","React","createRef","useCallback","useEffect","useMemo","useReducer","useRef","styled","useKeyboardListNavigation","useClickOutside","useContainsFocus","usePosition","useMouseActivity","useWidthOf","Box","splitBoxProps","Clickable","LabeledInput","Spinner","VisuallyHidden","AutocompleteInputOption","AutocompleteInputOptionLabel","jsx","_jsx","jsxs","_jsxs","reducer","state","action","type","open","query","payload","AutocompleteInput","clamp","defaultValue","dropdownMaxHeight","dropdownMinWidth","flip","footer","forwardRef","forwardedRef","header","height","id","loading","onChange","onClear","onClose","onKeyDown","onSelect","onSubmit","options","renderOption","option","rest","inputRef","containerRef","headerRef","footerRef","boxProps","inputProps","dispatch","optionsWithRefs","map","ref","resetUI","setTimeout","current","focus","reset","handleSelect","index","text","set","list","waitForInteractive","onEnter","element","i","event","preventDefault","stopPropagation","isDropdownVisible","length","anchorRef","tooltipRef","active","key","offset","position","width","handleFocus","handleMouseDown","handleClick","lastMouseMoveTimestamp","handleMouseEnter","now","performance","cursor","interactive","handleChange","currentTarget","value","handleClearOrSubmit","handleClose","ignoreFocusChangeRef","ignoreFocusChange","onMouseDown","onMouseUp","el","isPointerInteraction","preventScroll","scrollIntoView","block","handleFocusChange","focused","containsFocusRef","onClickOutside","when","handleInputKeydown","blur","handleContainerKeydown","staged","children","role","label","size","onClick","display","alignItems","fill","onFocus","autoComplete","AutocompleteInputDropdownContainer","minWidth","pt","AutocompleteInputDropdown","AutocompleteInputOptions","maxHeight","onMouseEnter","tabIndex","displayName","withConfig","componentId"],"sources":["../../../../src/elements/AutocompleteInput/AutocompleteInput.tsx"],"sourcesContent":["import CloseIcon from \"@artsy/icons/CloseIcon\"\nimport SearchIcon from \"@artsy/icons/SearchIcon\"\nimport composeRefs from \"@seznam/compose-react-refs\"\nimport { themeGet } from \"@styled-system/theme-get\"\nimport React, {\n createRef,\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n} from \"react\"\nimport styled from \"styled-components\"\nimport { ResponsiveValue } from \"styled-system\"\nimport { useKeyboardListNavigation } from \"use-keyboard-list-navigation\"\nimport { useClickOutside, useContainsFocus, usePosition } from \"../../utils\"\nimport { useMouseActivity } from \"../../utils/useMouseActivity\"\nimport { useWidthOf } from \"../../utils/useWidthOf\"\nimport { Box, splitBoxProps } from \"../Box\"\nimport { Clickable } from \"../Clickable\"\nimport { InputProps } from \"../Input\"\nimport { LabeledInput } from \"../LabeledInput\"\nimport { Spinner } from \"../Spinner\"\nimport { VisuallyHidden } from \"../VisuallyHidden\"\nimport { AutocompleteInputOption } from \"./AutocompleteInputOption\"\nimport { AutocompleteInputOptionLabel } from \"./AutocompleteInputOptionLabel\"\n\nexport interface AutocompleteFooterActions {\n /** Call to close dropdown */\n onClose(): void\n}\n\n/** Base option type — can be expanded */\nexport interface AutocompleteInputOptionType {\n text: string\n value: string\n}\n\ninterface State {\n open: boolean\n query: string\n}\n\ntype Action =\n | { type: \"OPEN\" }\n | { type: \"CLOSE\" }\n | { type: \"CLEAR\" }\n | { type: \"CHANGE\"; payload: { query: string } }\n | { type: \"SELECT\"; payload: { query: string } }\n\nconst reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"OPEN\":\n return { ...state, open: true }\n case \"CLOSE\":\n return { ...state, open: false }\n case \"CLEAR\":\n return { ...state, query: \"\" }\n case \"CHANGE\":\n return { ...state, query: action.payload.query, open: true }\n case \"SELECT\":\n return { ...state, query: action.payload.query, open: false }\n }\n}\n\nexport interface AutocompleteInputProps<T extends AutocompleteInputOptionType>\n extends Omit<InputProps, \"onSelect\" | \"onSubmit\"> {\n /** Optionally enable clamping (default: `false`) */\n clamp?: boolean\n defaultValue?: string\n dropdownMaxHeight?: ResponsiveValue<string | number>\n dropdownMinWidth?: ResponsiveValue<string | number>\n loading?: boolean\n header?: React.ReactNode\n /** Optionally disable flipping (default: `true`) */\n flip?: boolean\n footer?:\n | React.ReactNode\n | ((dropdownActions: AutocompleteFooterActions) => React.ReactNode)\n /** Ref to the input; workaround generics */\n forwardRef?: React.Ref<HTMLInputElement>\n /** on <enter> when no option is selected */\n onSubmit?(query: string): void\n /** on <click> or <enter> when an option is selected */\n onSelect?(option: T, index: number): void\n /** on <click> of the 'x' (clear) button */\n onClear?(): void\n /** Callback that runs when options are hidden */\n onClose?(): void\n options: T[]\n renderOption?(\n option: T,\n i: number\n ): React.ReactElement<any, string | React.JSXElementConstructor<any>>\n}\n\n/** AutocompleteInput */\nexport const AutocompleteInput = <T extends AutocompleteInputOptionType>({\n clamp = false,\n defaultValue = \"\",\n dropdownMaxHeight = 308, // 308 = roughly 5.5 options\n dropdownMinWidth,\n flip = true,\n footer,\n forwardRef: forwardedRef,\n header,\n height,\n id,\n loading,\n onChange,\n onClear,\n onClose,\n onKeyDown,\n onSelect,\n onSubmit,\n options,\n renderOption = (option) => <AutocompleteInputOptionLabel {...option} />,\n ...rest\n}: AutocompleteInputProps<T>) => {\n const inputRef = useRef<HTMLInputElement | null>(null)\n const containerRef = useRef<HTMLDivElement | null>(null)\n const headerRef = useRef<HTMLDivElement | null>(null)\n const footerRef = useRef<HTMLDivElement | null>(null)\n\n const [boxProps, inputProps] = splitBoxProps(rest)\n\n const [state, dispatch] = useReducer(reducer, {\n open: false,\n query: defaultValue,\n })\n\n const optionsWithRefs = useMemo(() => {\n return options.map((option) => ({\n option,\n ref: createRef<HTMLButtonElement>(),\n }))\n }, [options])\n\n const resetUI = () => {\n setTimeout(() => {\n inputRef.current?.focus()\n reset()\n dispatch({ type: \"CLOSE\" })\n }, 100)\n }\n\n const handleSelect = (option: T, index: number) => {\n dispatch({ type: \"SELECT\", payload: { query: option.text } })\n inputRef.current?.focus()\n onSelect?.(option, index)\n }\n\n const { index, reset, set } = useKeyboardListNavigation({\n ref: containerRef,\n list: options,\n waitForInteractive: true,\n onEnter: ({ element: option, index: i, event }) => {\n event.preventDefault()\n event.stopPropagation()\n handleSelect(option, i)\n resetUI()\n },\n })\n\n const isDropdownVisible = state.open && options.length > 0\n\n // Reset keyboard navigation when options change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(reset, [options])\n\n // Reset keyboard navigation when query is empty\n useEffect(() => {\n if (state.query === \"\") reset()\n }, [reset, state.query])\n\n const { anchorRef, tooltipRef } = usePosition({\n active: isDropdownVisible,\n clamp,\n flip,\n key: options.length,\n offset: 0,\n position: \"bottom\",\n })\n\n const { width } = useWidthOf({ ref: anchorRef })\n\n const handleFocus = () => {\n reset()\n dispatch({ type: \"OPEN\" })\n }\n\n const handleMouseDown = (option: T, i: number) => () => {\n handleSelect(option, i)\n resetUI()\n }\n\n const handleClick = () => {\n dispatch({ type: \"OPEN\" })\n }\n\n // Records the latest mouse movement and, inside the `handleMouseEnter` callback,\n // only treat the event as genuine if the mouse has moved very recently.\n // Otherwise, the event is presumed to have been triggered by scrolling and is ignored.\n const { lastMouseMoveTimestamp } = useMouseActivity()\n\n const handleMouseEnter = (i: number) => () => {\n const now = performance.now()\n\n // 50ms mouse move window\n if (now - lastMouseMoveTimestamp.current < 50) {\n set({ cursor: i, interactive: true })\n }\n }\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const {\n currentTarget: { value },\n } = event\n\n dispatch({ type: \"CHANGE\", payload: { query: value } })\n onChange?.(event)\n }\n\n const handleClearOrSubmit = () => {\n if (state.query === \"\") {\n onSubmit?.(state.query)\n return\n }\n\n dispatch({ type: \"CLEAR\" })\n inputRef.current?.focus()\n onClear?.()\n }\n\n const handleClose = useCallback(() => {\n dispatch({ type: \"CLOSE\" })\n reset()\n onClose?.()\n }, [onClose, reset])\n\n const ignoreFocusChangeRef = useRef<boolean>(false)\n const ignoreFocusChange = {\n onMouseDown: () => (ignoreFocusChangeRef.current = true),\n onMouseUp: () => (ignoreFocusChangeRef.current = false),\n }\n\n // Moves focus to different options when keyboard navigating using up/down\n useEffect(() => {\n const el = optionsWithRefs[index]?.ref?.current\n if (!el) return\n\n const isPointerInteraction =\n performance.now() - lastMouseMoveTimestamp.current < 50\n\n if (isPointerInteraction) {\n // Pointer interactions should not cause scroll\n el.focus({ preventScroll: true })\n return\n }\n\n // Keyboard navigation: focus and ensure visibility\n el.focus({ preventScroll: true })\n try {\n el.scrollIntoView({ block: \"nearest\" })\n } catch {}\n }, [index, optionsWithRefs])\n\n const handleFocusChange = useCallback(\n (focused: boolean) => {\n if (ignoreFocusChangeRef.current || focused || !isDropdownVisible) return\n\n handleClose()\n },\n\n [isDropdownVisible, handleClose]\n )\n\n // Handle closing the dropdown when clicking outside of the input\n // or when focus leaves the input completely\n const { ref: containsFocusRef } = useContainsFocus({\n onChange: handleFocusChange,\n })\n\n useClickOutside({\n ref: containsFocusRef,\n onClickOutside: handleClose,\n when: isDropdownVisible,\n type: \"click\",\n })\n\n const handleInputKeydown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n // Handle <Enter> when nothing is selected\n case \"Enter\":\n if (state.query !== \"\" && index === -1) {\n onSubmit?.(state.query)\n resetUI()\n }\n return\n\n // <Esc> to close dropdown\n case \"Escape\":\n event.preventDefault()\n event.stopPropagation()\n\n dispatch({ type: \"CLOSE\" })\n inputRef.current?.blur()\n\n return\n\n default:\n break\n }\n\n onKeyDown?.(event)\n }\n\n // Moves focus back to input when typing\n const handleContainerKeydown = (\n event: React.KeyboardEvent<HTMLDivElement>\n ) => {\n switch (event.key) {\n case \"Alt\":\n case \"ArrowDown\":\n case \"ArrowUp\":\n case \"Control\":\n case \"Enter\":\n case \"Meta\":\n case \"Shift\":\n case \"Tab\":\n // Ignore\n return\n\n case \"Escape\":\n event.preventDefault()\n event.stopPropagation()\n\n dispatch({ type: \"CLOSE\" })\n inputRef.current?.blur()\n reset()\n\n return\n\n default:\n inputRef.current?.focus()\n }\n }\n\n // Option that is being hovered or keyed into\n const staged = options[index]\n\n return (\n <Box\n ref={composeRefs(containerRef, containsFocusRef) as any}\n onKeyDown={handleContainerKeydown}\n {...boxProps}\n >\n <LabeledInput\n ref={composeRefs(inputRef, anchorRef, forwardedRef) as any}\n role=\"combobox\"\n aria-expanded={isDropdownVisible}\n aria-autocomplete=\"list\"\n {...(id ? { id, \"aria-describedby\": `${id}__assistiveHint` } : {})}\n label={\n loading ? (\n <Box width={18}>\n <Spinner size=\"small\" />\n </Box>\n ) : state.query ? (\n <Clickable\n onClick={handleClearOrSubmit}\n height=\"100%\"\n display=\"flex\"\n alignItems=\"center\"\n aria-label=\"Clear input\"\n >\n <CloseIcon fill=\"mono60\" aria-hidden />\n </Clickable>\n ) : (\n <SearchIcon fill=\"mono60\" aria-hidden />\n )\n }\n value={staged?.text ?? state.query}\n onChange={handleChange}\n onFocus={handleFocus}\n onKeyDown={handleInputKeydown}\n onClick={handleClick}\n autoComplete=\"off\"\n height={height}\n {...inputProps}\n />\n\n {isDropdownVisible && (\n <AutocompleteInputDropdownContainer\n ref={tooltipRef as any}\n role=\"listbox\"\n width={width}\n minWidth={200}\n pt={1} // Gap in place of `offset` for `usePosition`\n >\n <AutocompleteInputDropdown minWidth={dropdownMinWidth}>\n <div ref={headerRef} {...ignoreFocusChange}>\n {header}\n </div>\n\n <AutocompleteInputOptions maxHeight={dropdownMaxHeight}>\n {optionsWithRefs.map(({ option, ref }, i) => {\n return (\n <AutocompleteInputOption\n key={i}\n ref={ref}\n role=\"option\"\n aria-selected={i === index}\n aria-posinset={i + 1}\n aria-setsize={options.length}\n onMouseDown={handleMouseDown(option, i)}\n onMouseEnter={handleMouseEnter(i)}\n tabIndex={-1}\n >\n {renderOption(option, i)}\n </AutocompleteInputOption>\n )\n })}\n </AutocompleteInputOptions>\n\n <div ref={footerRef} {...ignoreFocusChange}>\n {typeof footer === \"function\"\n ? footer({ onClose: handleClose })\n : footer}\n </div>\n </AutocompleteInputDropdown>\n </AutocompleteInputDropdownContainer>\n )}\n\n <VisuallyHidden {...(id ? { id: `${id}__assistiveHint` } : {})}>\n When autocomplete results are available use up and down arrows to review\n and enter to select. Touch device users, explore by touch or with swipe\n gestures.\n </VisuallyHidden>\n\n {isDropdownVisible && (\n <VisuallyHidden role=\"status\" aria-atomic=\"true\" aria-live=\"polite\">\n {options.length === 1\n ? `1 result is available`\n : `${options.length} results are available`}\n </VisuallyHidden>\n )}\n </Box>\n )\n}\n\nconst AutocompleteInputDropdownContainer = styled(Box)`\n z-index: 1;\n`\n\nconst AutocompleteInputDropdown = styled(Box)`\n box-shadow: ${themeGet(\"effects.dropShadow\")};\n`\n\nconst AutocompleteInputOptions = styled(Box)`\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n`\n"],"mappings":"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"}
1
+ {"version":3,"file":"AutocompleteInput.js","names":["CloseIcon","SearchIcon","composeRefs","themeGet","React","createRef","useCallback","useEffect","useMemo","useReducer","useRef","styled","useKeyboardListNavigation","useClickOutside","useContainsFocus","usePosition","useMouseActivity","useWidthOf","Box","splitBoxProps","Clickable","LabeledInput","Spinner","VisuallyHidden","AutocompleteInputOption","AutocompleteInputOptionLabel","jsx","_jsx","jsxs","_jsxs","reducer","state","action","type","open","query","payload","AutocompleteInput","clamp","defaultValue","dropdownMaxHeight","dropdownMinWidth","flip","footer","forwardRef","forwardedRef","header","height","id","loading","onChange","onClear","onClose","onKeyDown","onSelect","onSubmit","options","renderOption","option","rest","inputRef","containerRef","headerRef","footerRef","boxProps","inputProps","dispatch","optionsWithRefs","map","ref","resetUI","setTimeout","current","focus","reset","handleSelect","index","text","set","list","waitForInteractive","onEnter","element","i","event","preventDefault","stopPropagation","isDropdownVisible","length","anchorRef","tooltipRef","floatingStyles","active","key","offset","position","width","handleFocus","handleMouseDown","handleClick","lastMouseMoveTimestamp","handleMouseEnter","now","performance","cursor","interactive","handleChange","currentTarget","value","handleClearOrSubmit","handleClose","ignoreFocusChangeRef","ignoreFocusChange","onMouseDown","onMouseUp","el","isPointerInteraction","preventScroll","scrollIntoView","block","handleFocusChange","focused","containsFocusRef","onClickOutside","when","handleInputKeydown","blur","handleContainerKeydown","staged","children","role","label","size","onClick","display","alignItems","fill","onFocus","autoComplete","AutocompleteInputDropdownContainer","minWidth","pt","style","AutocompleteInputDropdown","AutocompleteInputOptions","maxHeight","onMouseEnter","tabIndex","displayName","withConfig","componentId"],"sources":["../../../../src/elements/AutocompleteInput/AutocompleteInput.tsx"],"sourcesContent":["import CloseIcon from \"@artsy/icons/CloseIcon\"\nimport SearchIcon from \"@artsy/icons/SearchIcon\"\nimport composeRefs from \"@seznam/compose-react-refs\"\nimport { themeGet } from \"@styled-system/theme-get\"\nimport React, {\n createRef,\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n} from \"react\"\nimport styled from \"styled-components\"\nimport { ResponsiveValue } from \"styled-system\"\nimport { useKeyboardListNavigation } from \"use-keyboard-list-navigation\"\nimport { useClickOutside, useContainsFocus, usePosition } from \"../../utils\"\nimport { useMouseActivity } from \"../../utils/useMouseActivity\"\nimport { useWidthOf } from \"../../utils/useWidthOf\"\nimport { Box, splitBoxProps } from \"../Box\"\nimport { Clickable } from \"../Clickable\"\nimport { InputProps } from \"../Input\"\nimport { LabeledInput } from \"../LabeledInput\"\nimport { Spinner } from \"../Spinner\"\nimport { VisuallyHidden } from \"../VisuallyHidden\"\nimport { AutocompleteInputOption } from \"./AutocompleteInputOption\"\nimport { AutocompleteInputOptionLabel } from \"./AutocompleteInputOptionLabel\"\n\nexport interface AutocompleteFooterActions {\n /** Call to close dropdown */\n onClose(): void\n}\n\n/** Base option type — can be expanded */\nexport interface AutocompleteInputOptionType {\n text: string\n value: string\n}\n\ninterface State {\n open: boolean\n query: string\n}\n\ntype Action =\n | { type: \"OPEN\" }\n | { type: \"CLOSE\" }\n | { type: \"CLEAR\" }\n | { type: \"CHANGE\"; payload: { query: string } }\n | { type: \"SELECT\"; payload: { query: string } }\n\nconst reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"OPEN\":\n return { ...state, open: true }\n case \"CLOSE\":\n return { ...state, open: false }\n case \"CLEAR\":\n return { ...state, query: \"\" }\n case \"CHANGE\":\n return { ...state, query: action.payload.query, open: true }\n case \"SELECT\":\n return { ...state, query: action.payload.query, open: false }\n }\n}\n\nexport interface AutocompleteInputProps<T extends AutocompleteInputOptionType>\n extends Omit<InputProps, \"onSelect\" | \"onSubmit\"> {\n /** Optionally enable clamping (default: `false`) */\n clamp?: boolean\n defaultValue?: string\n dropdownMaxHeight?: ResponsiveValue<string | number>\n dropdownMinWidth?: ResponsiveValue<string | number>\n loading?: boolean\n header?: React.ReactNode\n /** Optionally disable flipping (default: `true`) */\n flip?: boolean\n footer?:\n | React.ReactNode\n | ((dropdownActions: AutocompleteFooterActions) => React.ReactNode)\n /** Ref to the input; workaround generics */\n forwardRef?: React.Ref<HTMLInputElement>\n /** on <enter> when no option is selected */\n onSubmit?(query: string): void\n /** on <click> or <enter> when an option is selected */\n onSelect?(option: T, index: number): void\n /** on <click> of the 'x' (clear) button */\n onClear?(): void\n /** Callback that runs when options are hidden */\n onClose?(): void\n options: T[]\n renderOption?(\n option: T,\n i: number\n ): React.ReactElement<any, string | React.JSXElementConstructor<any>>\n}\n\n/** AutocompleteInput */\nexport const AutocompleteInput = <T extends AutocompleteInputOptionType>({\n clamp = false,\n defaultValue = \"\",\n dropdownMaxHeight = 308, // 308 = roughly 5.5 options\n dropdownMinWidth,\n flip = true,\n footer,\n forwardRef: forwardedRef,\n header,\n height,\n id,\n loading,\n onChange,\n onClear,\n onClose,\n onKeyDown,\n onSelect,\n onSubmit,\n options,\n renderOption = (option) => <AutocompleteInputOptionLabel {...option} />,\n ...rest\n}: AutocompleteInputProps<T>) => {\n const inputRef = useRef<HTMLInputElement | null>(null)\n const containerRef = useRef<HTMLDivElement | null>(null)\n const headerRef = useRef<HTMLDivElement | null>(null)\n const footerRef = useRef<HTMLDivElement | null>(null)\n\n const [boxProps, inputProps] = splitBoxProps(rest)\n\n const [state, dispatch] = useReducer(reducer, {\n open: false,\n query: defaultValue,\n })\n\n const optionsWithRefs = useMemo(() => {\n return options.map((option) => ({\n option,\n ref: createRef<HTMLButtonElement>(),\n }))\n }, [options])\n\n const resetUI = () => {\n setTimeout(() => {\n inputRef.current?.focus()\n reset()\n dispatch({ type: \"CLOSE\" })\n }, 100)\n }\n\n const handleSelect = (option: T, index: number) => {\n dispatch({ type: \"SELECT\", payload: { query: option.text } })\n inputRef.current?.focus()\n onSelect?.(option, index)\n }\n\n const { index, reset, set } = useKeyboardListNavigation({\n ref: containerRef,\n list: options,\n waitForInteractive: true,\n onEnter: ({ element: option, index: i, event }) => {\n event.preventDefault()\n event.stopPropagation()\n handleSelect(option, i)\n resetUI()\n },\n })\n\n const isDropdownVisible = state.open && options.length > 0\n\n // Reset keyboard navigation when options change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(reset, [options])\n\n // Reset keyboard navigation when query is empty\n useEffect(() => {\n if (state.query === \"\") reset()\n }, [reset, state.query])\n\n const { anchorRef, tooltipRef, floatingStyles } = usePosition({\n active: isDropdownVisible,\n clamp,\n flip,\n key: options.length,\n offset: 0,\n position: \"bottom\",\n })\n\n const { width } = useWidthOf({ ref: anchorRef })\n\n const handleFocus = () => {\n reset()\n dispatch({ type: \"OPEN\" })\n }\n\n const handleMouseDown = (option: T, i: number) => () => {\n handleSelect(option, i)\n resetUI()\n }\n\n const handleClick = () => {\n dispatch({ type: \"OPEN\" })\n }\n\n // Records the latest mouse movement and, inside the `handleMouseEnter` callback,\n // only treat the event as genuine if the mouse has moved very recently.\n // Otherwise, the event is presumed to have been triggered by scrolling and is ignored.\n const { lastMouseMoveTimestamp } = useMouseActivity()\n\n const handleMouseEnter = (i: number) => () => {\n const now = performance.now()\n\n // 50ms mouse move window\n if (now - lastMouseMoveTimestamp.current < 50) {\n set({ cursor: i, interactive: true })\n }\n }\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const {\n currentTarget: { value },\n } = event\n\n dispatch({ type: \"CHANGE\", payload: { query: value } })\n onChange?.(event)\n }\n\n const handleClearOrSubmit = () => {\n if (state.query === \"\") {\n onSubmit?.(state.query)\n return\n }\n\n dispatch({ type: \"CLEAR\" })\n inputRef.current?.focus()\n onClear?.()\n }\n\n const handleClose = useCallback(() => {\n dispatch({ type: \"CLOSE\" })\n reset()\n onClose?.()\n }, [onClose, reset])\n\n const ignoreFocusChangeRef = useRef<boolean>(false)\n const ignoreFocusChange = {\n onMouseDown: () => (ignoreFocusChangeRef.current = true),\n onMouseUp: () => (ignoreFocusChangeRef.current = false),\n }\n\n // Moves focus to different options when keyboard navigating using up/down\n useEffect(() => {\n const el = optionsWithRefs[index]?.ref?.current\n if (!el) return\n\n const isPointerInteraction =\n performance.now() - lastMouseMoveTimestamp.current < 50\n\n if (isPointerInteraction) {\n // Pointer interactions should not cause scroll\n el.focus({ preventScroll: true })\n return\n }\n\n // Keyboard navigation: focus and ensure visibility\n el.focus({ preventScroll: true })\n try {\n el.scrollIntoView({ block: \"nearest\" })\n } catch {}\n }, [index, optionsWithRefs])\n\n const handleFocusChange = useCallback(\n (focused: boolean) => {\n if (ignoreFocusChangeRef.current || focused || !isDropdownVisible) return\n\n handleClose()\n },\n\n [isDropdownVisible, handleClose]\n )\n\n // Handle closing the dropdown when clicking outside of the input\n // or when focus leaves the input completely\n const { ref: containsFocusRef } = useContainsFocus({\n onChange: handleFocusChange,\n })\n\n useClickOutside({\n ref: containsFocusRef,\n onClickOutside: handleClose,\n when: isDropdownVisible,\n type: \"click\",\n })\n\n const handleInputKeydown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n // Handle <Enter> when nothing is selected\n case \"Enter\":\n if (state.query !== \"\" && index === -1) {\n onSubmit?.(state.query)\n resetUI()\n }\n return\n\n // <Esc> to close dropdown\n case \"Escape\":\n event.preventDefault()\n event.stopPropagation()\n\n dispatch({ type: \"CLOSE\" })\n inputRef.current?.blur()\n\n return\n\n default:\n break\n }\n\n onKeyDown?.(event)\n }\n\n // Moves focus back to input when typing\n const handleContainerKeydown = (\n event: React.KeyboardEvent<HTMLDivElement>\n ) => {\n switch (event.key) {\n case \"Alt\":\n case \"ArrowDown\":\n case \"ArrowUp\":\n case \"Control\":\n case \"Enter\":\n case \"Meta\":\n case \"Shift\":\n case \"Tab\":\n // Ignore\n return\n\n case \"Escape\":\n event.preventDefault()\n event.stopPropagation()\n\n dispatch({ type: \"CLOSE\" })\n inputRef.current?.blur()\n reset()\n\n return\n\n default:\n inputRef.current?.focus()\n }\n }\n\n // Option that is being hovered or keyed into\n const staged = options[index]\n\n return (\n <Box\n ref={composeRefs(containerRef, containsFocusRef) as any}\n onKeyDown={handleContainerKeydown}\n {...boxProps}\n >\n <LabeledInput\n ref={composeRefs(inputRef, anchorRef, forwardedRef) as any}\n role=\"combobox\"\n aria-expanded={isDropdownVisible}\n aria-autocomplete=\"list\"\n {...(id ? { id, \"aria-describedby\": `${id}__assistiveHint` } : {})}\n label={\n loading ? (\n <Box width={18}>\n <Spinner size=\"small\" />\n </Box>\n ) : state.query ? (\n <Clickable\n onClick={handleClearOrSubmit}\n height=\"100%\"\n display=\"flex\"\n alignItems=\"center\"\n aria-label=\"Clear input\"\n >\n <CloseIcon fill=\"mono60\" aria-hidden />\n </Clickable>\n ) : (\n <SearchIcon fill=\"mono60\" aria-hidden />\n )\n }\n value={staged?.text ?? state.query}\n onChange={handleChange}\n onFocus={handleFocus}\n onKeyDown={handleInputKeydown}\n onClick={handleClick}\n autoComplete=\"off\"\n height={height}\n {...inputProps}\n />\n\n {isDropdownVisible && (\n <AutocompleteInputDropdownContainer\n ref={tooltipRef as any}\n role=\"listbox\"\n width={width}\n minWidth={200}\n pt={1} // Gap in place of `offset` for `usePosition`\n style={floatingStyles}\n >\n <AutocompleteInputDropdown minWidth={dropdownMinWidth}>\n <div ref={headerRef} {...ignoreFocusChange}>\n {header}\n </div>\n\n <AutocompleteInputOptions maxHeight={dropdownMaxHeight}>\n {optionsWithRefs.map(({ option, ref }, i) => {\n return (\n <AutocompleteInputOption\n key={i}\n ref={ref}\n role=\"option\"\n aria-selected={i === index}\n aria-posinset={i + 1}\n aria-setsize={options.length}\n onMouseDown={handleMouseDown(option, i)}\n onMouseEnter={handleMouseEnter(i)}\n tabIndex={-1}\n >\n {renderOption(option, i)}\n </AutocompleteInputOption>\n )\n })}\n </AutocompleteInputOptions>\n\n <div ref={footerRef} {...ignoreFocusChange}>\n {typeof footer === \"function\"\n ? footer({ onClose: handleClose })\n : footer}\n </div>\n </AutocompleteInputDropdown>\n </AutocompleteInputDropdownContainer>\n )}\n\n <VisuallyHidden {...(id ? { id: `${id}__assistiveHint` } : {})}>\n When autocomplete results are available use up and down arrows to review\n and enter to select. Touch device users, explore by touch or with swipe\n gestures.\n </VisuallyHidden>\n\n {isDropdownVisible && (\n <VisuallyHidden role=\"status\" aria-atomic=\"true\" aria-live=\"polite\">\n {options.length === 1\n ? `1 result is available`\n : `${options.length} results are available`}\n </VisuallyHidden>\n )}\n </Box>\n )\n}\n\nconst AutocompleteInputDropdownContainer = styled(Box)`\n z-index: 1;\n`\n\nconst AutocompleteInputDropdown = styled(Box)`\n box-shadow: ${themeGet(\"effects.dropShadow\")};\n`\n\nconst AutocompleteInputOptions = styled(Box)`\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n`\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import styled from "styled-components";
2
+ import styled, { css } from "styled-components";
3
+ import { useHover, useClick, useDismiss, useInteractions, safePolygon, useTransitionStatus } from "@floating-ui/react";
3
4
  import { calculateMaxHeight, usePosition } from "../../utils";
4
5
  import { useDidMount } from "../../utils/useDidMount";
5
6
  import { usePortal } from "../../utils/usePortal";
@@ -23,223 +24,173 @@ export const Dropdown = ({
23
24
  openDropdownByClick,
24
25
  transition: _transition = true,
25
26
  flip = true,
27
+ autoPlacement = false,
26
28
  returnFocus = true,
27
29
  delay = 0,
30
+ safePolygonOptions,
28
31
  ...rest
29
32
  }) => {
30
33
  const [visible, setVisible] = useState(false);
31
34
 
32
- // If prop updates/set initial visibility.
35
+ // Sync with controlled `visible` prop
33
36
  useEffect(() => {
34
37
  setVisible(_visible);
35
38
  }, [_visible]);
36
- const timeoutRef = useRef(null);
37
39
 
38
- // We need to keep the pointer state in sync with the visibility state, else we
39
- // wind up with focus isolation out of sync.
40
- const setVisibility = useCallback(({
41
- visible,
42
- isPointer = false
43
- }) => {
44
- // Use custom delay when opening via pointer interaction (hover), but only if not using click mode
45
- const defaultDelay = _transition ? visible ? 50 : 150 : visible ? 1 : 50;
46
- const finalDelay = visible && isPointer && !openDropdownByClick ? delay : defaultDelay;
47
- timeoutRef.current && clearTimeout(timeoutRef.current);
48
- timeoutRef.current = setTimeout(() => {
49
- if (!visible && activeRef.current) return;
50
- pointerRef.current = isPointer;
51
- setVisible(visible);
52
- }, finalDelay);
53
- }, [_transition, delay, openDropdownByClick]);
54
- const onVisible = () => {
55
- setVisibility({
56
- visible: true
57
- });
58
- };
59
- const onHide = useCallback(() => {
60
- setVisibility({
61
- visible: false
62
- });
63
- }, [setVisibility]);
64
- const onToggleVisibility = () => {
65
- if (visible) {
66
- return onHide();
67
- }
68
- onVisible();
69
- };
40
+ // Track whether the current open was triggered by pointer (hover) so we know
41
+ // whether to enable keyboard focus-trapping via FocusOn.
42
+ const pointerRef = useRef(false);
43
+
44
+ // onOpenChange is called by Floating UI interaction hooks (useHover, useClick,
45
+ // useDismiss). The `reason` arg lets us detect pointer vs keyboard opens.
46
+ const onOpenChange = useCallback((open, _event, reason) => {
47
+ pointerRef.current = open && (reason === "hover" || reason === "safe-polygon");
48
+ setVisible(open);
49
+ }, []);
70
50
  const {
71
51
  anchorRef,
72
52
  tooltipRef: panelRef,
73
- state: {
74
- isFlipped
75
- }
53
+ floatingStyles,
54
+ resolvedPlacement,
55
+ context
76
56
  } = usePosition({
77
57
  position: placement,
78
58
  offset: 0,
79
59
  active: visible,
80
- flip,
81
- padding: offset
60
+ // Avoid running both middleware: autoPlacement supersedes flip.
61
+ flip: autoPlacement ? false : flip,
62
+ autoPlacement,
63
+ padding: offset,
64
+ onOpenChange
82
65
  });
83
- useEffect(() => {
84
- const handleKeyDown = event => {
85
- if (event.key === "Escape") {
86
- onHide();
87
- }
88
- };
89
66
 
90
- // Close dropdown when focus leaves element
67
+ // useHover: opens/closes on pointer enter/leave. Optionally use safePolygon
68
+ // to keep the panel open while the cursor traverses the gap (when safePolygonOptions is set).
69
+ const hover = useHover(context, {
70
+ enabled: !openDropdownByClick,
71
+ delay: {
72
+ open: delay ?? (_transition ? 50 : 1),
73
+ close: _transition ? 150 : 50
74
+ },
75
+ handleClose: safePolygonOptions !== undefined && safePolygonOptions !== null ? safePolygon(safePolygonOptions) : null
76
+ });
77
+
78
+ // useClick: toggles for click-mode; open-only (toggle:false) for hover-mode
79
+ // so keyboard users can press Enter/Space on a focused anchor to open it.
80
+ const click = useClick(context, {
81
+ toggle: !!openDropdownByClick
82
+ });
83
+
84
+ // useDismiss: closes on Escape key and click outside (replaces manual listeners).
85
+ const dismiss = useDismiss(context);
86
+ const {
87
+ getReferenceProps,
88
+ getFloatingProps
89
+ } = useInteractions([hover, click, dismiss]);
90
+
91
+ // Tab-key close for hover-mode: FocusOn is disabled so focus can leave the
92
+ // panel freely; we watch for that and close when it does.
93
+ useEffect(() => {
94
+ if (!visible || openDropdownByClick) return;
91
95
  const handleKeyUp = event => {
92
96
  if (!panelRef.current) return;
93
- if (event.key === "Tab" && !(panelRef.current === document.activeElement || panelRef.current.contains(document.activeElement))) {
94
- onHide();
97
+ if (event.key === "Tab" && !panelRef.current.contains(document.activeElement)) {
98
+ setVisible(false);
95
99
  }
96
100
  };
101
+ document.addEventListener("keyup", handleKeyUp);
102
+ return () => document.removeEventListener("keyup", handleKeyUp);
103
+ }, [visible, openDropdownByClick, panelRef]);
104
+
105
+ // Close when a link inside the panel is clicked (click-mode only).
106
+ useEffect(() => {
107
+ if (!openDropdownByClick) return;
97
108
  const handleClick = event => {
98
- if (!panelRef.current || !openDropdownByClick) return;
109
+ if (!panelRef.current) return;
99
110
  const target = event.target;
100
- const tagName = target.tagName.toLowerCase();
101
- let isClosableElement = tagName === "a";
102
- let element = target;
103
-
104
- // Find parent link element
105
- if (!isClosableElement) {
106
- element = target.closest("a");
107
- isClosableElement = !!element;
108
- }
109
- if (isClosableElement && element && panelRef.current.contains(element)) {
110
- onHide();
111
- }
111
+ const link = target.tagName.toLowerCase() === "a" ? target : target.closest("a");
112
+ if (link && panelRef.current.contains(link)) setVisible(false);
112
113
  };
113
- document.addEventListener("keydown", handleKeyDown);
114
- document.addEventListener("keyup", handleKeyUp);
115
114
  document.addEventListener("click", handleClick);
116
- return () => {
117
- document.removeEventListener("keydown", handleKeyDown);
118
- document.removeEventListener("keyup", handleKeyUp);
119
- document.removeEventListener("click", handleClick);
120
- };
121
- }, [panelRef, openDropdownByClick, onHide]);
122
- const activeRef = useRef(false);
123
- const handleMouseEnter = () => {
124
- activeRef.current = true;
125
- };
126
- const handleMouseLeave = () => {
127
- activeRef.current = false;
128
- onHide();
129
- };
130
- const [transition, setTransition] = useState(false);
115
+ return () => document.removeEventListener("click", handleClick);
116
+ }, [openDropdownByClick, panelRef]);
117
+ const onVisible = useCallback(() => setVisible(true), []);
118
+ const onHide = useCallback(() => setVisible(false), []);
131
119
 
132
- // Wait for next tick so that animation runs
133
- useEffect(() => {
134
- requestAnimationFrame(() => {
135
- setTransition(visible);
136
- });
137
- }, [visible]);
138
- const translation = useMemo(() => {
139
- switch (placement) {
140
- case "top-start":
141
- case "top":
142
- case "top-end":
143
- return `translateY(10px)`;
144
- case "bottom-start":
145
- case "bottom":
146
- case "bottom-end":
147
- return `translateY(-10px)`;
148
- case "left-start":
149
- case "left":
150
- case "left-end":
151
- return `translateX(10px)`;
152
- case "right-start":
153
- case "right":
154
- case "right-end":
155
- return `translateX(-10px)`;
156
- }
157
- }, [placement]);
120
+ // Placement-aware transitions via CSS data attributes (per Floating UI docs).
121
+ // CSS reads the current data-placement on every frame, so there's no stale
122
+ // capture when flip changes placement after mount.
123
+ const {
124
+ isMounted,
125
+ status
126
+ } = useTransitionStatus(context, {
127
+ duration: _transition ? 250 : 0
128
+ });
158
129
 
159
- // Fills offset gap between anchor and panel to prevent mouseout
130
+ // Padding on the panel that fills the gap between anchor and panel so the
131
+ // safePolygon cursor path isn't interrupted.
160
132
  const padding = useMemo(() => {
161
- switch (placement) {
133
+ switch (resolvedPlacement) {
162
134
  case "top-start":
163
135
  case "top":
164
136
  case "top-end":
165
137
  return {
166
- [isFlipped ? "pt" : "pb"]: offset
138
+ pb: offset
167
139
  };
168
140
  case "bottom-start":
169
141
  case "bottom":
170
142
  case "bottom-end":
171
143
  return {
172
- [isFlipped ? "pb" : "pt"]: offset
144
+ pt: offset
173
145
  };
174
146
  case "left-start":
175
147
  case "left":
176
148
  case "left-end":
177
149
  return {
178
- [isFlipped ? "pl" : "pr"]: offset
150
+ pr: offset
179
151
  };
180
152
  case "right-start":
181
153
  case "right":
182
154
  case "right-end":
183
155
  return {
184
- [isFlipped ? "pr" : "pl"]: offset
156
+ pl: offset
185
157
  };
186
158
  }
187
- }, [placement, isFlipped, offset]);
188
- const pointerRef = useRef(false);
189
- const handlePointerVisible = () => {
190
- setVisibility({
191
- visible: true,
192
- isPointer: true
193
- });
194
- };
195
- const handlePointerHide = () => {
196
- setVisibility({
197
- visible: false,
198
- isPointer: false
199
- });
200
- };
201
- const anchorProps = {
202
- "aria-expanded": visible,
203
- "aria-haspopup": true,
204
- ...(openDropdownByClick ? {
205
- onClick: onToggleVisibility
206
- } : {
207
- onMouseEnter: handlePointerVisible,
208
- onMouseLeave: handlePointerHide,
209
- onClick: onVisible
210
- })
211
- };
212
- const {
213
- createPortal
214
- } = usePortal();
215
- const isClient = useDidMount();
159
+ }, [resolvedPlacement, offset]);
216
160
  const isPointer = !openDropdownByClick && pointerRef.current;
217
161
  const focusEnabled = visible && !isPointer;
218
162
  const [maxHeight, setMaxHeight] = useState(0);
219
163
  useEffect(() => {
220
- const calculate = debounce(() => {
164
+ const calculate = () => {
221
165
  if (!anchorRef.current) return;
222
166
  const nextMaxHeight = calculateMaxHeight({
223
167
  anchorRect: anchorRef.current.getBoundingClientRect(),
224
- position: placement,
168
+ position: resolvedPlacement,
225
169
  offset
226
170
  });
227
171
  setMaxHeight(nextMaxHeight);
228
- }, 500);
229
- window.addEventListener("resize", calculate, {
172
+ };
173
+ const calculateOnViewportChange = debounce(calculate, 100);
174
+ window.addEventListener("resize", calculateOnViewportChange, {
230
175
  passive: true
231
176
  });
232
- window.addEventListener("scroll", calculate, {
177
+ window.addEventListener("scroll", calculateOnViewportChange, {
233
178
  passive: true
234
179
  });
235
180
  calculate();
236
181
  return () => {
237
- window.removeEventListener("resize", calculate);
238
- window.removeEventListener("scroll", calculate);
182
+ window.removeEventListener("resize", calculateOnViewportChange);
183
+ window.removeEventListener("scroll", calculateOnViewportChange);
239
184
  };
240
- }, [anchorRef, offset, placement, visible]);
185
+ }, [anchorRef, offset, resolvedPlacement, visible]);
186
+ const {
187
+ createPortal
188
+ } = usePortal();
189
+ const isClient = useDidMount();
241
190
  const dropdownPanel = useMemo(() => {
242
- if (!(visible || keepInDOM)) return null;
191
+ if (!(visible || isMounted || keepInDOM)) return null;
192
+ const panelVisible = _transition ? isMounted : visible;
193
+ const renderPanel = panelVisible || keepInDOM;
243
194
  return /*#__PURE__*/_jsx(Container, {
244
195
  "aria-label": "Press escape to close",
245
196
  tabIndex: 0,
@@ -248,35 +199,23 @@ export const Dropdown = ({
248
199
  display: "inline-block",
249
200
  placement: placement,
250
201
  style: {
202
+ ...floatingStyles,
251
203
  ...(keepInDOM ? {
252
204
  visibility: visible ? "visible" : "hidden"
253
205
  } : {})
254
206
  },
255
- ...(openDropdownByClick ? {} : {
256
- onMouseEnter: handleMouseEnter,
257
- onMouseLeave: handleMouseLeave
258
- }),
259
207
  maxHeight: maxHeight + offset,
260
208
  ...padding,
209
+ ...getFloatingProps(),
261
210
  ...rest,
262
- children: /*#__PURE__*/_jsx(Panel, {
263
- transition: _transition,
211
+ children: renderPanel && /*#__PURE__*/_jsx(Panel, {
212
+ "data-status": panelVisible ? status : "initial",
213
+ "data-placement": resolvedPlacement,
214
+ $animate: _transition,
264
215
  maxHeight: maxHeight,
265
- style: transition ?
266
- // In
267
- {
268
- opacity: 1,
269
- transform: "translate(0)"
270
- } :
271
- // Out
272
- {
273
- opacity: 0,
274
- transform: translation
275
- },
276
216
  children: /*#__PURE__*/_jsx(FocusOn, {
277
217
  noIsolation: true,
278
218
  enabled: focusEnabled,
279
- onClickOutside: onHide,
280
219
  returnFocus: returnFocus,
281
220
  children: /*#__PURE__*/_jsx(Pane, {
282
221
  maxHeight: maxHeight,
@@ -290,8 +229,11 @@ export const Dropdown = ({
290
229
  })
291
230
  })
292
231
  });
293
- // eslint-disable-next-line react-hooks/exhaustive-deps
294
- }, [visible, keepInDOM, dropdownZIndex, placement, openDropdownByClick, maxHeight, offset, _transition, transition, translation, focusEnabled, returnFocus, dropdown, padding]);
232
+ }, [visible, isMounted, keepInDOM, dropdownZIndex, placement, resolvedPlacement, maxHeight, offset, _transition, status, focusEnabled, returnFocus, dropdown, padding, floatingStyles, getFloatingProps, rest]);
233
+ const anchorProps = getReferenceProps({
234
+ "aria-expanded": visible,
235
+ "aria-haspopup": true
236
+ });
295
237
  return /*#__PURE__*/_jsxs(_Fragment, {
296
238
  children: [children?.({
297
239
  anchorRef: anchorRef,
@@ -306,13 +248,22 @@ export const Dropdown = ({
306
248
  const Container = styled(Box).withConfig({
307
249
  displayName: "Dropdown__Container",
308
250
  componentId: "sc-je1xqj-0"
309
- })(["position:fixed;text-align:left;outline:0;"]);
251
+ })(["text-align:left;outline:0;"]);
252
+
253
+ /**
254
+ * Placement-aware transitions driven by data attributes, following the pattern
255
+ * from https://floating-ui.com/docs/useTransition#placement-aware-transitions
256
+ *
257
+ * CSS reads data-placement (which is always the *resolved* placement from
258
+ * Floating UI, including flips) on every frame, so there is never a stale
259
+ * slide direction — even when flip changes the placement after mount.
260
+ */
310
261
  const Panel = styled(Box).withConfig({
311
262
  displayName: "Dropdown__Panel",
312
263
  componentId: "sc-je1xqj-1"
313
- })(["transition:", ";"], ({
314
- transition
315
- }) => transition ? "opacity 250ms ease-out, transform 250ms ease-out" : "none");
264
+ })(["", ""], ({
265
+ $animate
266
+ }) => $animate ? css(["transition-property:opacity,transform;&[data-status=\"open\"],&[data-status=\"close\"]{transition-duration:250ms;transition-timing-function:ease-out;}&[data-status=\"initial\"],&[data-status=\"close\"]{opacity:0;}&[data-status=\"initial\"][data-placement^=\"top\"],&[data-status=\"close\"][data-placement^=\"top\"]{transform:translateY(10px);}&[data-status=\"initial\"][data-placement^=\"bottom\"],&[data-status=\"close\"][data-placement^=\"bottom\"]{transform:translateY(-10px);}&[data-status=\"initial\"][data-placement^=\"left\"],&[data-status=\"close\"][data-placement^=\"left\"]{transform:translateX(10px);}&[data-status=\"initial\"][data-placement^=\"right\"],&[data-status=\"close\"][data-placement^=\"right\"]{transform:translateX(-10px);}"]) : css(["transition:none;"]));
316
267
  const Pane = styled(Box).withConfig({
317
268
  displayName: "Dropdown__Pane",
318
269
  componentId: "sc-je1xqj-2"