@jobber/components 8.23.0 → 8.23.1

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 (134) hide show
  1. package/dist/Autocomplete/index.cjs +1 -1
  2. package/dist/Autocomplete/index.mjs +1 -1
  3. package/dist/Card/index.cjs +4 -4
  4. package/dist/Card/index.mjs +4 -4
  5. package/dist/Chip/index.cjs +1 -1
  6. package/dist/Chip/index.mjs +1 -1
  7. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
  8. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
  9. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  11. package/dist/Chips/index.cjs +1 -1
  12. package/dist/Chips/index.mjs +1 -1
  13. package/dist/ComboboxChipRemove-cjs.js +668 -379
  14. package/dist/ComboboxChipRemove-es.js +508 -219
  15. package/dist/ConfirmationModal/index.cjs +1 -1
  16. package/dist/ConfirmationModal/index.mjs +1 -1
  17. package/dist/DataDump/index.cjs +4 -4
  18. package/dist/DataDump/index.mjs +4 -4
  19. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  20. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  21. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  22. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  23. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
  24. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
  25. package/dist/DataList/components/DataListFilters/index.cjs +1 -1
  26. package/dist/DataList/components/DataListFilters/index.mjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  28. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  30. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  32. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  34. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  36. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  38. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  40. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  41. package/dist/DataList/index.cjs +1 -1
  42. package/dist/DataList/index.mjs +1 -1
  43. package/dist/DatePicker/index.cjs +1 -1
  44. package/dist/DatePicker/index.mjs +1 -1
  45. package/dist/Dialog/index.cjs +3 -3
  46. package/dist/Dialog/index.mjs +3 -3
  47. package/dist/DrawerDescription-cjs.js +32 -33
  48. package/dist/DrawerDescription-es.js +19 -20
  49. package/dist/DrawerTitle-cjs.js +7 -5
  50. package/dist/DrawerTitle-es.js +7 -5
  51. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
  52. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
  53. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
  54. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
  55. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
  56. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
  57. package/dist/FilterPicker/index.cjs +1 -1
  58. package/dist/FilterPicker/index.mjs +1 -1
  59. package/dist/FormFieldWrapper-cjs.js +2 -2
  60. package/dist/FormFieldWrapper-es.js +2 -2
  61. package/dist/FormatFile/index.cjs +1 -1
  62. package/dist/FormatFile/index.mjs +1 -1
  63. package/dist/Gallery/index.cjs +1 -1
  64. package/dist/Gallery/index.mjs +1 -1
  65. package/dist/InputDate/index.cjs +1 -1
  66. package/dist/InputDate/index.mjs +1 -1
  67. package/dist/InputNumberExperimental-cjs.js +319 -102
  68. package/dist/InputNumberExperimental-es.js +321 -104
  69. package/dist/LightBox/index.cjs +1 -1
  70. package/dist/LightBox/index.mjs +1 -1
  71. package/dist/Menu/index.cjs +4 -4
  72. package/dist/Menu/index.mjs +4 -4
  73. package/dist/MenuSubmenuTrigger-cjs.js +545 -498
  74. package/dist/MenuSubmenuTrigger-es.js +490 -443
  75. package/dist/Modal/index.cjs +1 -1
  76. package/dist/Modal/index.mjs +1 -1
  77. package/dist/NumberFieldInput-cjs.js +609 -496
  78. package/dist/NumberFieldInput-es.js +612 -498
  79. package/dist/Page/index.cjs +4 -4
  80. package/dist/Page/index.mjs +4 -4
  81. package/dist/Popover/index.cjs +1 -1
  82. package/dist/Popover/index.mjs +1 -1
  83. package/dist/ScrollAreaViewport-cjs.js +326 -601
  84. package/dist/ScrollAreaViewport-es.js +278 -548
  85. package/dist/SelectGroupLabel-cjs.js +494 -528
  86. package/dist/SelectGroupLabel-es.js +411 -445
  87. package/dist/Separator-cjs.js +1 -0
  88. package/dist/Separator-es.js +1 -0
  89. package/dist/Tooltip/index.cjs +1 -1
  90. package/dist/Tooltip/index.mjs +1 -1
  91. package/dist/docs/InputTime/InputTime.md +8 -0
  92. package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
  93. package/dist/floating-ui.react-cjs.js +586 -10
  94. package/dist/floating-ui.react-dom-cjs.js +92 -92
  95. package/dist/floating-ui.react-dom-es.js +1 -1
  96. package/dist/floating-ui.react-es.js +580 -4
  97. package/dist/floating-ui.utils-cjs.js +159 -0
  98. package/dist/floating-ui.utils-es.js +136 -0
  99. package/dist/index.cjs +4 -4
  100. package/dist/index.mjs +4 -4
  101. package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
  102. package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
  103. package/dist/primitives/BottomSheet/index.cjs +3 -3
  104. package/dist/primitives/BottomSheet/index.mjs +3 -3
  105. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
  106. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
  107. package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
  108. package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
  109. package/dist/primitives/SelectPrimitive/index.cjs +5 -5
  110. package/dist/primitives/SelectPrimitive/index.mjs +5 -5
  111. package/dist/primitives/index.cjs +4 -4
  112. package/dist/primitives/index.mjs +4 -4
  113. package/dist/resolveAriaLabelledBy-cjs.js +5 -0
  114. package/dist/resolveAriaLabelledBy-es.js +5 -1
  115. package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
  116. package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
  117. package/dist/styles.css +9 -3
  118. package/dist/unstyledPrimitives/index.cjs +1217 -639
  119. package/dist/unstyledPrimitives/index.mjs +1163 -585
  120. package/dist/useButton-cjs.js +283 -317
  121. package/dist/useButton-es.js +277 -298
  122. package/dist/useCompositeListItem-cjs.js +328 -279
  123. package/dist/useCompositeListItem-es.js +293 -246
  124. package/dist/useLabel-cjs.js +11 -1
  125. package/dist/useLabel-es.js +11 -2
  126. package/dist/useLabelableId-cjs.js +37 -91
  127. package/dist/useLabelableId-es.js +37 -90
  128. package/dist/useRenderElement-cjs.js +81 -26
  129. package/dist/useRenderElement-es.js +81 -27
  130. package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
  131. package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
  132. package/package.json +3 -3
  133. package/dist/index.esm-cjs.js +0 -738
  134. package/dist/index.esm-es.js +0 -712
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
+ import { l as useId, a as useIsoLayoutEffect, m as useValueAsRef, b as useStableCallback, n as useAnimationFrame, p as activeElement, o as ownerDocument, q as contains, s as stopEvent, d as createChangeEventDetails, r as listNavigation, g as getTarget, v as focusOut, w as isVirtualClick, x as isVirtualPointerEvent, h as useTimeout, y as jsdom } from './useButton-es.js';
2
3
  import { b as isElement, a as isHTMLElement, o as getWindow } from './floating-ui.utils.dom-es.js';
