@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,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
- var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
5
4
  var useButton = require('./useButton-cjs.js');
6
- var InternalBackdrop = require('./InternalBackdrop-cjs.js');
5
+ var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
6
+ var useScrollLock = require('./useScrollLock-cjs.js');
7
7
  var useRenderElement = require('./useRenderElement-cjs.js');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
- var index_esm = require('./index.esm-cjs.js');
9
+ var floatingUi_utils = require('./floating-ui.utils-cjs.js');
10
10
  var floatingUi_reactDom = require('./floating-ui.react-dom-cjs.js');
11
11
 
12
12
  function _interopNamespaceDefault(e) {
@@ -35,22 +35,23 @@ function useFloatingRootContext(options) {
35
35
  elements = {}
36
36
  } = options;
37
37
  const floatingId = useButton.useId();
38
- const nested = InternalBackdrop.useFloatingParentNodeId() != null;
38
+ const nested = useScrollLock.useFloatingParentNodeId() != null;
39
39
  if (process.env.NODE_ENV !== 'production') {
40
40
  const optionDomReference = elements.reference;
41
41
  if (optionDomReference && !floatingUi_utils_dom.isElement(optionDomReference)) {
42
42
  console.error('Cannot pass a virtual element to the `elements.reference` option,', 'as it must be a real DOM element. Use `context.setPositionReference()`', 'instead.');
43
43
  }
44
44
  }
45
- const store = useRenderElement.useRefWithInit(() => new InternalBackdrop.FloatingRootStore({
45
+ const store = useRenderElement.useRefWithInit(() => new useScrollLock.FloatingRootStore({
46
46
  open,
47
+ transitionStatus: undefined,
47
48
  onOpenChange,
48
49
  referenceElement: elements.reference ?? null,
49
50
  floatingElement: elements.floating ?? null,
50
- triggerElements: new InternalBackdrop.PopupTriggerMap(),
51
+ triggerElements: new useScrollLock.PopupTriggerMap(),
51
52
  floatingId,
52
- nested,
53
- noEmit: false
53
+ syncOnly: false,
54
+ nested
54
55
  })).current;
55
56
  useButton.useIsoLayoutEffect(() => {
56
57
  const valuesToSync = {
@@ -70,7 +71,6 @@ function useFloatingRootContext(options) {
70
71
  }, [open, floatingId, elements.reference, elements.floating, store]);
71
72
  store.context.onOpenChange = onOpenChange;
72
73
  store.context.nested = nested;
73
- store.context.noEmit = false;
74
74
  return store;
75
75
  }
76
76
 
@@ -83,30 +83,37 @@ function useFloating(options = {}) {
83
83
  nodeId,
84
84
  externalTree
85
85
  } = options;
86
- const internalRootStore = useFloatingRootContext(options);
87
- const rootContext = options.rootContext || internalRootStore;
88
- const rootContextElements = {
89
- reference: rootContext.useState('referenceElement'),
90
- floating: rootContext.useState('floatingElement'),
91
- domReference: rootContext.useState('domReferenceElement')
92
- };
86
+ const internalStore = useFloatingRootContext(options);
87
+ const store = options.rootContext || internalStore;
88
+ const referenceElement = store.useState('referenceElement');
89
+ const floatingElement = store.useState('floatingElement');
90
+ const domReferenceElement = store.useState('domReferenceElement');
91
+ const open = store.useState('open');
92
+ const floatingId = store.useState('floatingId');
93
93
  const [positionReference, setPositionReferenceRaw] = React__namespace.useState(null);
94
+ const [localDomReference, setLocalDomReference] = React__namespace.useState(undefined);
95
+ const [localFloatingElement, setLocalFloatingElement] = React__namespace.useState(undefined);
94
96
  const domReferenceRef = React__namespace.useRef(null);
95
- const tree = InternalBackdrop.useFloatingTree(externalTree);
96
- useButton.useIsoLayoutEffect(() => {
97
- if (rootContextElements.domReference) {
98
- domReferenceRef.current = rootContextElements.domReference;
99
- }
100
- }, [rootContextElements.domReference]);
97
+ const tree = useScrollLock.useFloatingTree(externalTree);
98
+ const storeElements = React__namespace.useMemo(() => ({
99
+ reference: referenceElement,
100
+ floating: floatingElement,
101
+ domReference: domReferenceElement
102
+ }), [referenceElement, floatingElement, domReferenceElement]);
101
103
  const position = floatingUi_reactDom.useFloating({
102
104
  ...options,
103
105
  elements: {
104
- ...rootContextElements,
106
+ ...storeElements,
105
107
  ...(positionReference && {
106
108
  reference: positionReference
107
109
  })
108
110
  }
109
111
  });
112
+ const localDomReferenceElement = floatingUi_utils_dom.isElement(localDomReference) ? localDomReference : null;
113
+ const syncedFloatingElement = localFloatingElement === undefined ? store.state.floatingElement : localFloatingElement;
114
+ store.useSyncedValue('referenceElement', localDomReference ?? null);
115
+ store.useSyncedValue('domReferenceElement', localDomReference === undefined ? domReferenceElement : localDomReferenceElement);
116
+ store.useSyncedValue('floatingElement', syncedFloatingElement);
110
117
  const setPositionReference = React__namespace.useCallback(node => {
111
118
  const computedPositionReference = floatingUi_utils_dom.isElement(node) ? {
112
119
  getBoundingClientRect: () => node.getBoundingClientRect(),
@@ -118,11 +125,6 @@ function useFloating(options = {}) {
118
125
  setPositionReferenceRaw(computedPositionReference);
119
126
  position.refs.setReference(computedPositionReference);
120
127
  }, [position.refs]);
121
- const [localDomReference, setLocalDomReference] = React__namespace.useState(null);
122
- const [localFloatingElement, setLocalFloatingElement] = React__namespace.useState(null);
123
- rootContext.useSyncedValue('referenceElement', localDomReference);
124
- rootContext.useSyncedValue('domReferenceElement', floatingUi_utils_dom.isElement(localDomReference) ? localDomReference : null);
125
- rootContext.useSyncedValue('floatingElement', localFloatingElement);
126
128
  const setReference = React__namespace.useCallback(node => {
127
129
  if (floatingUi_utils_dom.isElement(node) || node === null) {
128
130
  domReferenceRef.current = node;
@@ -152,24 +154,27 @@ function useFloating(options = {}) {
152
154
  }), [position.refs, setReference, setFloating, setPositionReference]);
153
155
  const elements = React__namespace.useMemo(() => ({
154
156
  ...position.elements,
155
- domReference: rootContextElements.domReference
156
- }), [position.elements, rootContextElements.domReference]);
157
- const open = rootContext.useState('open');
158
- const floatingId = rootContext.useState('floatingId');
157
+ domReference: domReferenceElement
158
+ }), [position.elements, domReferenceElement]);
159
159
  const context = React__namespace.useMemo(() => ({
160
160
  ...position,
161
- dataRef: rootContext.context.dataRef,
161
+ dataRef: store.context.dataRef,
162
162
  open,
163
- onOpenChange: rootContext.setOpen,
164
- events: rootContext.context.events,
163
+ onOpenChange: store.setOpen,
164
+ events: store.context.events,
165
165
  floatingId,
166
166
  refs,
167
167
  elements,
168
168
  nodeId,
169
- rootStore: rootContext
170
- }), [position, refs, elements, nodeId, rootContext, open, floatingId]);
169
+ rootStore: store
170
+ }), [position, refs, elements, nodeId, store, open, floatingId]);
171
+ useButton.useIsoLayoutEffect(() => {
172
+ if (domReferenceElement) {
173
+ domReferenceRef.current = domReferenceElement;
174
+ }
175
+ }, [domReferenceElement]);
171
176
  useButton.useIsoLayoutEffect(() => {
172
- rootContext.context.dataRef.current.floatingContext = context;
177
+ store.context.dataRef.current.floatingContext = context;
173
178
  const node = tree?.nodesRef.current.find(n => n.id === nodeId);
174
179
  if (node) {
175
180
  node.context = context;
@@ -180,8 +185,8 @@ function useFloating(options = {}) {
180
185
  context,
181
186
  refs,
182
187
  elements,
183
- rootStore: rootContext
184
- }), [position, refs, elements, context, rootContext]);
188
+ rootStore: store
189
+ }), [position, refs, elements, context, store]);
185
190
  }
