@jobber/components 8.20.2 → 8.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/BottomSheet-cjs.js +9 -58
  2. package/dist/BottomSheet-es.js +2 -47
  3. package/dist/Card/index.cjs +4 -0
  4. package/dist/Card/index.mjs +4 -0
  5. package/dist/ComboboxChipRemove-cjs.js +3418 -0
  6. package/dist/ComboboxChipRemove-es.js +3366 -0
  7. package/dist/ComboboxPrimitive-cjs.js +170 -0
  8. package/dist/ComboboxPrimitive-es.js +168 -0
  9. package/dist/DataDump/index.cjs +4 -0
  10. package/dist/DataDump/index.mjs +4 -0
  11. package/dist/InputNumberExperimental-cjs.js +45 -44
  12. package/dist/InputNumberExperimental-es.js +3 -2
  13. package/dist/InternalBackdrop-cjs.js +4278 -0
  14. package/dist/InternalBackdrop-es.js +4204 -0
  15. package/dist/Menu/index.cjs +4 -0
  16. package/dist/Menu/index.mjs +4 -0
  17. package/dist/Menu-cjs.js +9 -8
  18. package/dist/Menu-es.js +2 -1
  19. package/dist/MenuSubmenuTrigger-cjs.js +208 -1969
  20. package/dist/MenuSubmenuTrigger-es.js +7 -1759
  21. package/dist/NumberFieldInput-cjs.js +56 -439
  22. package/dist/NumberFieldInput-es.js +6 -376
  23. package/dist/Page/index.cjs +4 -0
  24. package/dist/Page/index.mjs +4 -0
  25. package/dist/ScrollAreaViewport-cjs.js +108 -4356
  26. package/dist/ScrollAreaViewport-es.js +6 -4201
  27. package/dist/buttonRenderAdapter-cjs.js +56 -0
  28. package/dist/buttonRenderAdapter-es.js +51 -0
  29. package/dist/clamp-cjs.js +0 -1194
  30. package/dist/clamp-es.js +1 -1091
  31. package/dist/docs/empty-states/empty-states.md +29 -0
  32. package/dist/index.cjs +4 -0
  33. package/dist/index.mjs +4 -0
  34. package/dist/primitives/BottomSheet/index.cjs +5 -2
  35. package/dist/primitives/BottomSheet/index.mjs +5 -2
  36. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +32 -0
  37. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.types.d.ts +30 -0
  38. package/dist/primitives/ComboboxPrimitive/index.cjs +28 -0
  39. package/dist/primitives/ComboboxPrimitive/index.d.ts +2 -0
  40. package/dist/primitives/ComboboxPrimitive/index.mjs +22 -0
  41. package/dist/primitives/InputNumberExperimental/index.cjs +4 -2
  42. package/dist/primitives/InputNumberExperimental/index.mjs +4 -2
  43. package/dist/primitives/index.cjs +11 -2
  44. package/dist/primitives/index.d.ts +1 -0
  45. package/dist/primitives/index.mjs +10 -2
  46. package/dist/styles.css +767 -0
  47. package/dist/unstyledPrimitives/index.cjs +237 -3588
  48. package/dist/unstyledPrimitives/index.mjs +96 -3447
  49. package/dist/useButton-cjs.js +1196 -0
  50. package/dist/useButton-es.js +1091 -0
  51. package/dist/useCompositeListItem-cjs.js +1792 -0
  52. package/dist/useCompositeListItem-es.js +1762 -0
  53. package/dist/useLabel-cjs.js +411 -0
  54. package/dist/useLabel-es.js +378 -0
  55. package/package.json +2 -2
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
- import { u as useFloatingParentNodeId, F as FloatingRootStore, P as PopupTriggerMap, g as useFloatingTree, h as createAttribute, i as getNodeChildren, j as isIndexOutOfListBounds, k as getMinListIndex, l as getMaxListIndex, m as createGridCellMap, n as isListIndexDisabled, o as getGridNavigatedIndex, p as getGridCellIndices, q as getGridCellIndexOfCorner, r as findNonDisabledListIndex, s as enqueueFocus, t as isElementVisible, v as useDirection, w as DISABLED_TRANSITIONS_STYLE, x as useDialogRootContext, y as popupStateMapping, C as COMPOSITE_KEYS, z as FloatingFocusManager, A as FloatingPortal, B as POPUP_COLLISION_AVOIDANCE, I as InternalBackdrop, E as inertValue, G as FloatingNode, H as DROPDOWN_COLLISION_AVOIDANCE, J as popupStoreSelectors, K as createSelector, R as ReactStore, L as createInitialPopupStoreState, M as FloatingTreeStore, N as fastComponent, O as useOnFirstRender, Q as useFloatingNodeId, T as useOpenInteractionType, U as useImplicitActiveTrigger, V as useOpenStateTransitions, W as useScrollLock, X as useSyncedFloatingRootContext, Y as useDismiss, Z as useRole, _ as TYPEAHEAD_RESET_MS, $ as useInteractions, a0 as FloatingTree, a1 as fastComponentRef, a2 as useTriggerDataForwarding, a3 as useClick, a4 as getTabbableBeforeElement, a5 as isOutsideEvent, a6 as getTabbableAfterElement, a7 as getNextTabbable, a8 as pressableTriggerOpenStateMapping, a9 as FocusGuard, aa as PATIENT_CLICK_THRESHOLD, ab as useTriggerRegistration, ac as triggerOpenStateMapping } from './ScrollAreaViewport-es.js';
3
- import { a as useRefWithInit, E as EMPTY_OBJECT, u as useRenderElement, f as formatErrorMessage, m as mergeProps, b as useMergedRefs, d as EMPTY_ARRAY } from './useRenderElement-es.js';
4
- import { E as isMouseLikePointerType, F as useId, d as useIsoLayoutEffect, b as useTimeout, g as getTarget, G as isTypeableElement, H as matchesFocusVisible, I as isTargetInsideEnabledTrigger, m as createChangeEventDetails, J as triggerFocus, K as activeElement, L as contains, M as isMac, N as isSafari, o as ownerDocument, O as triggerPress, P as escapeKey, h as useOnMount, Q as TYPEABLE_SELECTOR, T as Timeout, a as useStableCallback, R as triggerHover, k as useValueAsRef, U as getFloatingFocusElement, V as isTypeableCombobox, C as stopEvent, W as listNavigation, X as ARROW_DOWN, Y as ARROW_LEFT, Z as ARROW_RIGHT, _ as focusOut, $ as isVirtualClick, a0 as isVirtualPointerEvent, a1 as ARROW_UP, A as useButton, a2 as closePress, u as useBaseUiId, a3 as itemPress, t as transitionStatusMapping, f as useOpenChangeComplete, a4 as outsidePress, a5 as useAnimationsFinished, a6 as siblingOpen, j as useControlled, c as useTransitionStatus, a7 as imperativeAction, a8 as useCompositeRootContext, a9 as cancelOpen } from './clamp-es.js';
2
+ import { u as useDialogRootContext, C as COMPOSITE_KEYS, p as popupStoreSelectors, g as createInitialPopupStoreState, h as useImplicitActiveTrigger, i as useOpenStateTransitions, j as useSyncedFloatingRootContext, k as useRole, l as useTriggerDataForwarding, m as useTriggerRegistration } from './ScrollAreaViewport-es.js';
3
+ import { a as useRefWithInit, u as useRenderElement, f as formatErrorMessage, m as mergeProps, b as useMergedRefs, E as EMPTY_OBJECT, d as EMPTY_ARRAY } from './useRenderElement-es.js';
4
+ import { W as isMouseLikePointerType, b as useTimeout, g as getTarget, X as isTypeableElement, Y as matchesFocusVisible, Z as isTargetInsideEnabledTrigger, n as createChangeEventDetails, _ as triggerFocus, l as activeElement, m as contains, $ as isMac, a0 as isSafari, o as ownerDocument, a1 as triggerPress, a2 as escapeKey, z as useOnMount, a3 as TYPEABLE_SELECTOR, T as Timeout, a as useStableCallback, d as useIsoLayoutEffect, a4 as triggerHover, j as useValueAsRef, N as useButton, a5 as closePress, u as useBaseUiId, a6 as itemPress, t as transitionStatusMapping, f as useOpenChangeComplete, a7 as outsidePress, a8 as useAnimationsFinished, a9 as siblingOpen, B as useControlled, c as useTransitionStatus, h as useId, v as focusOut, p as listNavigation, Q as imperativeAction, aa as useCompositeRootContext, ab as cancelOpen } from './useButton-es.js';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
- import { b as isElement, k as getWindow, a as isHTMLElement, d as isLastTraversableNode, e as getParentNode } from './floating-ui.utils.dom-es.js';
7
- import { e as evaluate, h as getPaddingObject, b as getAlignmentAxis, d as getAlignment, q as getAxisLength, n as clamp, c as getSide, g as getSideAxis } from './index.esm-es.js';
8
- import { u as useFloating$1, h as hide$1, f as flip, d as size, b as autoUpdate, o as offset, s as shift, l as limitShift } from './floating-ui.react-dom-es.js';
6
+ import { u as useCompositeListItem, g as getDisabledMountTransitionStyles, a as useAnchorPositioning, b as adaptiveOrigin, C as CompositeList, c as useListNavigation, d as useTypeahead, e as getPseudoElementBounds } from './useCompositeListItem-es.js';
7
+ import { B as createAttribute, a as useFloatingTree, u as useFloatingParentNodeId, E as getNodeChildren, p as popupStateMapping, o as FloatingFocusManager, q as FloatingPortal, G as POPUP_COLLISION_AVOIDANCE, I as InternalBackdrop, r as inertValue, H as FloatingNode, J as DROPDOWN_COLLISION_AVOIDANCE, n as createSelector, R as ReactStore, P as PopupTriggerMap, K as FloatingTreeStore, L as fastComponent, x as useOnFirstRender, M as useFloatingNodeId, s as useOpenInteractionType, v as useScrollLock, t as useDismiss, m as useDirection, T as TYPEAHEAD_RESET_MS, w as useInteractions, N as FloatingTree, O as fastComponentRef, y as useClick, Q as getTabbableBeforeElement, S as isOutsideEvent, U as getTabbableAfterElement, V as getNextTabbable, W as pressableTriggerOpenStateMapping, X as FocusGuard, Y as PATIENT_CLICK_THRESHOLD, A as triggerOpenStateMapping } from './InternalBackdrop-es.js';
8
+ import { k as getWindow, b as isElement, a as isHTMLElement, d as isLastTraversableNode, e as getParentNode } from './floating-ui.utils.dom-es.js';
9
9
  import * as ReactDOM from 'react-dom';
10
10
 