3
- import { l as useId, a as useIsoLayoutEffect, m as getFloatingFocusElement, n as useValueAsRef, p as isTypeableCombobox, b as useStableCallback, q as activeElement, o as ownerDocument, r as contains, s as stopEvent, d as createChangeEventDetails, v as listNavigation, A as ARROW_DOWN, w as ARROW_LEFT, x as ARROW_RIGHT, g as getTarget, y as focusOut, z as isVirtualClick, B as isVirtualPointerEvent, C as ARROW_UP, h as useTimeout } from './useButton-es.js';
4
- import { u as useFloatingParentNodeId, a as FloatingRootStore, P as PopupTriggerMap, b as useFloatingTree, i as isIndexOutOfListBounds, g as getMinListIndex, c as getMaxListIndex, d as createGridCellMap, e as isListIndexDisabled, f as getGridNavigatedIndex, h as getGridCellIndices, j as getGridCellIndexOfCorner, k as findNonDisabledListIndex, l as enqueueFocus, m as isElementVisible, n as useDirection, D as DISABLED_TRANSITIONS_STYLE } from './InternalBackdrop-es.js';
5
- import { a as useRefWithInit, E as EMPTY_OBJECT } from './useRenderElement-es.js';
4
+ import { u as useFloatingParentNodeId, b as FloatingRootStore, P as PopupTriggerMap, d as useFloatingTree, g as getFloatingFocusElement, i as isTypeableCombobox, e as isIndexOutOfListBounds, f as getMinListIndex, h as getMaxListIndex, j as findNonDisabledListIndex, k as enqueueFocus, A as ARROW_UP, l as ARROW_DOWN, m as ARROW_LEFT, n as ARROW_RIGHT, o as isListIndexDisabled, q as isElementVisible, r as useDirection, D as DISABLED_TRANSITIONS_STYLE, p as popupStateMapping, s as useScrollLock } from './useScrollLock-es.js';
5
+ import { a as useRefWithInit, b as EMPTY_ARRAY, E as EMPTY_OBJECT, u as useRenderElement } from './useRenderElement-es.js';
6
6
  import { jsx } from 'react/jsx-runtime';
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';
7
+ import { e as evaluate, d as getPaddingObject, a as getAlignmentAxis, c as getAlignment, o as getAxisLength, l as clamp, b as getSide, g as getSideAxis } from './floating-ui.utils-es.js';
8
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';
9
9
 
10
10
  function useFloatingRootContext(options) {
@@ -23,13 +23,14 @@ function useFloatingRootContext(options) {
23
23
  }
24
24
  const store = useRefWithInit(() => new FloatingRootStore({
25
25
  open,
26
+ transitionStatus: undefined,
26
27
  onOpenChange,
27
28
  referenceElement: elements.reference ?? null,
28
29
  floatingElement: elements.floating ?? null,
29
30
  triggerElements: new PopupTriggerMap(),
30
31
  floatingId,
31
- nested,
32
- noEmit: false
32
+ syncOnly: false,
33
+ nested
33
34
  })).current;
34
35
  useIsoLayoutEffect(() => {
35
36
  const valuesToSync = {
@@ -49,7 +50,6 @@ function useFloatingRootContext(options) {
49
50
  }, [open, floatingId, elements.reference, elements.floating, store]);
50
51
  store.context.onOpenChange = onOpenChange;
51
52
  store.context.nested = nested;
52
- store.context.noEmit = false;
53
53
  return store;
54
54
  }
55
55
 
@@ -62,30 +62,37 @@ function useFloating(options = {}) {
62
62
  nodeId,
63
63
  externalTree
64
64
  } = options;
65
- const internalRootStore = useFloatingRootContext(options);
66
- const rootContext = options.rootContext || internalRootStore;
67
- const rootContextElements = {
68
- reference: rootContext.useState('referenceElement'),
69
- floating: rootContext.useState('floatingElement'),
70
- domReference: rootContext.useState('domReferenceElement')
71
- };
65
+ const internalStore = useFloatingRootContext(options);
66
+ const store = options.rootContext || internalStore;
67
+ const referenceElement = store.useState('referenceElement');
68
+ const floatingElement = store.useState('floatingElement');
69
+ const domReferenceElement = store.useState('domReferenceElement');
70
+ const open = store.useState('open');
71
+ const floatingId = store.useState('floatingId');
72
72
  const [positionReference, setPositionReferenceRaw] = React.useState(null);
73
+ const [localDomReference, setLocalDomReference] = React.useState(undefined);
74
+ const [localFloatingElement, setLocalFloatingElement] = React.useState(undefined);
73
75
  const domReferenceRef = React.useRef(null);
74
76
  const tree = useFloatingTree(externalTree);
75
- useIsoLayoutEffect(() => {
76
- if (rootContextElements.domReference) {
77
- domReferenceRef.current = rootContextElements.domReference;
78
- }
79
- }, [rootContextElements.domReference]);
77
+ const storeElements = React.useMemo(() => ({
78
+ reference: referenceElement,
79
+ floating: floatingElement,
80
+ domReference: domReferenceElement
81
+ }), [referenceElement, floatingElement, domReferenceElement]);
80
82
  const position = useFloating$1({
81
83
  ...options,
82
84
  elements: {
83
- ...rootContextElements,
85
+ ...storeElements,
84
86
  ...(positionReference && {
85
87
  reference: positionReference
86
88
  })
87
89
  }
88
90
  });
91
+ const localDomReferenceElement = isElement(localDomReference) ? localDomReference : null;
92
+ const syncedFloatingElement = localFloatingElement === undefined ? store.state.floatingElement : localFloatingElement;
93
+ store.useSyncedValue('referenceElement', localDomReference ?? null);
94
+ store.useSyncedValue('domReferenceElement', localDomReference === undefined ? domReferenceElement : localDomReferenceElement);
95
+ store.useSyncedValue('floatingElement', syncedFloatingElement);
89
96
  const setPositionReference = React.useCallback(node => {
90
97
  const computedPositionReference = isElement(node) ? {
91
98
  getBoundingClientRect: () => node.getBoundingClientRect(),
@@ -97,11 +104,6 @@ function useFloating(options = {}) {
97
104
  setPositionReferenceRaw(computedPositionReference);
98
105
  position.refs.setReference(computedPositionReference);
99
106
  }, [position.refs]);
100
- const [localDomReference, setLocalDomReference] = React.useState(null);
101
- const [localFloatingElement, setLocalFloatingElement] = React.useState(null);
102
- rootContext.useSyncedValue('referenceElement', localDomReference);
103
- rootContext.useSyncedValue('domReferenceElement', isElement(localDomReference) ? localDomReference : null);
104
- rootContext.useSyncedValue('floatingElement', localFloatingElement);
105
107
  const setReference = React.useCallback(node => {
106
108
  if (isElement(node) || node === null) {
107
109
  domReferenceRef.current = node;
@@ -131,24 +133,27 @@ function useFloating(options = {}) {
131
133
  }), [position.refs, setReference, setFloating, setPositionReference]);
132
134
  const elements = React.useMemo(() => ({
133
135
  ...position.elements,
134
- domReference: rootContextElements.domReference
135
- }), [position.elements, rootContextElements.domReference]);
136
- const open = rootContext.useState('open');
137
- const floatingId = rootContext.useState('floatingId');
136
+ domReference: domReferenceElement
137
+ }), [position.elements, domReferenceElement]);
138
138
  const context = React.useMemo(() => ({
139
139
  ...position,
140
- dataRef: rootContext.context.dataRef,
140
+ dataRef: store.context.dataRef,
141
141
  open,
142
- onOpenChange: rootContext.setOpen,
143
- events: rootContext.context.events,
142
+ onOpenChange: store.setOpen,
143
+ events: store.context.events,
144
144
  floatingId,
145
145
  refs,
146
146
  elements,
147
147
  nodeId,
148
- rootStore: rootContext
149
- }), [position, refs, elements, nodeId, rootContext, open, floatingId]);
148
+ rootStore: store
149
+ }), [position, refs, elements, nodeId, store, open, floatingId]);
150
150
  useIsoLayoutEffect(() => {
151
- rootContext.context.dataRef.current.floatingContext = context;
151
+ if (domReferenceElement) {
152
+ domReferenceRef.current = domReferenceElement;
153
+ }
154
+ }, [domReferenceElement]);
155
+ useIsoLayoutEffect(() => {
156
+ store.context.dataRef.current.floatingContext = context;
152
157
  const node = tree?.nodesRef.current.find(n => n.id === nodeId);
153
158
  if (node) {
154
159
  node.context = context;
@@ -159,8 +164,8 @@ function useFloating(options = {}) {
159
164
  context,
160
165
  refs,
161
166
  elements,
162
- rootStore: rootContext
163
- }), [position, refs, elements, context, rootContext]);
167
+ rootStore: store
168
+ }), [position, refs, elements, context, store]);
164
169
  }