186
191
 
187
192
  const ESCAPE = 'Escape';
@@ -196,24 +201,24 @@ function doSwitch(orientation, vertical, horizontal) {
196
201
  }
197
202
  }
198
203
  function isMainOrientationKey(key, orientation) {
199
- const vertical = key === useButton.ARROW_UP || key === useButton.ARROW_DOWN;
200
- const horizontal = key === useButton.ARROW_LEFT || key === useButton.ARROW_RIGHT;
204
+ const vertical = key === useScrollLock.ARROW_UP || key === useScrollLock.ARROW_DOWN;
205
+ const horizontal = key === useScrollLock.ARROW_LEFT || key === useScrollLock.ARROW_RIGHT;
201
206
  return doSwitch(orientation, vertical, horizontal);
202
207
  }
203
208
  function isMainOrientationToEndKey(key, orientation, rtl) {
204
- const vertical = key === useButton.ARROW_DOWN;
205
- const horizontal = rtl ? key === useButton.ARROW_LEFT : key === useButton.ARROW_RIGHT;
209
+ const vertical = key === useScrollLock.ARROW_DOWN;
210
+ const horizontal = rtl ? key === useScrollLock.ARROW_LEFT : key === useScrollLock.ARROW_RIGHT;
206
211
  return doSwitch(orientation, vertical, horizontal) || key === 'Enter' || key === ' ' || key === '';
207
212
  }
208
213
  function isCrossOrientationOpenKey(key, orientation, rtl) {
209
- const vertical = rtl ? key === useButton.ARROW_LEFT : key === useButton.ARROW_RIGHT;
210
- const horizontal = key === useButton.ARROW_DOWN;
214
+ const vertical = rtl ? key === useScrollLock.ARROW_LEFT : key === useScrollLock.ARROW_RIGHT;
215
+ const horizontal = key === useScrollLock.ARROW_DOWN;
211
216
  return doSwitch(orientation, vertical, horizontal);
212
217
  }
213
- function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
214
- const vertical = rtl ? key === useButton.ARROW_RIGHT : key === useButton.ARROW_LEFT;
215
- const horizontal = key === useButton.ARROW_UP;
216
- if (orientation === 'both' || orientation === 'horizontal' && cols && cols > 1) {
218
+ function isCrossOrientationCloseKey(key, orientation, rtl, grid) {
219
+ const vertical = rtl ? key === useScrollLock.ARROW_RIGHT : key === useScrollLock.ARROW_LEFT;
220
+ const horizontal = key === useScrollLock.ARROW_UP;
221
+ if (orientation === 'both' || orientation === 'horizontal' && grid) {
217
222
  return key === ESCAPE;
218
223
  }
219
224
  return doSwitch(orientation, vertical, horizontal);
@@ -224,11 +229,6 @@ function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
224
229
  * @see https://floating-ui.com/docs/useListNavigation
225
230
  */
226
231
  function useListNavigation(context, props) {
227
- const store = 'rootStore' in context ? context.rootStore : context;
228
- const open = store.useState('open');
229
- const floatingElement = store.useState('floatingElement');
230
- const domReferenceElement = store.useState('domReferenceElement');
231
- const dataRef = store.context.dataRef;
232
232
  const {
233
233
  listRef,
234
234
  activeIndex,
@@ -246,11 +246,12 @@ function useListNavigation(context, props) {
246
246
  disabledIndices = undefined,
247
247
  orientation = 'vertical',
248
248
  parentOrientation,
249
- cols = 1,
250
249
  id,
251
250
  resetOnPointerLeave = true,
252
- externalTree
251
+ externalTree,
252
+ grid: navigateGrid
253
253
  } = props;
254
+ const isGrid = navigateGrid != null;
254
255
  if (process.env.NODE_ENV !== 'production') {
255
256
  if (allowEscape) {
256
257
  if (!loopFocus) {
@@ -260,18 +261,20 @@ function useListNavigation(context, props) {
260
261
  console.warn('`useListNavigation` must be virtual to allow escaping.');
261
262
  }
262
263
  }
263
- if (orientation === 'vertical' && cols > 1) {
264
- console.warn('In grid list navigation mode (`cols` > 1), the `orientation` should', 'be either "horizontal" or "both".');
264
+ if (orientation === 'vertical' && isGrid) {
265
+ console.warn('In grid list navigation mode, the `orientation` should', 'be either "horizontal" or "both".');
265
266
  }
266
267
  }
267
- const floatingFocusElement = useButton.getFloatingFocusElement(floatingElement);
268
+ const store = 'rootStore' in context ? context.rootStore : context;
269
+ const open = store.useState('open');
270
+ const floatingElement = store.useState('floatingElement');
271
+ const domReferenceElement = store.useState('domReferenceElement');
272
+ const dataRef = store.context.dataRef;
273
+ const floatingFocusElement = useScrollLock.getFloatingFocusElement(floatingElement);
274
+ const typeableComboboxReference = useScrollLock.isTypeableCombobox(domReferenceElement);
268
275
  const floatingFocusElementRef = useButton.useValueAsRef(floatingFocusElement);
269
- const parentId = InternalBackdrop.useFloatingParentNodeId();
270
- const tree = InternalBackdrop.useFloatingTree(externalTree);
271
- useButton.useIsoLayoutEffect(() => {
272
- dataRef.current.orientation = orientation;
273
- }, [dataRef, orientation]);
274
- const typeableComboboxReference = useButton.isTypeableCombobox(domReferenceElement);
276
+ const parentId = useScrollLock.useFloatingParentNodeId();
277
+ const tree = useScrollLock.useFloatingTree(externalTree);
275
278
  const focusItemOnOpenRef = React__namespace.useRef(focusItemOnOpen);
276
279
  const indexRef = React__namespace.useRef(selectedIndex ?? -1);
277
280
  const keyRef = React__namespace.useRef(null);
@@ -279,21 +282,23 @@ function useListNavigation(context, props) {
279
282
  const onNavigate = useButton.useStableCallback(event => {
280
283
  onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event);
281
284
  });
282
- const previousOnNavigateRef = React__namespace.useRef(onNavigate);
283
285
  const previousMountedRef = React__namespace.useRef(!!floatingElement);
284
286
  const previousOpenRef = React__namespace.useRef(open);
285
287
  const forceSyncFocusRef = React__namespace.useRef(false);
286
288
  const forceScrollIntoViewRef = React__namespace.useRef(false);
289
+ const cancelQueuedFocusRef = React__namespace.useRef(null);
287
290
  const disabledIndicesRef = useButton.useValueAsRef(disabledIndices);
288
291
  const latestOpenRef = useButton.useValueAsRef(open);
289
292
  const selectedIndexRef = useButton.useValueAsRef(selectedIndex);
290
293
  const resetOnPointerLeaveRef = useButton.useValueAsRef(resetOnPointerLeave);
294
+ const focusFrame = useButton.useAnimationFrame();
295
+ const waitForListPopulatedFrame = useButton.useAnimationFrame();
291
296
  const focusItem = useButton.useStableCallback(() => {
292
297
  function runFocus(item) {
293
298
  if (virtual) {
294
299
  tree?.events.emit('virtualfocus', item);
295
300
  } else {
296
- InternalBackdrop.enqueueFocus(item, {
301
+ cancelQueuedFocusRef.current = useScrollLock.enqueueFocus(item, {
297
302
  sync: forceSyncFocusRef.current,
298
303
  preventScroll: true
299
304
  });
@@ -304,7 +309,7 @@ function useListNavigation(context, props) {
304
309
  if (initialItem) {
305
310
  runFocus(initialItem);
306
311
  }
307
- const scheduler = forceSyncFocusRef.current ? v => v() : requestAnimationFrame;
312
+ const scheduler = forceSyncFocusRef.current ? callback => callback() : callback => focusFrame.request(callback);
308
313
  scheduler(() => {
309
314
  const waitedItem = listRef.current[indexRef.current] || initialItem;
310
315
  if (!waitedItem) {
@@ -326,6 +331,9 @@ function useListNavigation(context, props) {
326
331
  }
327
332
  });
328
333
  });
334
+ useButton.useIsoLayoutEffect(() => {
335
+ dataRef.current.orientation = orientation;
336
+ }, [dataRef, orientation]);
329
337
 
330
338
  // Sync `selectedIndex` to be the `activeIndex` upon opening the floating
331
339
  // element. Also, reset `activeIndex` upon closing the floating element.
@@ -342,11 +350,11 @@ function useListNavigation(context, props) {
342
350
  onNavigate();
343
351
  }
344
352
  } else if (previousMountedRef.current) {
345
- // Since the user can specify `onNavigate` conditionally
346
- // (onNavigate: open ? setActiveIndex : setSelectedIndex),
347
- // we store and call the previous function.
353
+ // Reset the active index when the list is no longer open and mounted (closing or
354
+ // unmounting). `onNavigate` is a stable callback that always forwards to the latest
355
+ // `onNavigate` prop.
348
356
  indexRef.current = -1;
349
- previousOnNavigateRef.current();
357
+ onNavigate();
350
358
  }
351
359
  }, [enabled, open, floatingElement, selectedIndex, onNavigate]);
352
360
 
@@ -384,25 +392,28 @@ function useListNavigation(context, props) {
384
392
  // otherwise use rAF. Don't try more than twice, since something
385
393
  // is wrong otherwise.
386
394
  if (runs < 2) {
387
- const scheduler = runs ? requestAnimationFrame : queueMicrotask;
395
+ const scheduler = runs ? callback => waitForListPopulatedFrame.request(callback) : queueMicrotask;
388
396
  scheduler(waitForListPopulated);
389
397
  }
390
398
  runs += 1;
391
399
  } else {
392
- // initially focus the first non-disabled item
393
- indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? InternalBackdrop.getMinListIndex(listRef) : InternalBackdrop.getMaxListIndex(listRef);
400
+ // Initially focus the first non-disabled item. `disabledIndices` is deliberately
401
+ // omitted here so attribute-disabled items (`disabled`/`aria-disabled`) are skipped
402
+ // on open even when the consumer passes an empty `disabledIndices` array. Passing it
403
+ // would regress that behavior (see mui/base-ui#2604).
404
+ indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? useScrollLock.getMinListIndex(listRef) : useScrollLock.getMaxListIndex(listRef);
394
405
  keyRef.current = null;
395
406
  onNavigate();
396
407
  }
397
408
  };
398
409
  waitForListPopulated();
399
410
  }
400
- } else if (!InternalBackdrop.isIndexOutOfListBounds(listRef, activeIndex)) {
411
+ } else if (!useScrollLock.isIndexOutOfListBounds(listRef.current, activeIndex)) {
401
412
  indexRef.current = activeIndex;
402
413
  focusItem();
403
414
  forceScrollIntoViewRef.current = false;
404
415
  }
405
- }, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, disabledIndicesRef]);
416
+ }, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, waitForListPopulatedFrame]);
406
417
 
