import * as React from 'react'; import { useModernLayoutEffect, useEffectEvent, getMinListIndex, getMaxListIndex, createGridCellMap, isListIndexDisabled, getGridNavigatedIndex, getGridCellIndexOfCorner, getGridCellIndices, findNonDisabledListIndex, isIndexOutOfListBounds, useLatestRef, getDocument as getDocument$1, isMouseLikePointerType, contains as contains$1, isSafari, enableFocusInside, isOutsideEvent, getPreviousTabbable, getNextTabbable, disableFocusInside, isTypeableCombobox, getFloatingFocusElement, getTabbableOptions, getNodeAncestors, activeElement, getNodeChildren as getNodeChildren$1, stopEvent, getTarget as getTarget$1, isVirtualClick, isVirtualPointerEvent, getPlatform, isTypeableElement, isReactEvent, isRootElement, isEventTargetWithin, matchesFocusVisible, isMac, getDeepestNode, getUserAgent } from '@floating-ui/react/utils'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { getComputedStyle, isElement, isShadowRoot, getNodeName, isNode, isHTMLElement, getWindow, isLastTraversableNode, getParentNode, isWebKit } from '@floating-ui/utils/dom'; import { tabbable, isTabbable, focusable } from 'tabbable'; import * as ReactDOM from 'react-dom'; import { getOverflowAncestors, useFloating as useFloating$1, offset, detectOverflow } from '@floating-ui/react-dom'; export { arrow, autoPlacement, autoUpdate, computePosition, detectOverflow, flip, getOverflowAncestors, hide, inline, limitShift, offset, platform, shift, size } from '@floating-ui/react-dom'; import { evaluate, max, round, min } from '@floating-ui/utils'; /** * Merges an array of refs into a single memoized callback ref or `null`. * @see https://floating-ui.com/docs/react-utils#usemergerefs */ function useMergeRefs(refs) { const cleanupRef = React.useRef(undefined); const refEffect = React.useCallback(instance => { const cleanups = refs.map(ref => { if (ref == null) { return; } if (typeof ref === 'function') { const refCallback = ref; const refCleanup = refCallback(instance); return typeof refCleanup === 'function' ? refCleanup : () => { refCallback(null); }; } ref.current = instance; return () => { ref.current = null; }; }); return () => { cleanups.forEach(refCleanup => refCleanup == null ? void 0 : refCleanup()); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, refs); return React.useMemo(() => { if (refs.every(ref => ref == null)) { return null; } return value => { if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = undefined; } if (value != null) { cleanupRef.current = refEffect(value); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, refs); } function sortByDocumentPosition(a, b) { const position = a.compareDocumentPosition(b); if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) { return -1; } if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) { return 1; } return 0; } const FloatingListContext = /*#__PURE__*/React.createContext({ register: () => {}, unregister: () => {}, map: /*#__PURE__*/new Map(), elementsRef: { current: [] } }); /** * Provides context for a list of items within the floating element. * @see https://floating-ui.com/docs/FloatingList */ function FloatingList(props) { const { children, elementsRef, labelsRef } = props; const [nodes, setNodes] = React.useState(() => new Set()); const register = React.useCallback(node => { setNodes(prevSet => new Set(prevSet).add(node)); }, []); const unregister = React.useCallback(node => { setNodes(prevSet => { const set = new Set(prevSet); set.delete(node); return set; }); }, []); const map = React.useMemo(() => { const newMap = new Map(); const sortedNodes = Array.from(nodes.keys()).sort(sortByDocumentPosition); sortedNodes.forEach((node, index) => { newMap.set(node, index); }); return newMap; }, [nodes]); return /*#__PURE__*/jsx(FloatingListContext.Provider, { value: React.useMemo(() => ({ register, unregister, map, elementsRef, labelsRef }), [register, unregister, map, elementsRef, labelsRef]), children: children }); } /** * Used to register a list item and its index (DOM position) in the * `FloatingList`. * @see https://floating-ui.com/docs/FloatingList#uselistitem */ function useListItem(props) { if (props === void 0) { props = {}; } const { label } = props; const { register, unregister, map, elementsRef, labelsRef } = React.useContext(FloatingListContext); const [index, setIndex] = React.useState(null); const componentRef = React.useRef(null); const ref = React.useCallback(node => { componentRef.current = node; if (index !== null) { elementsRef.current[index] = node; if (labelsRef) { var _node$textContent; const isLabelDefined = label !== undefined; labelsRef.current[index] = isLabelDefined ? label : (_node$textContent = node == null ? void 0 : node.textContent) != null ? _node$textContent : null; } } }, [index, elementsRef, labelsRef, label]); useModernLayoutEffect(() => { const node = componentRef.current; if (node) { register(node); return () => { unregister(node); }; } }, [register, unregister]); useModernLayoutEffect(() => { const index = componentRef.current ? map.get(componentRef.current) : null; if (index != null) { setIndex(index); } }, [map]); return React.useMemo(() => ({ ref, index: index == null ? -1 : index }), [index, ref]); } const FOCUSABLE_ATTRIBUTE = 'data-floating-ui-focusable'; const ACTIVE_KEY = 'active'; const SELECTED_KEY = 'selected'; const ARROW_LEFT = 'ArrowLeft'; const ARROW_RIGHT = 'ArrowRight'; const ARROW_UP = 'ArrowUp'; const ARROW_DOWN = 'ArrowDown'; function renderJsx(render, computedProps) { if (typeof render === 'function') { return render(computedProps); } if (render) { return /*#__PURE__*/React.cloneElement(render, computedProps); } return /*#__PURE__*/jsx("div", { ...computedProps }); } const CompositeContext = /*#__PURE__*/React.createContext({ activeIndex: 0, onNavigate: () => {} }); const horizontalKeys = [ARROW_LEFT, ARROW_RIGHT]; const verticalKeys = [ARROW_UP, ARROW_DOWN]; const allKeys = [...horizontalKeys, ...verticalKeys]; /** * Creates a single tab stop whose items are navigated by arrow keys, which * provides list navigation outside of floating element contexts. * * This is useful to enable navigation of a list of items that aren’t part of a * floating element. A menubar is an example of a composite, with each reference * element being an item. * @see https://floating-ui.com/docs/Composite */ const Composite = /*#__PURE__*/React.forwardRef(function Composite(props, forwardedRef) { const { render, orientation = 'both', loop = true, rtl = false, cols = 1, disabledIndices, activeIndex: externalActiveIndex, onNavigate: externalSetActiveIndex, itemSizes, dense = false, ...domProps } = props; const [internalActiveIndex, internalSetActiveIndex] = React.useState(0); const activeIndex = externalActiveIndex != null ? externalActiveIndex : internalActiveIndex; const onNavigate = useEffectEvent(externalSetActiveIndex != null ? externalSetActiveIndex : internalSetActiveIndex); const elementsRef = React.useRef([]); const renderElementProps = render && typeof render !== 'function' ? render.props : {}; const contextValue = React.useMemo(() => ({ activeIndex, onNavigate }), [activeIndex, onNavigate]); const isGrid = cols > 1; function handleKeyDown(event) { if (!allKeys.includes(event.key)) return; let nextIndex = activeIndex; const minIndex = getMinListIndex(elementsRef, disabledIndices); const maxIndex = getMaxListIndex(elementsRef, disabledIndices); const horizontalEndKey = rtl ? ARROW_LEFT : ARROW_RIGHT; const horizontalStartKey = rtl ? ARROW_RIGHT : ARROW_LEFT; if (isGrid) { const sizes = itemSizes || Array.from({ length: elementsRef.current.length }, () => ({ width: 1, height: 1 })); // To calculate movements on the grid, we use hypothetical cell indices // as if every item was 1x1, then convert back to real indices. const cellMap = createGridCellMap(sizes, cols, dense); const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(elementsRef, index, disabledIndices)); // last enabled index const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(elementsRef, index, disabledIndices) ? cellIndex : foundIndex, -1); const maybeNextIndex = cellMap[getGridNavigatedIndex({ current: cellMap.map(itemIndex => itemIndex ? elementsRef.current[itemIndex] : null) }, { event, orientation, loop, rtl, cols, // treat undefined (empty grid spaces) as disabled indices so we // don't end up in them disabledIndices: getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || elementsRef.current.map((_, index) => isListIndexDisabled(elementsRef, index, disabledIndices) ? index : undefined)), undefined], cellMap), minIndex: minGridIndex, maxIndex: maxGridIndex, prevIndex: getGridCellIndexOfCorner(activeIndex > maxIndex ? minIndex : activeIndex, sizes, cellMap, cols, // use a corner matching the edge closest to the direction we're // moving in so we don't end up in the same item. Prefer // top/left over bottom/right. event.key === ARROW_DOWN ? 'bl' : event.key === horizontalEndKey ? 'tr' : 'tl') })]; if (maybeNextIndex != null) { nextIndex = maybeNextIndex; } } const toEndKeys = { horizontal: [horizontalEndKey], vertical: [ARROW_DOWN], both: [horizontalEndKey, ARROW_DOWN] }[orientation]; const toStartKeys = { horizontal: [horizontalStartKey], vertical: [ARROW_UP], both: [horizontalStartKey, ARROW_UP] }[orientation]; const preventedKeys = isGrid ? allKeys : { horizontal: horizontalKeys, vertical: verticalKeys, both: allKeys }[orientation]; if (nextIndex === activeIndex && [...toEndKeys, ...toStartKeys].includes(event.key)) { if (loop && nextIndex === maxIndex && toEndKeys.includes(event.key)) { nextIndex = minIndex; } else if (loop && nextIndex === minIndex && toStartKeys.includes(event.key)) { nextIndex = maxIndex; } else { nextIndex = findNonDisabledListIndex(elementsRef, { startingIndex: nextIndex, decrement: toStartKeys.includes(event.key), disabledIndices }); } } if (nextIndex !== activeIndex && !isIndexOutOfListBounds(elementsRef, nextIndex)) { var _elementsRef$current$; event.stopPropagation(); if (preventedKeys.includes(event.key)) { event.preventDefault(); } onNavigate(nextIndex); (_elementsRef$current$ = elementsRef.current[nextIndex]) == null || _elementsRef$current$.focus(); } } const computedProps = { ...domProps, ...renderElementProps, ref: forwardedRef, 'aria-orientation': orientation === 'both' ? undefined : orientation, onKeyDown(e) { domProps.onKeyDown == null || domProps.onKeyDown(e); renderElementProps.onKeyDown == null || renderElementProps.onKeyDown(e); handleKeyDown(e); } }; return /*#__PURE__*/jsx(CompositeContext.Provider, { value: contextValue, children: /*#__PURE__*/jsx(FloatingList, { elementsRef: elementsRef, children: renderJsx(render, computedProps) }) }); }); /** * @see https://floating-ui.com/docs/Composite */ const CompositeItem = /*#__PURE__*/React.forwardRef(function CompositeItem(props, forwardedRef) { const { render, ...domProps } = props; const renderElementProps = render && typeof render !== 'function' ? render.props : {}; const { activeIndex, onNavigate } = React.useContext(CompositeContext); const { ref, index } = useListItem(); const mergedRef = useMergeRefs([ref, forwardedRef, renderElementProps.ref]); const isActive = activeIndex === index; const computedProps = { ...domProps, ...renderElementProps, ref: mergedRef, tabIndex: isActive ? 0 : -1, 'data-active': isActive ? '' : undefined, onFocus(e) { domProps.onFocus == null || domProps.onFocus(e); renderElementProps.onFocus == null || renderElementProps.onFocus(e); onNavigate(index); } }; return renderJsx(render, computedProps); }); // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379 const SafeReact = { ...React }; let serverHandoffComplete = false; let count = 0; const genId = () => // Ensure the id is unique with multiple independent versions of Floating UI // on serverHandoffComplete ? genId() : undefined); useModernLayoutEffect(() => { if (id == null) { setId(genId()); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); React.useEffect(() => { serverHandoffComplete = true; }, []); return id; } const useReactId = SafeReact.useId; /** * Uses React 18's built-in `useId()` when available, or falls back to a * slightly less performant (requiring a double render) implementation for * earlier React versions. * @see https://floating-ui.com/docs/react-utils#useid */ const useId = useReactId || useFloatingId; let devMessageSet; if (process.env.NODE_ENV !== "production") { devMessageSet = /*#__PURE__*/new Set(); } function warn() { var _devMessageSet; for (var _len = arguments.length, messages = new Array(_len), _key = 0; _key < _len; _key++) { messages[_key] = arguments[_key]; } const message = "Floating UI: " + messages.join(' '); if (!((_devMessageSet = devMessageSet) != null && _devMessageSet.has(message))) { var _devMessageSet2; (_devMessageSet2 = devMessageSet) == null || _devMessageSet2.add(message); console.warn(message); } } function error() { var _devMessageSet3; for (var _len2 = arguments.length, messages = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { messages[_key2] = arguments[_key2]; } const message = "Floating UI: " + messages.join(' '); if (!((_devMessageSet3 = devMessageSet) != null && _devMessageSet3.has(message))) { var _devMessageSet4; (_devMessageSet4 = devMessageSet) == null || _devMessageSet4.add(message); console.error(message); } } /** * Renders a pointing arrow triangle. * @see https://floating-ui.com/docs/FloatingArrow */ const FloatingArrow = /*#__PURE__*/React.forwardRef(function FloatingArrow(props, ref) { const { context: { placement, elements: { floating }, middlewareData: { arrow, shift } }, width = 14, height = 7, tipRadius = 0, strokeWidth = 0, staticOffset, stroke, d, style: { transform, ...restStyle } = {}, ...rest } = props; if (process.env.NODE_ENV !== "production") { if (!ref) { warn('The `ref` prop is required for `FloatingArrow`.'); } } const clipPathId = useId(); const [isRTL, setIsRTL] = React.useState(false); // https://github.com/floating-ui/floating-ui/issues/2932 useModernLayoutEffect(() => { if (!floating) return; const isRTL = getComputedStyle(floating).direction === 'rtl'; if (isRTL) { setIsRTL(true); } }, [floating]); if (!floating) { return null; } const [side, alignment] = placement.split('-'); const isVerticalSide = side === 'top' || side === 'bottom'; let computedStaticOffset = staticOffset; if (isVerticalSide && shift != null && shift.x || !isVerticalSide && shift != null && shift.y) { computedStaticOffset = null; } // Strokes must be double the border width, this ensures the stroke's width // works as you'd expect. const computedStrokeWidth = strokeWidth * 2; const halfStrokeWidth = computedStrokeWidth / 2; const svgX = width / 2 * (tipRadius / -8 + 1); const svgY = height / 2 * tipRadius / 4; const isCustomShape = !!d; const yOffsetProp = computedStaticOffset && alignment === 'end' ? 'bottom' : 'top'; let xOffsetProp = computedStaticOffset && alignment === 'end' ? 'right' : 'left'; if (computedStaticOffset && isRTL) { xOffsetProp = alignment === 'end' ? 'left' : 'right'; } const arrowX = (arrow == null ? void 0 : arrow.x) != null ? computedStaticOffset || arrow.x : ''; const arrowY = (arrow == null ? void 0 : arrow.y) != null ? computedStaticOffset || arrow.y : ''; const dValue = d || 'M0,0' + (" H" + width) + (" L" + (width - svgX) + "," + (height - svgY)) + (" Q" + width / 2 + "," + height + " " + svgX + "," + (height - svgY)) + ' Z'; const rotation = { top: isCustomShape ? 'rotate(180deg)' : '', left: isCustomShape ? 'rotate(90deg)' : 'rotate(-90deg)', bottom: isCustomShape ? '' : 'rotate(180deg)', right: isCustomShape ? 'rotate(-90deg)' : 'rotate(90deg)' }[side]; return /*#__PURE__*/jsxs("svg", { ...rest, "aria-hidden": true, ref: ref, width: isCustomShape ? width : width + computedStrokeWidth, height: width, viewBox: "0 0 " + width + " " + (height > width ? height : width), style: { position: 'absolute', pointerEvents: 'none', [xOffsetProp]: arrowX, [yOffsetProp]: arrowY, [side]: isVerticalSide || isCustomShape ? '100%' : "calc(100% - " + computedStrokeWidth / 2 + "px)", transform: [rotation, transform].filter(t => !!t).join(' '), ...restStyle }, children: [computedStrokeWidth > 0 && /*#__PURE__*/jsx("path", { clipPath: "url(#" + clipPathId + ")", fill: "none", stroke: stroke // Account for the stroke on the fill path rendered below. , strokeWidth: computedStrokeWidth + (d ? 0 : 1), d: dValue }), /*#__PURE__*/jsx("path", { stroke: computedStrokeWidth && !d ? rest.fill : 'none', d: dValue }), /*#__PURE__*/jsx("clipPath", { id: clipPathId, children: /*#__PURE__*/jsx("rect", { x: -halfStrokeWidth, y: halfStrokeWidth * (isCustomShape ? -1 : 1), width: width + computedStrokeWidth, height: width }) })] }); }); function createEventEmitter() { const map = new Map(); return { emit(event, data) { var _map$get; (_map$get = map.get(event)) == null || _map$get.forEach(listener => listener(data)); }, on(event, listener) { if (!map.has(event)) { map.set(event, new Set()); } map.get(event).add(listener); }, off(event, listener) { var _map$get2; (_map$get2 = map.get(event)) == null || _map$get2.delete(listener); } }; } const FloatingNodeContext = /*#__PURE__*/React.createContext(null); const FloatingTreeContext = /*#__PURE__*/React.createContext(null); /** * Returns the parent node id for nested floating elements, if available. * Returns `null` for top-level floating elements. */ const useFloatingParentNodeId = () => { var _React$useContext; return ((_React$useContext = React.useContext(FloatingNodeContext)) == null ? void 0 : _React$useContext.id) || null; }; /** * Returns the nearest floating tree context, if available. */ const useFloatingTree = () => React.useContext(FloatingTreeContext); /** * Registers a node into the `FloatingTree`, returning its id. * @see https://floating-ui.com/docs/FloatingTree */ function useFloatingNodeId(customParentId) { const id = useId(); const tree = useFloatingTree(); const reactParentId = useFloatingParentNodeId(); const parentId = customParentId || reactParentId; useModernLayoutEffect(() => { if (!id) return; const node = { id, parentId }; tree == null || tree.addNode(node); return () => { tree == null || tree.removeNode(node); }; }, [tree, id, parentId]); return id; } /** * Provides parent node context for nested floating elements. * @see https://floating-ui.com/docs/FloatingTree */ function FloatingNode(props) { const { children, id } = props; const parentId = useFloatingParentNodeId(); return /*#__PURE__*/jsx(FloatingNodeContext.Provider, { value: React.useMemo(() => ({ id, parentId }), [id, parentId]), children: children }); } /** * Provides context for nested floating elements when they are not children of * each other on the DOM. * This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for: * - The `bubbles` option in the `useDismiss()` Hook * - Nested virtual list navigation * - Nested floating elements that each open on hover * - Custom communication between parent and child floating elements * @see https://floating-ui.com/docs/FloatingTree */ function FloatingTree(props) { const { children } = props; const nodesRef = React.useRef([]); const addNode = React.useCallback(node => { nodesRef.current = [...nodesRef.current, node]; }, []); const removeNode = React.useCallback(node => { nodesRef.current = nodesRef.current.filter(n => n !== node); }, []); const [events] = React.useState(() => createEventEmitter()); return /*#__PURE__*/jsx(FloatingTreeContext.Provider, { value: React.useMemo(() => ({ nodesRef, addNode, removeNode, events }), [addNode, removeNode, events]), children: children }); } function createAttribute(name) { return "data-floating-ui-" + name; } function clearTimeoutIfSet(timeoutRef) { if (timeoutRef.current !== -1) { clearTimeout(timeoutRef.current); timeoutRef.current = -1; } } const safePolygonIdentifier = /*#__PURE__*/createAttribute('safe-polygon'); function getDelay(value, prop, pointerType) { if (pointerType && !isMouseLikePointerType(pointerType)) { return 0; } if (typeof value === 'number') { return value; } if (typeof value === 'function') { const result = value(); if (typeof result === 'number') { return result; } return result == null ? void 0 : result[prop]; } return value == null ? void 0 : value[prop]; } function getRestMs(value) { if (typeof value === 'function') { return value(); } return value; } /** * Opens the floating element while hovering over the reference element, like * CSS `:hover`. * @see https://floating-ui.com/docs/useHover */ function useHover(context, props) { if (props === void 0) { props = {}; } const { open, onOpenChange, dataRef, events, elements } = context; const { enabled = true, delay = 0, handleClose = null, mouseOnly = false, restMs = 0, move = true } = props; const tree = useFloatingTree(); const parentId = useFloatingParentNodeId(); const handleCloseRef = useLatestRef(handleClose); const delayRef = useLatestRef(delay); const openRef = useLatestRef(open); const restMsRef = useLatestRef(restMs); const pointerTypeRef = React.useRef(); const timeoutRef = React.useRef(-1); const handlerRef = React.useRef(); const restTimeoutRef = React.useRef(-1); const blockMouseMoveRef = React.useRef(true); const performedPointerEventsMutationRef = React.useRef(false); const unbindMouseMoveRef = React.useRef(() => {}); const restTimeoutPendingRef = React.useRef(false); const isHoverOpen = useEffectEvent(() => { var _dataRef$current$open; const type = (_dataRef$current$open = dataRef.current.openEvent) == null ? void 0 : _dataRef$current$open.type; return (type == null ? void 0 : type.includes('mouse')) && type !== 'mousedown'; }); // When closing before opening, clear the delay timeouts to cancel it // from showing. React.useEffect(() => { if (!enabled) return; function onOpenChange(_ref) { let { open } = _ref; if (!open) { clearTimeoutIfSet(timeoutRef); clearTimeoutIfSet(restTimeoutRef); blockMouseMoveRef.current = true; restTimeoutPendingRef.current = false; } } events.on('openchange', onOpenChange); return () => { events.off('openchange', onOpenChange); }; }, [enabled, events]); React.useEffect(() => { if (!enabled) return; if (!handleCloseRef.current) return; if (!open) return; function onLeave(event) { if (isHoverOpen()) { onOpenChange(false, event, 'hover'); } } const html = getDocument$1(elements.floating).documentElement; html.addEventListener('mouseleave', onLeave); return () => { html.removeEventListener('mouseleave', onLeave); }; }, [elements.floating, open, onOpenChange, enabled, handleCloseRef, isHoverOpen]); const closeWithDelay = React.useCallback(function (event, runElseBranch, reason) { if (runElseBranch === void 0) { runElseBranch = true; } if (reason === void 0) { reason = 'hover'; } const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current); if (closeDelay && !handlerRef.current) { clearTimeoutIfSet(timeoutRef); timeoutRef.current = window.setTimeout(() => onOpenChange(false, event, reason), closeDelay); } else if (runElseBranch) { clearTimeoutIfSet(timeoutRef); onOpenChange(false, event, reason); } }, [delayRef, onOpenChange]); const cleanupMouseMoveHandler = useEffectEvent(() => { unbindMouseMoveRef.current(); handlerRef.current = undefined; }); const clearPointerEvents = useEffectEvent(() => { if (performedPointerEventsMutationRef.current) { const body = getDocument$1(elements.floating).body; body.style.pointerEvents = ''; body.removeAttribute(safePolygonIdentifier); performedPointerEventsMutationRef.current = false; } }); const isClickLikeOpenEvent = useEffectEvent(() => { return dataRef.current.openEvent ? ['click', 'mousedown'].includes(dataRef.current.openEvent.type) : false; }); // Registering the mouse events on the reference directly to bypass React's // delegation system. If the cursor was on a disabled element and then entered // the reference (no gap), `mouseenter` doesn't fire in the delegation system. React.useEffect(() => { if (!enabled) return; function onReferenceMouseEnter(event) { clearTimeoutIfSet(timeoutRef); blockMouseMoveRef.current = false; if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current) || getRestMs(restMsRef.current) > 0 && !getDelay(delayRef.current, 'open')) { return; } const openDelay = getDelay(delayRef.current, 'open', pointerTypeRef.current); if (openDelay) { timeoutRef.current = window.setTimeout(() => { if (!openRef.current) { onOpenChange(true, event, 'hover'); } }, openDelay); } else if (!open) { onOpenChange(true, event, 'hover'); } } function onReferenceMouseLeave(event) { if (isClickLikeOpenEvent()) { clearPointerEvents(); return; } unbindMouseMoveRef.current(); const doc = getDocument$1(elements.floating); clearTimeoutIfSet(restTimeoutRef); restTimeoutPendingRef.current = false; if (handleCloseRef.current && dataRef.current.floatingContext) { // Prevent clearing `onScrollMouseLeave` timeout. if (!open) { clearTimeoutIfSet(timeoutRef); } handlerRef.current = handleCloseRef.current({ ...dataRef.current.floatingContext, tree, x: event.clientX, y: event.clientY, onClose() { clearPointerEvents(); cleanupMouseMoveHandler(); if (!isClickLikeOpenEvent()) { closeWithDelay(event, true, 'safe-polygon'); } } }); const handler = handlerRef.current; doc.addEventListener('mousemove', handler); unbindMouseMoveRef.current = () => { doc.removeEventListener('mousemove', handler); }; return; } // Allow interactivity without `safePolygon` on touch devices. With a // pointer, a short close delay is an alternative, so it should work // consistently. const shouldClose = pointerTypeRef.current === 'touch' ? !contains$1(elements.floating, event.relatedTarget) : true; if (shouldClose) { closeWithDelay(event); } } // Ensure the floating element closes after scrolling even if the pointer // did not move. // https://github.com/floating-ui/floating-ui/discussions/1692 function onScrollMouseLeave(event) { if (isClickLikeOpenEvent()) return; if (!dataRef.current.floatingContext) return; handleCloseRef.current == null || handleCloseRef.current({ ...dataRef.current.floatingContext, tree, x: event.clientX, y: event.clientY, onClose() { clearPointerEvents(); cleanupMouseMoveHandler(); if (!isClickLikeOpenEvent()) { closeWithDelay(event); } } })(event); } function onFloatingMouseEnter() { clearTimeoutIfSet(timeoutRef); } function onFloatingMouseLeave(event) { if (!isClickLikeOpenEvent()) { closeWithDelay(event, false); } } if (isElement(elements.domReference)) { const reference = elements.domReference; const floating = elements.floating; if (open) { reference.addEventListener('mouseleave', onScrollMouseLeave); } if (move) { reference.addEventListener('mousemove', onReferenceMouseEnter, { once: true }); } reference.addEventListener('mouseenter', onReferenceMouseEnter); reference.addEventListener('mouseleave', onReferenceMouseLeave); if (floating) { floating.addEventListener('mouseleave', onScrollMouseLeave); floating.addEventListener('mouseenter', onFloatingMouseEnter); floating.addEventListener('mouseleave', onFloatingMouseLeave); } return () => { if (open) { reference.removeEventListener('mouseleave', onScrollMouseLeave); } if (move) { reference.removeEventListener('mousemove', onReferenceMouseEnter); } reference.removeEventListener('mouseenter', onReferenceMouseEnter); reference.removeEventListener('mouseleave', onReferenceMouseLeave); if (floating) { floating.removeEventListener('mouseleave', onScrollMouseLeave); floating.removeEventListener('mouseenter', onFloatingMouseEnter); floating.removeEventListener('mouseleave', onFloatingMouseLeave); } }; } }, [elements, enabled, context, mouseOnly, move, closeWithDelay, cleanupMouseMoveHandler, clearPointerEvents, onOpenChange, open, openRef, tree, delayRef, handleCloseRef, dataRef, isClickLikeOpenEvent, restMsRef]); // Block pointer-events of every element other than the reference and floating // while the floating element is open and has a `handleClose` handler. Also // handles nested floating elements. // https://github.com/floating-ui/floating-ui/issues/1722 useModernLayoutEffect(() => { var _handleCloseRef$curre; if (!enabled) return; if (open && (_handleCloseRef$curre = handleCloseRef.current) != null && (_handleCloseRef$curre = _handleCloseRef$curre.__options) != null && _handleCloseRef$curre.blockPointerEvents && isHoverOpen()) { performedPointerEventsMutationRef.current = true; const floatingEl = elements.floating; if (isElement(elements.domReference) && floatingEl) { var _tree$nodesRef$curren; const body = getDocument$1(elements.floating).body; body.setAttribute(safePolygonIdentifier, ''); const ref = elements.domReference; const parentFloating = tree == null || (_tree$nodesRef$curren = tree.nodesRef.current.find(node => node.id === parentId)) == null || (_tree$nodesRef$curren = _tree$nodesRef$curren.context) == null ? void 0 : _tree$nodesRef$curren.elements.floating; if (parentFloating) { parentFloating.style.pointerEvents = ''; } body.style.pointerEvents = 'none'; ref.style.pointerEvents = 'auto'; floatingEl.style.pointerEvents = 'auto'; return () => { body.style.pointerEvents = ''; ref.style.pointerEvents = ''; floatingEl.style.pointerEvents = ''; }; } } }, [enabled, open, parentId, elements, tree, handleCloseRef, isHoverOpen]); useModernLayoutEffect(() => { if (!open) { pointerTypeRef.current = undefined; restTimeoutPendingRef.current = false; cleanupMouseMoveHandler(); clearPointerEvents(); } }, [open, cleanupMouseMoveHandler, clearPointerEvents]); React.useEffect(() => { return () => { cleanupMouseMoveHandler(); clearTimeoutIfSet(timeoutRef); clearTimeoutIfSet(restTimeoutRef); clearPointerEvents(); }; }, [enabled, elements.domReference, cleanupMouseMoveHandler, clearPointerEvents]); const reference = React.useMemo(() => { function setPointerRef(event) { pointerTypeRef.current = event.pointerType; } return { onPointerDown: setPointerRef, onPointerEnter: setPointerRef, onMouseMove(event) { const { nativeEvent } = event; function handleMouseMove() { if (!blockMouseMoveRef.current && !openRef.current) { onOpenChange(true, nativeEvent, 'hover'); } } if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current)) { return; } if (open || getRestMs(restMsRef.current) === 0) { return; } // Ignore insignificant movements to account for tremors. if (restTimeoutPendingRef.current && event.movementX ** 2 + event.movementY ** 2 < 2) { return; } clearTimeoutIfSet(restTimeoutRef); if (pointerTypeRef.current === 'touch') { handleMouseMove(); } else { restTimeoutPendingRef.current = true; restTimeoutRef.current = window.setTimeout(handleMouseMove, getRestMs(restMsRef.current)); } } }; }, [mouseOnly, onOpenChange, open, openRef, restMsRef]); return React.useMemo(() => enabled ? { reference } : {}, [enabled, reference]); } const NOOP = () => {}; const FloatingDelayGroupContext = /*#__PURE__*/React.createContext({ delay: 0, initialDelay: 0, timeoutMs: 0, currentId: null, setCurrentId: NOOP, setState: NOOP, isInstantPhase: false }); /** * @deprecated * Use the return value of `useDelayGroup()` instead. */ const useDelayGroupContext = () => React.useContext(FloatingDelayGroupContext); /** * Provides context for a group of floating elements that should share a * `delay`. * @see https://floating-ui.com/docs/FloatingDelayGroup */ function FloatingDelayGroup(props) { const { children, delay, timeoutMs = 0 } = props; const [state, setState] = React.useReducer((prev, next) => ({ ...prev, ...next }), { delay, timeoutMs, initialDelay: delay, currentId: null, isInstantPhase: false }); const initialCurrentIdRef = React.useRef(null); const setCurrentId = React.useCallback(currentId => { setState({ currentId }); }, []); useModernLayoutEffect(() => { if (state.currentId) { if (initialCurrentIdRef.current === null) { initialCurrentIdRef.current = state.currentId; } else if (!state.isInstantPhase) { setState({ isInstantPhase: true }); } } else { if (state.isInstantPhase) { setState({ isInstantPhase: false }); } initialCurrentIdRef.current = null; } }, [state.currentId, state.isInstantPhase]); return /*#__PURE__*/jsx(FloatingDelayGroupContext.Provider, { value: React.useMemo(() => ({ ...state, setState, setCurrentId }), [state, setCurrentId]), children: children }); } /** * Enables grouping when called inside a component that's a child of a * `FloatingDelayGroup`. * @see https://floating-ui.com/docs/FloatingDelayGroup */ function useDelayGroup(context, options) { if (options === void 0) { options = {}; } const { open, onOpenChange, floatingId } = context; const { id: optionId, enabled = true } = options; const id = optionId != null ? optionId : floatingId; const groupContext = useDelayGroupContext(); const { currentId, setCurrentId, initialDelay, setState, timeoutMs } = groupContext; useModernLayoutEffect(() => { if (!enabled) return; if (!currentId) return; setState({ delay: { open: 1, close: getDelay(initialDelay, 'close') } }); if (currentId !== id) { onOpenChange(false); } }, [enabled, id, onOpenChange, setState, currentId, initialDelay]); useModernLayoutEffect(() => { function unset() { onOpenChange(false); setState({ delay: initialDelay, currentId: null }); } if (!enabled) return; if (!currentId) return; if (!open && currentId === id) { if (timeoutMs) { const timeout = window.setTimeout(unset, timeoutMs); return () => { clearTimeout(timeout); }; } unset(); } }, [enabled, open, setState, currentId, id, onOpenChange, initialDelay, timeoutMs]); useModernLayoutEffect(() => { if (!enabled) return; if (setCurrentId === NOOP || !open) return; setCurrentId(id); }, [enabled, open, setCurrentId, id]); return groupContext; } const NextFloatingDelayGroupContext = /*#__PURE__*/React.createContext({ hasProvider: false, timeoutMs: 0, delayRef: { current: 0 }, initialDelayRef: { current: 0 }, timeoutIdRef: { current: -1 }, currentIdRef: { current: null }, currentContextRef: { current: null } }); /** * Experimental next version of `FloatingDelayGroup` to become the default * in the future. This component is not yet stable. * Provides context for a group of floating elements that should share a * `delay`. Unlike `FloatingDelayGroup`, `useNextDelayGroup` with this * component does not cause a re-render of unrelated consumers of the * context when the delay changes. * @see https://floating-ui.com/docs/FloatingDelayGroup */ function NextFloatingDelayGroup(props) { const { children, delay, timeoutMs = 0 } = props; const delayRef = React.useRef(delay); const initialDelayRef = React.useRef(delay); const currentIdRef = React.useRef(null); const currentContextRef = React.useRef(null); const timeoutIdRef = React.useRef(-1); return /*#__PURE__*/jsx(NextFloatingDelayGroupContext.Provider, { value: React.useMemo(() => ({ hasProvider: true, delayRef, initialDelayRef, currentIdRef, timeoutMs, currentContextRef, timeoutIdRef }), [timeoutMs]), children: children }); } /** * Enables grouping when called inside a component that's a child of a * `NextFloatingDelayGroup`. * @see https://floating-ui.com/docs/FloatingDelayGroup */ function useNextDelayGroup(context, options) { if (options === void 0) { options = {}; } const { open, onOpenChange, floatingId } = context; const { enabled = true } = options; const groupContext = React.useContext(NextFloatingDelayGroupContext); const { currentIdRef, delayRef, timeoutMs, initialDelayRef, currentContextRef, hasProvider, timeoutIdRef } = groupContext; const [isInstantPhase, setIsInstantPhase] = React.useState(false); useModernLayoutEffect(() => { function unset() { var _currentContextRef$cu; setIsInstantPhase(false); (_currentContextRef$cu = currentContextRef.current) == null || _currentContextRef$cu.setIsInstantPhase(false); currentIdRef.current = null; currentContextRef.current = null; delayRef.current = initialDelayRef.current; } if (!enabled) return; if (!currentIdRef.current) return; if (!open && currentIdRef.current === floatingId) { setIsInstantPhase(false); if (timeoutMs) { timeoutIdRef.current = window.setTimeout(unset, timeoutMs); return () => { clearTimeout(timeoutIdRef.current); }; } unset(); } }, [enabled, open, floatingId, currentIdRef, delayRef, timeoutMs, initialDelayRef, currentContextRef, timeoutIdRef]); useModernLayoutEffect(() => { if (!enabled) return; if (!open) return; const prevContext = currentContextRef.current; const prevId = currentIdRef.current; currentContextRef.current = { onOpenChange, setIsInstantPhase }; currentIdRef.current = floatingId; delayRef.current = { open: 0, close: getDelay(initialDelayRef.current, 'close') }; if (prevId !== null && prevId !== floatingId) { clearTimeoutIfSet(timeoutIdRef); setIsInstantPhase(true); prevContext == null || prevContext.setIsInstantPhase(true); prevContext == null || prevContext.onOpenChange(false); } else { setIsInstantPhase(false); prevContext == null || prevContext.setIsInstantPhase(false); } }, [enabled, open, floatingId, onOpenChange, currentIdRef, delayRef, timeoutMs, initialDelayRef, currentContextRef, timeoutIdRef]); useModernLayoutEffect(() => { return () => { currentContextRef.current = null; }; }, [currentContextRef]); return React.useMemo(() => ({ hasProvider, delayRef, isInstantPhase }), [hasProvider, delayRef, isInstantPhase]); } let rafId = 0; function enqueueFocus(el, options) { if (options === void 0) { options = {}; } const { preventScroll = false, cancelPrevious = true, sync = false } = options; cancelPrevious && cancelAnimationFrame(rafId); const exec = () => el == null ? void 0 : el.focus({ preventScroll }); if (sync) { exec(); } else { rafId = requestAnimationFrame(exec); } } function contains(parent, child) { if (!parent || !child) { return false; } const rootNode = child.getRootNode == null ? void 0 : child.getRootNode(); // First, attempt with faster native method if (parent.contains(child)) { return true; } // then fallback to custom implementation with Shadow DOM support if (rootNode && isShadowRoot(rootNode)) { let next = child; while (next) { if (parent === next) { return true; } // @ts-ignore next = next.parentNode || next.host; } } // Give up, the result is false return false; } function getTarget(event) { if ('composedPath' in event) { return event.composedPath()[0]; } // TS thinks `event` is of type never as it assumes all browsers support // `composedPath()`, but browsers without shadow DOM don't. return event.target; } function getDocument(node) { return (node == null ? void 0 : node.ownerDocument) || document; } // Modified to add conditional `aria-hidden` support: // https://github.com/theKashey/aria-hidden/blob/9220c8f4a4fd35f63bee5510a9f41a37264382d4/src/index.ts const counters = { inert: /*#__PURE__*/new WeakMap(), 'aria-hidden': /*#__PURE__*/new WeakMap(), none: /*#__PURE__*/new WeakMap() }; function getCounterMap(control) { if (control === 'inert') return counters.inert; if (control === 'aria-hidden') return counters['aria-hidden']; return counters.none; } let uncontrolledElementsSet = /*#__PURE__*/new WeakSet(); let markerMap = {}; let lockCount$1 = 0; const supportsInert = () => typeof HTMLElement !== 'undefined' && 'inert' in HTMLElement.prototype; const unwrapHost = node => node && (node.host || unwrapHost(node.parentNode)); const correctElements = (parent, targets) => targets.map(target => { if (parent.contains(target)) { return target; } const correctedTarget = unwrapHost(target); if (parent.contains(correctedTarget)) { return correctedTarget; } return null; }).filter(x => x != null); function applyAttributeToOthers(uncorrectedAvoidElements, body, ariaHidden, inert) { const markerName = 'data-floating-ui-inert'; const controlAttribute = inert ? 'inert' : ariaHidden ? 'aria-hidden' : null; const avoidElements = correctElements(body, uncorrectedAvoidElements); const elementsToKeep = new Set(); const elementsToStop = new Set(avoidElements); const hiddenElements = []; if (!markerMap[markerName]) { markerMap[markerName] = new WeakMap(); } const markerCounter = markerMap[markerName]; avoidElements.forEach(keep); deep(body); elementsToKeep.clear(); function keep(el) { if (!el || elementsToKeep.has(el)) { return; } elementsToKeep.add(el); el.parentNode && keep(el.parentNode); } function deep(parent) { if (!parent || elementsToStop.has(parent)) { return; } [].forEach.call(parent.children, node => { if (getNodeName(node) === 'script') return; if (elementsToKeep.has(node)) { deep(node); } else { const attr = controlAttribute ? node.getAttribute(controlAttribute) : null; const alreadyHidden = attr !== null && attr !== 'false'; const counterMap = getCounterMap(controlAttribute); const counterValue = (counterMap.get(node) || 0) + 1; const markerValue = (markerCounter.get(node) || 0) + 1; counterMap.set(node, counterValue); markerCounter.set(node, markerValue); hiddenElements.push(node); if (counterValue === 1 && alreadyHidden) { uncontrolledElementsSet.add(node); } if (markerValue === 1) { node.setAttribute(markerName, ''); } if (!alreadyHidden && controlAttribute) { node.setAttribute(controlAttribute, controlAttribute === 'inert' ? '' : 'true'); } } }); } lockCount$1++; return () => { hiddenElements.forEach(element => { const counterMap = getCounterMap(controlAttribute); const currentCounterValue = counterMap.get(element) || 0; const counterValue = currentCounterValue - 1; const markerValue = (markerCounter.get(element) || 0) - 1; counterMap.set(element, counterValue); markerCounter.set(element, markerValue); if (!counterValue) { if (!uncontrolledElementsSet.has(element) && controlAttribute) { element.removeAttribute(controlAttribute); } uncontrolledElementsSet.delete(element); } if (!markerValue) { element.removeAttribute(markerName); } }); lockCount$1--; if (!lockCount$1) { counters.inert = new WeakMap(); counters['aria-hidden'] = new WeakMap(); counters.none = new WeakMap(); uncontrolledElementsSet = new WeakSet(); markerMap = {}; } }; } function markOthers(avoidElements, ariaHidden, inert) { if (ariaHidden === void 0) { ariaHidden = false; } if (inert === void 0) { inert = false; } const body = getDocument(avoidElements[0]).body; return applyAttributeToOthers(avoidElements.concat(Array.from(body.querySelectorAll('[aria-live],[role="status"],output'))), body, ariaHidden, inert); } const HIDDEN_STYLES = { border: 0, clip: 'rect(0 0 0 0)', height: '1px', margin: '-1px', overflow: 'hidden', padding: 0, position: 'fixed', whiteSpace: 'nowrap', width: '1px', top: 0, left: 0 }; const FocusGuard = /*#__PURE__*/React.forwardRef(function FocusGuard(props, ref) { const [role, setRole] = React.useState(); useModernLayoutEffect(() => { if (isSafari()) { // Unlike other screen readers such as NVDA and JAWS, the virtual cursor // on VoiceOver does trigger the onFocus event, so we can use the focus // trap element. On Safari, only buttons trigger the onFocus event. // NB: "group" role in the Sandbox no longer appears to work, must be a // button role. setRole('button'); } }, []); const restProps = { ref, tabIndex: 0, // Role is only for VoiceOver role, 'aria-hidden': role ? undefined : true, [createAttribute('focus-guard')]: '', style: HIDDEN_STYLES }; return /*#__PURE__*/jsx("span", { ...props, ...restProps }); }); const PortalContext = /*#__PURE__*/React.createContext(null); const attr = /*#__PURE__*/createAttribute('portal'); /** * @see https://floating-ui.com/docs/FloatingPortal#usefloatingportalnode */ function useFloatingPortalNode(props) { if (props === void 0) { props = {}; } const { id, root } = props; const uniqueId = useId(); const portalContext = usePortalContext(); const [portalNode, setPortalNode] = React.useState(null); const portalNodeRef = React.useRef(null); useModernLayoutEffect(() => { return () => { portalNode == null || portalNode.remove(); // Allow the subsequent layout effects to create a new node on updates. // The portal node will still be cleaned up on unmount. // https://github.com/floating-ui/floating-ui/issues/2454 queueMicrotask(() => { portalNodeRef.current = null; }); }; }, [portalNode]); useModernLayoutEffect(() => { // Wait for the uniqueId to be generated before creating the portal node in // React <18 (using `useFloatingId` instead of the native `useId`). // https://github.com/floating-ui/floating-ui/issues/2778 if (!uniqueId) return; if (portalNodeRef.current) return; const existingIdRoot = id ? document.getElementById(id) : null; if (!existingIdRoot) return; const subRoot = document.createElement('div'); subRoot.id = uniqueId; subRoot.setAttribute(attr, ''); existingIdRoot.appendChild(subRoot); portalNodeRef.current = subRoot; setPortalNode(subRoot); }, [id, uniqueId]); useModernLayoutEffect(() => { // Wait for the root to exist before creating the portal node. The root must // be stored in state, not a ref, for this to work reactively. if (root === null) return; if (!uniqueId) return; if (portalNodeRef.current) return; let container = root || (portalContext == null ? void 0 : portalContext.portalNode); if (container && !isNode(container)) container = container.current; container = container || document.body; let idWrapper = null; if (id) { idWrapper = document.createElement('div'); idWrapper.id = id; container.appendChild(idWrapper); } const subRoot = document.createElement('div'); subRoot.id = uniqueId; subRoot.setAttribute(attr, ''); container = idWrapper || container; container.appendChild(subRoot); portalNodeRef.current = subRoot; setPortalNode(subRoot); }, [id, root, uniqueId, portalContext]); return portalNode; } /** * Portals the floating element into a given container element — by default, * outside of the app root and into the body. * This is necessary to ensure the floating element can appear outside any * potential parent containers that cause clipping (such as `overflow: hidden`), * while retaining its location in the React tree. * @see https://floating-ui.com/docs/FloatingPortal */ function FloatingPortal(props) { const { children, id, root, preserveTabOrder = true } = props; const portalNode = useFloatingPortalNode({ id, root }); const [focusManagerState, setFocusManagerState] = React.useState(null); const beforeOutsideRef = React.useRef(null); const afterOutsideRef = React.useRef(null); const beforeInsideRef = React.useRef(null); const afterInsideRef = React.useRef(null); const modal = focusManagerState == null ? void 0 : focusManagerState.modal; const open = focusManagerState == null ? void 0 : focusManagerState.open; const shouldRenderGuards = // The FocusManager and therefore floating element are currently open/ // rendered. !!focusManagerState && // Guards are only for non-modal focus management. !focusManagerState.modal && // Don't render if unmount is transitioning. focusManagerState.open && preserveTabOrder && !!(root || portalNode); // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx React.useEffect(() => { if (!portalNode || !preserveTabOrder || modal) { return; } // Make sure elements inside the portal element are tabbable only when the // portal has already been focused, either by tabbing into a focus trap // element outside or using the mouse. function onFocus(event) { if (portalNode && isOutsideEvent(event)) { const focusing = event.type === 'focusin'; const manageFocus = focusing ? enableFocusInside : disableFocusInside; manageFocus(portalNode); } } // Listen to the event on the capture phase so they run before the focus // trap elements onFocus prop is called. portalNode.addEventListener('focusin', onFocus, true); portalNode.addEventListener('focusout', onFocus, true); return () => { portalNode.removeEventListener('focusin', onFocus, true); portalNode.removeEventListener('focusout', onFocus, true); }; }, [portalNode, preserveTabOrder, modal]); React.useEffect(() => { if (!portalNode) return; if (open) return; enableFocusInside(portalNode); }, [open, portalNode]); return /*#__PURE__*/jsxs(PortalContext.Provider, { value: React.useMemo(() => ({ preserveTabOrder, beforeOutsideRef, afterOutsideRef, beforeInsideRef, afterInsideRef, portalNode, setFocusManagerState }), [preserveTabOrder, portalNode]), children: [shouldRenderGuards && portalNode && /*#__PURE__*/jsx(FocusGuard, { "data-type": "outside", ref: beforeOutsideRef, onFocus: event => { if (isOutsideEvent(event, portalNode)) { var _beforeInsideRef$curr; (_beforeInsideRef$curr = beforeInsideRef.current) == null || _beforeInsideRef$curr.focus(); } else { const domReference = focusManagerState ? focusManagerState.domReference : null; const prevTabbable = getPreviousTabbable(domReference); prevTabbable == null || prevTabbable.focus(); } } }), shouldRenderGuards && portalNode && /*#__PURE__*/jsx("span", { "aria-owns": portalNode.id, style: HIDDEN_STYLES }), portalNode && /*#__PURE__*/ReactDOM.createPortal(children, portalNode), shouldRenderGuards && portalNode && /*#__PURE__*/jsx(FocusGuard, { "data-type": "outside", ref: afterOutsideRef, onFocus: event => { if (isOutsideEvent(event, portalNode)) { var _afterInsideRef$curre; (_afterInsideRef$curre = afterInsideRef.current) == null || _afterInsideRef$curre.focus(); } else { const domReference = focusManagerState ? focusManagerState.domReference : null; const nextTabbable = getNextTabbable(domReference); nextTabbable == null || nextTabbable.focus(); (focusManagerState == null ? void 0 : focusManagerState.closeOnFocusOut) && (focusManagerState == null ? void 0 : focusManagerState.onOpenChange(false, event.nativeEvent, 'focus-out')); } } })] }); } const usePortalContext = () => React.useContext(PortalContext); function useLiteMergeRefs(refs) { return React.useMemo(() => { return value => { refs.forEach(ref => { if (ref) { ref.current = value; } }); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, refs); } const LIST_LIMIT = 20; let previouslyFocusedElements = []; function clearDisconnectedPreviouslyFocusedElements() { previouslyFocusedElements = previouslyFocusedElements.filter(el => el.isConnected); } function addPreviouslyFocusedElement(element) { clearDisconnectedPreviouslyFocusedElements(); if (element && getNodeName(element) !== 'body') { previouslyFocusedElements.push(element); if (previouslyFocusedElements.length > LIST_LIMIT) { previouslyFocusedElements = previouslyFocusedElements.slice(-20); } } } function getPreviouslyFocusedElement() { clearDisconnectedPreviouslyFocusedElements(); return previouslyFocusedElements[previouslyFocusedElements.length - 1]; } function getFirstTabbableElement(container) { const tabbableOptions = getTabbableOptions(); if (isTabbable(container, tabbableOptions)) { return container; } return tabbable(container, tabbableOptions)[0] || container; } function handleTabIndex(floatingFocusElement, orderRef) { var _floatingFocusElement; if (!orderRef.current.includes('floating') && !((_floatingFocusElement = floatingFocusElement.getAttribute('role')) != null && _floatingFocusElement.includes('dialog'))) { return; } const options = getTabbableOptions(); const focusableElements = focusable(floatingFocusElement, options); const tabbableContent = focusableElements.filter(element => { const dataTabIndex = element.getAttribute('data-tabindex') || ''; return isTabbable(element, options) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-'); }); const tabIndex = floatingFocusElement.getAttribute('tabindex'); if (orderRef.current.includes('floating') || tabbableContent.length === 0) { if (tabIndex !== '0') { floatingFocusElement.setAttribute('tabindex', '0'); } } else if (tabIndex !== '-1' || floatingFocusElement.hasAttribute('data-tabindex') && floatingFocusElement.getAttribute('data-tabindex') !== '-1') { floatingFocusElement.setAttribute('tabindex', '-1'); floatingFocusElement.setAttribute('data-tabindex', '-1'); } } const VisuallyHiddenDismiss = /*#__PURE__*/React.forwardRef(function VisuallyHiddenDismiss(props, ref) { return /*#__PURE__*/jsx("button", { ...props, type: "button", ref: ref, tabIndex: -1, style: HIDDEN_STYLES }); }); /** * Provides focus management for the floating element. * @see https://floating-ui.com/docs/FloatingFocusManager */ function FloatingFocusManager(props) { const { context, children, disabled = false, order = ['content'], guards: _guards = true, initialFocus = 0, returnFocus = true, restoreFocus = false, modal = true, visuallyHiddenDismiss = false, closeOnFocusOut = true, outsideElementsInert = false, getInsideElements: _getInsideElements = () => [] } = props; const { open, onOpenChange, events, dataRef, elements: { domReference, floating } } = context; const getNodeId = useEffectEvent(() => { var _dataRef$current$floa; return (_dataRef$current$floa = dataRef.current.floatingContext) == null ? void 0 : _dataRef$current$floa.nodeId; }); const getInsideElements = useEffectEvent(_getInsideElements); const ignoreInitialFocus = typeof initialFocus === 'number' && initialFocus < 0; // If the reference is a combobox and is typeable (e.g. input/textarea), // there are different focus semantics. The guards should not be rendered, but // aria-hidden should be applied to all nodes still. Further, the visually // hidden dismiss button should only appear at the end of the list, not the // start. const isUntrappedTypeableCombobox = isTypeableCombobox(domReference) && ignoreInitialFocus; // Force the guards to be rendered if the `inert` attribute is not supported. const inertSupported = supportsInert(); const guards = inertSupported ? _guards : true; const useInert = !guards || inertSupported && outsideElementsInert; const orderRef = useLatestRef(order); const initialFocusRef = useLatestRef(initialFocus); const returnFocusRef = useLatestRef(returnFocus); const tree = useFloatingTree(); const portalContext = usePortalContext(); const startDismissButtonRef = React.useRef(null); const endDismissButtonRef = React.useRef(null); const preventReturnFocusRef = React.useRef(false); const isPointerDownRef = React.useRef(false); const tabbableIndexRef = React.useRef(-1); const blurTimeoutRef = React.useRef(-1); const isInsidePortal = portalContext != null; const floatingFocusElement = getFloatingFocusElement(floating); const getTabbableContent = useEffectEvent(function (container) { if (container === void 0) { container = floatingFocusElement; } return container ? tabbable(container, getTabbableOptions()) : []; }); const getTabbableElements = useEffectEvent(container => { const content = getTabbableContent(container); return orderRef.current.map(type => { if (domReference && type === 'reference') { return domReference; } if (floatingFocusElement && type === 'floating') { return floatingFocusElement; } return content; }).filter(Boolean).flat(); }); React.useEffect(() => { if (disabled) return; if (!modal) return; function onKeyDown(event) { if (event.key === 'Tab') { // The focus guards have nothing to focus, so we need to stop the event. if (contains$1(floatingFocusElement, activeElement(getDocument$1(floatingFocusElement))) && getTabbableContent().length === 0 && !isUntrappedTypeableCombobox) { stopEvent(event); } const els = getTabbableElements(); const target = getTarget$1(event); if (orderRef.current[0] === 'reference' && target === domReference) { stopEvent(event); if (event.shiftKey) { enqueueFocus(els[els.length - 1]); } else { enqueueFocus(els[1]); } } if (orderRef.current[1] === 'floating' && target === floatingFocusElement && event.shiftKey) { stopEvent(event); enqueueFocus(els[0]); } } } const doc = getDocument$1(floatingFocusElement); doc.addEventListener('keydown', onKeyDown); return () => { doc.removeEventListener('keydown', onKeyDown); }; }, [disabled, domReference, floatingFocusElement, modal, orderRef, isUntrappedTypeableCombobox, getTabbableContent, getTabbableElements]); React.useEffect(() => { if (disabled) return; if (!floating) return; function handleFocusIn(event) { const target = getTarget$1(event); const tabbableContent = getTabbableContent(); const tabbableIndex = tabbableContent.indexOf(target); if (tabbableIndex !== -1) { tabbableIndexRef.current = tabbableIndex; } } floating.addEventListener('focusin', handleFocusIn); return () => { floating.removeEventListener('focusin', handleFocusIn); }; }, [disabled, floating, getTabbableContent]); React.useEffect(() => { if (disabled) return; if (!closeOnFocusOut) return; // In Safari, buttons lose focus when pressing them. function handlePointerDown() { isPointerDownRef.current = true; setTimeout(() => { isPointerDownRef.current = false; }); } function handleFocusOutside(event) { const relatedTarget = event.relatedTarget; const currentTarget = event.currentTarget; const target = getTarget$1(event); queueMicrotask(() => { const nodeId = getNodeId(); const movedToUnrelatedNode = !(contains$1(domReference, relatedTarget) || contains$1(floating, relatedTarget) || contains$1(relatedTarget, floating) || contains$1(portalContext == null ? void 0 : portalContext.portalNode, relatedTarget) || relatedTarget != null && relatedTarget.hasAttribute(createAttribute('focus-guard')) || tree && (getNodeChildren$1(tree.nodesRef.current, nodeId).find(node => { var _node$context, _node$context2; return contains$1((_node$context = node.context) == null ? void 0 : _node$context.elements.floating, relatedTarget) || contains$1((_node$context2 = node.context) == null ? void 0 : _node$context2.elements.domReference, relatedTarget); }) || getNodeAncestors(tree.nodesRef.current, nodeId).find(node => { var _node$context3, _node$context4, _node$context5; return [(_node$context3 = node.context) == null ? void 0 : _node$context3.elements.floating, getFloatingFocusElement((_node$context4 = node.context) == null ? void 0 : _node$context4.elements.floating)].includes(relatedTarget) || ((_node$context5 = node.context) == null ? void 0 : _node$context5.elements.domReference) === relatedTarget; }))); if (currentTarget === domReference && floatingFocusElement) { handleTabIndex(floatingFocusElement, orderRef); } // Restore focus to the previous tabbable element index to prevent // focus from being lost outside the floating tree. if (restoreFocus && currentTarget !== domReference && !(target != null && target.isConnected) && activeElement(getDocument$1(floatingFocusElement)) === getDocument$1(floatingFocusElement).body) { // Let `FloatingPortal` effect knows that focus is still inside the // floating tree. if (isHTMLElement(floatingFocusElement)) { floatingFocusElement.focus(); } const prevTabbableIndex = tabbableIndexRef.current; const tabbableContent = getTabbableContent(); const nodeToFocus = tabbableContent[prevTabbableIndex] || tabbableContent[tabbableContent.length - 1] || floatingFocusElement; if (isHTMLElement(nodeToFocus)) { nodeToFocus.focus(); } } // https://github.com/floating-ui/floating-ui/issues/3060 if (dataRef.current.insideReactTree) { dataRef.current.insideReactTree = false; return; } // Focus did not move inside the floating tree, and there are no tabbable // portal guards to handle closing. if ((isUntrappedTypeableCombobox ? true : !modal) && relatedTarget && movedToUnrelatedNode && !isPointerDownRef.current && // Fix React 18 Strict Mode returnFocus due to double rendering. relatedTarget !== getPreviouslyFocusedElement()) { preventReturnFocusRef.current = true; onOpenChange(false, event, 'focus-out'); } }); } const shouldHandleBlurCapture = Boolean(!tree && portalContext); function markInsideReactTree() { clearTimeoutIfSet(blurTimeoutRef); dataRef.current.insideReactTree = true; blurTimeoutRef.current = window.setTimeout(() => { dataRef.current.insideReactTree = false; }); } if (floating && isHTMLElement(domReference)) { domReference.addEventListener('focusout', handleFocusOutside); domReference.addEventListener('pointerdown', handlePointerDown); floating.addEventListener('focusout', handleFocusOutside); if (shouldHandleBlurCapture) { floating.addEventListener('focusout', markInsideReactTree, true); } return () => { domReference.removeEventListener('focusout', handleFocusOutside); domReference.removeEventListener('pointerdown', handlePointerDown); floating.removeEventListener('focusout', handleFocusOutside); if (shouldHandleBlurCapture) { floating.removeEventListener('focusout', markInsideReactTree, true); } }; } }, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, onOpenChange, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, orderRef, dataRef]); const beforeGuardRef = React.useRef(null); const afterGuardRef = React.useRef(null); const mergedBeforeGuardRef = useLiteMergeRefs([beforeGuardRef, portalContext == null ? void 0 : portalContext.beforeInsideRef]); const mergedAfterGuardRef = useLiteMergeRefs([afterGuardRef, portalContext == null ? void 0 : portalContext.afterInsideRef]); React.useEffect(() => { var _portalContext$portal, _ancestors$find; if (disabled) return; if (!floating) return; // Don't hide portals nested within the parent portal. const portalNodes = Array.from((portalContext == null || (_portalContext$portal = portalContext.portalNode) == null ? void 0 : _portalContext$portal.querySelectorAll("[" + createAttribute('portal') + "]")) || []); const ancestors = tree ? getNodeAncestors(tree.nodesRef.current, getNodeId()) : []; const rootAncestorComboboxDomReference = (_ancestors$find = ancestors.find(node => { var _node$context6; return isTypeableCombobox(((_node$context6 = node.context) == null ? void 0 : _node$context6.elements.domReference) || null); })) == null || (_ancestors$find = _ancestors$find.context) == null ? void 0 : _ancestors$find.elements.domReference; const insideElements = [floating, rootAncestorComboboxDomReference, ...portalNodes, ...getInsideElements(), startDismissButtonRef.current, endDismissButtonRef.current, beforeGuardRef.current, afterGuardRef.current, portalContext == null ? void 0 : portalContext.beforeOutsideRef.current, portalContext == null ? void 0 : portalContext.afterOutsideRef.current, orderRef.current.includes('reference') || isUntrappedTypeableCombobox ? domReference : null].filter(x => x != null); const cleanup = modal || isUntrappedTypeableCombobox ? markOthers(insideElements, !useInert, useInert) : markOthers(insideElements); return () => { cleanup(); }; }, [disabled, domReference, floating, modal, orderRef, portalContext, isUntrappedTypeableCombobox, guards, useInert, tree, getNodeId, getInsideElements]); useModernLayoutEffect(() => { if (disabled || !isHTMLElement(floatingFocusElement)) return; const doc = getDocument$1(floatingFocusElement); const previouslyFocusedElement = activeElement(doc); // Wait for any layout effect state setters to execute to set `tabIndex`. queueMicrotask(() => { const focusableElements = getTabbableElements(floatingFocusElement); const initialFocusValue = initialFocusRef.current; const elToFocus = (typeof initialFocusValue === 'number' ? focusableElements[initialFocusValue] : initialFocusValue.current) || floatingFocusElement; const focusAlreadyInsideFloatingEl = contains$1(floatingFocusElement, previouslyFocusedElement); if (!ignoreInitialFocus && !focusAlreadyInsideFloatingEl && open) { enqueueFocus(elToFocus, { preventScroll: elToFocus === floatingFocusElement }); } }); }, [disabled, open, floatingFocusElement, ignoreInitialFocus, getTabbableElements, initialFocusRef]); useModernLayoutEffect(() => { if (disabled || !floatingFocusElement) return; const doc = getDocument$1(floatingFocusElement); const previouslyFocusedElement = activeElement(doc); addPreviouslyFocusedElement(previouslyFocusedElement); // Dismissing via outside press should always ignore `returnFocus` to // prevent unwanted scrolling. function onOpenChange(_ref) { let { reason, event, nested } = _ref; if (['hover', 'safe-polygon'].includes(reason) && event.type === 'mouseleave') { preventReturnFocusRef.current = true; } if (reason !== 'outside-press') return; if (nested) { preventReturnFocusRef.current = false; } else if (isVirtualClick(event) || isVirtualPointerEvent(event)) { preventReturnFocusRef.current = false; } else { let isPreventScrollSupported = false; document.createElement('div').focus({ get preventScroll() { isPreventScrollSupported = true; return false; } }); if (isPreventScrollSupported) { preventReturnFocusRef.current = false; } else { preventReturnFocusRef.current = true; } } } events.on('openchange', onOpenChange); const fallbackEl = doc.createElement('span'); fallbackEl.setAttribute('tabindex', '-1'); fallbackEl.setAttribute('aria-hidden', 'true'); Object.assign(fallbackEl.style, HIDDEN_STYLES); if (isInsidePortal && domReference) { domReference.insertAdjacentElement('afterend', fallbackEl); } function getReturnElement() { if (typeof returnFocusRef.current === 'boolean') { const el = domReference || getPreviouslyFocusedElement(); return el && el.isConnected ? el : fallbackEl; } return returnFocusRef.current.current || fallbackEl; } return () => { events.off('openchange', onOpenChange); const activeEl = activeElement(doc); const isFocusInsideFloatingTree = contains$1(floating, activeEl) || tree && getNodeChildren$1(tree.nodesRef.current, getNodeId(), false).some(node => { var _node$context7; return contains$1((_node$context7 = node.context) == null ? void 0 : _node$context7.elements.floating, activeEl); }); const returnElement = getReturnElement(); queueMicrotask(() => { // This is `returnElement`, if it's tabbable, or its first tabbable child. const tabbableReturnElement = getFirstTabbableElement(returnElement); if ( // eslint-disable-next-line react-hooks/exhaustive-deps returnFocusRef.current && !preventReturnFocusRef.current && isHTMLElement(tabbableReturnElement) && ( // If the focus moved somewhere else after mount, avoid returning focus // since it likely entered a different element which should be // respected: https://github.com/floating-ui/floating-ui/issues/2607 tabbableReturnElement !== activeEl && activeEl !== doc.body ? isFocusInsideFloatingTree : true)) { tabbableReturnElement.focus({ preventScroll: true }); } fallbackEl.remove(); }); }; }, [disabled, floating, floatingFocusElement, returnFocusRef, dataRef, events, tree, isInsidePortal, domReference, getNodeId]); React.useEffect(() => { // The `returnFocus` cleanup behavior is inside a microtask; ensure we // wait for it to complete before resetting the flag. queueMicrotask(() => { preventReturnFocusRef.current = false; }); return () => { queueMicrotask(clearDisconnectedPreviouslyFocusedElements); }; }, [disabled]); // Synchronize the `context` & `modal` value to the FloatingPortal context. // It will decide whether or not it needs to render its own guards. useModernLayoutEffect(() => { if (disabled) return; if (!portalContext) return; portalContext.setFocusManagerState({ modal, closeOnFocusOut, open, onOpenChange, domReference }); return () => { portalContext.setFocusManagerState(null); }; }, [disabled, portalContext, modal, open, onOpenChange, closeOnFocusOut, domReference]); useModernLayoutEffect(() => { if (disabled) return; if (!floatingFocusElement) return; handleTabIndex(floatingFocusElement, orderRef); }, [disabled, floatingFocusElement, orderRef]); function renderDismissButton(location) { if (disabled || !visuallyHiddenDismiss || !modal) { return null; } return /*#__PURE__*/jsx(VisuallyHiddenDismiss, { ref: location === 'start' ? startDismissButtonRef : endDismissButtonRef, onClick: event => onOpenChange(false, event.nativeEvent), children: typeof visuallyHiddenDismiss === 'string' ? visuallyHiddenDismiss : 'Dismiss' }); } const shouldRenderGuards = !disabled && guards && (modal ? !isUntrappedTypeableCombobox : true) && (isInsidePortal || modal); return /*#__PURE__*/jsxs(Fragment, { children: [shouldRenderGuards && /*#__PURE__*/jsx(FocusGuard, { "data-type": "inside", ref: mergedBeforeGuardRef, onFocus: event => { if (modal) { const els = getTabbableElements(); enqueueFocus(order[0] === 'reference' ? els[0] : els[els.length - 1]); } else if (portalContext != null && portalContext.preserveTabOrder && portalContext.portalNode) { preventReturnFocusRef.current = false; if (isOutsideEvent(event, portalContext.portalNode)) { const nextTabbable = getNextTabbable(domReference); nextTabbable == null || nextTabbable.focus(); } else { var _portalContext$before; (_portalContext$before = portalContext.beforeOutsideRef.current) == null || _portalContext$before.focus(); } } } }), !isUntrappedTypeableCombobox && renderDismissButton('start'), children, renderDismissButton('end'), shouldRenderGuards && /*#__PURE__*/jsx(FocusGuard, { "data-type": "inside", ref: mergedAfterGuardRef, onFocus: event => { if (modal) { enqueueFocus(getTabbableElements()[0]); } else if (portalContext != null && portalContext.preserveTabOrder && portalContext.portalNode) { if (closeOnFocusOut) { preventReturnFocusRef.current = true; } if (isOutsideEvent(event, portalContext.portalNode)) { const prevTabbable = getPreviousTabbable(domReference); prevTabbable == null || prevTabbable.focus(); } else { var _portalContext$afterO; (_portalContext$afterO = portalContext.afterOutsideRef.current) == null || _portalContext$afterO.focus(); } } } })] }); } let lockCount = 0; const scrollbarProperty = '--floating-ui-scrollbar-width'; function enableScrollLock() { const platform = getPlatform(); const isIOS = /iP(hone|ad|od)|iOS/.test(platform) || // iPads can claim to be MacIntel platform === 'MacIntel' && navigator.maxTouchPoints > 1; const bodyStyle = document.body.style; // RTL scrollbar const scrollbarX = Math.round(document.documentElement.getBoundingClientRect().left) + document.documentElement.scrollLeft; const paddingProp = scrollbarX ? 'paddingLeft' : 'paddingRight'; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; const scrollX = bodyStyle.left ? parseFloat(bodyStyle.left) : window.scrollX; const scrollY = bodyStyle.top ? parseFloat(bodyStyle.top) : window.scrollY; bodyStyle.overflow = 'hidden'; bodyStyle.setProperty(scrollbarProperty, scrollbarWidth + "px"); if (scrollbarWidth) { bodyStyle[paddingProp] = scrollbarWidth + "px"; } // Only iOS doesn't respect `overflow: hidden` on document.body, and this // technique has fewer side effects. if (isIOS) { var _window$visualViewpor, _window$visualViewpor2; // iOS 12 does not support `visualViewport`. const offsetLeft = ((_window$visualViewpor = window.visualViewport) == null ? void 0 : _window$visualViewpor.offsetLeft) || 0; const offsetTop = ((_window$visualViewpor2 = window.visualViewport) == null ? void 0 : _window$visualViewpor2.offsetTop) || 0; Object.assign(bodyStyle, { position: 'fixed', top: -(scrollY - Math.floor(offsetTop)) + "px", left: -(scrollX - Math.floor(offsetLeft)) + "px", right: '0' }); } return () => { Object.assign(bodyStyle, { overflow: '', [paddingProp]: '' }); bodyStyle.removeProperty(scrollbarProperty); if (isIOS) { Object.assign(bodyStyle, { position: '', top: '', left: '', right: '' }); window.scrollTo(scrollX, scrollY); } }; } let cleanup = () => {}; /** * Provides base styling for a fixed overlay element to dim content or block * pointer events behind a floating element. * It's a regular `
`, so it can be styled via any CSS solution you prefer. * @see https://floating-ui.com/docs/FloatingOverlay */ const FloatingOverlay = /*#__PURE__*/React.forwardRef(function FloatingOverlay(props, ref) { const { lockScroll = false, ...rest } = props; useModernLayoutEffect(() => { if (!lockScroll) return; lockCount++; if (lockCount === 1) { cleanup = enableScrollLock(); } return () => { lockCount--; if (lockCount === 0) { cleanup(); } }; }, [lockScroll]); return /*#__PURE__*/jsx("div", { ref: ref, ...rest, style: { position: 'fixed', overflow: 'auto', top: 0, right: 0, bottom: 0, left: 0, ...rest.style } }); }); function isButtonTarget(event) { return isHTMLElement(event.target) && event.target.tagName === 'BUTTON'; } function isAnchorTarget(event) { return isHTMLElement(event.target) && event.target.tagName === 'A'; } function isSpaceIgnored(element) { return isTypeableElement(element); } /** * Opens or closes the floating element when clicking the reference element. * @see https://floating-ui.com/docs/useClick */ function useClick(context, props) { if (props === void 0) { props = {}; } const { open, onOpenChange, dataRef, elements: { domReference } } = context; const { enabled = true, event: eventOption = 'click', toggle = true, ignoreMouse = false, keyboardHandlers = true, stickIfOpen = true } = props; const pointerTypeRef = React.useRef(); const didKeyDownRef = React.useRef(false); const reference = React.useMemo(() => ({ onPointerDown(event) { pointerTypeRef.current = event.pointerType; }, onMouseDown(event) { const pointerType = pointerTypeRef.current; // Ignore all buttons except for the "main" button. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button if (event.button !== 0) return; if (eventOption === 'click') return; if (isMouseLikePointerType(pointerType, true) && ignoreMouse) return; if (open && toggle && (dataRef.current.openEvent && stickIfOpen ? dataRef.current.openEvent.type === 'mousedown' : true)) { onOpenChange(false, event.nativeEvent, 'click'); } else { // Prevent stealing focus from the floating element event.preventDefault(); onOpenChange(true, event.nativeEvent, 'click'); } }, onClick(event) { const pointerType = pointerTypeRef.current; if (eventOption === 'mousedown' && pointerTypeRef.current) { pointerTypeRef.current = undefined; return; } if (isMouseLikePointerType(pointerType, true) && ignoreMouse) return; if (open && toggle && (dataRef.current.openEvent && stickIfOpen ? dataRef.current.openEvent.type === 'click' : true)) { onOpenChange(false, event.nativeEvent, 'click'); } else { onOpenChange(true, event.nativeEvent, 'click'); } }, onKeyDown(event) { pointerTypeRef.current = undefined; if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event)) { return; } if (event.key === ' ' && !isSpaceIgnored(domReference)) { // Prevent scrolling event.preventDefault(); didKeyDownRef.current = true; } if (isAnchorTarget(event)) { return; } if (event.key === 'Enter') { if (open && toggle) { onOpenChange(false, event.nativeEvent, 'click'); } else { onOpenChange(true, event.nativeEvent, 'click'); } } }, onKeyUp(event) { if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event) || isSpaceIgnored(domReference)) { return; } if (event.key === ' ' && didKeyDownRef.current) { didKeyDownRef.current = false; if (open && toggle) { onOpenChange(false, event.nativeEvent, 'click'); } else { onOpenChange(true, event.nativeEvent, 'click'); } } } }), [dataRef, domReference, eventOption, ignoreMouse, keyboardHandlers, onOpenChange, open, stickIfOpen, toggle]); return React.useMemo(() => enabled ? { reference } : {}, [enabled, reference]); } function createVirtualElement(domElement, data) { let offsetX = null; let offsetY = null; let isAutoUpdateEvent = false; return { contextElement: domElement || undefined, getBoundingClientRect() { var _data$dataRef$current; const domRect = (domElement == null ? void 0 : domElement.getBoundingClientRect()) || { width: 0, height: 0, x: 0, y: 0 }; const isXAxis = data.axis === 'x' || data.axis === 'both'; const isYAxis = data.axis === 'y' || data.axis === 'both'; const canTrackCursorOnAutoUpdate = ['mouseenter', 'mousemove'].includes(((_data$dataRef$current = data.dataRef.current.openEvent) == null ? void 0 : _data$dataRef$current.type) || '') && data.pointerType !== 'touch'; let width = domRect.width; let height = domRect.height; let x = domRect.x; let y = domRect.y; if (offsetX == null && data.x && isXAxis) { offsetX = domRect.x - data.x; } if (offsetY == null && data.y && isYAxis) { offsetY = domRect.y - data.y; } x -= offsetX || 0; y -= offsetY || 0; width = 0; height = 0; if (!isAutoUpdateEvent || canTrackCursorOnAutoUpdate) { width = data.axis === 'y' ? domRect.width : 0; height = data.axis === 'x' ? domRect.height : 0; x = isXAxis && data.x != null ? data.x : x; y = isYAxis && data.y != null ? data.y : y; } else if (isAutoUpdateEvent && !canTrackCursorOnAutoUpdate) { height = data.axis === 'x' ? domRect.height : height; width = data.axis === 'y' ? domRect.width : width; } isAutoUpdateEvent = true; return { width, height, x, y, top: y, right: x + width, bottom: y + height, left: x }; } }; } function isMouseBasedEvent(event) { return event != null && event.clientX != null; } /** * Positions the floating element relative to a client point (in the viewport), * such as the mouse position. By default, it follows the mouse cursor. * @see https://floating-ui.com/docs/useClientPoint */ function useClientPoint(context, props) { if (props === void 0) { props = {}; } const { open, dataRef, elements: { floating, domReference }, refs } = context; const { enabled = true, axis = 'both', x = null, y = null } = props; const initialRef = React.useRef(false); const cleanupListenerRef = React.useRef(null); const [pointerType, setPointerType] = React.useState(); const [reactive, setReactive] = React.useState([]); const setReference = useEffectEvent((x, y) => { if (initialRef.current) return; // Prevent setting if the open event was not a mouse-like one // (e.g. focus to open, then hover over the reference element). // Only apply if the event exists. if (dataRef.current.openEvent && !isMouseBasedEvent(dataRef.current.openEvent)) { return; } refs.setPositionReference(createVirtualElement(domReference, { x, y, axis, dataRef, pointerType })); }); const handleReferenceEnterOrMove = useEffectEvent(event => { if (x != null || y != null) return; if (!open) { setReference(event.clientX, event.clientY); } else if (!cleanupListenerRef.current) { // If there's no cleanup, there's no listener, but we want to ensure // we add the listener if the cursor landed on the floating element and // then back on the reference (i.e. it's interactive). setReactive([]); } }); // If the pointer is a mouse-like pointer, we want to continue following the // mouse even if the floating element is transitioning out. On touch // devices, this is undesirable because the floating element will move to // the dismissal touch point. const openCheck = isMouseLikePointerType(pointerType) ? floating : open; const addListener = React.useCallback(() => { // Explicitly specified `x`/`y` coordinates shouldn't add a listener. if (!openCheck || !enabled || x != null || y != null) return; const win = getWindow(floating); function handleMouseMove(event) { const target = getTarget$1(event); if (!contains$1(floating, target)) { setReference(event.clientX, event.clientY); } else { win.removeEventListener('mousemove', handleMouseMove); cleanupListenerRef.current = null; } } if (!dataRef.current.openEvent || isMouseBasedEvent(dataRef.current.openEvent)) { win.addEventListener('mousemove', handleMouseMove); const cleanup = () => { win.removeEventListener('mousemove', handleMouseMove); cleanupListenerRef.current = null; }; cleanupListenerRef.current = cleanup; return cleanup; } refs.setPositionReference(domReference); }, [openCheck, enabled, x, y, floating, dataRef, refs, domReference, setReference]); React.useEffect(() => { return addListener(); }, [addListener, reactive]); React.useEffect(() => { if (enabled && !floating) { initialRef.current = false; } }, [enabled, floating]); React.useEffect(() => { if (!enabled && open) { initialRef.current = true; } }, [enabled, open]); useModernLayoutEffect(() => { if (enabled && (x != null || y != null)) { initialRef.current = false; setReference(x, y); } }, [enabled, x, y, setReference]); const reference = React.useMemo(() => { function setPointerTypeRef(_ref) { let { pointerType } = _ref; setPointerType(pointerType); } return { onPointerDown: setPointerTypeRef, onPointerEnter: setPointerTypeRef, onMouseMove: handleReferenceEnterOrMove, onMouseEnter: handleReferenceEnterOrMove }; }, [handleReferenceEnterOrMove]); return React.useMemo(() => enabled ? { reference } : {}, [enabled, reference]); } const bubbleHandlerKeys = { pointerdown: 'onPointerDown', mousedown: 'onMouseDown', click: 'onClick' }; const captureHandlerKeys = { pointerdown: 'onPointerDownCapture', mousedown: 'onMouseDownCapture', click: 'onClickCapture' }; const normalizeProp = normalizable => { var _normalizable$escapeK, _normalizable$outside; return { escapeKey: typeof normalizable === 'boolean' ? normalizable : (_normalizable$escapeK = normalizable == null ? void 0 : normalizable.escapeKey) != null ? _normalizable$escapeK : false, outsidePress: typeof normalizable === 'boolean' ? normalizable : (_normalizable$outside = normalizable == null ? void 0 : normalizable.outsidePress) != null ? _normalizable$outside : true }; }; /** * Closes the floating element when a dismissal is requested — by default, when * the user presses the `escape` key or outside of the floating element. * @see https://floating-ui.com/docs/useDismiss */ function useDismiss(context, props) { if (props === void 0) { props = {}; } const { open, onOpenChange, elements, dataRef } = context; const { enabled = true, escapeKey = true, outsidePress: unstable_outsidePress = true, outsidePressEvent = 'pointerdown', referencePress = false, referencePressEvent = 'pointerdown', ancestorScroll = false, bubbles, capture } = props; const tree = useFloatingTree(); const outsidePressFn = useEffectEvent(typeof unstable_outsidePress === 'function' ? unstable_outsidePress : () => false); const outsidePress = typeof unstable_outsidePress === 'function' ? outsidePressFn : unstable_outsidePress; const endedOrStartedInsideRef = React.useRef(false); const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp(bubbles); const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } = normalizeProp(capture); const isComposingRef = React.useRef(false); const closeOnEscapeKeyDown = useEffectEvent(event => { var _dataRef$current$floa; if (!open || !enabled || !escapeKey || event.key !== 'Escape') { return; } // Wait until IME is settled. Pressing `Escape` while composing should // close the compose menu, but not the floating element. if (isComposingRef.current) { return; } const nodeId = (_dataRef$current$floa = dataRef.current.floatingContext) == null ? void 0 : _dataRef$current$floa.nodeId; const children = tree ? getNodeChildren$1(tree.nodesRef.current, nodeId) : []; if (!escapeKeyBubbles) { event.stopPropagation(); if (children.length > 0) { let shouldDismiss = true; children.forEach(child => { var _child$context; if ((_child$context = child.context) != null && _child$context.open && !child.context.dataRef.current.__escapeKeyBubbles) { shouldDismiss = false; return; } }); if (!shouldDismiss) { return; } } } onOpenChange(false, isReactEvent(event) ? event.nativeEvent : event, 'escape-key'); }); const closeOnEscapeKeyDownCapture = useEffectEvent(event => { var _getTarget2; const callback = () => { var _getTarget; closeOnEscapeKeyDown(event); (_getTarget = getTarget$1(event)) == null || _getTarget.removeEventListener('keydown', callback); }; (_getTarget2 = getTarget$1(event)) == null || _getTarget2.addEventListener('keydown', callback); }); const closeOnPressOutside = useEffectEvent(event => { var _dataRef$current$floa2; // Given developers can stop the propagation of the synthetic event, // we can only be confident with a positive value. const insideReactTree = dataRef.current.insideReactTree; dataRef.current.insideReactTree = false; // When click outside is lazy (`click` event), handle dragging. // Don't close if: // - The click started inside the floating element. // - The click ended inside the floating element. const endedOrStartedInside = endedOrStartedInsideRef.current; endedOrStartedInsideRef.current = false; if (outsidePressEvent === 'click' && endedOrStartedInside) { return; } if (insideReactTree) { return; } if (typeof outsidePress === 'function' && !outsidePress(event)) { return; } const target = getTarget$1(event); const inertSelector = "[" + createAttribute('inert') + "]"; const markers = getDocument$1(elements.floating).querySelectorAll(inertSelector); let targetRootAncestor = isElement(target) ? target : null; while (targetRootAncestor && !isLastTraversableNode(targetRootAncestor)) { const nextParent = getParentNode(targetRootAncestor); if (isLastTraversableNode(nextParent) || !isElement(nextParent)) { break; } targetRootAncestor = nextParent; } // Check if the click occurred on a third-party element injected after the // floating element rendered. if (markers.length && isElement(target) && !isRootElement(target) && // Clicked on a direct ancestor (e.g. FloatingOverlay). !contains$1(target, elements.floating) && // If the target root element contains none of the markers, then the // element was injected after the floating element rendered. Array.from(markers).every(marker => !contains$1(targetRootAncestor, marker))) { return; } // Check if the click occurred on the scrollbar if (isHTMLElement(target) && floating) { const lastTraversableNode = isLastTraversableNode(target); const style = getComputedStyle(target); const scrollRe = /auto|scroll/; const isScrollableX = lastTraversableNode || scrollRe.test(style.overflowX); const isScrollableY = lastTraversableNode || scrollRe.test(style.overflowY); const canScrollX = isScrollableX && target.clientWidth > 0 && target.scrollWidth > target.clientWidth; const canScrollY = isScrollableY && target.clientHeight > 0 && target.scrollHeight > target.clientHeight; const isRTL = style.direction === 'rtl'; // Check click position relative to scrollbar. // In some browsers it is possible to change the (or window) // scrollbar to the left side, but is very rare and is difficult to // check for. Plus, for modal dialogs with backdrops, it is more // important that the backdrop is checked but not so much the window. const pressedVerticalScrollbar = canScrollY && (isRTL ? event.offsetX <= target.offsetWidth - target.clientWidth : event.offsetX > target.clientWidth); const pressedHorizontalScrollbar = canScrollX && event.offsetY > target.clientHeight; if (pressedVerticalScrollbar || pressedHorizontalScrollbar) { return; } } const nodeId = (_dataRef$current$floa2 = dataRef.current.floatingContext) == null ? void 0 : _dataRef$current$floa2.nodeId; const targetIsInsideChildren = tree && getNodeChildren$1(tree.nodesRef.current, nodeId).some(node => { var _node$context; return isEventTargetWithin(event, (_node$context = node.context) == null ? void 0 : _node$context.elements.floating); }); if (isEventTargetWithin(event, elements.floating) || isEventTargetWithin(event, elements.domReference) || targetIsInsideChildren) { return; } const children = tree ? getNodeChildren$1(tree.nodesRef.current, nodeId) : []; if (children.length > 0) { let shouldDismiss = true; children.forEach(child => { var _child$context2; if ((_child$context2 = child.context) != null && _child$context2.open && !child.context.dataRef.current.__outsidePressBubbles) { shouldDismiss = false; return; } }); if (!shouldDismiss) { return; } } onOpenChange(false, event, 'outside-press'); }); const closeOnPressOutsideCapture = useEffectEvent(event => { var _getTarget4; const callback = () => { var _getTarget3; closeOnPressOutside(event); (_getTarget3 = getTarget$1(event)) == null || _getTarget3.removeEventListener(outsidePressEvent, callback); }; (_getTarget4 = getTarget$1(event)) == null || _getTarget4.addEventListener(outsidePressEvent, callback); }); React.useEffect(() => { if (!open || !enabled) { return; } dataRef.current.__escapeKeyBubbles = escapeKeyBubbles; dataRef.current.__outsidePressBubbles = outsidePressBubbles; let compositionTimeout = -1; function onScroll(event) { onOpenChange(false, event, 'ancestor-scroll'); } function handleCompositionStart() { window.clearTimeout(compositionTimeout); isComposingRef.current = true; } function handleCompositionEnd() { // Safari fires `compositionend` before `keydown`, so we need to wait // until the next tick to set `isComposing` to `false`. // https://bugs.webkit.org/show_bug.cgi?id=165004 compositionTimeout = window.setTimeout(() => { isComposingRef.current = false; }, // 0ms or 1ms don't work in Safari. 5ms appears to consistently work. // Only apply to WebKit for the test to remain 0ms. isWebKit() ? 5 : 0); } const doc = getDocument$1(elements.floating); if (escapeKey) { doc.addEventListener('keydown', escapeKeyCapture ? closeOnEscapeKeyDownCapture : closeOnEscapeKeyDown, escapeKeyCapture); doc.addEventListener('compositionstart', handleCompositionStart); doc.addEventListener('compositionend', handleCompositionEnd); } outsidePress && doc.addEventListener(outsidePressEvent, outsidePressCapture ? closeOnPressOutsideCapture : closeOnPressOutside, outsidePressCapture); let ancestors = []; if (ancestorScroll) { if (isElement(elements.domReference)) { ancestors = getOverflowAncestors(elements.domReference); } if (isElement(elements.floating)) { ancestors = ancestors.concat(getOverflowAncestors(elements.floating)); } if (!isElement(elements.reference) && elements.reference && elements.reference.contextElement) { ancestors = ancestors.concat(getOverflowAncestors(elements.reference.contextElement)); } } // Ignore the visual viewport for scrolling dismissal (allow pinch-zoom) ancestors = ancestors.filter(ancestor => { var _doc$defaultView; return ancestor !== ((_doc$defaultView = doc.defaultView) == null ? void 0 : _doc$defaultView.visualViewport); }); ancestors.forEach(ancestor => { ancestor.addEventListener('scroll', onScroll, { passive: true }); }); return () => { if (escapeKey) { doc.removeEventListener('keydown', escapeKeyCapture ? closeOnEscapeKeyDownCapture : closeOnEscapeKeyDown, escapeKeyCapture); doc.removeEventListener('compositionstart', handleCompositionStart); doc.removeEventListener('compositionend', handleCompositionEnd); } outsidePress && doc.removeEventListener(outsidePressEvent, outsidePressCapture ? closeOnPressOutsideCapture : closeOnPressOutside, outsidePressCapture); ancestors.forEach(ancestor => { ancestor.removeEventListener('scroll', onScroll); }); window.clearTimeout(compositionTimeout); }; }, [dataRef, elements, escapeKey, outsidePress, outsidePressEvent, open, onOpenChange, ancestorScroll, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, escapeKeyCapture, closeOnEscapeKeyDownCapture, closeOnPressOutside, outsidePressCapture, closeOnPressOutsideCapture]); React.useEffect(() => { dataRef.current.insideReactTree = false; }, [dataRef, outsidePress, outsidePressEvent]); const reference = React.useMemo(() => ({ onKeyDown: closeOnEscapeKeyDown, ...(referencePress && { [bubbleHandlerKeys[referencePressEvent]]: event => { onOpenChange(false, event.nativeEvent, 'reference-press'); }, ...(referencePressEvent !== 'click' && { onClick(event) { onOpenChange(false, event.nativeEvent, 'reference-press'); } }) }) }), [closeOnEscapeKeyDown, onOpenChange, referencePress, referencePressEvent]); const floating = React.useMemo(() => ({ onKeyDown: closeOnEscapeKeyDown, onMouseDown() { endedOrStartedInsideRef.current = true; }, onMouseUp() { endedOrStartedInsideRef.current = true; }, [captureHandlerKeys[outsidePressEvent]]: () => { dataRef.current.insideReactTree = true; } }), [closeOnEscapeKeyDown, outsidePressEvent, dataRef]); return React.useMemo(() => enabled ? { reference, floating } : {}, [enabled, reference, floating]); } function useFloatingRootContext(options) { const { open = false, onOpenChange: onOpenChangeProp, elements: elementsProp } = options; const floatingId = useId(); const dataRef = React.useRef({}); const [events] = React.useState(() => createEventEmitter()); const nested = useFloatingParentNodeId() != null; if (process.env.NODE_ENV !== "production") { const optionDomReference = elementsProp.reference; if (optionDomReference && !isElement(optionDomReference)) { error('Cannot pass a virtual element to the `elements.reference` option,', 'as it must be a real DOM element. Use `refs.setPositionReference()`', 'instead.'); } } const [positionReference, setPositionReference] = React.useState(elementsProp.reference); const onOpenChange = useEffectEvent((open, event, reason) => { dataRef.current.openEvent = open ? event : undefined; events.emit('openchange', { open, event, reason, nested }); onOpenChangeProp == null || onOpenChangeProp(open, event, reason); }); const refs = React.useMemo(() => ({ setPositionReference }), []); const elements = React.useMemo(() => ({ reference: positionReference || elementsProp.reference || null, floating: elementsProp.floating || null, domReference: elementsProp.reference }), [positionReference, elementsProp.reference, elementsProp.floating]); return React.useMemo(() => ({ dataRef, open, onOpenChange, elements, events, floatingId, refs }), [open, onOpenChange, elements, events, floatingId, refs]); } /** * Provides data to position a floating element and context to add interactions. * @see https://floating-ui.com/docs/useFloating */ function useFloating(options) { if (options === void 0) { options = {}; } const { nodeId } = options; const internalRootContext = useFloatingRootContext({ ...options, elements: { reference: null, floating: null, ...options.elements } }); const rootContext = options.rootContext || internalRootContext; const computedElements = rootContext.elements; const [_domReference, setDomReference] = React.useState(null); const [positionReference, _setPositionReference] = React.useState(null); const optionDomReference = computedElements == null ? void 0 : computedElements.domReference; const domReference = optionDomReference || _domReference; const domReferenceRef = React.useRef(null); const tree = useFloatingTree(); useModernLayoutEffect(() => { if (domReference) { domReferenceRef.current = domReference; } }, [domReference]); const position = useFloating$1({ ...options, elements: { ...computedElements, ...(positionReference && { reference: positionReference }) } }); const setPositionReference = React.useCallback(node => { const computedPositionReference = isElement(node) ? { getBoundingClientRect: () => node.getBoundingClientRect(), getClientRects: () => node.getClientRects(), contextElement: node } : node; // Store the positionReference in state if the DOM reference is specified externally via the // `elements.reference` option. This ensures that it won't be overridden on future renders. _setPositionReference(computedPositionReference); position.refs.setReference(computedPositionReference); }, [position.refs]); const setReference = React.useCallback(node => { if (isElement(node) || node === null) { domReferenceRef.current = node; setDomReference(node); } // Backwards-compatibility for passing a virtual element to `reference` // after it has set the DOM reference. if (isElement(position.refs.reference.current) || position.refs.reference.current === null || // Don't allow setting virtual elements using the old technique back to // `null` to support `positionReference` + an unstable `reference` // callback ref. node !== null && !isElement(node)) { position.refs.setReference(node); } }, [position.refs]); const refs = React.useMemo(() => ({ ...position.refs, setReference, setPositionReference, domReference: domReferenceRef }), [position.refs, setReference, setPositionReference]); const elements = React.useMemo(() => ({ ...position.elements, domReference: domReference }), [position.elements, domReference]); const context = React.useMemo(() => ({ ...position, ...rootContext, refs, elements, nodeId }), [position, refs, elements, nodeId, rootContext]); useModernLayoutEffect(() => { rootContext.dataRef.current.floatingContext = context; const node = tree == null ? void 0 : tree.nodesRef.current.find(node => node.id === nodeId); if (node) { node.context = context; } }); return React.useMemo(() => ({ ...position, context, refs, elements }), [position, refs, elements, context]); } function isMacSafari() { return isMac() && isSafari(); } /** * Opens the floating element while the reference element has focus, like CSS * `:focus`. * @see https://floating-ui.com/docs/useFocus */ function useFocus(context, props) { if (props === void 0) { props = {}; } const { open, onOpenChange, events, dataRef, elements } = context; const { enabled = true, visibleOnly = true } = props; const blockFocusRef = React.useRef(false); const timeoutRef = React.useRef(-1); const keyboardModalityRef = React.useRef(true); React.useEffect(() => { if (!enabled) return; const win = getWindow(elements.domReference); // If the reference was focused and the user left the tab/window, and the // floating element was not open, the focus should be blocked when they // return to the tab/window. function onBlur() { if (!open && isHTMLElement(elements.domReference) && elements.domReference === activeElement(getDocument$1(elements.domReference))) { blockFocusRef.current = true; } } function onKeyDown() { keyboardModalityRef.current = true; } function onPointerDown() { keyboardModalityRef.current = false; } win.addEventListener('blur', onBlur); if (isMacSafari()) { win.addEventListener('keydown', onKeyDown, true); win.addEventListener('pointerdown', onPointerDown, true); } return () => { win.removeEventListener('blur', onBlur); if (isMacSafari()) { win.removeEventListener('keydown', onKeyDown, true); win.removeEventListener('pointerdown', onPointerDown, true); } }; }, [elements.domReference, open, enabled]); React.useEffect(() => { if (!enabled) return; function onOpenChange(_ref) { let { reason } = _ref; if (reason === 'reference-press' || reason === 'escape-key') { blockFocusRef.current = true; } } events.on('openchange', onOpenChange); return () => { events.off('openchange', onOpenChange); }; }, [events, enabled]); React.useEffect(() => { return () => { clearTimeoutIfSet(timeoutRef); }; }, []); const reference = React.useMemo(() => ({ onMouseLeave() { blockFocusRef.current = false; }, onFocus(event) { if (blockFocusRef.current) return; const target = getTarget$1(event.nativeEvent); if (visibleOnly && isElement(target)) { // Safari fails to match `:focus-visible` if focus was initially // outside the document. if (isMacSafari() && !event.relatedTarget) { if (!keyboardModalityRef.current && !isTypeableElement(target)) { return; } } else if (!matchesFocusVisible(target)) { return; } } onOpenChange(true, event.nativeEvent, 'focus'); }, onBlur(event) { blockFocusRef.current = false; const relatedTarget = event.relatedTarget; const nativeEvent = event.nativeEvent; // Hit the non-modal focus management portal guard. Focus will be // moved into the floating element immediately after. const movedToFocusGuard = isElement(relatedTarget) && relatedTarget.hasAttribute(createAttribute('focus-guard')) && relatedTarget.getAttribute('data-type') === 'outside'; // Wait for the window blur listener to fire. timeoutRef.current = window.setTimeout(() => { var _dataRef$current$floa; const activeEl = activeElement(elements.domReference ? elements.domReference.ownerDocument : document); // Focus left the page, keep it open. if (!relatedTarget && activeEl === elements.domReference) return; // When focusing the reference element (e.g. regular click), then // clicking into the floating element, prevent it from hiding. // Note: it must be focusable, e.g. `tabindex="-1"`. // We can not rely on relatedTarget to point to the correct element // as it will only point to the shadow host of the newly focused element // and not the element that actually has received focus if it is located // inside a shadow root. if (contains$1((_dataRef$current$floa = dataRef.current.floatingContext) == null ? void 0 : _dataRef$current$floa.refs.floating.current, activeEl) || contains$1(elements.domReference, activeEl) || movedToFocusGuard) { return; } onOpenChange(false, nativeEvent, 'focus'); }); } }), [dataRef, elements.domReference, onOpenChange, visibleOnly]); return React.useMemo(() => enabled ? { reference } : {}, [enabled, reference]); } function mergeProps(userProps, propsList, elementKey) { const map = new Map(); const isItem = elementKey === 'item'; let domUserProps = userProps; if (isItem && userProps) { const { [ACTIVE_KEY]: _, [SELECTED_KEY]: __, ...validProps } = userProps; domUserProps = validProps; } return { ...(elementKey === 'floating' && { tabIndex: -1, [FOCUSABLE_ATTRIBUTE]: '' }), ...domUserProps, ...propsList.map(value => { const propsOrGetProps = value ? value[elementKey] : null; if (typeof propsOrGetProps === 'function') { return userProps ? propsOrGetProps(userProps) : null; } return propsOrGetProps; }).concat(userProps).reduce((acc, props) => { if (!props) { return acc; } Object.entries(props).forEach(_ref => { let [key, value] = _ref; if (isItem && [ACTIVE_KEY, SELECTED_KEY].includes(key)) { return; } if (key.indexOf('on') === 0) { if (!map.has(key)) { map.set(key, []); } if (typeof value === 'function') { var _map$get; (_map$get = map.get(key)) == null || _map$get.push(value); acc[key] = function () { var _map$get2; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return (_map$get2 = map.get(key)) == null ? void 0 : _map$get2.map(fn => fn(...args)).find(val => val !== undefined); }; } } else { acc[key] = value; } }); return acc; }, {}) }; } /** * Merges an array of interaction hooks' props into prop getters, allowing * event handler functions to be composed together without overwriting one * another. * @see https://floating-ui.com/docs/useInteractions */ function useInteractions(propsList) { if (propsList === void 0) { propsList = []; } const referenceDeps = propsList.map(key => key == null ? void 0 : key.reference); const floatingDeps = propsList.map(key => key == null ? void 0 : key.floating); const itemDeps = propsList.map(key => key == null ? void 0 : key.item); const getReferenceProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'reference'), // eslint-disable-next-line react-hooks/exhaustive-deps referenceDeps); const getFloatingProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'floating'), // eslint-disable-next-line react-hooks/exhaustive-deps floatingDeps); const getItemProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'item'), // eslint-disable-next-line react-hooks/exhaustive-deps itemDeps); return React.useMemo(() => ({ getReferenceProps, getFloatingProps, getItemProps }), [getReferenceProps, getFloatingProps, getItemProps]); } const ESCAPE = 'Escape'; function doSwitch(orientation, vertical, horizontal) { switch (orientation) { case 'vertical': return vertical; case 'horizontal': return horizontal; default: return vertical || horizontal; } } function isMainOrientationKey(key, orientation) { const vertical = key === ARROW_UP || key === ARROW_DOWN; const horizontal = key === ARROW_LEFT || key === ARROW_RIGHT; return doSwitch(orientation, vertical, horizontal); } function isMainOrientationToEndKey(key, orientation, rtl) { const vertical = key === ARROW_DOWN; const horizontal = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT; return doSwitch(orientation, vertical, horizontal) || key === 'Enter' || key === ' ' || key === ''; } function isCrossOrientationOpenKey(key, orientation, rtl) { const vertical = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT; const horizontal = key === ARROW_DOWN; return doSwitch(orientation, vertical, horizontal); } function isCrossOrientationCloseKey(key, orientation, rtl, cols) { const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT; const horizontal = key === ARROW_UP; if (orientation === 'both' || orientation === 'horizontal' && cols && cols > 1) { return key === ESCAPE; } return doSwitch(orientation, vertical, horizontal); } /** * Adds arrow key-based navigation of a list of items, either using real DOM * focus or virtual focus. * @see https://floating-ui.com/docs/useListNavigation */ function useListNavigation(context, props) { const { open, onOpenChange, elements, floatingId } = context; const { listRef, activeIndex, onNavigate: unstable_onNavigate = () => {}, enabled = true, selectedIndex = null, allowEscape = false, loop = false, nested = false, rtl = false, virtual = false, focusItemOnOpen = 'auto', focusItemOnHover = true, openOnArrowKeyDown = true, disabledIndices = undefined, orientation = 'vertical', parentOrientation, cols = 1, scrollItemIntoView = true, virtualItemRef, itemSizes, dense = false } = props; if (process.env.NODE_ENV !== "production") { if (allowEscape) { if (!loop) { warn('`useListNavigation` looping must be enabled to allow escaping.'); } if (!virtual) { warn('`useListNavigation` must be virtual to allow escaping.'); } } if (orientation === 'vertical' && cols > 1) { warn('In grid list navigation mode (`cols` > 1), the `orientation` should', 'be either "horizontal" or "both".'); } } const floatingFocusElement = getFloatingFocusElement(elements.floating); const floatingFocusElementRef = useLatestRef(floatingFocusElement); const parentId = useFloatingParentNodeId(); const tree = useFloatingTree(); useModernLayoutEffect(() => { context.dataRef.current.orientation = orientation; }, [context, orientation]); const onNavigate = useEffectEvent(() => { unstable_onNavigate(indexRef.current === -1 ? null : indexRef.current); }); const typeableComboboxReference = isTypeableCombobox(elements.domReference); const focusItemOnOpenRef = React.useRef(focusItemOnOpen); const indexRef = React.useRef(selectedIndex != null ? selectedIndex : -1); const keyRef = React.useRef(null); const isPointerModalityRef = React.useRef(true); const previousOnNavigateRef = React.useRef(onNavigate); const previousMountedRef = React.useRef(!!elements.floating); const previousOpenRef = React.useRef(open); const forceSyncFocusRef = React.useRef(false); const forceScrollIntoViewRef = React.useRef(false); const disabledIndicesRef = useLatestRef(disabledIndices); const latestOpenRef = useLatestRef(open); const scrollItemIntoViewRef = useLatestRef(scrollItemIntoView); const selectedIndexRef = useLatestRef(selectedIndex); const [activeId, setActiveId] = React.useState(); const [virtualId, setVirtualId] = React.useState(); const focusItem = useEffectEvent(() => { function runFocus(item) { if (virtual) { var _item$id; if ((_item$id = item.id) != null && _item$id.endsWith('-fui-option')) { item.id = floatingId + "-" + Math.random().toString(16).slice(2, 10); } setActiveId(item.id); tree == null || tree.events.emit('virtualfocus', item); if (virtualItemRef) { virtualItemRef.current = item; } } else { enqueueFocus(item, { sync: forceSyncFocusRef.current, preventScroll: true }); } } const initialItem = listRef.current[indexRef.current]; const forceScrollIntoView = forceScrollIntoViewRef.current; if (initialItem) { runFocus(initialItem); } const scheduler = forceSyncFocusRef.current ? v => v() : requestAnimationFrame; scheduler(() => { const waitedItem = listRef.current[indexRef.current] || initialItem; if (!waitedItem) return; if (!initialItem) { runFocus(waitedItem); } const scrollIntoViewOptions = scrollItemIntoViewRef.current; const shouldScrollIntoView = scrollIntoViewOptions && item && (forceScrollIntoView || !isPointerModalityRef.current); if (shouldScrollIntoView) { // JSDOM doesn't support `.scrollIntoView()` but it's widely supported // by all browsers. waitedItem.scrollIntoView == null || waitedItem.scrollIntoView(typeof scrollIntoViewOptions === 'boolean' ? { block: 'nearest', inline: 'nearest' } : scrollIntoViewOptions); } }); }); // Sync `selectedIndex` to be the `activeIndex` upon opening the floating // element. Also, reset `activeIndex` upon closing the floating element. useModernLayoutEffect(() => { if (!enabled) return; if (open && elements.floating) { if (focusItemOnOpenRef.current && selectedIndex != null) { // Regardless of the pointer modality, we want to ensure the selected // item comes into view when the floating element is opened. forceScrollIntoViewRef.current = true; indexRef.current = selectedIndex; onNavigate(); } } else if (previousMountedRef.current) { // Since the user can specify `onNavigate` conditionally // (onNavigate: open ? setActiveIndex : setSelectedIndex), // we store and call the previous function. indexRef.current = -1; previousOnNavigateRef.current(); } }, [enabled, open, elements.floating, selectedIndex, onNavigate]); // Sync `activeIndex` to be the focused item while the floating element is // open. useModernLayoutEffect(() => { if (!enabled) return; if (!open) return; if (!elements.floating) return; if (activeIndex == null) { forceSyncFocusRef.current = false; if (selectedIndexRef.current != null) { return; } // Reset while the floating element was open (e.g. the list changed). if (previousMountedRef.current) { indexRef.current = -1; focusItem(); } // Initial sync. if ((!previousOpenRef.current || !previousMountedRef.current) && focusItemOnOpenRef.current && (keyRef.current != null || focusItemOnOpenRef.current === true && keyRef.current == null)) { let runs = 0; const waitForListPopulated = () => { if (listRef.current[0] == null) { // Avoid letting the browser paint if possible on the first try, // otherwise use rAF. Don't try more than twice, since something // is wrong otherwise. if (runs < 2) { const scheduler = runs ? requestAnimationFrame : queueMicrotask; scheduler(waitForListPopulated); } runs++; } else { indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? getMinListIndex(listRef, disabledIndicesRef.current) : getMaxListIndex(listRef, disabledIndicesRef.current); keyRef.current = null; onNavigate(); } }; waitForListPopulated(); } } else if (!isIndexOutOfListBounds(listRef, activeIndex)) { indexRef.current = activeIndex; focusItem(); forceScrollIntoViewRef.current = false; } }, [enabled, open, elements.floating, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, disabledIndicesRef]); // Ensure the parent floating element has focus when a nested child closes // to allow arrow key navigation to work after the pointer leaves the child. useModernLayoutEffect(() => { var _nodes$find; if (!enabled || elements.floating || !tree || virtual || !previousMountedRef.current) { return; } const nodes = tree.nodesRef.current; const parent = (_nodes$find = nodes.find(node => node.id === parentId)) == null || (_nodes$find = _nodes$find.context) == null ? void 0 : _nodes$find.elements.floating; const activeEl = activeElement(getDocument$1(elements.floating)); const treeContainsActiveEl = nodes.some(node => node.context && contains$1(node.context.elements.floating, activeEl)); if (parent && !treeContainsActiveEl && isPointerModalityRef.current) { parent.focus({ preventScroll: true }); } }, [enabled, elements.floating, tree, parentId, virtual]); useModernLayoutEffect(() => { if (!enabled) return; if (!tree) return; if (!virtual) return; if (parentId) return; function handleVirtualFocus(item) { setVirtualId(item.id); if (virtualItemRef) { virtualItemRef.current = item; } } tree.events.on('virtualfocus', handleVirtualFocus); return () => { tree.events.off('virtualfocus', handleVirtualFocus); }; }, [enabled, tree, virtual, parentId, virtualItemRef]); useModernLayoutEffect(() => { previousOnNavigateRef.current = onNavigate; previousOpenRef.current = open; previousMountedRef.current = !!elements.floating; }); useModernLayoutEffect(() => { if (!open) { keyRef.current = null; focusItemOnOpenRef.current = focusItemOnOpen; } }, [open, focusItemOnOpen]); const hasActiveIndex = activeIndex != null; const item = React.useMemo(() => { function syncCurrentTarget(currentTarget) { if (!latestOpenRef.current) return; const index = listRef.current.indexOf(currentTarget); if (index !== -1 && indexRef.current !== index) { indexRef.current = index; onNavigate(); } } const props = { onFocus(_ref) { let { currentTarget } = _ref; forceSyncFocusRef.current = true; syncCurrentTarget(currentTarget); }, onClick: _ref2 => { let { currentTarget } = _ref2; return currentTarget.focus({ preventScroll: true }); }, // Safari onMouseMove(_ref3) { let { currentTarget } = _ref3; forceSyncFocusRef.current = true; forceScrollIntoViewRef.current = false; if (focusItemOnHover) { syncCurrentTarget(currentTarget); } }, onPointerLeave(_ref4) { let { pointerType } = _ref4; if (!isPointerModalityRef.current || pointerType === 'touch') { return; } forceSyncFocusRef.current = true; if (!focusItemOnHover) { return; } indexRef.current = -1; onNavigate(); if (!virtual) { var _floatingFocusElement; (_floatingFocusElement = floatingFocusElementRef.current) == null || _floatingFocusElement.focus({ preventScroll: true }); } } }; return props; }, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, virtual]); const getParentOrientation = React.useCallback(() => { var _tree$nodesRef$curren; return parentOrientation != null ? parentOrientation : tree == null || (_tree$nodesRef$curren = tree.nodesRef.current.find(node => node.id === parentId)) == null || (_tree$nodesRef$curren = _tree$nodesRef$curren.context) == null || (_tree$nodesRef$curren = _tree$nodesRef$curren.dataRef) == null ? void 0 : _tree$nodesRef$curren.current.orientation; }, [parentId, tree, parentOrientation]); const commonOnKeyDown = useEffectEvent(event => { isPointerModalityRef.current = false; forceSyncFocusRef.current = true; // When composing a character, Chrome fires ArrowDown twice. Firefox/Safari // don't appear to suffer from this. `event.isComposing` is avoided due to // Safari not supporting it properly (although it's not needed in the first // place for Safari, just avoiding any possible issues). if (event.which === 229) { return; } // If the floating element is animating out, ignore navigation. Otherwise, // the `activeIndex` gets set to 0 despite not being open so the next time // the user ArrowDowns, the first item won't be focused. if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) { return; } if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) { // If the nested list's close key is also the parent navigation key, // let the parent navigate. Otherwise, stop propagating the event. if (!isMainOrientationKey(event.key, getParentOrientation())) { stopEvent(event); } onOpenChange(false, event.nativeEvent, 'list-navigation'); if (isHTMLElement(elements.domReference)) { if (virtual) { tree == null || tree.events.emit('virtualfocus', elements.domReference); } else { elements.domReference.focus(); } } return; } const currentIndex = indexRef.current; const minIndex = getMinListIndex(listRef, disabledIndices); const maxIndex = getMaxListIndex(listRef, disabledIndices); if (!typeableComboboxReference) { if (event.key === 'Home') { stopEvent(event); indexRef.current = minIndex; onNavigate(); } if (event.key === 'End') { stopEvent(event); indexRef.current = maxIndex; onNavigate(); } } // Grid navigation. if (cols > 1) { const sizes = itemSizes || Array.from({ length: listRef.current.length }, () => ({ width: 1, height: 1 })); // To calculate movements on the grid, we use hypothetical cell indices // as if every item was 1x1, then convert back to real indices. const cellMap = createGridCellMap(sizes, cols, dense); const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(listRef, index, disabledIndices)); // last enabled index const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(listRef, index, disabledIndices) ? cellIndex : foundIndex, -1); const index = cellMap[getGridNavigatedIndex({ current: cellMap.map(itemIndex => itemIndex != null ? listRef.current[itemIndex] : null) }, { event, orientation, loop, rtl, cols, // treat undefined (empty grid spaces) as disabled indices so we // don't end up in them disabledIndices: getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || listRef.current.map((_, index) => isListIndexDisabled(listRef, index, disabledIndices) ? index : undefined)), undefined], cellMap), minIndex: minGridIndex, maxIndex: maxGridIndex, prevIndex: getGridCellIndexOfCorner(indexRef.current > maxIndex ? minIndex : indexRef.current, sizes, cellMap, cols, // use a corner matching the edge closest to the direction // we're moving in so we don't end up in the same item. Prefer // top/left over bottom/right. event.key === ARROW_DOWN ? 'bl' : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT) ? 'tr' : 'tl'), stopEvent: true })]; if (index != null) { indexRef.current = index; onNavigate(); } if (orientation === 'both') { return; } } if (isMainOrientationKey(event.key, orientation)) { stopEvent(event); // Reset the index if no item is focused. if (open && !virtual && activeElement(event.currentTarget.ownerDocument) === event.currentTarget) { indexRef.current = isMainOrientationToEndKey(event.key, orientation, rtl) ? minIndex : maxIndex; onNavigate(); return; } if (isMainOrientationToEndKey(event.key, orientation, rtl)) { if (loop) { indexRef.current = currentIndex >= maxIndex ? allowEscape && currentIndex !== listRef.current.length ? -1 : minIndex : findNonDisabledListIndex(listRef, { startingIndex: currentIndex, disabledIndices }); } else { indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef, { startingIndex: currentIndex, disabledIndices })); } } else { if (loop) { indexRef.current = currentIndex <= minIndex ? allowEscape && currentIndex !== -1 ? listRef.current.length : maxIndex : findNonDisabledListIndex(listRef, { startingIndex: currentIndex, decrement: true, disabledIndices }); } else { indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef, { startingIndex: currentIndex, decrement: true, disabledIndices })); } } if (isIndexOutOfListBounds(listRef, indexRef.current)) { indexRef.current = -1; } onNavigate(); } }); const ariaActiveDescendantProp = React.useMemo(() => { return virtual && open && hasActiveIndex && { 'aria-activedescendant': virtualId || activeId }; }, [virtual, open, hasActiveIndex, virtualId, activeId]); const floating = React.useMemo(() => { return { 'aria-orientation': orientation === 'both' ? undefined : orientation, ...(!typeableComboboxReference ? ariaActiveDescendantProp : {}), onKeyDown: commonOnKeyDown, onPointerMove() { isPointerModalityRef.current = true; } }; }, [ariaActiveDescendantProp, commonOnKeyDown, orientation, typeableComboboxReference]); const reference = React.useMemo(() => { function checkVirtualMouse(event) { if (focusItemOnOpen === 'auto' && isVirtualClick(event.nativeEvent)) { focusItemOnOpenRef.current = true; } } function checkVirtualPointer(event) { // `pointerdown` fires first, reset the state then perform the checks. focusItemOnOpenRef.current = focusItemOnOpen; if (focusItemOnOpen === 'auto' && isVirtualPointerEvent(event.nativeEvent)) { focusItemOnOpenRef.current = true; } } return { ...ariaActiveDescendantProp, onKeyDown(event) { isPointerModalityRef.current = false; const isArrowKey = event.key.startsWith('Arrow'); const isHomeOrEndKey = ['Home', 'End'].includes(event.key); const isMoveKey = isArrowKey || isHomeOrEndKey; const isCrossOpenKey = isCrossOrientationOpenKey(event.key, orientation, rtl); const isCrossCloseKey = isCrossOrientationCloseKey(event.key, orientation, rtl, cols); const isParentCrossOpenKey = isCrossOrientationOpenKey(event.key, getParentOrientation(), rtl); const isMainKey = isMainOrientationKey(event.key, orientation); const isNavigationKey = (nested ? isParentCrossOpenKey : isMainKey) || event.key === 'Enter' || event.key.trim() === ''; if (virtual && open) { const rootNode = tree == null ? void 0 : tree.nodesRef.current.find(node => node.parentId == null); const deepestNode = tree && rootNode ? getDeepestNode(tree.nodesRef.current, rootNode.id) : null; if (isMoveKey && deepestNode && virtualItemRef) { const eventObject = new KeyboardEvent('keydown', { key: event.key, bubbles: true }); if (isCrossOpenKey || isCrossCloseKey) { var _deepestNode$context, _deepestNode$context2; const isCurrentTarget = ((_deepestNode$context = deepestNode.context) == null ? void 0 : _deepestNode$context.elements.domReference) === event.currentTarget; const dispatchItem = isCrossCloseKey && !isCurrentTarget ? (_deepestNode$context2 = deepestNode.context) == null ? void 0 : _deepestNode$context2.elements.domReference : isCrossOpenKey ? listRef.current.find(item => (item == null ? void 0 : item.id) === activeId) : null; if (dispatchItem) { stopEvent(event); dispatchItem.dispatchEvent(eventObject); setVirtualId(undefined); } } if ((isMainKey || isHomeOrEndKey) && deepestNode.context) { if (deepestNode.context.open && deepestNode.parentId && event.currentTarget !== deepestNode.context.elements.domReference) { var _deepestNode$context$; stopEvent(event); (_deepestNode$context$ = deepestNode.context.elements.domReference) == null || _deepestNode$context$.dispatchEvent(eventObject); return; } } } return commonOnKeyDown(event); } // If a floating element should not open on arrow key down, avoid // setting `activeIndex` while it's closed. if (!open && !openOnArrowKeyDown && isArrowKey) { return; } if (isNavigationKey) { const isParentMainKey = isMainOrientationKey(event.key, getParentOrientation()); keyRef.current = nested && isParentMainKey ? null : event.key; } if (nested) { if (isParentCrossOpenKey) { stopEvent(event); if (open) { indexRef.current = getMinListIndex(listRef, disabledIndicesRef.current); onNavigate(); } else { onOpenChange(true, event.nativeEvent, 'list-navigation'); } } return; } if (isMainKey) { if (selectedIndex != null) { indexRef.current = selectedIndex; } stopEvent(event); if (!open && openOnArrowKeyDown) { onOpenChange(true, event.nativeEvent, 'list-navigation'); } else { commonOnKeyDown(event); } if (open) { onNavigate(); } } }, onFocus() { if (open && !virtual) { indexRef.current = -1; onNavigate(); } }, onPointerDown: checkVirtualPointer, onPointerEnter: checkVirtualPointer, onMouseDown: checkVirtualMouse, onClick: checkVirtualMouse }; }, [activeId, ariaActiveDescendantProp, cols, commonOnKeyDown, disabledIndicesRef, focusItemOnOpen, listRef, nested, onNavigate, onOpenChange, open, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndex, tree, virtual, virtualItemRef]); return React.useMemo(() => enabled ? { reference, floating, item } : {}, [enabled, reference, floating, item]); } const componentRoleToAriaRoleMap = /*#__PURE__*/new Map([['select', 'listbox'], ['combobox', 'listbox'], ['label', false]]); /** * Adds base screen reader props to the reference and floating elements for a * given floating element `role`. * @see https://floating-ui.com/docs/useRole */ function useRole(context, props) { var _elements$domReferenc, _componentRoleToAriaR; if (props === void 0) { props = {}; } const { open, elements, floatingId: defaultFloatingId } = context; const { enabled = true, role = 'dialog' } = props; const defaultReferenceId = useId(); const referenceId = ((_elements$domReferenc = elements.domReference) == null ? void 0 : _elements$domReferenc.id) || defaultReferenceId; const floatingId = React.useMemo(() => { var _getFloatingFocusElem; return ((_getFloatingFocusElem = getFloatingFocusElement(elements.floating)) == null ? void 0 : _getFloatingFocusElem.id) || defaultFloatingId; }, [elements.floating, defaultFloatingId]); const ariaRole = (_componentRoleToAriaR = componentRoleToAriaRoleMap.get(role)) != null ? _componentRoleToAriaR : role; const parentId = useFloatingParentNodeId(); const isNested = parentId != null; const reference = React.useMemo(() => { if (ariaRole === 'tooltip' || role === 'label') { return { ["aria-" + (role === 'label' ? 'labelledby' : 'describedby')]: open ? floatingId : undefined }; } return { 'aria-expanded': open ? 'true' : 'false', 'aria-haspopup': ariaRole === 'alertdialog' ? 'dialog' : ariaRole, 'aria-controls': open ? floatingId : undefined, ...(ariaRole === 'listbox' && { role: 'combobox' }), ...(ariaRole === 'menu' && { id: referenceId }), ...(ariaRole === 'menu' && isNested && { role: 'menuitem' }), ...(role === 'select' && { 'aria-autocomplete': 'none' }), ...(role === 'combobox' && { 'aria-autocomplete': 'list' }) }; }, [ariaRole, floatingId, isNested, open, referenceId, role]); const floating = React.useMemo(() => { const floatingProps = { id: floatingId, ...(ariaRole && { role: ariaRole }) }; if (ariaRole === 'tooltip' || role === 'label') { return floatingProps; } return { ...floatingProps, ...(ariaRole === 'menu' && { 'aria-labelledby': referenceId }) }; }, [ariaRole, floatingId, referenceId, role]); const item = React.useCallback(_ref => { let { active, selected } = _ref; const commonProps = { role: 'option', ...(active && { id: floatingId + "-fui-option" }) }; // For `menu`, we are unable to tell if the item is a `menuitemradio` // or `menuitemcheckbox`. For backwards-compatibility reasons, also // avoid defaulting to `menuitem` as it may overwrite custom role props. switch (role) { case 'select': case 'combobox': return { ...commonProps, 'aria-selected': selected }; } return {}; }, [floatingId, role]); return React.useMemo(() => enabled ? { reference, floating, item } : {}, [enabled, reference, floating, item]); } // Converts a JS style key like `backgroundColor` to a CSS transition-property // like `background-color`. const camelCaseToKebabCase = str => str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); function execWithArgsOrReturn(valueOrFn, args) { return typeof valueOrFn === 'function' ? valueOrFn(args) : valueOrFn; } function useDelayUnmount(open, durationMs) { const [isMounted, setIsMounted] = React.useState(open); if (open && !isMounted) { setIsMounted(true); } React.useEffect(() => { if (!open && isMounted) { const timeout = setTimeout(() => setIsMounted(false), durationMs); return () => clearTimeout(timeout); } }, [open, isMounted, durationMs]); return isMounted; } /** * Provides a status string to apply CSS transitions to a floating element, * correctly handling placement-aware transitions. * @see https://floating-ui.com/docs/useTransition#usetransitionstatus */ function useTransitionStatus(context, props) { if (props === void 0) { props = {}; } const { open, elements: { floating } } = context; const { duration = 250 } = props; const isNumberDuration = typeof duration === 'number'; const closeDuration = (isNumberDuration ? duration : duration.close) || 0; const [status, setStatus] = React.useState('unmounted'); const isMounted = useDelayUnmount(open, closeDuration); if (!isMounted && status === 'close') { setStatus('unmounted'); } useModernLayoutEffect(() => { if (!floating) return; if (open) { setStatus('initial'); const frame = requestAnimationFrame(() => { // Ensure it opens before paint. With `FloatingDelayGroup`, // this avoids a flicker when moving between floating elements // to ensure one is always open with no missing frames. ReactDOM.flushSync(() => { setStatus('open'); }); }); return () => { cancelAnimationFrame(frame); }; } setStatus('close'); }, [open, floating]); return { isMounted, status }; } /** * Provides styles to apply CSS transitions to a floating element, correctly * handling placement-aware transitions. Wrapper around `useTransitionStatus`. * @see https://floating-ui.com/docs/useTransition#usetransitionstyles */ function useTransitionStyles(context, props) { if (props === void 0) { props = {}; } const { initial: unstable_initial = { opacity: 0 }, open: unstable_open, close: unstable_close, common: unstable_common, duration = 250 } = props; const placement = context.placement; const side = placement.split('-')[0]; const fnArgs = React.useMemo(() => ({ side, placement }), [side, placement]); const isNumberDuration = typeof duration === 'number'; const openDuration = (isNumberDuration ? duration : duration.open) || 0; const closeDuration = (isNumberDuration ? duration : duration.close) || 0; const [styles, setStyles] = React.useState(() => ({ ...execWithArgsOrReturn(unstable_common, fnArgs), ...execWithArgsOrReturn(unstable_initial, fnArgs) })); const { isMounted, status } = useTransitionStatus(context, { duration }); const initialRef = useLatestRef(unstable_initial); const openRef = useLatestRef(unstable_open); const closeRef = useLatestRef(unstable_close); const commonRef = useLatestRef(unstable_common); useModernLayoutEffect(() => { const initialStyles = execWithArgsOrReturn(initialRef.current, fnArgs); const closeStyles = execWithArgsOrReturn(closeRef.current, fnArgs); const commonStyles = execWithArgsOrReturn(commonRef.current, fnArgs); const openStyles = execWithArgsOrReturn(openRef.current, fnArgs) || Object.keys(initialStyles).reduce((acc, key) => { acc[key] = ''; return acc; }, {}); if (status === 'initial') { setStyles(styles => ({ transitionProperty: styles.transitionProperty, ...commonStyles, ...initialStyles })); } if (status === 'open') { setStyles({ transitionProperty: Object.keys(openStyles).map(camelCaseToKebabCase).join(','), transitionDuration: openDuration + "ms", ...commonStyles, ...openStyles }); } if (status === 'close') { const styles = closeStyles || initialStyles; setStyles({ transitionProperty: Object.keys(styles).map(camelCaseToKebabCase).join(','), transitionDuration: closeDuration + "ms", ...commonStyles, ...styles }); } }, [closeDuration, closeRef, initialRef, openRef, commonRef, openDuration, status, fnArgs]); return { isMounted, styles }; } /** * Provides a matching callback that can be used to focus an item as the user * types, often used in tandem with `useListNavigation()`. * @see https://floating-ui.com/docs/useTypeahead */ function useTypeahead(context, props) { var _ref; const { open, dataRef } = context; const { listRef, activeIndex, onMatch: unstable_onMatch, onTypingChange: unstable_onTypingChange, enabled = true, findMatch = null, resetMs = 750, ignoreKeys = [], selectedIndex = null } = props; const timeoutIdRef = React.useRef(-1); const stringRef = React.useRef(''); const prevIndexRef = React.useRef((_ref = selectedIndex != null ? selectedIndex : activeIndex) != null ? _ref : -1); const matchIndexRef = React.useRef(null); const onMatch = useEffectEvent(unstable_onMatch); const onTypingChange = useEffectEvent(unstable_onTypingChange); const findMatchRef = useLatestRef(findMatch); const ignoreKeysRef = useLatestRef(ignoreKeys); useModernLayoutEffect(() => { if (open) { clearTimeoutIfSet(timeoutIdRef); matchIndexRef.current = null; stringRef.current = ''; } }, [open]); useModernLayoutEffect(() => { // Sync arrow key navigation but not typeahead navigation. if (open && stringRef.current === '') { var _ref2; prevIndexRef.current = (_ref2 = selectedIndex != null ? selectedIndex : activeIndex) != null ? _ref2 : -1; } }, [open, selectedIndex, activeIndex]); const setTypingChange = useEffectEvent(value => { if (value) { if (!dataRef.current.typing) { dataRef.current.typing = value; onTypingChange(value); } } else { if (dataRef.current.typing) { dataRef.current.typing = value; onTypingChange(value); } } }); const onKeyDown = useEffectEvent(event => { function getMatchingIndex(list, orderedList, string) { const str = findMatchRef.current ? findMatchRef.current(orderedList, string) : orderedList.find(text => (text == null ? void 0 : text.toLocaleLowerCase().indexOf(string.toLocaleLowerCase())) === 0); return str ? list.indexOf(str) : -1; } const listContent = listRef.current; if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') { if (getMatchingIndex(listContent, listContent, stringRef.current) === -1) { setTypingChange(false); } else if (event.key === ' ') { stopEvent(event); } } if (listContent == null || ignoreKeysRef.current.includes(event.key) || // Character key. event.key.length !== 1 || // Modifier key. event.ctrlKey || event.metaKey || event.altKey) { return; } if (open && event.key !== ' ') { stopEvent(event); setTypingChange(true); } // Bail out if the list contains a word like "llama" or "aaron". TODO: // allow it in this case, too. const allowRapidSuccessionOfFirstLetter = listContent.every(text => { var _text$, _text$2; return text ? ((_text$ = text[0]) == null ? void 0 : _text$.toLocaleLowerCase()) !== ((_text$2 = text[1]) == null ? void 0 : _text$2.toLocaleLowerCase()) : true; }); // Allows the user to cycle through items that start with the same letter // in rapid succession. if (allowRapidSuccessionOfFirstLetter && stringRef.current === event.key) { stringRef.current = ''; prevIndexRef.current = matchIndexRef.current; } stringRef.current += event.key; clearTimeoutIfSet(timeoutIdRef); timeoutIdRef.current = window.setTimeout(() => { stringRef.current = ''; prevIndexRef.current = matchIndexRef.current; setTypingChange(false); }, resetMs); const prevIndex = prevIndexRef.current; const index = getMatchingIndex(listContent, [...listContent.slice((prevIndex || 0) + 1), ...listContent.slice(0, (prevIndex || 0) + 1)], stringRef.current); if (index !== -1) { onMatch(index); matchIndexRef.current = index; } else if (event.key !== ' ') { stringRef.current = ''; setTypingChange(false); } }); const reference = React.useMemo(() => ({ onKeyDown }), [onKeyDown]); const floating = React.useMemo(() => { return { onKeyDown, onKeyUp(event) { if (event.key === ' ') { setTypingChange(false); } } }; }, [onKeyDown, setTypingChange]); return React.useMemo(() => enabled ? { reference, floating } : {}, [enabled, reference, floating]); } function getArgsWithCustomFloatingHeight(state, height) { return { ...state, rects: { ...state.rects, floating: { ...state.rects.floating, height } } }; } /** * Positions the floating element such that an inner element inside of it is * anchored to the reference element. * @see https://floating-ui.com/docs/inner * @deprecated */ const inner = props => ({ name: 'inner', options: props, async fn(state) { const { listRef, overflowRef, onFallbackChange, offset: innerOffset = 0, index = 0, minItemsVisible = 4, referenceOverflowThreshold = 0, scrollRef, ...detectOverflowOptions } = evaluate(props, state); const { rects, elements: { floating } } = state; const item = listRef.current[index]; const scrollEl = (scrollRef == null ? void 0 : scrollRef.current) || floating; // Valid combinations: // 1. Floating element is the scrollRef and has a border (default) // 2. Floating element is not the scrollRef, floating element has a border // 3. Floating element is not the scrollRef, scrollRef has a border // Floating > {...getFloatingProps()} wrapper > scrollRef > items is not // allowed as VoiceOver doesn't work. const clientTop = floating.clientTop || scrollEl.clientTop; const floatingIsBordered = floating.clientTop !== 0; const scrollElIsBordered = scrollEl.clientTop !== 0; const floatingIsScrollEl = floating === scrollEl; if (process.env.NODE_ENV !== "production") { if (!state.placement.startsWith('bottom')) { warn('`placement` side must be "bottom" when using the `inner`', 'middleware.'); } } if (!item) { return {}; } const nextArgs = { ...state, ...(await offset(-item.offsetTop - floating.clientTop - rects.reference.height / 2 - item.offsetHeight / 2 - innerOffset).fn(state)) }; const overflow = await detectOverflow(getArgsWithCustomFloatingHeight(nextArgs, scrollEl.scrollHeight + clientTop + floating.clientTop), detectOverflowOptions); const refOverflow = await detectOverflow(nextArgs, { ...detectOverflowOptions, elementContext: 'reference' }); const diffY = max(0, overflow.top); const nextY = nextArgs.y + diffY; const isScrollable = scrollEl.scrollHeight > scrollEl.clientHeight; const rounder = isScrollable ? v => v : round; const maxHeight = rounder(max(0, scrollEl.scrollHeight + (floatingIsBordered && floatingIsScrollEl || scrollElIsBordered ? clientTop * 2 : 0) - diffY - max(0, overflow.bottom))); scrollEl.style.maxHeight = maxHeight + "px"; scrollEl.scrollTop = diffY; // There is not enough space, fallback to standard anchored positioning if (onFallbackChange) { const shouldFallback = scrollEl.offsetHeight < item.offsetHeight * min(minItemsVisible, listRef.current.length) - 1 || refOverflow.top >= -referenceOverflowThreshold || refOverflow.bottom >= -referenceOverflowThreshold; ReactDOM.flushSync(() => onFallbackChange(shouldFallback)); } if (overflowRef) { overflowRef.current = await detectOverflow(getArgsWithCustomFloatingHeight({ ...nextArgs, y: nextY }, scrollEl.offsetHeight + clientTop + floating.clientTop), detectOverflowOptions); } return { y: nextY }; } }); /** * Changes the `inner` middleware's `offset` upon a `wheel` event to * expand the floating element's height, revealing more list items. * @see https://floating-ui.com/docs/inner * @deprecated */ function useInnerOffset(context, props) { const { open, elements } = context; const { enabled = true, overflowRef, scrollRef, onChange: unstable_onChange } = props; const onChange = useEffectEvent(unstable_onChange); const controlledScrollingRef = React.useRef(false); const prevScrollTopRef = React.useRef(null); const initialOverflowRef = React.useRef(null); React.useEffect(() => { if (!enabled) return; function onWheel(e) { if (e.ctrlKey || !el || overflowRef.current == null) { return; } const dY = e.deltaY; const isAtTop = overflowRef.current.top >= -0.5; const isAtBottom = overflowRef.current.bottom >= -0.5; const remainingScroll = el.scrollHeight - el.clientHeight; const sign = dY < 0 ? -1 : 1; const method = dY < 0 ? 'max' : 'min'; if (el.scrollHeight <= el.clientHeight) { return; } if (!isAtTop && dY > 0 || !isAtBottom && dY < 0) { e.preventDefault(); ReactDOM.flushSync(() => { onChange(d => d + Math[method](dY, remainingScroll * sign)); }); } else if (/firefox/i.test(getUserAgent())) { // Needed to propagate scrolling during momentum scrolling phase once // it gets limited by the boundary. UX improvement, not critical. el.scrollTop += dY; } } const el = (scrollRef == null ? void 0 : scrollRef.current) || elements.floating; if (open && el) { el.addEventListener('wheel', onWheel); // Wait for the position to be ready. requestAnimationFrame(() => { prevScrollTopRef.current = el.scrollTop; if (overflowRef.current != null) { initialOverflowRef.current = { ...overflowRef.current }; } }); return () => { prevScrollTopRef.current = null; initialOverflowRef.current = null; el.removeEventListener('wheel', onWheel); }; } }, [enabled, open, elements.floating, overflowRef, scrollRef, onChange]); const floating = React.useMemo(() => ({ onKeyDown() { controlledScrollingRef.current = true; }, onWheel() { controlledScrollingRef.current = false; }, onPointerMove() { controlledScrollingRef.current = false; }, onScroll() { const el = (scrollRef == null ? void 0 : scrollRef.current) || elements.floating; if (!overflowRef.current || !el || !controlledScrollingRef.current) { return; } if (prevScrollTopRef.current !== null) { const scrollDiff = el.scrollTop - prevScrollTopRef.current; if (overflowRef.current.bottom < -0.5 && scrollDiff < -1 || overflowRef.current.top < -0.5 && scrollDiff > 1) { ReactDOM.flushSync(() => onChange(d => d + scrollDiff)); } } // [Firefox] Wait for the height change to have been applied. requestAnimationFrame(() => { prevScrollTopRef.current = el.scrollTop; }); } }), [elements.floating, onChange, overflowRef, scrollRef]); return React.useMemo(() => enabled ? { floating } : {}, [enabled, floating]); } function getNodeChildren(nodes, id, onlyOpenChildren) { if (onlyOpenChildren === void 0) { onlyOpenChildren = true; } const directChildren = nodes.filter(node => { var _node$context; return node.parentId === id && (!onlyOpenChildren || ((_node$context = node.context) == null ? void 0 : _node$context.open)); }); return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]); } function isPointInPolygon(point, polygon) { const [x, y] = point; let isInside = false; const length = polygon.length; for (let i = 0, j = length - 1; i < length; j = i++) { const [xi, yi] = polygon[i] || [0, 0]; const [xj, yj] = polygon[j] || [0, 0]; const intersect = yi >= y !== yj >= y && x <= (xj - xi) * (y - yi) / (yj - yi) + xi; if (intersect) { isInside = !isInside; } } return isInside; } function isInside(point, rect) { return point[0] >= rect.x && point[0] <= rect.x + rect.width && point[1] >= rect.y && point[1] <= rect.y + rect.height; } /** * Generates a safe polygon area that the user can traverse without closing the * floating element once leaving the reference element. * @see https://floating-ui.com/docs/useHover#safepolygon */ function safePolygon(options) { if (options === void 0) { options = {}; } const { buffer = 0.5, blockPointerEvents = false, requireIntent = true } = options; const timeoutRef = { current: -1 }; let hasLanded = false; let lastX = null; let lastY = null; let lastCursorTime = typeof performance !== 'undefined' ? performance.now() : 0; function getCursorSpeed(x, y) { const currentTime = performance.now(); const elapsedTime = currentTime - lastCursorTime; if (lastX === null || lastY === null || elapsedTime === 0) { lastX = x; lastY = y; lastCursorTime = currentTime; return null; } const deltaX = x - lastX; const deltaY = y - lastY; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); const speed = distance / elapsedTime; // px / ms lastX = x; lastY = y; lastCursorTime = currentTime; return speed; } const fn = _ref => { let { x, y, placement, elements, onClose, nodeId, tree } = _ref; return function onMouseMove(event) { function close() { clearTimeoutIfSet(timeoutRef); onClose(); } clearTimeoutIfSet(timeoutRef); if (!elements.domReference || !elements.floating || placement == null || x == null || y == null) { return; } const { clientX, clientY } = event; const clientPoint = [clientX, clientY]; const target = getTarget(event); const isLeave = event.type === 'mouseleave'; const isOverFloatingEl = contains(elements.floating, target); const isOverReferenceEl = contains(elements.domReference, target); const refRect = elements.domReference.getBoundingClientRect(); const rect = elements.floating.getBoundingClientRect(); const side = placement.split('-')[0]; const cursorLeaveFromRight = x > rect.right - rect.width / 2; const cursorLeaveFromBottom = y > rect.bottom - rect.height / 2; const isOverReferenceRect = isInside(clientPoint, refRect); const isFloatingWider = rect.width > refRect.width; const isFloatingTaller = rect.height > refRect.height; const left = (isFloatingWider ? refRect : rect).left; const right = (isFloatingWider ? refRect : rect).right; const top = (isFloatingTaller ? refRect : rect).top; const bottom = (isFloatingTaller ? refRect : rect).bottom; if (isOverFloatingEl) { hasLanded = true; if (!isLeave) { return; } } if (isOverReferenceEl) { hasLanded = false; } if (isOverReferenceEl && !isLeave) { hasLanded = true; return; } // Prevent overlapping floating element from being stuck in an open-close // loop: https://github.com/floating-ui/floating-ui/issues/1910 if (isLeave && isElement(event.relatedTarget) && contains(elements.floating, event.relatedTarget)) { return; } // If any nested child is open, abort. if (tree && getNodeChildren(tree.nodesRef.current, nodeId).length) { return; } // If the pointer is leaving from the opposite side, the "buffer" logic // creates a point where the floating element remains open, but should be // ignored. // A constant of 1 handles floating point rounding errors. if (side === 'top' && y >= refRect.bottom - 1 || side === 'bottom' && y <= refRect.top + 1 || side === 'left' && x >= refRect.right - 1 || side === 'right' && x <= refRect.left + 1) { return close(); } // Ignore when the cursor is within the rectangular trough between the // two elements. Since the triangle is created from the cursor point, // which can start beyond the ref element's edge, traversing back and // forth from the ref to the floating element can cause it to close. This // ensures it always remains open in that case. let rectPoly = []; switch (side) { case 'top': rectPoly = [[left, refRect.top + 1], [left, rect.bottom - 1], [right, rect.bottom - 1], [right, refRect.top + 1]]; break; case 'bottom': rectPoly = [[left, rect.top + 1], [left, refRect.bottom - 1], [right, refRect.bottom - 1], [right, rect.top + 1]]; break; case 'left': rectPoly = [[rect.right - 1, bottom], [rect.right - 1, top], [refRect.left + 1, top], [refRect.left + 1, bottom]]; break; case 'right': rectPoly = [[refRect.right - 1, bottom], [refRect.right - 1, top], [rect.left + 1, top], [rect.left + 1, bottom]]; break; } function getPolygon(_ref2) { let [x, y] = _ref2; switch (side) { case 'top': { const cursorPointOne = [isFloatingWider ? x + buffer / 2 : cursorLeaveFromRight ? x + buffer * 4 : x - buffer * 4, y + buffer + 1]; const cursorPointTwo = [isFloatingWider ? x - buffer / 2 : cursorLeaveFromRight ? x + buffer * 4 : x - buffer * 4, y + buffer + 1]; const commonPoints = [[rect.left, cursorLeaveFromRight ? rect.bottom - buffer : isFloatingWider ? rect.bottom - buffer : rect.top], [rect.right, cursorLeaveFromRight ? isFloatingWider ? rect.bottom - buffer : rect.top : rect.bottom - buffer]]; return [cursorPointOne, cursorPointTwo, ...commonPoints]; } case 'bottom': { const cursorPointOne = [isFloatingWider ? x + buffer / 2 : cursorLeaveFromRight ? x + buffer * 4 : x - buffer * 4, y - buffer]; const cursorPointTwo = [isFloatingWider ? x - buffer / 2 : cursorLeaveFromRight ? x + buffer * 4 : x - buffer * 4, y - buffer]; const commonPoints = [[rect.left, cursorLeaveFromRight ? rect.top + buffer : isFloatingWider ? rect.top + buffer : rect.bottom], [rect.right, cursorLeaveFromRight ? isFloatingWider ? rect.top + buffer : rect.bottom : rect.top + buffer]]; return [cursorPointOne, cursorPointTwo, ...commonPoints]; } case 'left': { const cursorPointOne = [x + buffer + 1, isFloatingTaller ? y + buffer / 2 : cursorLeaveFromBottom ? y + buffer * 4 : y - buffer * 4]; const cursorPointTwo = [x + buffer + 1, isFloatingTaller ? y - buffer / 2 : cursorLeaveFromBottom ? y + buffer * 4 : y - buffer * 4]; const commonPoints = [[cursorLeaveFromBottom ? rect.right - buffer : isFloatingTaller ? rect.right - buffer : rect.left, rect.top], [cursorLeaveFromBottom ? isFloatingTaller ? rect.right - buffer : rect.left : rect.right - buffer, rect.bottom]]; return [...commonPoints, cursorPointOne, cursorPointTwo]; } case 'right': { const cursorPointOne = [x - buffer, isFloatingTaller ? y + buffer / 2 : cursorLeaveFromBottom ? y + buffer * 4 : y - buffer * 4]; const cursorPointTwo = [x - buffer, isFloatingTaller ? y - buffer / 2 : cursorLeaveFromBottom ? y + buffer * 4 : y - buffer * 4]; const commonPoints = [[cursorLeaveFromBottom ? rect.left + buffer : isFloatingTaller ? rect.left + buffer : rect.right, rect.top], [cursorLeaveFromBottom ? isFloatingTaller ? rect.left + buffer : rect.right : rect.left + buffer, rect.bottom]]; return [cursorPointOne, cursorPointTwo, ...commonPoints]; } } } if (isPointInPolygon([clientX, clientY], rectPoly)) { return; } if (hasLanded && !isOverReferenceRect) { return close(); } if (!isLeave && requireIntent) { const cursorSpeed = getCursorSpeed(event.clientX, event.clientY); const cursorSpeedThreshold = 0.1; if (cursorSpeed !== null && cursorSpeed < cursorSpeedThreshold) { return close(); } } if (!isPointInPolygon([clientX, clientY], getPolygon([x, y]))) { close(); } else if (!hasLanded && requireIntent) { timeoutRef.current = window.setTimeout(close, 40); } }; }; fn.__options = { blockPointerEvents }; return fn; } export { Composite, CompositeItem, FloatingArrow, FloatingDelayGroup, FloatingFocusManager, FloatingList, FloatingNode, FloatingOverlay, FloatingPortal, FloatingTree, NextFloatingDelayGroup, inner, safePolygon, useClick, useClientPoint, useDelayGroup, useDelayGroupContext, useDismiss, useFloating, useFloatingNodeId, useFloatingParentNodeId, useFloatingPortalNode, useFloatingRootContext, useFloatingTree, useFocus, useHover, useId, useInnerOffset, useInteractions, useListItem, useListNavigation, useMergeRefs, useNextDelayGroup, useRole, useTransitionStatus, useTransitionStyles, useTypeahead };