11
11
  function resolveValue(value, pointerType) {
@@ -34,162 +34,6 @@ function isClickLikeOpenEvent(openEventType, interactedInside) {
34
34
  return interactedInside || openEventType === 'click' || openEventType === 'mousedown';
35
35
  }
36
36
 
37
- function useFloatingRootContext(options) {
38
- const {
39
- open = false,
40
- onOpenChange,
41
- elements = {}
42
- } = options;
43
- const floatingId = useId();
44
- const nested = useFloatingParentNodeId() != null;
45
- if (process.env.NODE_ENV !== 'production') {
46
- const optionDomReference = elements.reference;
47
- if (optionDomReference && !isElement(optionDomReference)) {
48
- console.error('Cannot pass a virtual element to the `elements.reference` option,', 'as it must be a real DOM element. Use `context.setPositionReference()`', 'instead.');
49
- }
50
- }
51
- const store = useRefWithInit(() => new FloatingRootStore({
52
- open,
53
- onOpenChange,
54
- referenceElement: elements.reference ?? null,
55
- floatingElement: elements.floating ?? null,
56
- triggerElements: new PopupTriggerMap(),
57
- floatingId,
58
- nested,
59
- noEmit: false
60
- })).current;
61
- useIsoLayoutEffect(() => {
62
- const valuesToSync = {
63
- open,
64
- floatingId
65
- };
66
-
67
- // Only sync elements that are defined to avoid overwriting existing ones
68
- if (elements.reference !== undefined) {
69
- valuesToSync.referenceElement = elements.reference;
70
- valuesToSync.domReferenceElement = isElement(elements.reference) ? elements.reference : null;
71
- }
72
- if (elements.floating !== undefined) {
73
- valuesToSync.floatingElement = elements.floating;
74
- }
75
- store.update(valuesToSync);
76
- }, [open, floatingId, elements.reference, elements.floating, store]);
77
- store.context.onOpenChange = onOpenChange;
78
- store.context.nested = nested;
79
- store.context.noEmit = false;
80
- return store;
81
- }
82
-
83
- /**
84
- * Provides data to position a floating element and context to add interactions.
85
- * @see https://floating-ui.com/docs/useFloating
86
- */
87
- function useFloating(options = {}) {
88
- const {
89
- nodeId,
90
- externalTree
91
- } = options;
92
- const internalRootStore = useFloatingRootContext(options);
93
- const rootContext = options.rootContext || internalRootStore;
94
- const rootContextElements = {
95
- reference: rootContext.useState('referenceElement'),
96
- floating: rootContext.useState('floatingElement'),
97
- domReference: rootContext.useState('domReferenceElement')
98
- };
99
- const [positionReference, setPositionReferenceRaw] = React.useState(null);
100
- const domReferenceRef = React.useRef(null);
101
- const tree = useFloatingTree(externalTree);
102
- useIsoLayoutEffect(() => {
103
- if (rootContextElements.domReference) {
104
- domReferenceRef.current = rootContextElements.domReference;
105
- }
106
- }, [rootContextElements.domReference]);
107
- const position = useFloating$1({
108
- ...options,
109
- elements: {
110
- ...rootContextElements,
111
- ...(positionReference && {
112
- reference: positionReference
113
- })
114
- }
115
- });
116
- const setPositionReference = React.useCallback(node => {
117
- const computedPositionReference = isElement(node) ? {
118
- getBoundingClientRect: () => node.getBoundingClientRect(),
119
- getClientRects: () => node.getClientRects(),
120
- contextElement: node
121
- } : node;
122
- // Store the positionReference in state if the DOM reference is specified externally via the
123
- // `elements.reference` option. This ensures that it won't be overridden on future renders.
124
- setPositionReferenceRaw(computedPositionReference);
125
- position.refs.setReference(computedPositionReference);
126
- }, [position.refs]);
127
- const [localDomReference, setLocalDomReference] = React.useState(null);
128
- const [localFloatingElement, setLocalFloatingElement] = React.useState(null);
129
- rootContext.useSyncedValue('referenceElement', localDomReference);
130
- rootContext.useSyncedValue('domReferenceElement', isElement(localDomReference) ? localDomReference : null);
131
- rootContext.useSyncedValue('floatingElement', localFloatingElement);
132
- const setReference = React.useCallback(node => {
133
- if (isElement(node) || node === null) {
134
- domReferenceRef.current = node;
135
- setLocalDomReference(node);
136
- }
137
-
138
- // Backwards-compatibility for passing a virtual element to `reference`
139
- // after it has set the DOM reference.
140
- if (isElement(position.refs.reference.current) || position.refs.reference.current === null ||
141
- // Don't allow setting virtual elements using the old technique back to
142
- // `null` to support `positionReference` + an unstable `reference`
143
- // callback ref.
144
- node !== null && !isElement(node)) {
145
- position.refs.setReference(node);
146
- }
147
- }, [position.refs, setLocalDomReference]);
148
- const setFloating = React.useCallback(node => {
149
- setLocalFloatingElement(node);
150
- position.refs.setFloating(node);
151
- }, [position.refs]);
152
- const refs = React.useMemo(() => ({
153
- ...position.refs,
154
- setReference,
155
- setFloating,
156
- setPositionReference,
157
- domReference: domReferenceRef
158
- }), [position.refs, setReference, setFloating, setPositionReference]);
159
- const elements = React.useMemo(() => ({
160
- ...position.elements,
161
- domReference: rootContextElements.domReference
162
- }), [position.elements, rootContextElements.domReference]);
163
- const open = rootContext.useState('open');
164
- const floatingId = rootContext.useState('floatingId');
165
- const context = React.useMemo(() => ({
166
- ...position,
167
- dataRef: rootContext.context.dataRef,
168
- open,
169
- onOpenChange: rootContext.setOpen,
170
- events: rootContext.context.events,
171
- floatingId,
172
- refs,
173
- elements,
174
- nodeId,
175
- rootStore: rootContext
176
- }), [position, refs, elements, nodeId, rootContext, open, floatingId]);
177
- useIsoLayoutEffect(() => {
178
- rootContext.context.dataRef.current.floatingContext = context;
179
- const node = tree?.nodesRef.current.find(n => n.id === nodeId);
180
- if (node) {
181
- node.context = context;
182
- }
183
- });
184
- return React.useMemo(() => ({
185
- ...position,
186
- context,
187
- refs,
188
- elements,
189
- rootStore: rootContext
190
- }), [position, refs, elements, context, rootContext]);
191
- }
192
-
193
37
  const isMacSafari = isMac && isSafari;
194
38
  /**
195
39
  * Opens the floating element while the reference element has focus, like CSS
@@ -827,756 +671,6 @@ function useHoverReferenceInteraction(context, props = {}) {
827
671
  }, [enabled, instance, isClickLikeOpenEvent$1, isOverInactiveTrigger, mouseOnly, store, restMsRef]);
828
672
  }
829
673
 
830
- const ESCAPE = 'Escape';
831
- function doSwitch(orientation, vertical, horizontal) {
832
- switch (orientation) {
833
- case 'vertical':
834
- return vertical;
835
- case 'horizontal':
836
- return horizontal;
837
- default:
838
- return vertical || horizontal;
839
- }
840
- }
841
- function isMainOrientationKey(key, orientation) {
842
- const vertical = key === ARROW_UP || key === ARROW_DOWN;
843
- const horizontal = key === ARROW_LEFT || key === ARROW_RIGHT;
844
- return doSwitch(orientation, vertical, horizontal);
845
- }
846
- function isMainOrientationToEndKey(key, orientation, rtl) {
847
- const vertical = key === ARROW_DOWN;
848
- const horizontal = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;
849
- return doSwitch(orientation, vertical, horizontal) || key === 'Enter' || key === ' ' || key === '';
850
- }
851
- function isCrossOrientationOpenKey(key, orientation, rtl) {
852
- const vertical = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;
853
- const horizontal = key === ARROW_DOWN;
854
- return doSwitch(orientation, vertical, horizontal);
855
- }
856
- function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
857
- const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT;
858
- const horizontal = key === ARROW_UP;
859
- if (orientation === 'both' || orientation === 'horizontal' && cols && cols > 1) {
860
- return key === ESCAPE;
861
- }
862
- return doSwitch(orientation, vertical, horizontal);
863
- }
864
- /**
865
- * Adds arrow key-based navigation of a list of items, either using real DOM
866
- * focus or virtual focus.
867
- * @see https://floating-ui.com/docs/useListNavigation
868
- */
869
- function useListNavigation(context, props) {
870
- const store = 'rootStore' in context ? context.rootStore : context;
871
- const open = store.useState('open');
872
- const floatingElement = store.useState('floatingElement');
873
- const domReferenceElement = store.useState('domReferenceElement');
874
- const dataRef = store.context.dataRef;
875
- const {
876
- listRef,
877
- activeIndex,
878
- onNavigate: onNavigateProp = () => {},
879
- enabled = true,
880
- selectedIndex = null,
881
- allowEscape = false,
882
- loopFocus = false,
883
- nested = false,
884
- rtl = false,
885
- virtual = false,
886
- focusItemOnOpen = 'auto',
887
- focusItemOnHover = true,
888
- openOnArrowKeyDown = true,
889
- disabledIndices = undefined,
890
- orientation = 'vertical',
891
- parentOrientation,
892
- cols = 1,
893
- id,
894
- resetOnPointerLeave = true,
895
- externalTree
896
- } = props;
897
- if (process.env.NODE_ENV !== 'production') {
898
- if (allowEscape) {
899
- if (!loopFocus) {
900
- console.warn('`useListNavigation` looping must be enabled to allow escaping.');
901
- }
902
- if (!virtual) {
903
- console.warn('`useListNavigation` must be virtual to allow escaping.');
904
- }
905
- }
906
- if (orientation === 'vertical' && cols > 1) {
907
- console.warn('In grid list navigation mode (`cols` > 1), the `orientation` should', 'be either "horizontal" or "both".');
908
- }
909
- }
910
- const floatingFocusElement = getFloatingFocusElement(floatingElement);
911
- const floatingFocusElementRef = useValueAsRef(floatingFocusElement);
912
- const parentId = useFloatingParentNodeId();
913
- const tree = useFloatingTree(externalTree);
914
- useIsoLayoutEffect(() => {
915
- dataRef.current.orientation = orientation;
916
- }, [dataRef, orientation]);
917
- const typeableComboboxReference = isTypeableCombobox(domReferenceElement);
918
- const focusItemOnOpenRef = React.useRef(focusItemOnOpen);
919
- const indexRef = React.useRef(selectedIndex ?? -1);
920
- const keyRef = React.useRef(null);
921
- const isPointerModalityRef = React.useRef(true);
922
- const onNavigate = useStableCallback(event => {
923
- onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event);
924
- });
925
- const previousOnNavigateRef = React.useRef(onNavigate);
926
- const previousMountedRef = React.useRef(!!floatingElement);
927
- const previousOpenRef = React.useRef(open);
928
- const forceSyncFocusRef = React.useRef(false);
929
- const forceScrollIntoViewRef = React.useRef(false);
930
- const disabledIndicesRef = useValueAsRef(disabledIndices);
931
- const latestOpenRef = useValueAsRef(open);
932
- const selectedIndexRef = useValueAsRef(selectedIndex);
933
- const resetOnPointerLeaveRef = useValueAsRef(resetOnPointerLeave);
934
- const focusItem = useStableCallback(() => {
935
- function runFocus(item) {
936
- if (virtual) {
937
- tree?.events.emit('virtualfocus', item);
938
- } else {
939
- enqueueFocus(item, {
940
- sync: forceSyncFocusRef.current,
941
- preventScroll: true
942
- });
943
- }
944
- }
945
- const initialItem = listRef.current[indexRef.current];
946
- const forceScrollIntoView = forceScrollIntoViewRef.current;
947
- if (initialItem) {
948
- runFocus(initialItem);
949
- }
950
- const scheduler = forceSyncFocusRef.current ? v => v() : requestAnimationFrame;
951
- scheduler(() => {
952
- const waitedItem = listRef.current[indexRef.current] || initialItem;
953
- if (!waitedItem) {
954
- return;
955
- }
956
- if (!initialItem) {
957
- runFocus(waitedItem);
958
- }
959
- const shouldScrollIntoView =
960
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
961
- item && (forceScrollIntoView || !isPointerModalityRef.current);
962
- if (shouldScrollIntoView) {
963
- // JSDOM doesn't support `.scrollIntoView()` but it's widely supported
964
- // by all browsers.
965
- waitedItem.scrollIntoView?.({
966
- block: 'nearest',
967
- inline: 'nearest'
968
- });
969
- }
970
- });
971
- });
972
-
973
- // Sync `selectedIndex` to be the `activeIndex` upon opening the floating
974
- // element. Also, reset `activeIndex` upon closing the floating element.
975
- useIsoLayoutEffect(() => {
976
- if (!enabled) {
977
- return;
978
- }
979
- if (open && floatingElement) {
980
- indexRef.current = selectedIndex ?? -1;
981
- if (focusItemOnOpenRef.current && selectedIndex != null) {
982
- // Regardless of the pointer modality, we want to ensure the selected
983
- // item comes into view when the floating element is opened.
984
- forceScrollIntoViewRef.current = true;
985
- onNavigate();
986
- }
987
- } else if (previousMountedRef.current) {
988
- // Since the user can specify `onNavigate` conditionally
989
- // (onNavigate: open ? setActiveIndex : setSelectedIndex),
990
- // we store and call the previous function.
991
- indexRef.current = -1;
992
- previousOnNavigateRef.current();
993
- }
994
- }, [enabled, open, floatingElement, selectedIndex, onNavigate]);
995
-
996
- // Sync `activeIndex` to be the focused item while the floating element is
997
- // open.
998
- useIsoLayoutEffect(() => {
999
- if (!enabled) {
1000
- return;
1001
- }
1002
- if (!open) {
1003
- forceSyncFocusRef.current = false;
1004
- return;
1005
- }
1006
- if (!floatingElement) {
1007
- return;
1008
- }
1009
- if (activeIndex == null) {
1010
- forceSyncFocusRef.current = false;
1011
- if (selectedIndexRef.current != null) {
1012
- return;
1013
- }
1014
-
1015
- // Reset while the floating element was open (e.g. the list changed).
1016
- if (previousMountedRef.current) {
1017
- indexRef.current = -1;
1018
- focusItem();
1019
- }
1020
-
1021
- // Initial sync.
1022
- if ((!previousOpenRef.current || !previousMountedRef.current) && focusItemOnOpenRef.current && (keyRef.current != null || focusItemOnOpenRef.current === true && keyRef.current == null)) {
1023
- let runs = 0;
1024
- const waitForListPopulated = () => {
1025
- if (listRef.current[0] == null) {
1026
- // Avoid letting the browser paint if possible on the first try,
1027
- // otherwise use rAF. Don't try more than twice, since something
1028
- // is wrong otherwise.
1029
- if (runs < 2) {
1030
- const scheduler = runs ? requestAnimationFrame : queueMicrotask;
1031
- scheduler(waitForListPopulated);
1032
- }
1033
- runs += 1;
1034
- } else {
1035
- // initially focus the first non-disabled item
1036
- indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? getMinListIndex(listRef) : getMaxListIndex(listRef);
1037
- keyRef.current = null;
1038
- onNavigate();
1039
- }
1040
- };
1041
- waitForListPopulated();
1042
- }
1043
- } else if (!isIndexOutOfListBounds(listRef, activeIndex)) {
1044
- indexRef.current = activeIndex;
1045
- focusItem();
1046
- forceScrollIntoViewRef.current = false;
1047
- }
1048
- }, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, disabledIndicesRef]);
1049
-
1050
- // Ensure the parent floating element has focus when a nested child closes
1051
- // to allow arrow key navigation to work after the pointer leaves the child.
1052
- useIsoLayoutEffect(() => {
1053
- if (!enabled || floatingElement || !tree || virtual || !previousMountedRef.current) {
1054
- return;
1055
- }
1056
- const nodes = tree.nodesRef.current;
1057
- const parent = nodes.find(node => node.id === parentId)?.context?.elements.floating;
1058
- const activeEl = activeElement(ownerDocument(floatingElement));
1059
- const treeContainsActiveEl = nodes.some(node => node.context && contains(node.context.elements.floating, activeEl));
1060
- if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {
1061
- parent.focus({
1062
- preventScroll: true
1063
- });
1064
- }
1065
- }, [enabled, floatingElement, tree, parentId, virtual]);
1066
- useIsoLayoutEffect(() => {
1067
- previousOnNavigateRef.current = onNavigate;
1068
- previousOpenRef.current = open;
1069
- previousMountedRef.current = !!floatingElement;
1070
- });
1071
- useIsoLayoutEffect(() => {
1072
- if (!open) {
1073
- keyRef.current = null;
1074
- focusItemOnOpenRef.current = focusItemOnOpen;
1075
- }
1076
- }, [open, focusItemOnOpen]);
1077
- const hasActiveIndex = activeIndex != null;
1078
- const item = React.useMemo(() => {
1079
- function syncCurrentTarget(event) {
1080
- if (!latestOpenRef.current) {
1081
- return;
1082
- }
1083
- const index = listRef.current.indexOf(event.currentTarget);
1084
- if (index !== -1 && indexRef.current !== index) {
1085
- indexRef.current = index;
1086
- onNavigate(event);
1087
- }
1088
- }
1089
- const itemProps = {
1090
- onFocus(event) {
1091
- forceSyncFocusRef.current = true;
1092
- syncCurrentTarget(event);
1093
- },
1094
- onClick: ({
1095
- currentTarget
1096
- }) => currentTarget.focus({
1097
- preventScroll: true
1098
- }),
1099
- // Safari
1100
- onMouseMove(event) {
1101
- forceSyncFocusRef.current = true;
1102
- forceScrollIntoViewRef.current = false;
1103
- if (focusItemOnHover) {
1104
- syncCurrentTarget(event);
1105
- }
1106
- },
1107
- onPointerLeave(event) {
1108
- if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
1109
- return;
1110
- }
1111
- forceSyncFocusRef.current = true;
1112
- const relatedTarget = event.relatedTarget;
1113
- if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
1114
- return;
1115
- }
1116
- if (!resetOnPointerLeaveRef.current) {
1117
- return;
1118
- }
1119
- enqueueFocus(null, {
1120
- sync: true
1121
- });
1122
- indexRef.current = -1;
1123
- onNavigate(event);
1124
- if (!virtual) {
1125
- const floatingFocusEl = floatingFocusElementRef.current;
1126
- const activeEl = activeElement(ownerDocument(floatingFocusEl));
1127
- if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {
1128
- floatingFocusEl.focus({
1129
- preventScroll: true
1130
- });
1131
- }
1132
- }
1133
- }
1134
- };
1135
- return itemProps;
1136
- }, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
1137
- const getParentOrientation = React.useCallback(() => {
1138
- return parentOrientation ?? tree?.nodesRef.current.find(node => node.id === parentId)?.context?.dataRef?.current.orientation;
1139
- }, [parentId, tree, parentOrientation]);
1140
- const commonOnKeyDown = useStableCallback(event => {
1141
- isPointerModalityRef.current = false;
1142
- forceSyncFocusRef.current = true;
1143
-
1144
- // When composing a character, Chrome fires ArrowDown twice. Firefox/Safari
1145
- // don't appear to suffer from this. `event.isComposing` is avoided due to
1146
- // Safari not supporting it properly (although it's not needed in the first
1147
- // place for Safari, just avoiding any possible issues).
1148
- if (event.which === 229) {
1149
- return;
1150
- }
1151
-
1152
- // If the floating element is animating out, ignore navigation. Otherwise,
1153
- // the `activeIndex` gets set to 0 despite not being open so the next time
1154
- // the user ArrowDowns, the first item won't be focused.
1155
- if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {
1156
- return;
1157
- }
1158
- if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) {
1159
- // If the nested list's close key is also the parent navigation key,
1160
- // let the parent navigate. Otherwise, stop propagating the event.
1161
- if (!isMainOrientationKey(event.key, getParentOrientation())) {
1162
- stopEvent(event);
1163
- }
1164
- store.setOpen(false, createChangeEventDetails(listNavigation, event.nativeEvent));
1165
- if (isHTMLElement(domReferenceElement)) {
1166
- if (virtual) {
1167
- tree?.events.emit('virtualfocus', domReferenceElement);
1168
- } else {
1169
- domReferenceElement.focus();
1170
- }
1171
- }
1172
- return;
1173
- }
1174
- const currentIndex = indexRef.current;
1175
- const minIndex = getMinListIndex(listRef, disabledIndices);
1176
- const maxIndex = getMaxListIndex(listRef, disabledIndices);
1177
- if (!typeableComboboxReference) {
1178
- if (event.key === 'Home') {
1179
- stopEvent(event);
1180
- indexRef.current = minIndex;
1181
- onNavigate(event);
1182
- }
1183
- if (event.key === 'End') {
1184
- stopEvent(event);
1185
- indexRef.current = maxIndex;
1186
- onNavigate(event);
1187
- }
1188
- }
1189
-
1190
- // Grid navigation.
1191
- if (cols > 1) {
1192
- const sizes = Array.from({
1193
- length: listRef.current.length
1194
- }, () => ({
1195
- width: 1,
1196
- height: 1
1197
- }));
1198
- // To calculate movements on the grid, we use hypothetical cell indices
1199
- // as if every item was 1x1, then convert back to real indices.
1200
- const cellMap = createGridCellMap(sizes, cols);
1201
- const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(listRef, index, disabledIndices));
1202
- // last enabled index
1203
- const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(listRef, index, disabledIndices) ? cellIndex : foundIndex, -1);
1204
- const index = cellMap[getGridNavigatedIndex({
1205
- current: cellMap.map(itemIndex => itemIndex != null ? listRef.current[itemIndex] : null)
1206
- }, {
1207
- event,
1208
- orientation,
1209
- loopFocus,
1210
- rtl,
1211
- cols,
1212
- // treat undefined (empty grid spaces) as disabled indices so we
1213
- // don't end up in them
1214
- disabledIndices: getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || listRef.current.map((_, listIndex) => isListIndexDisabled(listRef, listIndex, disabledIndices) ? listIndex : undefined)), undefined], cellMap),
1215
- minIndex: minGridIndex,
1216
- maxIndex: maxGridIndex,
1217
- prevIndex: getGridCellIndexOfCorner(indexRef.current > maxIndex ? minIndex : indexRef.current, sizes, cellMap, cols,
1218
- // use a corner matching the edge closest to the direction
1219
- // we're moving in so we don't end up in the same item. Prefer
1220
- // top/left over bottom/right.
1221
- // eslint-disable-next-line no-nested-ternary
1222
- event.key === ARROW_DOWN ? 'bl' : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT) ? 'tr' : 'tl'),
1223
- stopEvent: true
1224
- })];
1225
- if (index != null) {
1226
- indexRef.current = index;
1227
- onNavigate(event);
1228
- }
1229
- if (orientation === 'both') {
1230
- return;
1231
- }
1232
- }
1233
- if (isMainOrientationKey(event.key, orientation)) {
1234
- stopEvent(event);
1235
-
1236
- // Reset the index if no item is focused.
1237
- if (open && !virtual && activeElement(event.currentTarget.ownerDocument) === event.currentTarget) {
1238
- indexRef.current = isMainOrientationToEndKey(event.key, orientation, rtl) ? minIndex : maxIndex;
1239
- onNavigate(event);
1240
- return;
1241
- }
1242
- if (isMainOrientationToEndKey(event.key, orientation, rtl)) {
1243
- if (loopFocus) {
1244
- if (currentIndex >= maxIndex) {
1245
- if (allowEscape && currentIndex !== listRef.current.length) {
1246
- indexRef.current = -1;
1247
- } else {
1248
- // Give time for virtualizers to update the listRef.
1249
- forceSyncFocusRef.current = false;
1250
- indexRef.current = minIndex;
1251
- }
1252
- } else {
1253
- indexRef.current = findNonDisabledListIndex(listRef, {
1254
- startingIndex: currentIndex,
1255
- disabledIndices
1256
- });
1257
- }
1258
- } else {
1259
- indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef, {
1260
- startingIndex: currentIndex,
1261
- disabledIndices
1262
- }));
1263
- }
1264
- } else if (loopFocus) {
1265
- if (currentIndex <= minIndex) {
1266
- if (allowEscape && currentIndex !== -1) {
1267
- indexRef.current = listRef.current.length;
1268
- } else {
1269
- // Give time for virtualizers to update the listRef.
1270
- forceSyncFocusRef.current = false;
1271
- indexRef.current = maxIndex;
1272
- }
1273
- } else {
1274
- indexRef.current = findNonDisabledListIndex(listRef, {
1275
- startingIndex: currentIndex,
1276
- decrement: true,
1277
- disabledIndices
1278
- });
1279
- }
1280
- } else {
1281
- indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef, {
1282
- startingIndex: currentIndex,
1283
- decrement: true,
1284
- disabledIndices
1285
- }));
1286
- }
1287
- if (isIndexOutOfListBounds(listRef, indexRef.current)) {
1288
- indexRef.current = -1;
1289
- }
1290
- onNavigate(event);
1291
- }
1292
- });
1293
- const ariaActiveDescendantProp = React.useMemo(() => {
1294
- return virtual && open && hasActiveIndex && {
1295
- 'aria-activedescendant': `${id}-${activeIndex}`
1296
- };
1297
- }, [virtual, open, hasActiveIndex, id, activeIndex]);
1298
- const floating = React.useMemo(() => {
1299
- return {
1300
- 'aria-orientation': orientation === 'both' ? undefined : orientation,
1301
- ...(!typeableComboboxReference ? ariaActiveDescendantProp : {}),
1302
- onKeyDown(event) {
1303
- // Close submenu on Shift+Tab
1304
- if (event.key === 'Tab' && event.shiftKey && open && !virtual) {
1305
- // If the event originated from within a nested element (e.g., a Dialog opened from
1306
- // within the menu), don't close the menu. The nested element has its own focus
1307
- // management and should handle the Tab key.
1308
- const target = getTarget(event.nativeEvent);
1309
- if (target && !contains(floatingFocusElementRef.current, target)) {
1310
- return;
1311
- }
1312
- stopEvent(event);
1313
- store.setOpen(false, createChangeEventDetails(focusOut, event.nativeEvent));
1314
- if (isHTMLElement(domReferenceElement)) {
1315
- domReferenceElement.focus();
1316
- }
1317
- return;
1318
- }
1319
- commonOnKeyDown(event);
1320
- },
1321
- onPointerMove() {
1322
- isPointerModalityRef.current = true;
1323
- }
1324
- };
1325
- }, [ariaActiveDescendantProp, commonOnKeyDown, floatingFocusElementRef, orientation, typeableComboboxReference, store, open, virtual, domReferenceElement]);
1326
- const trigger = React.useMemo(() => {
1327
- function checkVirtualMouse(event) {
1328
- if (focusItemOnOpen === 'auto' && isVirtualClick(event.nativeEvent)) {
1329
- focusItemOnOpenRef.current = !virtual;
1330
- }
1331
- }
1332
- function checkVirtualPointer(event) {
1333
- // `pointerdown` fires first, reset the state then perform the checks.
1334
- focusItemOnOpenRef.current = focusItemOnOpen;
1335
- if (focusItemOnOpen === 'auto' && isVirtualPointerEvent(event.nativeEvent)) {
1336
- focusItemOnOpenRef.current = true;
1337
- }
1338
- }
1339
- return {
1340
- onKeyDown(event) {
1341
- // non-reactive open state (to prevent re-creation of the handler)
1342
- const currentOpen = store.select('open');
1343
- isPointerModalityRef.current = false;
1344
- const isArrowKey = event.key.startsWith('Arrow');
1345
- const isParentCrossOpenKey = isCrossOrientationOpenKey(event.key, getParentOrientation(), rtl);
1346
- const isMainKey = isMainOrientationKey(event.key, orientation);
1347
- const isNavigationKey = (nested ? isParentCrossOpenKey : isMainKey) || event.key === 'Enter' || event.key.trim() === '';
1348
- if (virtual && currentOpen) {
1349
- return commonOnKeyDown(event);
1350
- }
1351
-
1352
- // If a floating element should not open on arrow key down, avoid
1353
- // setting `activeIndex` while it's closed.
1354
- if (!currentOpen && !openOnArrowKeyDown && isArrowKey) {
1355
- return undefined;
1356
- }
1357
- if (isNavigationKey) {
1358
- const isParentMainKey = isMainOrientationKey(event.key, getParentOrientation());
1359
- keyRef.current = nested && isParentMainKey ? null : event.key;
1360
- }
1361
- if (nested) {
1362
- if (isParentCrossOpenKey) {
1363
- stopEvent(event);
1364
- if (currentOpen) {
1365
- indexRef.current = getMinListIndex(listRef, disabledIndicesRef.current);
1366
- onNavigate(event);
1367
- } else {
1368
- store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
1369
- }
1370
- }
1371
- return undefined;
1372
- }
1373
- if (isMainKey) {
1374
- if (selectedIndexRef.current != null) {
1375
- indexRef.current = selectedIndexRef.current;
1376
- }
1377
- stopEvent(event);
1378
- if (!currentOpen && openOnArrowKeyDown) {
1379
- store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
1380
- } else {
1381
- commonOnKeyDown(event);
1382
- }
1383
- if (currentOpen) {
1384
- onNavigate(event);
1385
- }
1386
- }
1387
- return undefined;
1388
- },
1389
- onFocus(event) {
1390
- if (store.select('open') && !virtual) {
1391
- indexRef.current = -1;
1392
- onNavigate(event);
1393
- }
1394
- },
1395
- onPointerDown: checkVirtualPointer,
1396
- onPointerEnter: checkVirtualPointer,
1397
- onMouseDown: checkVirtualMouse,
1398
- onClick: checkVirtualMouse
1399
- };
1400
- }, [commonOnKeyDown, disabledIndicesRef, focusItemOnOpen, listRef, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
1401
- const reference = React.useMemo(() => {
1402
- return {
1403
- ...ariaActiveDescendantProp,
1404
- ...trigger
1405
- };
1406
- }, [ariaActiveDescendantProp, trigger]);
1407
- return React.useMemo(() => enabled ? {
1408
- reference,
1409
- floating,
1410
- item,
1411
- trigger
1412
- } : {}, [enabled, reference, floating, trigger, item]);
1413
- }
1414
-
1415
- /**
1416
- * Provides a matching callback that can be used to focus an item as the user
1417
- * types, often used in tandem with `useListNavigation()`.
1418
- * @see https://floating-ui.com/docs/useTypeahead
1419
- */
1420
- function useTypeahead(context, props) {
1421
- const store = 'rootStore' in context ? context.rootStore : context;
1422
- const dataRef = store.context.dataRef;
1423
- const open = store.useState('open');
1424
- const {
1425
- listRef,
1426
- elementsRef,
1427
- activeIndex,
1428
- onMatch: onMatchProp,
1429
- onTypingChange,
1430
- enabled = true,
1431
- resetMs = 750,
1432
- selectedIndex = null
1433
- } = props;
1434
- const timeout = useTimeout();
1435
- const stringRef = React.useRef('');
1436
- const prevIndexRef = React.useRef(selectedIndex ?? activeIndex ?? -1);
1437
- const matchIndexRef = React.useRef(null);
1438
- useIsoLayoutEffect(() => {
1439
- if (!open && selectedIndex !== null) {
1440
- return;
1441
- }
1442
- timeout.clear();
1443
- matchIndexRef.current = null;
1444
- if (stringRef.current !== '') {
1445
- stringRef.current = '';
1446
- }
1447
- }, [open, selectedIndex, timeout]);
1448
- useIsoLayoutEffect(() => {
1449
- // Sync arrow key navigation but not typeahead navigation.
1450
- if (open && stringRef.current === '') {
1451
- prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
1452
- }
1453
- }, [open, selectedIndex, activeIndex]);
1454
- const setTypingChange = useStableCallback(value => {
1455
- if (value) {
1456
- if (!dataRef.current.typing) {
1457
- dataRef.current.typing = value;
1458
- onTypingChange?.(value);
1459
- }
1460
- } else if (dataRef.current.typing) {
1461
- dataRef.current.typing = value;
1462
- onTypingChange?.(value);
1463
- }
1464
- });
1465
- const onKeyDown = useStableCallback(event => {
1466
- function isVisible(index) {
1467
- const element = elementsRef?.current[index];
1468
- return !element || isElementVisible(element);
1469
- }
1470
- function getMatchingIndex(list, string, startIndex = 0) {
1471
- if (list.length === 0) {
1472
- return -1;
1473
- }
1474
- const normalizedStartIndex = (startIndex % list.length + list.length) % list.length;
1475
- const lowerString = string.toLocaleLowerCase();
1476
- for (let offset = 0; offset < list.length; offset += 1) {
1477
- const index = (normalizedStartIndex + offset) % list.length;
1478
- const text = list[index];
1479
- if (!text?.toLocaleLowerCase().startsWith(lowerString) || !isVisible(index)) {
1480
- continue;
1481
- }
1482
- return index;
1483
- }
1484
- return -1;
1485
- }
1486
- const listContent = listRef.current;
1487
- if (stringRef.current.length > 0 && event.key === ' ') {
1488
- // Space should continue the in-progress typeahead session.
1489
- stopEvent(event);
1490
- setTypingChange(true);
1491
- }
1492
- if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') {
1493
- if (getMatchingIndex(listContent, stringRef.current) === -1 && event.key !== ' ') {
1494
- setTypingChange(false);
1495
- }
1496
- }
1497
- if (listContent == null ||
1498
- // Character key.
1499
- event.key.length !== 1 ||
1500
- // Modifier key.
1501
- event.ctrlKey || event.metaKey || event.altKey) {
1502
- return;
1503
- }
1504
- if (open && event.key !== ' ') {
1505
- stopEvent(event);
1506
- setTypingChange(true);
1507
- }
1508
-
1509
- // Capture whether this is a new typing session before mutating the string.
1510
- const isNewSession = stringRef.current === '';
1511
- if (isNewSession) {
1512
- prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
1513
- }
1514
-
1515
- // Bail out if the list contains a word like "llama" or "aaron". TODO:
1516
- // allow it in this case, too.
1517
- const allowRapidSuccessionOfFirstLetter = listContent.every(text => text ? text[0]?.toLocaleLowerCase() !== text[1]?.toLocaleLowerCase() : true);
1518
-
1519
- // Allows the user to cycle through items that start with the same letter
1520
- // in rapid succession.
1521
- if (allowRapidSuccessionOfFirstLetter && stringRef.current === event.key) {
1522
- stringRef.current = '';
1523
- prevIndexRef.current = matchIndexRef.current;
1524
- }
1525
- stringRef.current += event.key;
1526
- timeout.start(resetMs, () => {
1527
- stringRef.current = '';
1528
- prevIndexRef.current = matchIndexRef.current;
1529
- setTypingChange(false);
1530
- });
1531
-
1532
- // Compute the starting index for this search.
1533
- // If this is a new typing session (string is empty), base it on the current
1534
- // selection/active item; otherwise continue from the last matched index.
1535
- const prevIndex = isNewSession ? selectedIndex ?? activeIndex ?? -1 : prevIndexRef.current;
1536
- const startIndex = (prevIndex ?? 0) + 1;
1537
- const index = getMatchingIndex(listContent, stringRef.current, startIndex);
1538
- if (index !== -1) {
1539
- onMatchProp?.(index);
1540
- matchIndexRef.current = index;
1541
- } else if (event.key !== ' ') {
1542
- stringRef.current = '';
1543
- setTypingChange(false);
1544
- }
1545
- });
1546
- const onBlur = useStableCallback(event => {
1547
- const next = event.relatedTarget;
1548
- const currentDomReferenceElement = store.select('domReferenceElement');
1549
- const currentFloatingElement = store.select('floatingElement');
1550
- const withinReference = contains(currentDomReferenceElement, next);
1551
- const withinFloating = contains(currentFloatingElement, next);
1552
-
1553
- // Keep the session if focus moves within the composite (reference <-> floating).
1554
- if (withinReference || withinFloating) {
1555
- return;
1556
- }
1557
-
1558
- // End the current typing session when focus leaves the composite entirely.
1559
- timeout.clear();
1560
- stringRef.current = '';
1561
- prevIndexRef.current = matchIndexRef.current;
1562
- setTypingChange(false);
1563
- });
1564
- const reference = React.useMemo(() => ({
1565
- onKeyDown,
1566
- onBlur
1567
- }), [onKeyDown, onBlur]);
1568
- const floating = React.useMemo(() => {
1569
- return {
1570
- onKeyDown,
1571
- onBlur
1572
- };
1573
- }, [onKeyDown, onBlur]);
1574
- return React.useMemo(() => enabled ? {
1575
- reference,
1576
- floating
1577
- } : {}, [enabled, reference, floating]);
1578
- }
1579
-
1580
674
  /* eslint-disable no-nested-ternary */