407
418
  // Ensure the parent floating element has focus when a nested child closes
408
419
  // to allow arrow key navigation to work after the pointer leaves the child.
@@ -412,16 +423,18 @@ function useListNavigation(context, props) {
412
423
  }
413
424
  const nodes = tree.nodesRef.current;
414
425
  const parent = nodes.find(node => node.id === parentId)?.context?.elements.floating;
415
- const activeEl = useButton.activeElement(useButton.ownerDocument(floatingElement));
426
+ // `floatingElement` is null here (see the guard above), so resolve the owner document from an
427
+ // in-DOM element for realm-safety (shadow DOM/iframes): the reference element, falling back to
428
+ // the parent floating element when the reference is virtual (`domReferenceElement` is null).
429
+ const activeEl = useButton.activeElement(useButton.ownerDocument(domReferenceElement ?? parent ?? null));
416
430
  const treeContainsActiveEl = nodes.some(node => node.context && useButton.contains(node.context.elements.floating, activeEl));
417
431
  if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {
418
432
  parent.focus({
419
433
  preventScroll: true
420
434
  });
421
435
  }
422
- }, [enabled, floatingElement, tree, parentId, virtual]);
436
+ }, [enabled, floatingElement, domReferenceElement, tree, parentId, virtual]);
423
437
  useButton.useIsoLayoutEffect(() => {
424
- previousOnNavigateRef.current = onNavigate;
425
438
  previousOpenRef.current = open;
426
439
  previousMountedRef.current = !!floatingElement;
427
440
  });
@@ -432,68 +445,22 @@ function useListNavigation(context, props) {
432
445
  }
433
446
  }, [open, focusItemOnOpen]);
434
447
  const hasActiveIndex = activeIndex != null;