165
170
 
166
171
  const ESCAPE = 'Escape';
@@ -189,10 +194,10 @@ function isCrossOrientationOpenKey(key, orientation, rtl) {
189
194
  const horizontal = key === ARROW_DOWN;
190
195
  return doSwitch(orientation, vertical, horizontal);
191
196
  }
192
- function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
197
+ function isCrossOrientationCloseKey(key, orientation, rtl, grid) {
193
198
  const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT;
194
199
  const horizontal = key === ARROW_UP;
195
- if (orientation === 'both' || orientation === 'horizontal' && cols && cols > 1) {
200
+ if (orientation === 'both' || orientation === 'horizontal' && grid) {
196
201
  return key === ESCAPE;
197
202
  }
198
203
  return doSwitch(orientation, vertical, horizontal);
@@ -203,11 +208,6 @@ function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
203
208
  * @see https://floating-ui.com/docs/useListNavigation
204
209
  */
205
210
  function useListNavigation(context, props) {
206
- const store = 'rootStore' in context ? context.rootStore : context;
207
- const open = store.useState('open');
208
- const floatingElement = store.useState('floatingElement');
209
- const domReferenceElement = store.useState('domReferenceElement');
210
- const dataRef = store.context.dataRef;
211
211
  const {
212
212
  listRef,
213
213
  activeIndex,
@@ -225,11 +225,12 @@ function useListNavigation(context, props) {
225
225
  disabledIndices = undefined,
226
226
  orientation = 'vertical',
227
227
  parentOrientation,
228
- cols = 1,
229
228
  id,
230
229
  resetOnPointerLeave = true,
231
- externalTree
230
+ externalTree,
231
+ grid: navigateGrid
232
232
  } = props;
233
+ const isGrid = navigateGrid != null;
233
234
  if (process.env.NODE_ENV !== 'production') {
234
235
  if (allowEscape) {
235
236
  if (!loopFocus) {
@@ -239,18 +240,20 @@ function useListNavigation(context, props) {
239
240
  console.warn('`useListNavigation` must be virtual to allow escaping.');
240
241
  }
241
242
  }
242
- if (orientation === 'vertical' && cols > 1) {
243
- console.warn('In grid list navigation mode (`cols` > 1), the `orientation` should', 'be either "horizontal" or "both".');
243
+ if (orientation === 'vertical' && isGrid) {
244
+ console.warn('In grid list navigation mode, the `orientation` should', 'be either "horizontal" or "both".');
244
245
  }
245
246
  }
247
+ const store = 'rootStore' in context ? context.rootStore : context;
248
+ const open = store.useState('open');
249
+ const floatingElement = store.useState('floatingElement');
250
+ const domReferenceElement = store.useState('domReferenceElement');
251
+ const dataRef = store.context.dataRef;
246
252
  const floatingFocusElement = getFloatingFocusElement(floatingElement);
253
+ const typeableComboboxReference = isTypeableCombobox(domReferenceElement);
247
254
  const floatingFocusElementRef = useValueAsRef(floatingFocusElement);
248
255
  const parentId = useFloatingParentNodeId();
249
256
  const tree = useFloatingTree(externalTree);
250
- useIsoLayoutEffect(() => {
251
- dataRef.current.orientation = orientation;
252
- }, [dataRef, orientation]);
253
- const typeableComboboxReference = isTypeableCombobox(domReferenceElement);
254
257
  const focusItemOnOpenRef = React.useRef(focusItemOnOpen);
255
258
  const indexRef = React.useRef(selectedIndex ?? -1);
256
259
  const keyRef = React.useRef(null);
@@ -258,21 +261,23 @@ function useListNavigation(context, props) {
258
261
  const onNavigate = useStableCallback(event => {
259
262
  onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event);
260
263
  });
261
- const previousOnNavigateRef = React.useRef(onNavigate);
262
264
  const previousMountedRef = React.useRef(!!floatingElement);
263
265
  const previousOpenRef = React.useRef(open);
264
266
  const forceSyncFocusRef = React.useRef(false);
265
267
  const forceScrollIntoViewRef = React.useRef(false);
268
+ const cancelQueuedFocusRef = React.useRef(null);
266
269
  const disabledIndicesRef = useValueAsRef(disabledIndices);
267
270
  const latestOpenRef = useValueAsRef(open);
268
271
  const selectedIndexRef = useValueAsRef(selectedIndex);
269
272
  const resetOnPointerLeaveRef = useValueAsRef(resetOnPointerLeave);
273
+ const focusFrame = useAnimationFrame();
274
+ const waitForListPopulatedFrame = useAnimationFrame();
270
275
  const focusItem = useStableCallback(() => {
271
276
  function runFocus(item) {
272
277
  if (virtual) {
273
278
  tree?.events.emit('virtualfocus', item);
274
279
  } else {
275
- enqueueFocus(item, {
280
+ cancelQueuedFocusRef.current = enqueueFocus(item, {
276
281
  sync: forceSyncFocusRef.current,
277
282
  preventScroll: true
278
283
  });
@@ -283,7 +288,7 @@ function useListNavigation(context, props) {
283
288
  if (initialItem) {
284
289
  runFocus(initialItem);
285
290
  }
286
- const scheduler = forceSyncFocusRef.current ? v => v() : requestAnimationFrame;
291
+ const scheduler = forceSyncFocusRef.current ? callback => callback() : callback => focusFrame.request(callback);
287
292
  scheduler(() => {
288
293
  const waitedItem = listRef.current[indexRef.current] || initialItem;
289
294
  if (!waitedItem) {
@@ -305,6 +310,9 @@ function useListNavigation(context, props) {
305
310
  }
306
311
  });
307
312
  });
313
+ useIsoLayoutEffect(() => {
314
+ dataRef.current.orientation = orientation;
315
+ }, [dataRef, orientation]);
308
316
 
309
317
  // Sync `selectedIndex` to be the `activeIndex` upon opening the floating
310
318
  // element. Also, reset `activeIndex` upon closing the floating element.
@@ -321,11 +329,11 @@ function useListNavigation(context, props) {
321
329
  onNavigate();
322
330
  }
323
331
  } else if (previousMountedRef.current) {
324
- // Since the user can specify `onNavigate` conditionally
325
- // (onNavigate: open ? setActiveIndex : setSelectedIndex),
326
- // we store and call the previous function.
332
+ // Reset the active index when the list is no longer open and mounted (closing or
333
+ // unmounting). `onNavigate` is a stable callback that always forwards to the latest
334
+ // `onNavigate` prop.
327
335
  indexRef.current = -1;
328
- previousOnNavigateRef.current();
336
+ onNavigate();
329
337
  }
330
338
  }, [enabled, open, floatingElement, selectedIndex, onNavigate]);
331
339
 
@@ -363,12 +371,15 @@ function useListNavigation(context, props) {
363
371
  // otherwise use rAF. Don't try more than twice, since something
364
372
  // is wrong otherwise.
365
373
  if (runs < 2) {
366
- const scheduler = runs ? requestAnimationFrame : queueMicrotask;
374
+ const scheduler = runs ? callback => waitForListPopulatedFrame.request(callback) : queueMicrotask;
367
375
  scheduler(waitForListPopulated);
368
376
  }
369
377
  runs += 1;
370
378
  } else {
371
- // initially focus the first non-disabled item
379
+ // Initially focus the first non-disabled item. `disabledIndices` is deliberately
380
+ // omitted here so attribute-disabled items (`disabled`/`aria-disabled`) are skipped
381
+ // on open even when the consumer passes an empty `disabledIndices` array. Passing it
382
+ // would regress that behavior (see mui/base-ui#2604).
372
383
  indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? getMinListIndex(listRef) : getMaxListIndex(listRef);
373
384
  keyRef.current = null;
374
385
  onNavigate();
@@ -376,12 +387,12 @@ function useListNavigation(context, props) {
376
387
  };
377
388
  waitForListPopulated();
378
389
  }
379
- } else if (!isIndexOutOfListBounds(listRef, activeIndex)) {
390
+ } else if (!isIndexOutOfListBounds(listRef.current, activeIndex)) {
380
391
  indexRef.current = activeIndex;
381
392
  focusItem();
382
393
  forceScrollIntoViewRef.current = false;
383
394
  }
384
- }, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, disabledIndicesRef]);
395
+ }, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, waitForListPopulatedFrame]);
385
396
 
386
397
  // Ensure the parent floating element has focus when a nested child closes
387
398
  // to allow arrow key navigation to work after the pointer leaves the child.
@@ -391,16 +402,18 @@ function useListNavigation(context, props) {
391
402
  }
392
403
  const nodes = tree.nodesRef.current;
393
404
  const parent = nodes.find(node => node.id === parentId)?.context?.elements.floating;
394
- const activeEl = activeElement(ownerDocument(floatingElement));
405
+ // `floatingElement` is null here (see the guard above), so resolve the owner document from an
406
+ // in-DOM element for realm-safety (shadow DOM/iframes): the reference element, falling back to
407
+ // the parent floating element when the reference is virtual (`domReferenceElement` is null).
408
+ const activeEl = activeElement(ownerDocument(domReferenceElement ?? parent ?? null));
395
409
  const treeContainsActiveEl = nodes.some(node => node.context && contains(node.context.elements.floating, activeEl));
396
410
  if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {
397
411
  parent.focus({
398
412
  preventScroll: true
399
413
  });
400
414
  }
401
- }, [enabled, floatingElement, tree, parentId, virtual]);
415
+ }, [enabled, floatingElement, domReferenceElement, tree, parentId, virtual]);
402
416
  useIsoLayoutEffect(() => {
403
- previousOnNavigateRef.current = onNavigate;
404
417
  previousOpenRef.current = open;
405
418
  previousMountedRef.current = !!floatingElement;
406
419
  });