1581
675
 
1582
676
  const CURSOR_SPEED_THRESHOLD = 0.1;
@@ -1818,852 +912,6 @@ function safePolygon(options = {}) {
1818
912
  return fn;
1819
913
  }
1820
914
 
1821
- function getPseudoElementBounds(element) {
1822
- const elementRect = element.getBoundingClientRect();
1823
-
1824
- // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
1825
- if (process.env.NODE_ENV !== 'production') {
1826
- return elementRect;
1827
- }
1828
- const beforeStyles = window.getComputedStyle(element, '::before');
1829
- const afterStyles = window.getComputedStyle(element, '::after');
1830
- const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
1831
- if (!hasPseudoElements) {
1832
- return elementRect;
1833
- }
1834
-
1835
- // Get dimensions of pseudo-elements
1836
- const beforeWidth = parseFloat(beforeStyles.width) || 0;
1837
- const beforeHeight = parseFloat(beforeStyles.height) || 0;
1838
- const afterWidth = parseFloat(afterStyles.width) || 0;
1839
- const afterHeight = parseFloat(afterStyles.height) || 0;
1840
-
1841
- // Calculate max dimensions including pseudo-elements
1842
- const totalWidth = Math.max(elementRect.width, beforeWidth, afterWidth);
1843
- const totalHeight = Math.max(elementRect.height, beforeHeight, afterHeight);
1844
-
1845
- // Calculate the differences to extend the bounds
1846
- const widthDiff = totalWidth - elementRect.width;
1847
- const heightDiff = totalHeight - elementRect.height;
1848
- return {
1849
- left: elementRect.left - widthDiff / 2,
1850
- right: elementRect.right + widthDiff / 2,
1851
- top: elementRect.top - heightDiff / 2,
1852
- bottom: elementRect.bottom + heightDiff / 2
1853
- };
1854
- }
1855
-
1856
- const CompositeListContext = /*#__PURE__*/React.createContext({
1857
- register: () => {},
1858
- unregister: () => {},
1859
- subscribeMapChange: () => {
1860
- return () => {};
1861
- },
1862
- elementsRef: {
1863
- current: []
1864
- },
1865
- nextIndexRef: {
1866
- current: 0
1867
- }
1868
- });
1869
- if (process.env.NODE_ENV !== "production") CompositeListContext.displayName = "CompositeListContext";
1870
- function useCompositeListContext() {
1871
- return React.useContext(CompositeListContext);
1872
- }
1873
-
1874
- /* eslint-disable no-bitwise */
1875
- /**
1876
- * Provides context for a list of items in a composite component.
1877
- * @internal
1878
- */
1879
- function CompositeList(props) {
1880
- const {
1881
- children,
1882
- elementsRef,
1883
- labelsRef,
1884
- onMapChange: onMapChangeProp
1885
- } = props;
1886
- const onMapChange = useStableCallback(onMapChangeProp);
1887
- const nextIndexRef = React.useRef(0);
1888
- const listeners = useRefWithInit(createListeners).current;
1889
-
1890
- // We use a stable `map` to avoid O(n^2) re-allocation costs for large lists.
1891
- // `mapTick` is our re-render trigger mechanism. We also need to update the
1892
- // elements and label refs, but there's a lot of async work going on and sometimes
1893
- // the effect that handles `onMapChange` gets called after those refs have been
1894
- // filled, and we don't want to lose those values by setting their lengths to `0`.
1895
- // We also need to have them at the proper length because floating-ui uses that
1896
- // information for list navigation.
1897
-
1898
- const map = useRefWithInit(createMap).current;
1899
- // `mapTick` uses a counter rather than objects for low precision-loss risk and better memory efficiency
1900
- const [mapTick, setMapTick] = React.useState(0);
1901
- const lastTickRef = React.useRef(mapTick);
1902
- const register = useStableCallback((node, metadata) => {
1903
- map.set(node, metadata ?? null);
1904
- lastTickRef.current += 1;
1905
- setMapTick(lastTickRef.current);
1906
- });
1907
- const unregister = useStableCallback(node => {
1908
- map.delete(node);
1909
- lastTickRef.current += 1;
1910
- setMapTick(lastTickRef.current);
1911
- });
1912
- const sortedMap = React.useMemo(() => {
1913
- const newMap = new Map();
1914
- // Filter out disconnected elements before sorting to avoid inconsistent
1915
- // compareDocumentPosition results when elements are detached from the DOM.
1916
- const sortedNodes = Array.from(map.keys()).filter(node => node.isConnected).sort(sortByDocumentPosition);
1917
- sortedNodes.forEach((node, index) => {
1918
- const metadata = map.get(node) ?? {};
1919
- newMap.set(node, {
1920
- ...metadata,
1921
- index
1922
- });
1923
- });
1924
- return newMap;
1925
- }, [map, mapTick]);
1926
- useIsoLayoutEffect(() => {
1927
- if (typeof MutationObserver !== 'function' || sortedMap.size === 0) {
1928
- return undefined;
1929
- }
1930
- const mutationObserver = new MutationObserver(entries => {
1931
- const diff = new Set();
1932
- const updateDiff = node => diff.has(node) ? diff.delete(node) : diff.add(node);
1933
- entries.forEach(entry => {
1934
- entry.removedNodes.forEach(updateDiff);
1935
- entry.addedNodes.forEach(updateDiff);
1936
- });
1937
- if (diff.size === 0) {
1938
- lastTickRef.current += 1;
1939
- setMapTick(lastTickRef.current);
1940
- }
1941
- });
1942
- sortedMap.forEach((_, node) => {
1943
- if (node.parentElement) {
1944
- mutationObserver.observe(node.parentElement, {
1945
- childList: true
1946
- });
1947
- }
1948
- });
1949
- return () => {
1950
- mutationObserver.disconnect();
1951
- };
1952
- }, [sortedMap]);
1953
- useIsoLayoutEffect(() => {
1954
- const shouldUpdateLengths = lastTickRef.current === mapTick;
1955
- if (shouldUpdateLengths) {
1956
- if (elementsRef.current.length !== sortedMap.size) {
1957
- elementsRef.current.length = sortedMap.size;
1958
- }
1959
- if (labelsRef && labelsRef.current.length !== sortedMap.size) {
1960
- labelsRef.current.length = sortedMap.size;
1961
- }
1962
- nextIndexRef.current = sortedMap.size;
1963
- }
1964
- onMapChange(sortedMap);
1965
- }, [onMapChange, sortedMap, elementsRef, labelsRef, mapTick]);
1966
- useIsoLayoutEffect(() => {
1967
- return () => {
1968
- elementsRef.current = [];
1969
- };
1970
- }, [elementsRef]);
1971
- useIsoLayoutEffect(() => {
1972
- return () => {
1973
- if (labelsRef) {
1974
- labelsRef.current = [];
1975
- }
1976
- };
1977
- }, [labelsRef]);
1978
- const subscribeMapChange = useStableCallback(fn => {
1979
- listeners.add(fn);
1980
- return () => {
1981
- listeners.delete(fn);
1982
- };
1983
- });
1984
- useIsoLayoutEffect(() => {
1985
- listeners.forEach(l => l(sortedMap));
1986
- }, [listeners, sortedMap]);
1987
- const contextValue = React.useMemo(() => ({
1988
- register,
1989
- unregister,
1990
- subscribeMapChange,
1991
- elementsRef,
1992
- labelsRef,
1993
- nextIndexRef
1994
- }), [register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef]);
1995
- return /*#__PURE__*/jsx(CompositeListContext.Provider, {
1996
- value: contextValue,
1997
- children: children
1998
- });
1999
- }
2000
- function createMap() {
2001
- return new Map();
2002
- }
2003
- function createListeners() {
2004
- return new Set();
2005
- }
2006
- function sortByDocumentPosition(a, b) {
2007
- const position = a.compareDocumentPosition(b);
2008
- if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2009
- return -1;
2010
- }
2011
- if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
2012
- return 1;
2013
- }
2014
- return 0;
2015
- }
2016
-
2017
- /**
2018
- * Fork of the original `arrow` middleware from Floating UI that allows
2019
- * configuring the offset parent.
2020
- */
2021
- const baseArrow = options => ({
2022
- name: 'arrow',
2023
- options,
2024
- async fn(state) {
2025
- const {
2026
- x,
2027
- y,
2028
- placement,
2029
- rects,
2030
- platform,
2031
- elements,
2032
- middlewareData
2033
- } = state;
2034
- // Since `element` is required, we don't Partial<> the type.
2035
- const {
2036
- element,
2037
- padding = 0,
2038
- offsetParent = 'real'
2039
- } = evaluate(options, state) || {};
2040
- if (element == null) {
2041
- return {};
2042
- }
2043
- const paddingObject = getPaddingObject(padding);
2044
- const coords = {
2045
- x,
2046
- y
2047
- };
2048
- const axis = getAlignmentAxis(placement);
2049
- const length = getAxisLength(axis);
2050
- const arrowDimensions = await platform.getDimensions(element);
2051
- const isYAxis = axis === 'y';
2052
- const minProp = isYAxis ? 'top' : 'left';
2053
- const maxProp = isYAxis ? 'bottom' : 'right';
2054
- const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
2055
- const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
2056
- const startDiff = coords[axis] - rects.reference[axis];
2057
- const arrowOffsetParent = offsetParent === 'real' ? await platform.getOffsetParent?.(element) : elements.floating;
2058
- let clientSize = elements.floating[clientProp] || rects.floating[length];
2059
-
2060
- // DOM platform can return `window` as the `offsetParent`.
2061
- if (!clientSize || !(await platform.isElement?.(arrowOffsetParent))) {
2062
- clientSize = elements.floating[clientProp] || rects.floating[length];
2063
- }
2064
- const centerToReference = endDiff / 2 - startDiff / 2;
2065
-
2066
- // If the padding is large enough that it causes the arrow to no longer be
2067
- // centered, modify the padding so that it is centered.
2068
- const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
2069
- const minPadding = Math.min(paddingObject[minProp], largestPossiblePadding);
2070
- const maxPadding = Math.min(paddingObject[maxProp], largestPossiblePadding);
2071
-
2072
- // Make sure the arrow doesn't overflow the floating element if the center
2073
- // point is outside the floating element's bounds.
2074
- const min = minPadding;
2075
- const max = clientSize - arrowDimensions[length] - maxPadding;
2076
- const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
2077
- const offset = clamp(min, center, max);
2078
-
2079
- // If the reference is small enough that the arrow's padding causes it to
2080
- // to point to nothing for an aligned placement, adjust the offset of the
2081
- // floating element itself. To ensure `shift()` continues to take action,
2082
- // a single reset is performed when this is true.
2083
- const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
2084
- // eslint-disable-next-line no-nested-ternary
2085
- const alignmentOffset = shouldAddOffset ? center < min ? center - min : center - max : 0;
2086
- return {
2087
- [axis]: coords[axis] + alignmentOffset,
2088
- data: {
2089
- [axis]: offset,
2090
- centerOffset: center - offset - alignmentOffset,
2091
- ...(shouldAddOffset && {
2092
- alignmentOffset
2093
- })
2094
- },
2095
- reset: shouldAddOffset
2096
- };
2097
- }
2098
- });
2099
-
2100
- /**
2101
- * Provides data to position an inner element of the floating element so that it
2102
- * appears centered to the reference element.
2103
- * This wraps the core `arrow` middleware to allow React refs as the element.
2104
- * @see https://floating-ui.com/docs/arrow
2105
- */
2106
- const arrow = (options, deps) => ({
2107
- ...baseArrow(options),
2108
- options: [options, deps]
2109
- });
2110
-
2111
- const hide = {
2112
- name: 'hide',
2113
- async fn(state) {
2114
- const {
2115
- width,
2116
- height,
2117
- x,
2118
- y
2119
- } = state.rects.reference;
2120
- const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;
2121
- const nativeHideResult = await hide$1().fn(state);
2122
- return {
2123
- data: {
2124
- referenceHidden: nativeHideResult.data?.referenceHidden || anchorHidden
2125
- }
2126
- };
2127
- }
2128
- };
2129
-
2130
- const DEFAULT_SIDES = {
2131
- sideX: 'left',
2132
- sideY: 'top'
2133
- };
2134
- const adaptiveOrigin = {
2135
- name: 'adaptiveOrigin',
2136
- async fn(state) {
2137
- const {
2138
- x: rawX,
2139
- y: rawY,
2140
- rects: {
2141
- floating: floatRect
2142
- },
2143
- elements: {
2144
- floating
2145
- },
2146
- platform,
2147
- strategy,
2148
- placement
2149
- } = state;
2150
- const win = getWindow(floating);
2151
- const styles = win.getComputedStyle(floating);
2152
- const hasTransition = styles.transitionDuration !== '0s' && styles.transitionDuration !== '';
2153
- if (!hasTransition) {
2154
- return {
2155
- x: rawX,
2156
- y: rawY,
2157
- data: DEFAULT_SIDES
2158
- };
2159
- }
2160
- const offsetParent = await platform.getOffsetParent?.(floating);
2161
- let offsetDimensions = {
2162
- width: 0,
2163
- height: 0
2164
- };
2165
-
2166
- // For fixed strategy, prefer visualViewport if available
2167
- if (strategy === 'fixed' && win?.visualViewport) {
2168
- offsetDimensions = {
2169
- width: win.visualViewport.width,
2170
- height: win.visualViewport.height
2171
- };
2172
- } else if (offsetParent === win) {
2173
- const doc = ownerDocument(floating);
2174
- offsetDimensions = {
2175
- width: doc.documentElement.clientWidth,
2176
- height: doc.documentElement.clientHeight
2177
- };
2178
- } else if (await platform.isElement?.(offsetParent)) {
2179
- offsetDimensions = await platform.getDimensions(offsetParent);
2180
- }
2181
- const currentSide = getSide(placement);
2182
- let x = rawX;
2183
- let y = rawY;
2184
- if (currentSide === 'left') {
2185
- x = offsetDimensions.width - (rawX + floatRect.width);
2186
- }
2187
- if (currentSide === 'top') {
2188
- y = offsetDimensions.height - (rawY + floatRect.height);
2189
- }
2190
- const sideX = currentSide === 'left' ? 'right' : DEFAULT_SIDES.sideX;
2191
- const sideY = currentSide === 'top' ? 'bottom' : DEFAULT_SIDES.sideY;
2192
- return {
2193
- x,
2194
- y,
2195
- data: {
2196
- sideX,
2197
- sideY
2198
- }
2199
- };
2200
- }
2201
- };
2202
-
2203
- function getLogicalSide(sideParam, renderedSide, isRtl) {
2204
- const isLogicalSideParam = sideParam === 'inline-start' || sideParam === 'inline-end';
2205
- const logicalRight = isRtl ? 'inline-start' : 'inline-end';
2206
- const logicalLeft = isRtl ? 'inline-end' : 'inline-start';
2207
- return {
2208
- top: 'top',
2209
- right: isLogicalSideParam ? logicalRight : 'right',
2210
- bottom: 'bottom',
2211
- left: isLogicalSideParam ? logicalLeft : 'left'
2212
- }[renderedSide];
2213
- }
2214
- function getOffsetData(state, sideParam, isRtl) {
2215
- const {
2216
- rects,
2217
- placement
2218
- } = state;
2219
- const data = {
2220
- side: getLogicalSide(sideParam, getSide(placement), isRtl),
2221
- align: getAlignment(placement) || 'center',
2222
- anchor: {
2223
- width: rects.reference.width,
2224
- height: rects.reference.height
2225
- },
2226
- positioner: {
2227
- width: rects.floating.width,
2228
- height: rects.floating.height
2229
- }
2230
- };
2231
- return data;
2232
- }
2233
- /**
2234
- * Provides standardized anchor positioning behavior for floating elements. Wraps Floating UI's
2235
- * `useFloating` hook.
2236
- */
2237
- function useAnchorPositioning(params) {
2238
- const {
2239
- // Public parameters
2240
- anchor,
2241
- positionMethod = 'absolute',
2242
- side: sideParam = 'bottom',
2243
- sideOffset = 0,
2244
- align = 'center',
2245
- alignOffset = 0,
2246
- collisionBoundary,
2247
- collisionPadding: collisionPaddingParam = 5,
2248
- sticky = false,
2249
- arrowPadding = 5,
2250
- disableAnchorTracking = false,
2251
- // Private parameters
2252
- keepMounted = false,
2253
- floatingRootContext,
2254
- mounted,
2255
- collisionAvoidance,
2256
- shiftCrossAxis = false,
2257
- nodeId,
2258
- adaptiveOrigin,
2259
- lazyFlip = false,
2260
- externalTree
2261
- } = params;
2262
- const [mountSide, setMountSide] = React.useState(null);
2263
- if (!mounted && mountSide !== null) {
2264
- setMountSide(null);
2265
- }
2266
- const collisionAvoidanceSide = collisionAvoidance.side || 'flip';
2267
- const collisionAvoidanceAlign = collisionAvoidance.align || 'flip';
2268
- const collisionAvoidanceFallbackAxisSide = collisionAvoidance.fallbackAxisSide || 'end';
2269
- const anchorFn = typeof anchor === 'function' ? anchor : undefined;
2270
- const anchorFnCallback = useStableCallback(anchorFn);
2271
- const anchorDep = anchorFn ? anchorFnCallback : anchor;
2272
- const anchorValueRef = useValueAsRef(anchor);
2273
- const direction = useDirection();
2274
- const isRtl = direction === 'rtl';
2275
- const side = mountSide || {
2276
- top: 'top',
2277
- right: 'right',
2278
- bottom: 'bottom',
2279
- left: 'left',
2280
- 'inline-end': isRtl ? 'left' : 'right',
2281
- 'inline-start': isRtl ? 'right' : 'left'
2282
- }[sideParam];
2283
- const placement = align === 'center' ? side : `${side}-${align}`;
2284
- let collisionPadding = collisionPaddingParam;
2285
-
2286
- // Create a bias to the preferred side.
2287
- // On iOS, when the mobile software keyboard opens, the input is exactly centered
2288
- // in the viewport, but this can cause it to flip to the top undesirably.
2289
- const bias = 1;
2290
- const biasTop = sideParam === 'bottom' ? bias : 0;
2291
- const biasBottom = sideParam === 'top' ? bias : 0;
2292
- const biasLeft = sideParam === 'right' ? bias : 0;
2293
- const biasRight = sideParam === 'left' ? bias : 0;
2294
- if (typeof collisionPadding === 'number') {
2295
- collisionPadding = {
2296
- top: collisionPadding + biasTop,
2297
- right: collisionPadding + biasRight,
2298
- bottom: collisionPadding + biasBottom,
2299
- left: collisionPadding + biasLeft
2300
- };
2301
- } else if (collisionPadding) {
2302
- collisionPadding = {
2303
- top: (collisionPadding.top || 0) + biasTop,
2304
- right: (collisionPadding.right || 0) + biasRight,
2305
- bottom: (collisionPadding.bottom || 0) + biasBottom,
2306
- left: (collisionPadding.left || 0) + biasLeft
2307
- };
2308
- }
2309
- const commonCollisionProps = {
2310
- boundary: collisionBoundary === 'clipping-ancestors' ? 'clippingAncestors' : collisionBoundary,
2311
- padding: collisionPadding
2312
- };
2313
-
2314
- // Using a ref assumes that the arrow element is always present in the DOM for the lifetime of the
2315
- // popup. If this assumption ends up being false, we can switch to state to manage the arrow's
2316
- // presence.
2317
- const arrowRef = React.useRef(null);
2318
-
2319
- // Keep these reactive if they're not functions
2320
- const sideOffsetRef = useValueAsRef(sideOffset);
2321
- const alignOffsetRef = useValueAsRef(alignOffset);
2322
- const sideOffsetDep = typeof sideOffset !== 'function' ? sideOffset : 0;
2323
- const alignOffsetDep = typeof alignOffset !== 'function' ? alignOffset : 0;
2324
- const middleware = [offset(state => {
2325
- const data = getOffsetData(state, sideParam, isRtl);
2326
- const sideAxis = typeof sideOffsetRef.current === 'function' ? sideOffsetRef.current(data) : sideOffsetRef.current;
2327
- const alignAxis = typeof alignOffsetRef.current === 'function' ? alignOffsetRef.current(data) : alignOffsetRef.current;
2328
- return {
2329
- mainAxis: sideAxis,
2330
- crossAxis: alignAxis,
2331
- alignmentAxis: alignAxis
2332
- };
2333
- }, [sideOffsetDep, alignOffsetDep, isRtl, sideParam])];
2334
- const shiftDisabled = collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift';
2335
- const crossAxisShiftEnabled = !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift');
2336
- const flipMiddleware = collisionAvoidanceSide === 'none' ? null : flip({
2337
- ...commonCollisionProps,
2338
- // Ensure the popup flips if it's been limited by its --available-height and it resizes.
2339
- // Since the size() padding is smaller than the flip() padding, flip() will take precedence.
2340
- padding: {
2341
- top: collisionPadding.top + bias,
2342
- right: collisionPadding.right + bias,
2343
- bottom: collisionPadding.bottom + bias,
2344
- left: collisionPadding.left + bias
2345
- },
2346
- mainAxis: !shiftCrossAxis && collisionAvoidanceSide === 'flip',
2347
- crossAxis: collisionAvoidanceAlign === 'flip' ? 'alignment' : false,
2348
- fallbackAxisSideDirection: collisionAvoidanceFallbackAxisSide
2349
- });
2350
- const shiftMiddleware = shiftDisabled ? null : shift(data => {
2351
- const html = ownerDocument(data.elements.floating).documentElement;
2352
- return {
2353
- ...commonCollisionProps,
2354
- // Use the Layout Viewport to avoid shifting around when pinch-zooming
2355
- // for context menus.
2356
- rootBoundary: shiftCrossAxis ? {
2357
- x: 0,
2358
- y: 0,
2359
- width: html.clientWidth,
2360
- height: html.clientHeight
2361
- } : undefined,
2362
- mainAxis: collisionAvoidanceAlign !== 'none',
2363
- crossAxis: crossAxisShiftEnabled,
2364
- limiter: sticky || shiftCrossAxis ? undefined : limitShift(limitData => {
2365
- if (!arrowRef.current) {
2366
- return {};
2367
- }
2368
- const {
2369
- width,
2370
- height
2371
- } = arrowRef.current.getBoundingClientRect();
2372
- const sideAxis = getSideAxis(getSide(limitData.placement));
2373
- const arrowSize = sideAxis === 'y' ? width : height;
2374
- const offsetAmount = sideAxis === 'y' ? collisionPadding.left + collisionPadding.right : collisionPadding.top + collisionPadding.bottom;
2375
- return {
2376
- offset: arrowSize / 2 + offsetAmount / 2
2377
- };
2378
- })
2379
- };
2380
- }, [commonCollisionProps, sticky, shiftCrossAxis, collisionPadding, collisionAvoidanceAlign]);
2381
-
2382
- // https://floating-ui.com/docs/flip#combining-with-shift
2383
- if (collisionAvoidanceSide === 'shift' || collisionAvoidanceAlign === 'shift' || align === 'center') {
2384
- middleware.push(shiftMiddleware, flipMiddleware);
2385
- } else {
2386
- middleware.push(flipMiddleware, shiftMiddleware);
2387
- }
2388
- middleware.push(size({
2389
- ...commonCollisionProps,
2390
- apply({
2391
- elements: {
2392
- floating
2393
- },
2394
- availableWidth,
2395
- availableHeight,
2396
- rects
2397
- }) {
2398
- const floatingStyle = floating.style;
2399
- floatingStyle.setProperty('--available-width', `${availableWidth}px`);
2400
- floatingStyle.setProperty('--available-height', `${availableHeight}px`);
2401
-
2402
- // Snap anchor dimensions to device pixels to ensure the popup's visual width matches the anchor's one.
2403
- const dpr = window.devicePixelRatio || 1;
2404
- const {
2405
- x,
2406
- y,
2407
- width,
2408
- height
2409
- } = rects.reference;
2410
- const anchorWidth = (Math.round((x + width) * dpr) - Math.round(x * dpr)) / dpr;
2411
- const anchorHeight = (Math.round((y + height) * dpr) - Math.round(y * dpr)) / dpr;
2412
- floatingStyle.setProperty('--anchor-width', `${anchorWidth}px`);
2413
- floatingStyle.setProperty('--anchor-height', `${anchorHeight}px`);
2414
- }
2415
- }), arrow(() => ({
2416
- // `transform-origin` calculations rely on an element existing. If the arrow hasn't been set,
2417
- // we'll create a fake element.
2418
- element: arrowRef.current || document.createElement('div'),
2419
- padding: arrowPadding,
2420
- offsetParent: 'floating'
2421
- }), [arrowPadding]), {
2422
- name: 'transformOrigin',
2423
- fn(state) {
2424
- const {
2425
- elements,
2426
- middlewareData,
2427
- placement: renderedPlacement,
2428
- rects,
2429
- y
2430
- } = state;
2431
- const currentRenderedSide = getSide(renderedPlacement);
2432
- const currentRenderedAxis = getSideAxis(currentRenderedSide);
2433
- const arrowEl = arrowRef.current;
2434
- const arrowX = middlewareData.arrow?.x || 0;
2435
- const arrowY = middlewareData.arrow?.y || 0;
2436
- const arrowWidth = arrowEl?.clientWidth || 0;
2437
- const arrowHeight = arrowEl?.clientHeight || 0;
2438
- const transformX = arrowX + arrowWidth / 2;
2439
- const transformY = arrowY + arrowHeight / 2;
2440
- const shiftY = Math.abs(middlewareData.shift?.y || 0);
2441
- const halfAnchorHeight = rects.reference.height / 2;
2442
- const sideOffsetValue = typeof sideOffset === 'function' ? sideOffset(getOffsetData(state, sideParam, isRtl)) : sideOffset;
2443
- const isOverlappingAnchor = shiftY > sideOffsetValue;
2444
- const adjacentTransformOrigin = {
2445
- top: `${transformX}px calc(100% + ${sideOffsetValue}px)`,
2446
- bottom: `${transformX}px ${-sideOffsetValue}px`,
2447
- left: `calc(100% + ${sideOffsetValue}px) ${transformY}px`,
2448
- right: `${-sideOffsetValue}px ${transformY}px`
2449
- }[currentRenderedSide];
2450
- const overlapTransformOrigin = `${transformX}px ${rects.reference.y + halfAnchorHeight - y}px`;
2451
- elements.floating.style.setProperty('--transform-origin', crossAxisShiftEnabled && currentRenderedAxis === 'y' && isOverlappingAnchor ? overlapTransformOrigin : adjacentTransformOrigin);
2452
- return {};
2453
- }
2454
- }, hide, adaptiveOrigin);
2455
- useIsoLayoutEffect(() => {
2456
- // Ensure positioning doesn't run initially for `keepMounted` elements that
2457
- // aren't initially open.
2458
- if (!mounted && floatingRootContext) {
2459
- floatingRootContext.update({
2460
- referenceElement: null,
2461
- floatingElement: null,
2462
- domReferenceElement: null
2463
- });
2464
- }
2465
- }, [mounted, floatingRootContext]);
2466
- const autoUpdateOptions = React.useMemo(() => ({
2467
- elementResize: !disableAnchorTracking && typeof ResizeObserver !== 'undefined',
2468
- layoutShift: !disableAnchorTracking && typeof IntersectionObserver !== 'undefined'
2469
- }), [disableAnchorTracking]);
2470
- const {
2471
- refs,
2472
- elements,
2473
- x,
2474
- y,
2475
- middlewareData,
2476
- update,
2477
- placement: renderedPlacement,
2478
- context,
2479
- isPositioned,
2480
- floatingStyles: originalFloatingStyles
2481
- } = useFloating({
2482
- rootContext: floatingRootContext,
2483
- placement,
2484
- middleware,
2485
- strategy: positionMethod,
2486
- whileElementsMounted: keepMounted ? undefined : (...args) => autoUpdate(...args, autoUpdateOptions),
2487
- nodeId,
2488
- externalTree
2489
- });
2490
- const {
2491
- sideX,
2492
- sideY
2493
- } = middlewareData.adaptiveOrigin || DEFAULT_SIDES;
2494
-
2495
- // Default to `fixed` when not positioned to prevent `autoFocus` scroll jumps.
2496
- // This ensures the popup is inside the viewport initially before it gets positioned.
2497
- const resolvedPosition = isPositioned ? positionMethod : 'fixed';
2498
- const floatingStyles = React.useMemo(() => {
2499
- const base = adaptiveOrigin ? {
2500
- position: resolvedPosition,
2501
- [sideX]: x,
2502
- [sideY]: y
2503
- } : {
2504
- position: resolvedPosition,
2505
- ...originalFloatingStyles
2506
- };
2507
- if (!isPositioned) {
2508
- base.opacity = 0;
2509
- }
2510
- return base;
2511
- }, [adaptiveOrigin, resolvedPosition, sideX, x, sideY, y, originalFloatingStyles, isPositioned]);
2512
- const registeredPositionReferenceRef = React.useRef(null);
2513
- useIsoLayoutEffect(() => {
2514
- if (!mounted) {
2515
- return;
2516
- }
2517
- const anchorValue = anchorValueRef.current;
2518
- const resolvedAnchor = typeof anchorValue === 'function' ? anchorValue() : anchorValue;
2519
- const unwrappedElement = (isRef(resolvedAnchor) ? resolvedAnchor.current : resolvedAnchor) || null;
2520
- const finalAnchor = unwrappedElement || null;
2521
- if (finalAnchor !== registeredPositionReferenceRef.current) {
2522
- refs.setPositionReference(finalAnchor);
2523
- registeredPositionReferenceRef.current = finalAnchor;
2524
- }
2525
- }, [mounted, refs, anchorDep, anchorValueRef]);
2526
- React.useEffect(() => {
2527
- if (!mounted) {
2528
- return;
2529
- }
2530
- const anchorValue = anchorValueRef.current;
2531
-
2532
- // Refs from parent components are set after useLayoutEffect runs and are available in useEffect.
2533
- // Therefore, if the anchor is a ref, we need to update the position reference in useEffect.
2534
- if (typeof anchorValue === 'function') {
2535
- return;
2536
- }
2537
- if (isRef(anchorValue) && anchorValue.current !== registeredPositionReferenceRef.current) {
2538
- refs.setPositionReference(anchorValue.current);
2539
- registeredPositionReferenceRef.current = anchorValue.current;
2540
- }
2541
- }, [mounted, refs, anchorDep, anchorValueRef]);
2542
- React.useEffect(() => {
2543
- if (keepMounted && mounted && elements.domReference && elements.floating) {
2544
- return autoUpdate(elements.domReference, elements.floating, update, autoUpdateOptions);
2545
- }
2546
- return undefined;
2547
- }, [keepMounted, mounted, elements, update, autoUpdateOptions]);
2548
- const renderedSide = getSide(renderedPlacement);
2549
- const logicalRenderedSide = getLogicalSide(sideParam, renderedSide, isRtl);
2550
- const renderedAlign = getAlignment(renderedPlacement) || 'center';
2551
- const anchorHidden = Boolean(middlewareData.hide?.referenceHidden);
2552
-
2553
- /**
2554
- * Locks the flip (makes it "sticky") so it doesn't prefer a given placement
2555
- * and flips back lazily, not eagerly. Ideal for filtered lists that change
2556
- * the size of the popup dynamically to avoid unwanted flipping when typing.
2557
- */
2558
- useIsoLayoutEffect(() => {
2559
- if (lazyFlip && mounted && isPositioned) {
2560
- setMountSide(renderedSide);
2561
- }
2562
- }, [lazyFlip, mounted, isPositioned, renderedSide]);
2563
- const arrowStyles = React.useMemo(() => ({
2564
- position: 'absolute',
2565
- top: middlewareData.arrow?.y,
2566
- left: middlewareData.arrow?.x
2567
- }), [middlewareData.arrow]);
2568
- const arrowUncentered = middlewareData.arrow?.centerOffset !== 0;
2569
- return React.useMemo(() => ({
2570
- positionerStyles: floatingStyles,
2571
- arrowStyles,
2572
- arrowRef,
2573
- arrowUncentered,
2574
- side: logicalRenderedSide,
2575
- align: renderedAlign,
2576
- physicalSide: renderedSide,
2577
- anchorHidden,
2578
- refs,
2579
- context,
2580
- isPositioned,
2581
- update
2582
- }), [floatingStyles, arrowStyles, arrowRef, arrowUncentered, logicalRenderedSide, renderedAlign, renderedSide, anchorHidden, refs, context, isPositioned, update]);
2583
- }
2584
- function isRef(param) {
2585
- return param != null && 'current' in param;
2586
- }
2587
-
2588
- function getDisabledMountTransitionStyles(transitionStatus) {
2589
- return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;
2590
- }
2591
-
2592
- let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
2593
- IndexGuessBehavior[IndexGuessBehavior["None"] = 0] = "None";
2594
- IndexGuessBehavior[IndexGuessBehavior["GuessFromOrder"] = 1] = "GuessFromOrder";
2595
- return IndexGuessBehavior;
2596
- }({});
2597
-
2598
- /**
2599
- * Used to register a list item and its index (DOM position) in the `CompositeList`.
2600
- */
2601
- function useCompositeListItem(params = {}) {
2602
- const {
2603
- label,
2604
- metadata,
2605
- textRef,
2606
- indexGuessBehavior,
2607
- index: externalIndex
2608
- } = params;
2609
- const {
2610
- register,
2611
- unregister,
2612
- subscribeMapChange,
2613
- elementsRef,
2614
- labelsRef,
2615
- nextIndexRef
2616
- } = useCompositeListContext();
2617
- const indexRef = React.useRef(-1);
2618
- const [index, setIndex] = React.useState(externalIndex ?? (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder ? () => {
2619
- if (indexRef.current === -1) {
2620
- const newIndex = nextIndexRef.current;
2621
- nextIndexRef.current += 1;
2622
- indexRef.current = newIndex;
2623
- }
2624
- return indexRef.current;
2625
- } : -1));
2626
- const componentRef = React.useRef(null);
2627
- const ref = React.useCallback(node => {
2628
- componentRef.current = node;
2629
- if (index !== -1 && node !== null) {
2630
- elementsRef.current[index] = node;
2631
- if (labelsRef) {
2632
- const isLabelDefined = label !== undefined;
2633
- labelsRef.current[index] = isLabelDefined ? label : textRef?.current?.textContent ?? node.textContent;
2634
- }
2635
- }
2636
- }, [index, elementsRef, labelsRef, label, textRef]);
2637
- useIsoLayoutEffect(() => {
2638
- if (externalIndex != null) {
2639
- return undefined;
2640
- }
2641
- const node = componentRef.current;
2642
- if (node) {
2643
- register(node, metadata);
2644
- return () => {
2645
- unregister(node);
2646
- };
2647
- }
2648
- return undefined;
2649
- }, [externalIndex, register, unregister, metadata]);
2650
- useIsoLayoutEffect(() => {
2651
- if (externalIndex != null) {
2652
- return undefined;
2653
- }
2654
- return subscribeMapChange(map => {
2655
- const i = componentRef.current ? map.get(componentRef.current)?.index : null;
2656
- if (i != null) {
2657
- setIndex(i);
2658
- }
2659
- });
2660
- }, [externalIndex, subscribeMapChange, setIndex]);
2661
- return React.useMemo(() => ({
2662
- ref,
2663
- index
2664
- }), [index, ref]);
2665
- }
2666
-
2667
915
  /**
2668
916
  * A button that closes the dialog.
2669
917
  * Renders a `<button>` element.
@@ -4797,4 +3045,4 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
4797
3045
  });
4798
3046
  if (process.env.NODE_ENV !== "production") MenuSubmenuTrigger.displayName = "MenuSubmenuTrigger";
4799
3047
 
4800
- export { MenuStore as A, CompositeList as C, DrawerTitle as D, IndexGuessBehavior as I, MenuTrigger as M, REGULAR_ITEM as R, DrawerClose as a, MenuLinkItem as b, MenuItem as c, MenuRadioItem as d, MenuRadioItemIndicator as e, MenuSubmenuTrigger as f, MenuRoot as g, MenuPortal as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useListNavigation as o, useTypeahead as p, getPseudoElementBounds as q, useAnchorPositioning as r, getDisabledMountTransitionStyles as s, useCompositeListItem as t, useFloatingRootContext as u, useMenuRootContext as v, useMenuPositionerContext as w, useContextMenuRootContext as x, useMenuItem as y, itemMapping as z };
3048
+ export { DrawerTitle as D, MenuTrigger as M, REGULAR_ITEM as R, DrawerClose as a, MenuLinkItem as b, MenuItem as c, MenuRadioItem as d, MenuRadioItemIndicator as e, MenuSubmenuTrigger as f, MenuRoot as g, MenuPortal as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useMenuPositionerContext as o, useContextMenuRootContext as p, useMenuItem as q, itemMapping as r, MenuStore as s, useMenuRootContext as u };