435
- const item = React__namespace.useMemo(() => {
436
- function syncCurrentTarget(event) {
437
- if (!latestOpenRef.current) {
438
- return;
439
- }
440
- const index = listRef.current.indexOf(event.currentTarget);
441
- if (index !== -1 && indexRef.current !== index) {
442
- indexRef.current = index;
443
- onNavigate(event);
444
- }
448
+ const syncCurrentTarget = useButton.useStableCallback(event => {
449
+ if (!latestOpenRef.current) {
450
+ return;
445
451
  }
446
- const itemProps = {
447
- onFocus(event) {
448
- forceSyncFocusRef.current = true;
449
- syncCurrentTarget(event);
450
- },
451
- onClick: ({
452
- currentTarget
453
- }) => currentTarget.focus({
454
- preventScroll: true
455
- }),
456
- // Safari
457
- onMouseMove(event) {
458
- forceSyncFocusRef.current = true;
459
- forceScrollIntoViewRef.current = false;
460
- if (focusItemOnHover) {
461
- syncCurrentTarget(event);
462
- }
463
- },
464
- onPointerLeave(event) {
465
- if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
466
- return;
467
- }
468
- forceSyncFocusRef.current = true;
469
- const relatedTarget = event.relatedTarget;
470
- if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
471
- return;
472
- }
473
- if (!resetOnPointerLeaveRef.current) {
474
- return;
475
- }
476
- InternalBackdrop.enqueueFocus(null, {
477
- sync: true
478
- });
479
- indexRef.current = -1;
480
- onNavigate(event);
481
- if (!virtual) {
482
- const floatingFocusEl = floatingFocusElementRef.current;
483
- const activeEl = useButton.activeElement(useButton.ownerDocument(floatingFocusEl));
484
- if (floatingFocusEl && useButton.contains(floatingFocusEl, activeEl)) {
485
- floatingFocusEl.focus({
486
- preventScroll: true
487
- });
488
- }
489
- }
490
- }
491
- };
492
- return itemProps;
493
- }, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
494
- const getParentOrientation = React__namespace.useCallback(() => {
452
+ const index = listRef.current.indexOf(event.currentTarget);
453
+ if (index !== -1 && (indexRef.current !== index || activeIndex !== index)) {
454
+ indexRef.current = index;
455
+ onNavigate(event);
456
+ }
457
+ });
458
+ const getParentOrientation = useButton.useStableCallback(() => {
495
459
  return parentOrientation ?? tree?.nodesRef.current.find(node => node.id === parentId)?.context?.dataRef?.current.orientation;
496
- }, [parentId, tree, parentOrientation]);
460
+ });
461
+ const getMinEnabledIndex = useButton.useStableCallback(() => {
462
+ return useScrollLock.getMinListIndex(listRef, disabledIndicesRef.current);
463
+ });
497
464
  const commonOnKeyDown = useButton.useStableCallback(event => {
498
465
  isPointerModalityRef.current = false;
499
466
  forceSyncFocusRef.current = true;
@@ -512,7 +479,7 @@ function useListNavigation(context, props) {
512
479
  if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {
513
480
  return;
514
481
  }
515
- if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) {
482
+ if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, isGrid)) {
516
483
  // If the nested list's close key is also the parent navigation key,
517
484
  // let the parent navigate. Otherwise, stop propagating the event.
518
485
  if (!isMainOrientationKey(event.key, getParentOrientation())) {
@@ -529,8 +496,8 @@ function useListNavigation(context, props) {
529
496
  return;
530
497
  }
531
498
  const currentIndex = indexRef.current;
532
- const minIndex = InternalBackdrop.getMinListIndex(listRef, disabledIndices);
533
- const maxIndex = InternalBackdrop.getMaxListIndex(listRef, disabledIndices);
499
+ const minIndex = useScrollLock.getMinListIndex(listRef, disabledIndices);
500
+ const maxIndex = useScrollLock.getMaxListIndex(listRef, disabledIndices);
534
501
  if (!typeableComboboxReference) {
535
502
  if (event.key === 'Home') {
536
503
  useButton.stopEvent(event);
@@ -544,41 +511,10 @@ function useListNavigation(context, props) {
544
511
  }
545
512
  }
546
513
 
547
- // Grid navigation.
548
- if (cols > 1) {
549
- const sizes = Array.from({
550
- length: listRef.current.length
551
- }, () => ({
552
- width: 1,
553
- height: 1
554
- }));
555
- // To calculate movements on the grid, we use hypothetical cell indices
556
- // as if every item was 1x1, then convert back to real indices.
557
- const cellMap = InternalBackdrop.createGridCellMap(sizes, cols);
558
- const minGridIndex = cellMap.findIndex(index => index != null && !InternalBackdrop.isListIndexDisabled(listRef, index, disabledIndices));
559
- // last enabled index
560
- const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !InternalBackdrop.isListIndexDisabled(listRef, index, disabledIndices) ? cellIndex : foundIndex, -1);
561
- const index = cellMap[InternalBackdrop.getGridNavigatedIndex({
562
- current: cellMap.map(itemIndex => itemIndex != null ? listRef.current[itemIndex] : null)
563
- }, {
564
- event,
565
- orientation,
566
- loopFocus,
567
- rtl,
568
- cols,
569
- // treat undefined (empty grid spaces) as disabled indices so we
570
- // don't end up in them
571
- disabledIndices: InternalBackdrop.getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || listRef.current.map((_, listIndex) => InternalBackdrop.isListIndexDisabled(listRef, listIndex, disabledIndices) ? listIndex : undefined)), undefined], cellMap),
572
- minIndex: minGridIndex,
573
- maxIndex: maxGridIndex,
574
- prevIndex: InternalBackdrop.getGridCellIndexOfCorner(indexRef.current > maxIndex ? minIndex : indexRef.current, sizes, cellMap, cols,
575
- // use a corner matching the edge closest to the direction
576
- // we're moving in so we don't end up in the same item. Prefer
577
- // top/left over bottom/right.
578
- // eslint-disable-next-line no-nested-ternary
579
- event.key === useButton.ARROW_DOWN ? 'bl' : event.key === (rtl ? useButton.ARROW_LEFT : useButton.ARROW_RIGHT) ? 'tr' : 'tl'),
580
- stopEvent: true
581
- })];
514
+ // Grid navigation is injected by grid-capable consumers so non-grid
515
+ // consumers (menu, select) tree-shake the grid helpers out.
516
+ if (navigateGrid != null) {
517
+ const index = navigateGrid(event, indexRef.current, listRef, orientation, loopFocus, rtl, disabledIndices, minIndex, maxIndex);
582
518
  if (index != null) {
583
519
  indexRef.current = index;
584
520
  onNavigate(event);
@@ -607,13 +543,13 @@ function useListNavigation(context, props) {
607
543
  indexRef.current = minIndex;
608
544
  }
609
545
  } else {
610
- indexRef.current = InternalBackdrop.findNonDisabledListIndex(listRef, {
546
+ indexRef.current = useScrollLock.findNonDisabledListIndex(listRef.current, {
611
547
  startingIndex: currentIndex,
612
548
  disabledIndices
613
549
  });
614
550
  }
615
551
  } else {
616
- indexRef.current = Math.min(maxIndex, InternalBackdrop.findNonDisabledListIndex(listRef, {
552
+ indexRef.current = Math.min(maxIndex, useScrollLock.findNonDisabledListIndex(listRef.current, {
617
553
  startingIndex: currentIndex,
618
554
  disabledIndices
619
555
  }));
@@ -628,25 +564,73 @@ function useListNavigation(context, props) {
628
564
  indexRef.current = maxIndex;
629
565
  }
630
566
  } else {
631
- indexRef.current = InternalBackdrop.findNonDisabledListIndex(listRef, {
567
+ indexRef.current = useScrollLock.findNonDisabledListIndex(listRef.current, {
632
568
  startingIndex: currentIndex,
633
569
  decrement: true,
634
570
  disabledIndices
635
571
  });
636
572
  }
637
573
  } else {
638
- indexRef.current = Math.max(minIndex, InternalBackdrop.findNonDisabledListIndex(listRef, {
574
+ indexRef.current = Math.max(minIndex, useScrollLock.findNonDisabledListIndex(listRef.current, {
639
575
  startingIndex: currentIndex,
640
576
  decrement: true,
641
577
  disabledIndices
642
578
  }));
643
579
  }
644
- if (InternalBackdrop.isIndexOutOfListBounds(listRef, indexRef.current)) {
580
+ if (useScrollLock.isIndexOutOfListBounds(listRef.current, indexRef.current)) {
645
581
  indexRef.current = -1;
646
582
  }
647
583
  onNavigate(event);
648
584
  }
649
585
  });
586
+ const item = React__namespace.useMemo(() => {
587
+ const itemProps = {
588
+ onFocus(event) {
589
+ forceSyncFocusRef.current = true;
590
+ syncCurrentTarget(event);
591
+ },
592
+ onClick: ({
593
+ currentTarget
594
+ }) => currentTarget.focus({
595
+ preventScroll: true
596
+ }),
597
+ // Safari
598
+ onMouseMove(event) {
599
+ forceSyncFocusRef.current = true;
600
+ forceScrollIntoViewRef.current = false;
601
+ if (focusItemOnHover) {
602
+ syncCurrentTarget(event);
603
+ }
604
+ },
605
+ onPointerLeave(event) {
606
+ if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
607
+ return;
608
+ }
609
+ forceSyncFocusRef.current = true;
610
+ const relatedTarget = event.relatedTarget;
611
+ if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
612
+ return;
613
+ }
614
+ if (!resetOnPointerLeaveRef.current) {
615
+ return;
616
+ }
617
+ cancelQueuedFocusRef.current?.();
618
+ cancelQueuedFocusRef.current = null;
619
+ indexRef.current = -1;
620
+ onNavigate(event);
621
+ if (!virtual) {
622
+ const floatingFocusEl = floatingFocusElementRef.current;
623
+ const activeEl = useButton.activeElement(useButton.ownerDocument(floatingFocusEl));
624
+ if (floatingFocusEl && useButton.contains(floatingFocusEl, activeEl)) {
625
+ floatingFocusEl.focus({
626
+ preventScroll: true
627
+ });
628
+ }
629
+ }
630
+ }
631
+ };
632
+ return itemProps;
633
+ }, [syncCurrentTarget, latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
650
634
  const ariaActiveDescendantProp = React__namespace.useMemo(() => {
651
635
  return virtual && open && hasActiveIndex && {
652
636
  'aria-activedescendant': `${id}-${activeIndex}`
@@ -681,6 +665,9 @@ function useListNavigation(context, props) {
681
665
  };
682
666
  }, [ariaActiveDescendantProp, commonOnKeyDown, floatingFocusElementRef, orientation, typeableComboboxReference, store, open, virtual, domReferenceElement]);
683
667
  const trigger = React__namespace.useMemo(() => {
668
+ function openOnNavigationKeyDown(event) {
669
+ store.setOpen(true, useButton.createChangeEventDetails(useButton.listNavigation, event.nativeEvent, event.currentTarget));
670
+ }
684
671
  function checkVirtualMouse(event) {
685
672
  if (focusItemOnOpen === 'auto' && useButton.isVirtualClick(event.nativeEvent)) {
686
673
  focusItemOnOpenRef.current = !virtual;
@@ -719,10 +706,10 @@ function useListNavigation(context, props) {
719
706
  if (isParentCrossOpenKey) {
720
707
  useButton.stopEvent(event);
721
708
  if (currentOpen) {
722
- indexRef.current = InternalBackdrop.getMinListIndex(listRef, disabledIndicesRef.current);
709
+ indexRef.current = getMinEnabledIndex();
723
710
  onNavigate(event);
724
711
  } else {
725
- store.setOpen(true, useButton.createChangeEventDetails(useButton.listNavigation, event.nativeEvent, event.currentTarget));
712
+ openOnNavigationKeyDown(event);
726
713
  }
727
714
  }
728
715
  return undefined;
@@ -733,7 +720,7 @@ function useListNavigation(context, props) {
733
720
  }
734
721
  useButton.stopEvent(event);
735
722
  if (!currentOpen && openOnArrowKeyDown) {
736
- store.setOpen(true, useButton.createChangeEventDetails(useButton.listNavigation, event.nativeEvent, event.currentTarget));
723
+ openOnNavigationKeyDown(event);
737
724
  } else {
738
725
  commonOnKeyDown(event);
739
726
  }
@@ -754,7 +741,7 @@ function useListNavigation(context, props) {
754
741
  onMouseDown: checkVirtualMouse,
755
742
  onClick: checkVirtualMouse
756
743
  };
757
- }, [commonOnKeyDown, disabledIndicesRef, focusItemOnOpen, listRef, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
744
+ }, [commonOnKeyDown, focusItemOnOpen, getMinEnabledIndex, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
758
745
  const reference = React__namespace.useMemo(() => {
759
746
  return {
760
747
  ...ariaActiveDescendantProp,
@@ -775,65 +762,48 @@ function useListNavigation(context, props) {
775
762
  * @see https://floating-ui.com/docs/useTypeahead
776
763
  */
777
764
  function useTypeahead(context, props) {
778
- const store = 'rootStore' in context ? context.rootStore : context;
779
- const dataRef = store.context.dataRef;
780
- const open = store.useState('open');
781
765
  const {
782
766
  listRef,
783
767
  elementsRef,
784
768
  activeIndex,
785
769
  onMatch: onMatchProp,
786
- onTypingChange,
770
+ disabledIndices,
771
+ onTyping,
787
772
  enabled = true,
788
773
  resetMs = 750,
789
774
  selectedIndex = null
790
775
  } = props;
776
+ const store = 'rootStore' in context ? context.rootStore : context;
777
+ const open = store.useState('open');
791
778
  const timeout = useButton.useTimeout();
792
779
  const stringRef = React__namespace.useRef('');
793
780
  const prevIndexRef = React__namespace.useRef(selectedIndex ?? activeIndex ?? -1);
794
781
  const matchIndexRef = React__namespace.useRef(null);
795
- useButton.useIsoLayoutEffect(() => {
796
- if (!open && selectedIndex !== null) {
797
- return;
798
- }
799
- timeout.clear();
800
- matchIndexRef.current = null;
801
- if (stringRef.current !== '') {
802
- stringRef.current = '';
803
- }
804
- }, [open, selectedIndex, timeout]);
805
- useButton.useIsoLayoutEffect(() => {
806
- // Sync arrow key navigation but not typeahead navigation.
807
- if (open && stringRef.current === '') {
808
- prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
809
- }
810
- }, [open, selectedIndex, activeIndex]);
811
- const setTypingChange = useButton.useStableCallback(value => {
812
- if (value) {
813
- if (!dataRef.current.typing) {
814
- dataRef.current.typing = value;
815
- onTypingChange?.(value);
816
- }
817
- } else if (dataRef.current.typing) {
818
- dataRef.current.typing = value;
819
- onTypingChange?.(value);
820
- }
821
- });
822
782
  const onKeyDown = useButton.useStableCallback(event => {
823
783
  function isVisible(index) {
824
784
  const element = elementsRef?.current[index];
825
- return !element || InternalBackdrop.isElementVisible(element);
785
+ return !element || useScrollLock.isElementVisible(element);
786
+ }
787
+ function isItemAvailable(index) {
788
+ if (!isVisible(index)) {
789
+ return false;
790
+ }
791
+ // Visibility is handled above; pass an empty element list so `isListIndexDisabled`
792
+ // resolves only the explicit `disabledIndices` (array/predicate) and skips its own
793
+ // visibility/attribute fallbacks. Consumers that don't opt in keep matching every
794
+ // visible item.
795
+ return disabledIndices == null || !useScrollLock.isListIndexDisabled(useRenderElement.EMPTY_ARRAY, index, disabledIndices);
826
796
  }
827
797
  function getMatchingIndex(list, string, startIndex = 0) {
828
798
  if (list.length === 0) {
829
799
  return -1;
830
800
  }
831
801
  const normalizedStartIndex = (startIndex % list.length + list.length) % list.length;
832
- const lowerString = string.toLocaleLowerCase();
802
+ const lowerString = string.toLowerCase();
833
803
  for (let offset = 0; offset < list.length; offset += 1) {
834
804
  const index = (normalizedStartIndex + offset) % list.length;
835
805
  const text = list[index];
836
- if (!text?.toLocaleLowerCase().startsWith(lowerString) || !isVisible(index)) {
806
+ if (!text?.toLowerCase().startsWith(lowerString) || !isItemAvailable(index)) {
837
807
  continue;
838
808
  }
839
809
  return index;
@@ -844,11 +814,11 @@ function useTypeahead(context, props) {
844
814
  if (stringRef.current.length > 0 && event.key === ' ') {
845
815
  // Space should continue the in-progress typeahead session.
846
816
  useButton.stopEvent(event);
847
- setTypingChange(true);
817
+ onTyping?.(true);
848
818
  }
849
819
  if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') {
850
820
  if (getMatchingIndex(listContent, stringRef.current) === -1 && event.key !== ' ') {
851
- setTypingChange(false);
821
+ onTyping?.(false);
852
822
  }
853
823
  }
854
824
  if (listContent == null ||
@@ -860,7 +830,7 @@ function useTypeahead(context, props) {
860
830
  }
861
831
  if (open && event.key !== ' ') {
862
832
  useButton.stopEvent(event);
863
- setTypingChange(true);
833
+ onTyping?.(true);
864
834
  }
865
835
 
866
836
  // Capture whether this is a new typing session before mutating the string.
@@ -870,8 +840,10 @@ function useTypeahead(context, props) {
870
840
  }
871
841
 
872
842
  // Bail out if the list contains a word like "llama" or "aaron". TODO:
873
- // allow it in this case, too.
874
- const allowRapidSuccessionOfFirstLetter = listContent.every(text => text ? text[0]?.toLocaleLowerCase() !== text[1]?.toLocaleLowerCase() : true);
843
+ // allow it in this case, too. Unavailable items are skipped while matching, so
844
+ // they must be ignored here as well otherwise a hidden or disabled double-letter
845
+ // label would block rapid cycling through the available items.
846
+ const allowRapidSuccessionOfFirstLetter = listContent.every((text, index) => text && isItemAvailable(index) ? text[0]?.toLowerCase() !== text[1]?.toLowerCase() : true);
875
847
 
876
848
  // Allows the user to cycle through items that start with the same letter
877
849
  // in rapid succession.
@@ -883,7 +855,7 @@ function useTypeahead(context, props) {
883
855
  timeout.start(resetMs, () => {
884
856
  stringRef.current = '';
885
857
  prevIndexRef.current = matchIndexRef.current;
886
- setTypingChange(false);
858
+ onTyping?.(false);
887
859
  });
888
860
 
889
861
  // Compute the starting index for this search.
@@ -897,18 +869,17 @@ function useTypeahead(context, props) {
897
869
  matchIndexRef.current = index;
898
870
  } else if (event.key !== ' ') {
899
871
  stringRef.current = '';
900
- setTypingChange(false);
872
+ onTyping?.(false);
901
873
  }
902
874
  });
903
875
  const onBlur = useButton.useStableCallback(event => {
904
876
  const next = event.relatedTarget;
905
877
  const currentDomReferenceElement = store.select('domReferenceElement');
906
878
  const currentFloatingElement = store.select('floatingElement');
907
- const withinReference = useButton.contains(currentDomReferenceElement, next);
908
- const withinFloating = useButton.contains(currentFloatingElement, next);
879
+ const withinComposite = useButton.contains(currentDomReferenceElement, next) || useButton.contains(currentFloatingElement, next);
909
880
 
910
881
  // Keep the session if focus moves within the composite (reference <-> floating).
911
- if (withinReference || withinFloating) {
882
+ if (withinComposite) {
912
883
  return;
913
884
  }
914
885
 
@@ -916,33 +887,44 @@ function useTypeahead(context, props) {
916
887
  timeout.clear();
917
888
  stringRef.current = '';
918
889
  prevIndexRef.current = matchIndexRef.current;
919
- setTypingChange(false);
890
+ onTyping?.(false);
920
891
  });
921
- const reference = React__namespace.useMemo(() => ({
892
+ useButton.useIsoLayoutEffect(() => {
893
+ if (!open && selectedIndex !== null) {
894
+ return;
895
+ }
896
+ timeout.clear();
897
+ matchIndexRef.current = null;
898
+ if (stringRef.current !== '') {
899
+ stringRef.current = '';
900
+ }
901
+ }, [open, selectedIndex, timeout]);
902
+ useButton.useIsoLayoutEffect(() => {
903
+ // Sync arrow key navigation but not typeahead navigation.
904
+ if (open && stringRef.current === '') {
905
+ prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
906
+ }
907
+ }, [open, selectedIndex, activeIndex]);
908
+ const sharedProps = React__namespace.useMemo(() => ({
922
909
  onKeyDown,
923
910
  onBlur
924
911
  }), [onKeyDown, onBlur]);
925
- const floating = React__namespace.useMemo(() => {
926
- return {
927
- onKeyDown,
928
- onBlur
929
- };
930
- }, [onKeyDown, onBlur]);
931
912
  return React__namespace.useMemo(() => enabled ? {
932
- reference,
933
- floating
934
- } : {}, [enabled, reference, floating]);
913
+ reference: sharedProps,
914
+ floating: sharedProps
915
+ } : {}, [enabled, sharedProps]);
935
916
  }
936
917
 
937
918
  function getPseudoElementBounds(element) {
938
919
  const elementRect = element.getBoundingClientRect();
920
+ const win = floatingUi_utils_dom.getWindow(element);
939
921
 
940
- // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
941
- if (process.env.NODE_ENV !== 'production') {
922
+ // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)" in jsdom.
923
+ if (useButton.jsdom) {
942
924
  return elementRect;
943
925
  }
944
- const beforeStyles = window.getComputedStyle(element, '::before');
945
- const afterStyles = window.getComputedStyle(element, '::after');
926
+ const beforeStyles = win.getComputedStyle(element, '::before');
927
+ const afterStyles = win.getComputedStyle(element, '::after');
946
928
  const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
947
929
  if (!hasPseudoElements) {
948
930
  return elementRect;
@@ -1152,17 +1134,17 @@ const baseArrow = options => ({
1152
1134
  element,
1153
1135
  padding = 0,
1154
1136
  offsetParent = 'real'
1155
- } = index_esm.evaluate(options, state) || {};
1137
+ } = floatingUi_utils.evaluate(options, state) || {};
1156
1138
  if (element == null) {
1157
1139
  return {};
1158
1140
  }
1159
- const paddingObject = index_esm.getPaddingObject(padding);
1141
+ const paddingObject = floatingUi_utils.getPaddingObject(padding);
1160
1142
  const coords = {
1161
1143
  x,
1162
1144
  y
1163
1145
  };
1164
- const axis = index_esm.getAlignmentAxis(placement);
1165
- const length = index_esm.getAxisLength(axis);
1146
+ const axis = floatingUi_utils.getAlignmentAxis(placement);
1147
+ const length = floatingUi_utils.getAxisLength(axis);
1166
1148
  const arrowDimensions = await platform.getDimensions(element);
1167
1149
  const isYAxis = axis === 'y';
1168
1150
  const minProp = isYAxis ? 'top' : 'left';
@@ -1190,13 +1172,13 @@ const baseArrow = options => ({
1190
1172
  const min = minPadding;
1191
1173
  const max = clientSize - arrowDimensions[length] - maxPadding;
1192
1174
  const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
1193
- const offset = index_esm.clamp(min, center, max);
1175
+ const offset = floatingUi_utils.clamp(min, center, max);
1194
1176
 
1195
1177
  // If the reference is small enough that the arrow's padding causes it to
1196
1178
  // to point to nothing for an aligned placement, adjust the offset of the
1197
1179
  // floating element itself. To ensure `shift()` continues to take action,
1198
1180
  // a single reset is performed when this is true.
1199
- const shouldAddOffset = !middlewareData.arrow && index_esm.getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
1181
+ const shouldAddOffset = !middlewareData.arrow && floatingUi_utils.getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
1200
1182
  // eslint-disable-next-line no-nested-ternary
1201
1183
  const alignmentOffset = shouldAddOffset ? center < min ? center - min : center - max : 0;
1202
1184
  return {
@@ -1224,6 +1206,7 @@ const arrow = (options, deps) => ({
1224
1206
  options: [options, deps]
1225
1207
  });
1226
1208
 
1209
+ const nativeHideFn = floatingUi_reactDom.hide().fn;
1227
1210
  const hide = {
1228
1211
  name: 'hide',
1229
1212
  async fn(state) {
@@ -1234,7 +1217,7 @@ const hide = {
1234
1217
  y
1235
1218
  } = state.rects.reference;
1236
1219
  const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;
1237
- const nativeHideResult = await floatingUi_reactDom.hide().fn(state);
1220
+ const nativeHideResult = await nativeHideFn(state);
1238
1221
  return {
1239
1222
  data: {
1240
1223
  referenceHidden: nativeHideResult.data?.referenceHidden || anchorHidden
@@ -1294,7 +1277,7 @@ const adaptiveOrigin = {
1294
1277
  } else if (await platform.isElement?.(offsetParent)) {
1295
1278
  offsetDimensions = await platform.getDimensions(offsetParent);
1296
1279
  }
1297
- const currentSide = index_esm.getSide(placement);
1280
+ const currentSide = floatingUi_utils.getSide(placement);
1298
1281
  let x = rawX;
1299
1282
  let y = rawY;
1300
1283
  if (currentSide === 'left') {
@@ -1333,8 +1316,8 @@ function getOffsetData(state, sideParam, isRtl) {
1333
1316
  placement
1334
1317
  } = state;
1335
1318
  const data = {
1336
- side: getLogicalSide(sideParam, index_esm.getSide(placement), isRtl),
1337
- align: index_esm.getAlignment(placement) || 'center',
1319
+ side: getLogicalSide(sideParam, floatingUi_utils.getSide(placement), isRtl),
1320
+ align: floatingUi_utils.getAlignment(placement) || 'center',
1338
1321
  anchor: {
1339
1322
  width: rects.reference.width,
1340
1323
  height: rects.reference.height
@@ -1364,6 +1347,7 @@ function useAnchorPositioning(params) {
1364
1347
  sticky = false,
1365
1348
  arrowPadding = 5,
1366
1349
  disableAnchorTracking = false,
1350
+ inline: inlineMiddleware,
1367
1351
  // Private parameters
1368
1352
  keepMounted = false,
1369
1353
  floatingRootContext,
@@ -1386,7 +1370,8 @@ function useAnchorPositioning(params) {
1386
1370
  const anchorFnCallback = useButton.useStableCallback(anchorFn);
1387
1371
  const anchorDep = anchorFn ? anchorFnCallback : anchor;
1388
1372
  const anchorValueRef = useButton.useValueAsRef(anchor);
1389
- const direction = InternalBackdrop.useDirection();
1373
+ const mountedRef = useButton.useValueAsRef(mounted);
1374
+ const direction = useScrollLock.useDirection();
1390
1375
  const isRtl = direction === 'rtl';
1391
1376
  const side = mountSide || {
1392
1377
  top: 'top',
@@ -1437,7 +1422,11 @@ function useAnchorPositioning(params) {
1437
1422
  const alignOffsetRef = useButton.useValueAsRef(alignOffset);
1438
1423
  const sideOffsetDep = typeof sideOffset !== 'function' ? sideOffset : 0;
1439
1424
  const alignOffsetDep = typeof alignOffset !== 'function' ? alignOffset : 0;
1440
- const middleware = [floatingUi_reactDom.offset(state => {
1425
+ const middleware = [];
1426
+ if (inlineMiddleware) {
1427
+ middleware.push(inlineMiddleware);
1428
+ }
1429
+ middleware.push(floatingUi_reactDom.offset(state => {
1441
1430
  const data = getOffsetData(state, sideParam, isRtl);
1442
1431
  const sideAxis = typeof sideOffsetRef.current === 'function' ? sideOffsetRef.current(data) : sideOffsetRef.current;
1443
1432
  const alignAxis = typeof alignOffsetRef.current === 'function' ? alignOffsetRef.current(data) : alignOffsetRef.current;
@@ -1446,7 +1435,7 @@ function useAnchorPositioning(params) {
1446
1435
  crossAxis: alignAxis,
1447
1436
  alignmentAxis: alignAxis
1448
1437
  };
1449
- }, [sideOffsetDep, alignOffsetDep, isRtl, sideParam])];
1438
+ }, [sideOffsetDep, alignOffsetDep, isRtl, sideParam]));
1450
1439
  const shiftDisabled = collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift';
1451
1440
  const crossAxisShiftEnabled = !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift');
1452
1441
  const flipMiddleware = collisionAvoidanceSide === 'none' ? null : floatingUi_reactDom.flip({
@@ -1485,7 +1474,7 @@ function useAnchorPositioning(params) {
1485
1474
  width,
1486
1475
  height
1487
1476
  } = arrowRef.current.getBoundingClientRect();
1488
- const sideAxis = index_esm.getSideAxis(index_esm.getSide(limitData.placement));
1477
+ const sideAxis = floatingUi_utils.getSideAxis(floatingUi_utils.getSide(limitData.placement));
1489
1478
  const arrowSize = sideAxis === 'y' ? width : height;
1490
1479
  const offsetAmount = sideAxis === 'y' ? collisionPadding.left + collisionPadding.right : collisionPadding.top + collisionPadding.bottom;
1491
1480
  return {
@@ -1511,12 +1500,15 @@ function useAnchorPositioning(params) {
1511
1500
  availableHeight,
1512
1501
  rects
1513
1502
  }) {
1503
+ if (!mountedRef.current) {
1504
+ return;
1505
+ }
1514
1506
  const floatingStyle = floating.style;
1515
1507
  floatingStyle.setProperty('--available-width', `${availableWidth}px`);
1516
1508
  floatingStyle.setProperty('--available-height', `${availableHeight}px`);
1517
1509
 
1518
1510
  // Snap anchor dimensions to device pixels to ensure the popup's visual width matches the anchor's one.
1519
- const dpr = window.devicePixelRatio || 1;
1511
+ const dpr = floatingUi_utils_dom.getWindow(floating).devicePixelRatio || 1;
1520
1512
  const {
1521
1513
  x,
1522
1514
  y,
@@ -1528,10 +1520,10 @@ function useAnchorPositioning(params) {
1528
1520
  floatingStyle.setProperty('--anchor-width', `${anchorWidth}px`);
1529
1521
  floatingStyle.setProperty('--anchor-height', `${anchorHeight}px`);
1530
1522
  }
1531
- }), arrow(() => ({
1523
+ }), arrow(state => ({
1532
1524
  // `transform-origin` calculations rely on an element existing. If the arrow hasn't been set,
1533
1525
  // we'll create a fake element.
1534
- element: arrowRef.current || document.createElement('div'),
1526
+ element: arrowRef.current || useButton.ownerDocument(state.elements.floating).createElement('div'),
1535
1527
  padding: arrowPadding,
1536
1528
  offsetParent: 'floating'
1537
1529
  }), [arrowPadding]), {
@@ -1544,8 +1536,8 @@ function useAnchorPositioning(params) {
1544
1536
  rects,
1545
1537
  y
1546
1538
  } = state;
1547
- const currentRenderedSide = index_esm.getSide(renderedPlacement);
1548
- const currentRenderedAxis = index_esm.getSideAxis(currentRenderedSide);
1539
+ const currentRenderedSide = floatingUi_utils.getSide(renderedPlacement);
1540
+ const currentRenderedAxis = floatingUi_utils.getSideAxis(currentRenderedSide);
1549
1541
  const arrowEl = arrowRef.current;
1550
1542
  const arrowX = middlewareData.arrow?.x || 0;
1551
1543
  const arrowY = middlewareData.arrow?.y || 0;
@@ -1575,7 +1567,8 @@ function useAnchorPositioning(params) {
1575
1567
  floatingRootContext.update({
1576
1568
  referenceElement: null,
1577
1569
  floatingElement: null,
1578
- domReferenceElement: null
1570
+ domReferenceElement: null,
1571
+ positionReference: null
1579
1572
  });
1580
1573
  }
1581
1574
  }, [mounted, floatingRootContext]);
@@ -1596,6 +1589,7 @@ function useAnchorPositioning(params) {
1596
1589
  floatingStyles: originalFloatingStyles
1597
1590
  } = useFloating({
1598
1591
  rootContext: floatingRootContext,
1592
+ open: keepMounted ? mounted : undefined,
1599
1593
  placement,
1600
1594
  middleware,
1601
1595
  strategy: positionMethod,
@@ -1656,21 +1650,19 @@ function useAnchorPositioning(params) {
1656
1650
  }
1657
1651
  }, [mounted, refs, anchorDep, anchorValueRef]);
1658
1652
  React__namespace.useEffect(() => {
1659
- if (keepMounted && mounted && elements.domReference && elements.floating) {
1660
- return floatingUi_reactDom.autoUpdate(elements.domReference, elements.floating, update, autoUpdateOptions);
1653
+ if (keepMounted && mounted && elements.reference && elements.floating) {
1654
+ return floatingUi_reactDom.autoUpdate(elements.reference, elements.floating, update, autoUpdateOptions);
1661
1655
  }
1662
1656
  return undefined;
1663
1657
  }, [keepMounted, mounted, elements, update, autoUpdateOptions]);
1664
- const renderedSide = index_esm.getSide(renderedPlacement);
1658
+ const renderedSide = floatingUi_utils.getSide(renderedPlacement);
1665
1659
  const logicalRenderedSide = getLogicalSide(sideParam, renderedSide, isRtl);
1666
- const renderedAlign = index_esm.getAlignment(renderedPlacement) || 'center';
1660
+ const renderedAlign = floatingUi_utils.getAlignment(renderedPlacement) || 'center';
1667
1661
  const anchorHidden = Boolean(middlewareData.hide?.referenceHidden);
1668
1662
 
1669
- /**
1670
- * Locks the flip (makes it "sticky") so it doesn't prefer a given placement
1671
- * and flips back lazily, not eagerly. Ideal for filtered lists that change
1672
- * the size of the popup dynamically to avoid unwanted flipping when typing.
1673
- */
1663
+ // Locks the flip (makes it "sticky") so it doesn't prefer a given placement
1664
+ // and flips back lazily, not eagerly. Ideal for filtered lists that change
1665
+ // the size of the popup dynamically to avoid unwanted flipping when typing.
1674
1666
  useButton.useIsoLayoutEffect(() => {
1675
1667
  if (lazyFlip && mounted && isPositioned) {
1676
1668
  setMountSide(renderedSide);
@@ -1702,7 +1694,62 @@ function isRef(param) {
1702
1694
  }
1703
1695
 
1704
1696
  function getDisabledMountTransitionStyles(transitionStatus) {
1705
- return transitionStatus === 'starting' ? InternalBackdrop.DISABLED_TRANSITIONS_STYLE : useRenderElement.EMPTY_OBJECT;
1697
+ return transitionStatus === 'starting' ? useScrollLock.DISABLED_TRANSITIONS_STYLE : useRenderElement.EMPTY_OBJECT;
1698
+ }
1699
+
1700
+ /**
1701
+ * Renders the shared outer Positioner element used by popup components.
1702
+ * Applies the common role, hidden state, transition styles, state attributes, and optional inert styling.
1703
+ */
1704
+ function usePositioner(componentProps, state, {
1705
+ styles,
1706
+ transitionStatus,
1707
+ props,
1708
+ refs,
1709
+ hidden,
1710
+ inert = false
1711
+ }) {
1712
+ const style = {
1713
+ ...styles
1714
+ };
1715
+ if (inert) {
1716
+ style.pointerEvents = 'none';
1717
+ }
1718
+ return useRenderElement.useRenderElement('div', componentProps, {
1719
+ state,
1720
+ ref: refs,
1721
+ props: [{
1722
+ role: 'presentation',
1723
+ hidden,
1724
+ style
1725
+ }, getDisabledMountTransitionStyles(transitionStatus), props],
1726
+ stateAttributesMapping: useScrollLock.popupStateMapping
1727
+ });
1728
+ }
1729
+
1730
+ // Touch-opened popups normally avoid scroll locking so users can still swipe outside to dismiss.
1731
+ // This hook re-enables scroll lock only when the popup is effectively full-width.
1732
+ // Treat popups with up to 20px of total horizontal gutter as full-width so common ~10px side
1733
+ // padding still locks scroll, since that leaves too little outside space for a reliable swipe.
1734
+ const VIEWPORT_WIDTH_TOLERANCE_PX = 20;
1735
+
1736
+ /**
1737
+ * Manages scroll lock for anchored popups. For non-touch opens, scroll lock is applied when
1738
+ * enabled. For touch opens, scroll lock is applied only when the positioner width is effectively
1739
+ * viewport-sized.
1740
+ */
1741
+ function useAnchoredPopupScrollLock(enabled, touchOpen, positionerElement, referenceElement) {
1742
+ const [touchOpenShouldLockScroll, setTouchOpenShouldLockScroll] = React__namespace.useState(false);
1743
+ useButton.useIsoLayoutEffect(() => {
1744
+ if (!enabled || !touchOpen || positionerElement == null) {
1745
+ setTouchOpenShouldLockScroll(false);
1746
+ return;
1747
+ }
1748
+ const viewportWidth = useButton.ownerDocument(positionerElement).documentElement.clientWidth;
1749
+ const popupWidth = positionerElement.offsetWidth;
1750
+ setTouchOpenShouldLockScroll(viewportWidth > 0 && popupWidth > 0 && popupWidth >= viewportWidth - VIEWPORT_WIDTH_TOLERANCE_PX);
1751
+ }, [enabled, touchOpen, positionerElement]);
1752
+ useScrollLock.useScrollLock(enabled && (!touchOpen || touchOpenShouldLockScroll), referenceElement);
1706
1753
  }
1707
1754
 
1708
1755
  let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
@@ -1774,10 +1821,10 @@ function useCompositeListItem(params = {}) {
1774
1821
  }
1775
1822
  });
1776
1823
  }, [externalIndex, subscribeMapChange, setIndex]);
1777
- return React__namespace.useMemo(() => ({
1824
+ return {
1778
1825
  ref,
1779
1826
  index
1780
- }), [index, ref]);
1827
+ };
1781
1828
  }
1782
1829
 
1783
1830
  exports.CompositeList = CompositeList;
@@ -1786,7 +1833,9 @@ exports.adaptiveOrigin = adaptiveOrigin;
1786
1833
  exports.getDisabledMountTransitionStyles = getDisabledMountTransitionStyles;
1787
1834
  exports.getPseudoElementBounds = getPseudoElementBounds;
1788
1835
  exports.useAnchorPositioning = useAnchorPositioning;
1836
+ exports.useAnchoredPopupScrollLock = useAnchoredPopupScrollLock;
1789
1837
  exports.useCompositeListItem = useCompositeListItem;
1790
1838
  exports.useFloatingRootContext = useFloatingRootContext;
1791
1839
  exports.useListNavigation = useListNavigation;
1840
+ exports.usePositioner = usePositioner;
1792
1841
  exports.useTypeahead = useTypeahead;