@@ -411,68 +424,22 @@ function useListNavigation(context, props) {
411
424
  }
412
425
  }, [open, focusItemOnOpen]);
413
426
  const hasActiveIndex = activeIndex != null;
414
- const item = React.useMemo(() => {
415
- function syncCurrentTarget(event) {
416
- if (!latestOpenRef.current) {
417
- return;
418
- }
419
- const index = listRef.current.indexOf(event.currentTarget);
420
- if (index !== -1 && indexRef.current !== index) {
421
- indexRef.current = index;
422
- onNavigate(event);
423
- }
427
+ const syncCurrentTarget = useStableCallback(event => {
428
+ if (!latestOpenRef.current) {
429
+ return;
424
430
  }
425
- const itemProps = {
426
- onFocus(event) {
427
- forceSyncFocusRef.current = true;
428
- syncCurrentTarget(event);
429
- },
430
- onClick: ({
431
- currentTarget
432
- }) => currentTarget.focus({
433
- preventScroll: true
434
- }),
435
- // Safari
436
- onMouseMove(event) {
437
- forceSyncFocusRef.current = true;
438
- forceScrollIntoViewRef.current = false;
439
- if (focusItemOnHover) {
440
- syncCurrentTarget(event);
441
- }
442
- },
443
- onPointerLeave(event) {
444
- if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
445
- return;
446
- }
447
- forceSyncFocusRef.current = true;
448
- const relatedTarget = event.relatedTarget;
449
- if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
450
- return;
451
- }
452
- if (!resetOnPointerLeaveRef.current) {
453
- return;
454
- }
455
- enqueueFocus(null, {
456
- sync: true
457
- });
458
- indexRef.current = -1;
459
- onNavigate(event);
460
- if (!virtual) {
461
- const floatingFocusEl = floatingFocusElementRef.current;
462
- const activeEl = activeElement(ownerDocument(floatingFocusEl));
463
- if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {
464
- floatingFocusEl.focus({
465
- preventScroll: true
466
- });
467
- }
468
- }
469
- }
470
- };
471
- return itemProps;
472
- }, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
473
- const getParentOrientation = React.useCallback(() => {
431
+ const index = listRef.current.indexOf(event.currentTarget);
432
+ if (index !== -1 && (indexRef.current !== index || activeIndex !== index)) {
433
+ indexRef.current = index;
434
+ onNavigate(event);
435
+ }
436
+ });
437
+ const getParentOrientation = useStableCallback(() => {
474
438
  return parentOrientation ?? tree?.nodesRef.current.find(node => node.id === parentId)?.context?.dataRef?.current.orientation;
475
- }, [parentId, tree, parentOrientation]);
439
+ });
440
+ const getMinEnabledIndex = useStableCallback(() => {
441
+ return getMinListIndex(listRef, disabledIndicesRef.current);
442
+ });
476
443
  const commonOnKeyDown = useStableCallback(event => {
477
444
  isPointerModalityRef.current = false;
478
445
  forceSyncFocusRef.current = true;
@@ -491,7 +458,7 @@ function useListNavigation(context, props) {
491
458
  if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {
492
459
  return;
493
460
  }
494
- if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) {
461
+ if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, isGrid)) {
495
462
  // If the nested list's close key is also the parent navigation key,
496
463
  // let the parent navigate. Otherwise, stop propagating the event.
497
464
  if (!isMainOrientationKey(event.key, getParentOrientation())) {
@@ -523,41 +490,10 @@ function useListNavigation(context, props) {
523
490
  }
524
491
  }
525
492
 
526
- // Grid navigation.
527
- if (cols > 1) {
528
- const sizes = Array.from({
529
- length: listRef.current.length
530
- }, () => ({
531
- width: 1,
532
- height: 1
533
- }));
534
- // To calculate movements on the grid, we use hypothetical cell indices
535
- // as if every item was 1x1, then convert back to real indices.
536
- const cellMap = createGridCellMap(sizes, cols);
537
- const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(listRef, index, disabledIndices));
538
- // last enabled index
539
- const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(listRef, index, disabledIndices) ? cellIndex : foundIndex, -1);
540
- const index = cellMap[getGridNavigatedIndex({
541
- current: cellMap.map(itemIndex => itemIndex != null ? listRef.current[itemIndex] : null)
542
- }, {
543
- event,
544
- orientation,
545
- loopFocus,
546
- rtl,
547
- cols,
548
- // treat undefined (empty grid spaces) as disabled indices so we
549
- // don't end up in them
550
- disabledIndices: getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || listRef.current.map((_, listIndex) => isListIndexDisabled(listRef, listIndex, disabledIndices) ? listIndex : undefined)), undefined], cellMap),
551
- minIndex: minGridIndex,
552
- maxIndex: maxGridIndex,
553
- prevIndex: getGridCellIndexOfCorner(indexRef.current > maxIndex ? minIndex : indexRef.current, sizes, cellMap, cols,
554
- // use a corner matching the edge closest to the direction
555
- // we're moving in so we don't end up in the same item. Prefer
556
- // top/left over bottom/right.
557
- // eslint-disable-next-line no-nested-ternary
558
- event.key === ARROW_DOWN ? 'bl' : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT) ? 'tr' : 'tl'),
559
- stopEvent: true
560
- })];
493
+ // Grid navigation is injected by grid-capable consumers so non-grid
494
+ // consumers (menu, select) tree-shake the grid helpers out.
495
+ if (navigateGrid != null) {
496
+ const index = navigateGrid(event, indexRef.current, listRef, orientation, loopFocus, rtl, disabledIndices, minIndex, maxIndex);
561
497
  if (index != null) {
562
498
  indexRef.current = index;
563
499
  onNavigate(event);
@@ -586,13 +522,13 @@ function useListNavigation(context, props) {
586
522
  indexRef.current = minIndex;
587
523
  }
588
524
  } else {
589
- indexRef.current = findNonDisabledListIndex(listRef, {
525
+ indexRef.current = findNonDisabledListIndex(listRef.current, {
590
526
  startingIndex: currentIndex,
591
527
  disabledIndices
592
528
  });
593
529
  }
594
530
  } else {
595
- indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef, {
531
+ indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef.current, {
596
532
  startingIndex: currentIndex,
597
533
  disabledIndices
598
534
  }));
@@ -607,25 +543,73 @@ function useListNavigation(context, props) {
607
543
  indexRef.current = maxIndex;
608
544
  }
609
545
  } else {
610
- indexRef.current = findNonDisabledListIndex(listRef, {
546
+ indexRef.current = findNonDisabledListIndex(listRef.current, {
611
547
  startingIndex: currentIndex,
612
548
  decrement: true,
613
549
  disabledIndices
614
550
  });
615
551
  }
616
552
  } else {
617
- indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef, {
553
+ indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef.current, {
618
554
  startingIndex: currentIndex,
619
555
  decrement: true,
620
556
  disabledIndices
621
557
  }));
622
558
  }
623
- if (isIndexOutOfListBounds(listRef, indexRef.current)) {
559
+ if (isIndexOutOfListBounds(listRef.current, indexRef.current)) {
624
560
  indexRef.current = -1;
625
561
  }
626
562
  onNavigate(event);
627
563
  }
628
564
  });
565
+ const item = React.useMemo(() => {
566
+ const itemProps = {
567
+ onFocus(event) {
568
+ forceSyncFocusRef.current = true;
569
+ syncCurrentTarget(event);
570
+ },
571
+ onClick: ({
572
+ currentTarget
573
+ }) => currentTarget.focus({
574
+ preventScroll: true
575
+ }),
576
+ // Safari
577
+ onMouseMove(event) {
578
+ forceSyncFocusRef.current = true;
579
+ forceScrollIntoViewRef.current = false;
580
+ if (focusItemOnHover) {
581
+ syncCurrentTarget(event);
582
+ }
583
+ },
584
+ onPointerLeave(event) {
585
+ if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
586
+ return;
587
+ }
588
+ forceSyncFocusRef.current = true;
589
+ const relatedTarget = event.relatedTarget;
590
+ if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
591
+ return;
592
+ }
593
+ if (!resetOnPointerLeaveRef.current) {
594
+ return;
595
+ }
596
+ cancelQueuedFocusRef.current?.();
597
+ cancelQueuedFocusRef.current = null;
598
+ indexRef.current = -1;
599
+ onNavigate(event);
600
+ if (!virtual) {
601
+ const floatingFocusEl = floatingFocusElementRef.current;
602
+ const activeEl = activeElement(ownerDocument(floatingFocusEl));
603
+ if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {
604
+ floatingFocusEl.focus({
605
+ preventScroll: true
606
+ });
607
+ }
608
+ }
609
+ }
610
+ };
611
+ return itemProps;
612
+ }, [syncCurrentTarget, latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
629
613
  const ariaActiveDescendantProp = React.useMemo(() => {
630
614
  return virtual && open && hasActiveIndex && {
631
615
  'aria-activedescendant': `${id}-${activeIndex}`
@@ -660,6 +644,9 @@ function useListNavigation(context, props) {
660
644
  };
661
645
  }, [ariaActiveDescendantProp, commonOnKeyDown, floatingFocusElementRef, orientation, typeableComboboxReference, store, open, virtual, domReferenceElement]);
662
646
  const trigger = React.useMemo(() => {
647
+ function openOnNavigationKeyDown(event) {
648
+ store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
649
+ }
663
650
  function checkVirtualMouse(event) {
664
651
  if (focusItemOnOpen === 'auto' && isVirtualClick(event.nativeEvent)) {
665
652
  focusItemOnOpenRef.current = !virtual;
@@ -698,10 +685,10 @@ function useListNavigation(context, props) {
698
685
  if (isParentCrossOpenKey) {
699
686
  stopEvent(event);
700
687
  if (currentOpen) {
701
- indexRef.current = getMinListIndex(listRef, disabledIndicesRef.current);
688
+ indexRef.current = getMinEnabledIndex();
702
689
  onNavigate(event);
703
690
  } else {
704
- store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
691
+ openOnNavigationKeyDown(event);
705
692
  }
706
693
  }
707
694
  return undefined;
@@ -712,7 +699,7 @@ function useListNavigation(context, props) {
712
699
  }
713
700
  stopEvent(event);
714
701
  if (!currentOpen && openOnArrowKeyDown) {
715
- store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
702
+ openOnNavigationKeyDown(event);
716
703
  } else {
717
704
  commonOnKeyDown(event);
718
705
  }
@@ -733,7 +720,7 @@ function useListNavigation(context, props) {
733
720
  onMouseDown: checkVirtualMouse,
734
721
  onClick: checkVirtualMouse
735
722
  };
736
- }, [commonOnKeyDown, disabledIndicesRef, focusItemOnOpen, listRef, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
723
+ }, [commonOnKeyDown, focusItemOnOpen, getMinEnabledIndex, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
737
724
  const reference = React.useMemo(() => {
738
725
  return {
739
726
  ...ariaActiveDescendantProp,
@@ -754,65 +741,48 @@ function useListNavigation(context, props) {
754
741
  * @see https://floating-ui.com/docs/useTypeahead
755
742
  */
756
743
  function useTypeahead(context, props) {
757
- const store = 'rootStore' in context ? context.rootStore : context;
758
- const dataRef = store.context.dataRef;
759
- const open = store.useState('open');
760
744
  const {
761
745
  listRef,
762
746
  elementsRef,
763
747
  activeIndex,
764
748
  onMatch: onMatchProp,
765
- onTypingChange,
749
+ disabledIndices,
750
+ onTyping,
766
751
  enabled = true,
767
752
  resetMs = 750,
768
753
  selectedIndex = null
769
754
  } = props;
755
+ const store = 'rootStore' in context ? context.rootStore : context;
756
+ const open = store.useState('open');
770
757
  const timeout = useTimeout();
771
758
  const stringRef = React.useRef('');
772
759
  const prevIndexRef = React.useRef(selectedIndex ?? activeIndex ?? -1);
773
760
  const matchIndexRef = React.useRef(null);
774
- useIsoLayoutEffect(() => {
775
- if (!open && selectedIndex !== null) {
776
- return;
777
- }
778
- timeout.clear();
779
- matchIndexRef.current = null;
780
- if (stringRef.current !== '') {
781
- stringRef.current = '';
782
- }
783
- }, [open, selectedIndex, timeout]);
784
- useIsoLayoutEffect(() => {
785
- // Sync arrow key navigation but not typeahead navigation.
786
- if (open && stringRef.current === '') {
787
- prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
788
- }
789
- }, [open, selectedIndex, activeIndex]);
790
- const setTypingChange = useStableCallback(value => {
791
- if (value) {
792
- if (!dataRef.current.typing) {
793
- dataRef.current.typing = value;
794
- onTypingChange?.(value);
795
- }
796
- } else if (dataRef.current.typing) {
797
- dataRef.current.typing = value;
798
- onTypingChange?.(value);
799
- }
800
- });
801
761
  const onKeyDown = useStableCallback(event => {
802
762
  function isVisible(index) {
803
763
  const element = elementsRef?.current[index];
804
764
  return !element || isElementVisible(element);
805
765
  }
766
+ function isItemAvailable(index) {
767
+ if (!isVisible(index)) {
768
+ return false;
769
+ }
770
+ // Visibility is handled above; pass an empty element list so `isListIndexDisabled`
771
+ // resolves only the explicit `disabledIndices` (array/predicate) and skips its own
772
+ // visibility/attribute fallbacks. Consumers that don't opt in keep matching every
773
+ // visible item.
774
+ return disabledIndices == null || !isListIndexDisabled(EMPTY_ARRAY, index, disabledIndices);
775
+ }
806
776
  function getMatchingIndex(list, string, startIndex = 0) {
807
777
  if (list.length === 0) {
808
778
  return -1;
809
779
  }
810
780
  const normalizedStartIndex = (startIndex % list.length + list.length) % list.length;
811
- const lowerString = string.toLocaleLowerCase();
781
+ const lowerString = string.toLowerCase();
812
782
  for (let offset = 0; offset < list.length; offset += 1) {
813
783
  const index = (normalizedStartIndex + offset) % list.length;
814
784
  const text = list[index];
815
- if (!text?.toLocaleLowerCase().startsWith(lowerString) || !isVisible(index)) {
785
+ if (!text?.toLowerCase().startsWith(lowerString) || !isItemAvailable(index)) {
816
786
  continue;
817
787
  }
818
788
  return index;
@@ -823,11 +793,11 @@ function useTypeahead(context, props) {
823
793
  if (stringRef.current.length > 0 && event.key === ' ') {
824
794
  // Space should continue the in-progress typeahead session.
825
795
  stopEvent(event);
826
- setTypingChange(true);
796
+ onTyping?.(true);
827
797
  }
828
798
  if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') {
829
799
  if (getMatchingIndex(listContent, stringRef.current) === -1 && event.key !== ' ') {
830
- setTypingChange(false);
800
+ onTyping?.(false);
831
801
  }
832
802
  }
833
803
  if (listContent == null ||
@@ -839,7 +809,7 @@ function useTypeahead(context, props) {
839
809
  }
840
810
  if (open && event.key !== ' ') {
841
811
  stopEvent(event);
842
- setTypingChange(true);
812
+ onTyping?.(true);
843
813
  }
844
814
 
845
815
  // Capture whether this is a new typing session before mutating the string.
@@ -849,8 +819,10 @@ function useTypeahead(context, props) {
849
819
  }
850
820
 
851
821
  // Bail out if the list contains a word like "llama" or "aaron". TODO:
852
- // allow it in this case, too.
853
- const allowRapidSuccessionOfFirstLetter = listContent.every(text => text ? text[0]?.toLocaleLowerCase() !== text[1]?.toLocaleLowerCase() : true);
822
+ // allow it in this case, too. Unavailable items are skipped while matching, so
823
+ // they must be ignored here as well otherwise a hidden or disabled double-letter
824
+ // label would block rapid cycling through the available items.
825
+ const allowRapidSuccessionOfFirstLetter = listContent.every((text, index) => text && isItemAvailable(index) ? text[0]?.toLowerCase() !== text[1]?.toLowerCase() : true);
854
826
 
855
827
  // Allows the user to cycle through items that start with the same letter
856
828
  // in rapid succession.
@@ -862,7 +834,7 @@ function useTypeahead(context, props) {
862
834
  timeout.start(resetMs, () => {
863
835
  stringRef.current = '';
864
836
  prevIndexRef.current = matchIndexRef.current;
865
- setTypingChange(false);
837
+ onTyping?.(false);
866
838
  });
867
839
 
868
840
  // Compute the starting index for this search.
@@ -876,18 +848,17 @@ function useTypeahead(context, props) {
876
848
  matchIndexRef.current = index;
877
849
  } else if (event.key !== ' ') {
878
850
  stringRef.current = '';
879
- setTypingChange(false);
851
+ onTyping?.(false);
880
852
  }
881
853
  });
882
854
  const onBlur = useStableCallback(event => {
883
855
  const next = event.relatedTarget;
884
856
  const currentDomReferenceElement = store.select('domReferenceElement');
885
857
  const currentFloatingElement = store.select('floatingElement');
886
- const withinReference = contains(currentDomReferenceElement, next);
887
- const withinFloating = contains(currentFloatingElement, next);
858
+ const withinComposite = contains(currentDomReferenceElement, next) || contains(currentFloatingElement, next);
888
859
 
889
860
  // Keep the session if focus moves within the composite (reference <-> floating).
890
- if (withinReference || withinFloating) {
861
+ if (withinComposite) {
891
862
  return;
892
863
  }
893
864
 
@@ -895,33 +866,44 @@ function useTypeahead(context, props) {
895
866
  timeout.clear();
896
867
  stringRef.current = '';
897
868
  prevIndexRef.current = matchIndexRef.current;
898
- setTypingChange(false);
869
+ onTyping?.(false);
899
870
  });
900
- const reference = React.useMemo(() => ({
871
+ useIsoLayoutEffect(() => {
872
+ if (!open && selectedIndex !== null) {
873
+ return;
874
+ }
875
+ timeout.clear();
876
+ matchIndexRef.current = null;
877
+ if (stringRef.current !== '') {
878
+ stringRef.current = '';
879
+ }
880
+ }, [open, selectedIndex, timeout]);
881
+ useIsoLayoutEffect(() => {
882
+ // Sync arrow key navigation but not typeahead navigation.
883
+ if (open && stringRef.current === '') {
884
+ prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
885
+ }
886
+ }, [open, selectedIndex, activeIndex]);
887
+ const sharedProps = React.useMemo(() => ({
901
888
  onKeyDown,
902
889
  onBlur
903
890
  }), [onKeyDown, onBlur]);
904
- const floating = React.useMemo(() => {
905
- return {
906
- onKeyDown,
907
- onBlur
908
- };
909
- }, [onKeyDown, onBlur]);
910
891
  return React.useMemo(() => enabled ? {
911
- reference,
912
- floating
913
- } : {}, [enabled, reference, floating]);
892
+ reference: sharedProps,
893
+ floating: sharedProps
894
+ } : {}, [enabled, sharedProps]);
914
895
  }
915
896
 
916
897
  function getPseudoElementBounds(element) {
917
898
  const elementRect = element.getBoundingClientRect();
899
+ const win = getWindow(element);
918
900
 
919
- // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
920
- if (process.env.NODE_ENV !== 'production') {
901
+ // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)" in jsdom.
902
+ if (jsdom) {
921
903
  return elementRect;
922
904
  }
923
- const beforeStyles = window.getComputedStyle(element, '::before');
924
- const afterStyles = window.getComputedStyle(element, '::after');
905
+ const beforeStyles = win.getComputedStyle(element, '::before');
906
+ const afterStyles = win.getComputedStyle(element, '::after');
925
907
  const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
926
908
  if (!hasPseudoElements) {
927
909
  return elementRect;
@@ -1203,6 +1185,7 @@ const arrow = (options, deps) => ({
1203
1185
  options: [options, deps]
1204
1186
  });
1205
1187
 
1188
+ const nativeHideFn = hide$1().fn;
1206
1189
  const hide = {
1207
1190
  name: 'hide',
1208
1191
  async fn(state) {
@@ -1213,7 +1196,7 @@ const hide = {
1213
1196
  y
1214
1197
  } = state.rects.reference;
1215
1198
  const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;
1216
- const nativeHideResult = await hide$1().fn(state);
1199
+ const nativeHideResult = await nativeHideFn(state);
1217
1200
  return {
1218
1201
  data: {
1219
1202
  referenceHidden: nativeHideResult.data?.referenceHidden || anchorHidden
@@ -1343,6 +1326,7 @@ function useAnchorPositioning(params) {
1343
1326
  sticky = false,
1344
1327
  arrowPadding = 5,
1345
1328
  disableAnchorTracking = false,
1329
+ inline: inlineMiddleware,
1346
1330
  // Private parameters
1347
1331
  keepMounted = false,
1348
1332
  floatingRootContext,
@@ -1365,6 +1349,7 @@ function useAnchorPositioning(params) {
1365
1349
  const anchorFnCallback = useStableCallback(anchorFn);
1366
1350
  const anchorDep = anchorFn ? anchorFnCallback : anchor;
1367
1351
  const anchorValueRef = useValueAsRef(anchor);
1352
+ const mountedRef = useValueAsRef(mounted);
1368
1353
  const direction = useDirection();
1369
1354
  const isRtl = direction === 'rtl';
1370
1355
  const side = mountSide || {
@@ -1416,7 +1401,11 @@ function useAnchorPositioning(params) {
1416
1401
  const alignOffsetRef = useValueAsRef(alignOffset);
1417
1402
  const sideOffsetDep = typeof sideOffset !== 'function' ? sideOffset : 0;
1418
1403
  const alignOffsetDep = typeof alignOffset !== 'function' ? alignOffset : 0;
1419
- const middleware = [offset(state => {
1404
+ const middleware = [];
1405
+ if (inlineMiddleware) {
1406
+ middleware.push(inlineMiddleware);
1407
+ }
1408
+ middleware.push(offset(state => {
1420
1409
  const data = getOffsetData(state, sideParam, isRtl);
1421
1410
  const sideAxis = typeof sideOffsetRef.current === 'function' ? sideOffsetRef.current(data) : sideOffsetRef.current;
1422
1411
  const alignAxis = typeof alignOffsetRef.current === 'function' ? alignOffsetRef.current(data) : alignOffsetRef.current;
@@ -1425,7 +1414,7 @@ function useAnchorPositioning(params) {
1425
1414
  crossAxis: alignAxis,
1426
1415
  alignmentAxis: alignAxis
1427
1416
  };
1428
- }, [sideOffsetDep, alignOffsetDep, isRtl, sideParam])];
1417
+ }, [sideOffsetDep, alignOffsetDep, isRtl, sideParam]));
1429
1418
  const shiftDisabled = collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift';
1430
1419
  const crossAxisShiftEnabled = !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift');
1431
1420
  const flipMiddleware = collisionAvoidanceSide === 'none' ? null : flip({
@@ -1490,12 +1479,15 @@ function useAnchorPositioning(params) {
1490
1479
  availableHeight,
1491
1480
  rects
1492
1481
  }) {
1482
+ if (!mountedRef.current) {
1483
+ return;
1484
+ }
1493
1485
  const floatingStyle = floating.style;
1494
1486
  floatingStyle.setProperty('--available-width', `${availableWidth}px`);
1495
1487
  floatingStyle.setProperty('--available-height', `${availableHeight}px`);
1496
1488
 
1497
1489
  // Snap anchor dimensions to device pixels to ensure the popup's visual width matches the anchor's one.
1498
- const dpr = window.devicePixelRatio || 1;
1490
+ const dpr = getWindow(floating).devicePixelRatio || 1;
1499
1491
  const {
1500
1492
  x,
1501
1493
  y,
@@ -1507,10 +1499,10 @@ function useAnchorPositioning(params) {
1507
1499
  floatingStyle.setProperty('--anchor-width', `${anchorWidth}px`);
1508
1500
  floatingStyle.setProperty('--anchor-height', `${anchorHeight}px`);
1509
1501
  }
1510
- }), arrow(() => ({
1502
+ }), arrow(state => ({
1511
1503
  // `transform-origin` calculations rely on an element existing. If the arrow hasn't been set,
1512
1504
  // we'll create a fake element.
1513
- element: arrowRef.current || document.createElement('div'),
1505
+ element: arrowRef.current || ownerDocument(state.elements.floating).createElement('div'),
1514
1506
  padding: arrowPadding,
1515
1507
  offsetParent: 'floating'
1516
1508
  }), [arrowPadding]), {
@@ -1554,7 +1546,8 @@ function useAnchorPositioning(params) {
1554
1546
  floatingRootContext.update({
1555
1547
  referenceElement: null,
1556
1548
  floatingElement: null,
1557
- domReferenceElement: null
1549
+ domReferenceElement: null,
1550
+ positionReference: null
1558
1551
  });
1559
1552
  }
1560
1553
  }, [mounted, floatingRootContext]);
@@ -1575,6 +1568,7 @@ function useAnchorPositioning(params) {
1575
1568
  floatingStyles: originalFloatingStyles
1576
1569
  } = useFloating({
1577
1570
  rootContext: floatingRootContext,
1571
+ open: keepMounted ? mounted : undefined,
1578
1572
  placement,
1579
1573
  middleware,
1580
1574
  strategy: positionMethod,
@@ -1635,8 +1629,8 @@ function useAnchorPositioning(params) {
1635
1629
  }
1636
1630
  }, [mounted, refs, anchorDep, anchorValueRef]);
1637
1631
  React.useEffect(() => {
1638
- if (keepMounted && mounted && elements.domReference && elements.floating) {
1639
- return autoUpdate(elements.domReference, elements.floating, update, autoUpdateOptions);
1632
+ if (keepMounted && mounted && elements.reference && elements.floating) {
1633
+ return autoUpdate(elements.reference, elements.floating, update, autoUpdateOptions);
1640
1634
  }
1641
1635
  return undefined;
1642
1636
  }, [keepMounted, mounted, elements, update, autoUpdateOptions]);
@@ -1645,11 +1639,9 @@ function useAnchorPositioning(params) {
1645
1639
  const renderedAlign = getAlignment(renderedPlacement) || 'center';
1646
1640
  const anchorHidden = Boolean(middlewareData.hide?.referenceHidden);
1647
1641
 
1648
- /**
1649
- * Locks the flip (makes it "sticky") so it doesn't prefer a given placement
1650
- * and flips back lazily, not eagerly. Ideal for filtered lists that change
1651
- * the size of the popup dynamically to avoid unwanted flipping when typing.
1652
- */
1642
+ // Locks the flip (makes it "sticky") so it doesn't prefer a given placement
1643
+ // and flips back lazily, not eagerly. Ideal for filtered lists that change
1644
+ // the size of the popup dynamically to avoid unwanted flipping when typing.
1653
1645
  useIsoLayoutEffect(() => {
1654
1646
  if (lazyFlip && mounted && isPositioned) {
1655
1647
  setMountSide(renderedSide);
@@ -1684,6 +1676,61 @@ function getDisabledMountTransitionStyles(transitionStatus) {
1684
1676
  return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;
1685
1677
  }
1686
1678
 
1679
+ /**
1680
+ * Renders the shared outer Positioner element used by popup components.
1681
+ * Applies the common role, hidden state, transition styles, state attributes, and optional inert styling.
1682
+ */
1683
+ function usePositioner(componentProps, state, {
1684
+ styles,
1685
+ transitionStatus,
1686
+ props,
1687
+ refs,
1688
+ hidden,
1689
+ inert = false
1690
+ }) {
1691
+ const style = {
1692
+ ...styles
1693
+ };
1694
+ if (inert) {
1695
+ style.pointerEvents = 'none';
1696
+ }
1697
+ return useRenderElement('div', componentProps, {
1698
+ state,
1699
+ ref: refs,
1700
+ props: [{
1701
+ role: 'presentation',
1702
+ hidden,
1703
+ style
1704
+ }, getDisabledMountTransitionStyles(transitionStatus), props],
1705
+ stateAttributesMapping: popupStateMapping
1706
+ });
1707
+ }
1708
+
1709
+ // Touch-opened popups normally avoid scroll locking so users can still swipe outside to dismiss.
1710
+ // This hook re-enables scroll lock only when the popup is effectively full-width.
1711
+ // Treat popups with up to 20px of total horizontal gutter as full-width so common ~10px side
1712
+ // padding still locks scroll, since that leaves too little outside space for a reliable swipe.
1713
+ const VIEWPORT_WIDTH_TOLERANCE_PX = 20;
1714
+
1715
+ /**
1716
+ * Manages scroll lock for anchored popups. For non-touch opens, scroll lock is applied when
1717
+ * enabled. For touch opens, scroll lock is applied only when the positioner width is effectively
1718
+ * viewport-sized.
1719
+ */
1720
+ function useAnchoredPopupScrollLock(enabled, touchOpen, positionerElement, referenceElement) {
1721
+ const [touchOpenShouldLockScroll, setTouchOpenShouldLockScroll] = React.useState(false);
1722
+ useIsoLayoutEffect(() => {
1723
+ if (!enabled || !touchOpen || positionerElement == null) {
1724
+ setTouchOpenShouldLockScroll(false);
1725
+ return;
1726
+ }
1727
+ const viewportWidth = ownerDocument(positionerElement).documentElement.clientWidth;
1728
+ const popupWidth = positionerElement.offsetWidth;
1729
+ setTouchOpenShouldLockScroll(viewportWidth > 0 && popupWidth > 0 && popupWidth >= viewportWidth - VIEWPORT_WIDTH_TOLERANCE_PX);
1730
+ }, [enabled, touchOpen, positionerElement]);
1731
+ useScrollLock(enabled && (!touchOpen || touchOpenShouldLockScroll), referenceElement);
1732
+ }
1733
+
1687
1734
  let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
1688
1735
  IndexGuessBehavior[IndexGuessBehavior["None"] = 0] = "None";
1689
1736
  IndexGuessBehavior[IndexGuessBehavior["GuessFromOrder"] = 1] = "GuessFromOrder";
@@ -1753,10 +1800,10 @@ function useCompositeListItem(params = {}) {
1753
1800
  }
1754
1801
  });
1755
1802
  }, [externalIndex, subscribeMapChange, setIndex]);
1756
- return React.useMemo(() => ({
1803
+ return {
1757
1804
  ref,
1758
1805
  index
1759
- }), [index, ref]);
1806
+ };
1760
1807
  }
1761
1808
 
1762
- export { CompositeList as C, IndexGuessBehavior as I, useListNavigation as a, useTypeahead as b, useAnchorPositioning as c, getDisabledMountTransitionStyles as d, useCompositeListItem as e, adaptiveOrigin as f, getPseudoElementBounds as g, useFloatingRootContext as u };
1809
+ export { CompositeList as C, IndexGuessBehavior as I, useListNavigation as a, useTypeahead as b, useAnchoredPopupScrollLock as c, useAnchorPositioning as d, usePositioner as e, getDisabledMountTransitionStyles as f, getPseudoElementBounds as g, useCompositeListItem as h, adaptiveOrigin as i, useFloatingRootContext as u };