@jobber/components 9.10.0 → 9.10.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 (141) hide show
  1. package/dist/Autocomplete/index.cjs +0 -2
  2. package/dist/Autocomplete/index.mjs +0 -2
  3. package/dist/Autocomplete-cjs.js +4 -5
  4. package/dist/Autocomplete-es.js +1 -2
  5. package/dist/Card/index.cjs +1 -2
  6. package/dist/Card/index.mjs +1 -2
  7. package/dist/Chip/index.cjs +0 -2
  8. package/dist/Chip/index.mjs +0 -2
  9. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
  10. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
  11. package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
  12. package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
  13. package/dist/Chips/index.cjs +0 -2
  14. package/dist/Chips/index.mjs +0 -2
  15. package/dist/Combobox/index.cjs +2 -4
  16. package/dist/Combobox/index.mjs +2 -4
  17. package/dist/ComboboxPrimitive-cjs.js +29 -30
  18. package/dist/ComboboxPrimitive-es.js +2 -3
  19. package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
  20. package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
  21. package/dist/ConfirmationModal/index.cjs +0 -2
  22. package/dist/ConfirmationModal/index.mjs +0 -2
  23. package/dist/DataDump/index.cjs +1 -2
  24. package/dist/DataDump/index.mjs +1 -2
  25. package/dist/DataList/components/DataListActions/index.cjs +0 -2
  26. package/dist/DataList/components/DataListActions/index.mjs +0 -2
  27. package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
  28. package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
  29. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
  30. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
  31. package/dist/DataList/components/DataListFilters/index.cjs +0 -2
  32. package/dist/DataList/components/DataListFilters/index.mjs +0 -2
  33. package/dist/DataList/components/DataListHeader/index.cjs +0 -2
  34. package/dist/DataList/components/DataListHeader/index.mjs +0 -2
  35. package/dist/DataList/components/DataListItem/index.cjs +0 -2
  36. package/dist/DataList/components/DataListItem/index.mjs +0 -2
  37. package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
  38. package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
  39. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
  40. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
  41. package/dist/DataList/components/DataListItems/index.cjs +0 -2
  42. package/dist/DataList/components/DataListItems/index.mjs +0 -2
  43. package/dist/DataList/components/DataListLayout/index.cjs +0 -2
  44. package/dist/DataList/components/DataListLayout/index.mjs +0 -2
  45. package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
  46. package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
  47. package/dist/DataList/index.cjs +0 -2
  48. package/dist/DataList/index.mjs +0 -2
  49. package/dist/DatePicker/index.cjs +0 -2
  50. package/dist/DatePicker/index.mjs +0 -2
  51. package/dist/DatePicker-cjs.js +4 -5
  52. package/dist/DatePicker-es.js +1 -2
  53. package/dist/Dialog/index.cjs +1 -2
  54. package/dist/Dialog/index.mjs +1 -2
  55. package/dist/DrawerDescription-cjs.js +268 -99
  56. package/dist/DrawerDescription-es.js +270 -103
  57. package/dist/DrawerTitle-cjs.js +2 -6
  58. package/dist/DrawerTitle-es.js +3 -7
  59. package/dist/FieldDescription-cjs.js +184 -181
  60. package/dist/FieldDescription-es.js +187 -184
  61. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
  62. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
  63. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
  64. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
  65. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
  66. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
  67. package/dist/FilterPicker/index.cjs +0 -2
  68. package/dist/FilterPicker/index.mjs +0 -2
  69. package/dist/FilterPickerContent-cjs.js +4 -5
  70. package/dist/FilterPickerContent-es.js +1 -2
  71. package/dist/FormatFile/index.cjs +0 -2
  72. package/dist/FormatFile/index.mjs +0 -2
  73. package/dist/Gallery/index.cjs +1 -2
  74. package/dist/Gallery/index.mjs +1 -2
  75. package/dist/InputDate/index.cjs +0 -2
  76. package/dist/InputDate/index.mjs +0 -2
  77. package/dist/InputNumber/index.cjs +1 -2
  78. package/dist/InputNumber/index.mjs +1 -2
  79. package/dist/LightBox/index.cjs +1 -2
  80. package/dist/LightBox/index.mjs +1 -2
  81. package/dist/Menu/index.cjs +1 -2
  82. package/dist/Menu/index.mjs +1 -2
  83. package/dist/Menu-cjs.js +4 -5
  84. package/dist/Menu-es.js +1 -2
  85. package/dist/MenuSubmenuTrigger-cjs.js +206 -164
  86. package/dist/MenuSubmenuTrigger-es.js +194 -152
  87. package/dist/Modal/index.cjs +0 -2
  88. package/dist/Modal/index.mjs +1 -3
  89. package/dist/NumberFieldInput-cjs.js +187 -308
  90. package/dist/NumberFieldInput-es.js +187 -308
  91. package/dist/Page/index.cjs +1 -2
  92. package/dist/Page/index.mjs +1 -2
  93. package/dist/Popover/index.cjs +0 -2
  94. package/dist/Popover/index.mjs +0 -2
  95. package/dist/Popover-cjs.js +7 -8
  96. package/dist/Popover-es.js +1 -2
  97. package/dist/ScrollAreaViewport-cjs.js +747 -1093
  98. package/dist/ScrollAreaViewport-es.js +734 -1081
  99. package/dist/Select/index.cjs +1 -2
  100. package/dist/Select/index.mjs +1 -2
  101. package/dist/SelectGroupLabel-cjs.js +207 -306
  102. package/dist/SelectGroupLabel-es.js +203 -302
  103. package/dist/Tooltip/index.cjs +0 -2
  104. package/dist/Tooltip/index.mjs +0 -2
  105. package/dist/Tooltip-cjs.js +6 -7
  106. package/dist/Tooltip-es.js +1 -2
  107. package/dist/dialogReturnFocus-cjs.js +1 -1
  108. package/dist/dialogReturnFocus-es.js +2 -2
  109. package/dist/floating-ui.react-cjs.js +2557 -185
  110. package/dist/floating-ui.react-es.js +2512 -149
  111. package/dist/index.cjs +3 -4
  112. package/dist/index.mjs +3 -4
  113. package/dist/primitives/BottomSheet/index.cjs +1 -1
  114. package/dist/primitives/BottomSheet/index.mjs +1 -1
  115. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -4
  116. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -4
  117. package/dist/primitives/SelectPrimitive/index.cjs +0 -2
  118. package/dist/primitives/SelectPrimitive/index.mjs +0 -2
  119. package/dist/primitives/index.cjs +2 -3
  120. package/dist/primitives/index.mjs +2 -3
  121. package/dist/scrollEdges-cjs.js +1 -11
  122. package/dist/scrollEdges-es.js +1 -11
  123. package/dist/scrollable-cjs.js +58 -0
  124. package/dist/scrollable-es.js +54 -0
  125. package/dist/unstyledPrimitives/index.cjs +720 -478
  126. package/dist/unstyledPrimitives/index.mjs +663 -421
  127. package/dist/useCompositeListItem-cjs.js +2108 -361
  128. package/dist/useCompositeListItem-es.js +2076 -331
  129. package/dist/useLabel-cjs.js +2 -3
  130. package/dist/useLabel-es.js +2 -3
  131. package/dist/useRenderElement-cjs.js +9 -7
  132. package/dist/useRenderElement-es.js +9 -7
  133. package/dist/useScrollLock-cjs.js +819 -344
  134. package/dist/useScrollLock-es.js +732 -296
  135. package/dist/useScrollToActive-cjs.js +4 -5
  136. package/dist/useScrollToActive-es.js +1 -2
  137. package/package.json +3 -3
  138. package/dist/floating-ui.react-dom-cjs.js +0 -2103
  139. package/dist/floating-ui.react-dom-es.js +0 -2072
  140. package/dist/floating-ui.utils-cjs.js +0 -342
  141. package/dist/floating-ui.utils-es.js +0 -300
@@ -7,8 +7,8 @@ var useCompositeListItem = require('./useCompositeListItem-cjs.js');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var ReactDOM = require('react-dom');
9
9
  var stringifyLocale = require('./stringifyLocale-cjs.js');
10
+ var scrollable = require('./scrollable-cjs.js');
10
11
  var useLabel = require('./useLabel-cjs.js');
11
- var floatingUi_utils = require('./floating-ui.utils-cjs.js');
12
12
 
13
13
  function _interopNamespaceDefault(e) {
14
14
  var n = Object.create(null);
@@ -104,15 +104,15 @@ function useComboboxHasItemsContext() {
104
104
  }
105
105
 
106
106
  const selectors = {
107
- id: useScrollLock.createSelector(state => state.id),
108
- labelId: useScrollLock.createSelector(state => state.labelId),
109
- items: useScrollLock.createSelector(state => state.items),
110
- selectedValue: useScrollLock.createSelector(state => state.selectedValue),
111
- hasSelectionChips: useScrollLock.createSelector(state => {
107
+ id: state => state.id,
108
+ labelId: state => state.labelId,
109
+ items: state => state.items,
110
+ selectedValue: state => state.selectedValue,
111
+ hasSelectionChips: state => {
112
112
  const selectedValue = state.selectedValue;
113
113
  return Array.isArray(selectedValue) && selectedValue.length > 0;
114
- }),
115
- hasSelectedValue: useScrollLock.createSelector(state => {
114
+ },
115
+ hasSelectedValue: state => {
116
116
  const {
117
117
  selectedValue,
118
118
  selectionMode
@@ -124,53 +124,53 @@ const selectors = {
124
124
  return selectedValue.length > 0;
125
125
  }
126
126
  return true;
127
- }),
128
- hasNullItemLabel: useScrollLock.createSelector((state, enabled) => {
127
+ },
128
+ hasNullItemLabel: (state, enabled) => {
129
129
  return enabled ? useCompositeListItem.hasNullItemLabel(state.items) : false;
130
- }),
131
- open: useScrollLock.createSelector(state => state.open),
132
- mounted: useScrollLock.createSelector(state => state.mounted),
133
- forceMounted: useScrollLock.createSelector(state => state.forceMounted),
134
- inline: useScrollLock.createSelector(state => state.inline),
135
- activeIndex: useScrollLock.createSelector(state => state.activeIndex),
136
- selectedIndex: useScrollLock.createSelector(state => state.selectedIndex),
137
- isActive: useScrollLock.createSelector((state, index) => state.activeIndex === index),
138
- isSelected: useScrollLock.createSelector((state, itemValue) => {
130
+ },
131
+ open: state => state.open,
132
+ mounted: state => state.mounted,
133
+ forceMounted: state => state.forceMounted,
134
+ inline: state => state.inline,
135
+ activeIndex: state => state.activeIndex,
136
+ selectedIndex: state => state.selectedIndex,
137
+ isActive: (state, index) => state.activeIndex === index,
138
+ isSelected: (state, itemValue) => {
139
139
  const comparer = state.isItemEqualToValue;
140
140
  const selectedValue = state.selectedValue;
141
141
  if (Array.isArray(selectedValue)) {
142
142
  return selectedValue.some(selectedItem => useCompositeListItem.compareItemEquality(itemValue, selectedItem, comparer));
143
143
  }
144
144
  return useCompositeListItem.compareItemEquality(itemValue, selectedValue, comparer);
145
- }),
146
- transitionStatus: useScrollLock.createSelector(state => state.transitionStatus),
147
- popupProps: useScrollLock.createSelector(state => state.popupProps),
148
- inputProps: useScrollLock.createSelector(state => state.inputProps),
149
- triggerProps: useScrollLock.createSelector(state => state.triggerProps),
150
- itemProps: useScrollLock.createSelector(state => state.itemProps),
151
- positionerElement: useScrollLock.createSelector(state => state.positionerElement),
152
- listElement: useScrollLock.createSelector(state => state.listElement),
153
- popupId: useScrollLock.createSelector(state => state.popupId),
154
- triggerElement: useScrollLock.createSelector(state => state.triggerElement),
155
- inputElement: useScrollLock.createSelector(state => state.inputElement),
156
- inputGroupElement: useScrollLock.createSelector(state => state.inputGroupElement),
157
- popupSide: useScrollLock.createSelector(state => state.popupSide),
158
- openMethod: useScrollLock.createSelector(state => state.openMethod),
159
- inputInsidePopup: useScrollLock.createSelector(state => state.inputInsidePopup),
160
- inputOwnsFormValue: useScrollLock.createSelector(state => state.inputOwnsFormValue),
161
- selectionMode: useScrollLock.createSelector(state => state.selectionMode),
162
- name: useScrollLock.createSelector(state => state.name),
163
- form: useScrollLock.createSelector(state => state.form),
164
- disabled: useScrollLock.createSelector(state => state.disabled),
165
- readOnly: useScrollLock.createSelector(state => state.readOnly),
166
- required: useScrollLock.createSelector(state => state.required),
167
- grid: useScrollLock.createSelector(state => state.grid),
168
- virtualized: useScrollLock.createSelector(state => state.virtualized),
169
- itemToStringLabel: useScrollLock.createSelector(state => state.itemToStringLabel),
170
- isItemEqualToValue: useScrollLock.createSelector(state => state.isItemEqualToValue),
171
- modal: useScrollLock.createSelector(state => state.modal),
172
- autoHighlight: useScrollLock.createSelector(state => state.autoHighlight),
173
- submitOnItemClick: useScrollLock.createSelector(state => state.submitOnItemClick)
145
+ },
146
+ transitionStatus: state => state.transitionStatus,
147
+ popupProps: state => state.popupProps,
148
+ listProps: state => state.listProps,
149
+ inputProps: state => state.inputProps,
150
+ triggerProps: state => state.triggerProps,
151
+ itemProps: state => state.itemProps,
152
+ positionerElement: state => state.positionerElement,
153
+ listElement: state => state.listElement,
154
+ popupId: state => state.popupId,
155
+ triggerElement: state => state.triggerElement,
156
+ inputElement: state => state.inputElement,
157
+ inputGroupElement: state => state.inputGroupElement,
158
+ popupSide: state => state.popupSide,
159
+ openMethod: state => state.openMethod,
160
+ inputInsidePopup: state => state.inputInsidePopup,
161
+ inputOwnsFormValue: state => state.inputOwnsFormValue,
162
+ selectionMode: state => state.selectionMode,
163
+ name: state => state.name,
164
+ form: state => state.form,
165
+ disabled: state => state.disabled,
166
+ readOnly: state => state.readOnly,
167
+ required: state => state.required,
168
+ grid: state => state.grid,
169
+ virtualized: state => state.virtualized,
170
+ itemToStringLabel: state => state.itemToStringLabel,
171
+ isItemEqualToValue: state => state.isItemEqualToValue,
172
+ modal: state => state.modal,
173
+ autoHighlight: state => state.autoHighlight
174
174
  };
175
175
 
176
176
  /**
@@ -193,8 +193,7 @@ function createCollatorItemFilter(collatorFilter, itemToStringLabel) {
193
193
  if (item == null) {
194
194
  return false;
195
195
  }
196
- const itemString = useCompositeListItem.stringifyAsLabel(item, itemToStringLabel);
197
- return collatorFilter.contains(itemString, query);
196
+ return collatorFilter.contains(item, query, itemToStringLabel);
198
197
  };
199
198
  }
200
199
 
@@ -210,14 +209,13 @@ function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel,
210
209
  if (!query) {
211
210
  return true;
212
211
  }
213
- const itemString = useCompositeListItem.stringifyAsLabel(item, itemToStringLabel);
214
212
  const selectedString = selectedValue != null ? useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel) : '';
215
213
 
216
214
  // Handle case-insensitive matching consistently
217
215
  if (selectedString && collatorFilter.contains(selectedString, query) && selectedString.length === query.length) {
218
216
  return true;
219
217
  }
220
- return collatorFilter.contains(itemString, query);
218
+ return collatorFilter.contains(item, query, itemToStringLabel);
221
219
  };
222
220
  }
223
221
 
@@ -289,9 +287,8 @@ function useComboboxFilter(options = {}) {
289
287
  return createSingleSelectionCollatorFilter(coreFilter, itemToString, value)(item, query);
290
288
  }, [coreFilter, value, multiple]);
291
289
  return React__namespace.useMemo(() => ({
292
- contains,
293
- startsWith: coreFilter.startsWith,
294
- endsWith: coreFilter.endsWith
290
+ ...coreFilter,
291
+ contains
295
292
  }), [contains, coreFilter]);
296
293
  }
297
294
 
@@ -312,7 +309,7 @@ function AriaCombobox(props) {
312
309
  inputValue: inputValueProp,
313
310
  open: openProp,
314
311
  defaultOpen = false,
315
- selectionMode = 'none',
312
+ selectionMode,
316
313
  onItemHighlighted: onItemHighlightedProp,
317
314
  name: nameProp,
318
315
  form,
@@ -385,12 +382,10 @@ function AriaCombobox(props) {
385
382
  */
386
383
  const valuesRef = React__namespace.useRef([]);
387
384
  /**
388
- * Contains all item values in a stable, unfiltered order.
389
- * This is only used when `items` prop is not provided.
390
- * It accumulates values on first mount and does not remove them on unmount due to
391
- * filtering, providing a stable index for selected value tracking.
385
+ * The item element that received the last `pointerdown`, used to detect whether a
386
+ * `mouseup` on an item belongs to a drag-select gesture that started elsewhere.
392
387
  */
393
- const allValuesRef = React__namespace.useRef([]);
388
+ const pointerDownItemRef = React__namespace.useRef(null);
394
389
  const disabled = fieldDisabled || disabledProp;
395
390
  const name = fieldName ?? nameProp;
396
391
  const multiple = selectionMode === 'multiple';
@@ -417,11 +412,10 @@ function AriaCombobox(props) {
417
412
  if (filterProp !== undefined) {
418
413
  return filterProp;
419
414
  }
420
- if (single && !queryChangedAfterOpen) {
421
- return createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue);
422
- }
415
+ // `shouldBypassFiltering` already empties the query whenever a single selection's label
416
+ // matches it exactly, so the filter never needs a selection-aware variant here.
423
417
  return createCollatorItemFilter(collatorFilter, itemToStringLabel);
424
- }, [filterProp, single, selectedValue, queryChangedAfterOpen, collatorFilter, itemToStringLabel]);
418
+ }, [filterProp, collatorFilter, itemToStringLabel]);
425
419
 
426
420
  // If neither inputValue nor defaultInputValue are provided, derive it from the
427
421
  // selected value for single mode so the input reflects the selection on mount.
@@ -447,9 +441,9 @@ function AriaCombobox(props) {
447
441
  state: 'open'
448
442
  });
449
443
  const isGrouped = useCompositeListItem.isGroupedItems(items);
450
- const query = closeQuery ?? (inputValue === '' ? '' : String(inputValue).trim());
444
+ const query = closeQuery ?? String(inputValue).trim();
451
445
  const selectedLabelString = single ? useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel) : '';
452
- const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
446
+ const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
453
447
  const filterQuery = shouldBypassFiltering ? '' : query;
454
448
  const shouldIgnoreExternalFiltering = hasItems && hasFilteredItemsProp && shouldBypassFiltering;
455
449
  const flatItems = React__namespace.useMemo(() => {
@@ -476,12 +470,18 @@ function AriaCombobox(props) {
476
470
  if (limit > -1 && currentCount >= limit) {
477
471
  break;
478
472
  }
479
- const candidateItems = filterQuery === '' ? group.items : group.items.filter(item => filter(item, filterQuery, itemToStringLabel));
480
- if (candidateItems.length === 0) {
481
- continue;
482
- }
483
473
  const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
484
- const itemsToTake = candidateItems.slice(0, remainingLimit);
474
+ const itemsToTake = filterQuery === '' ? group.items.slice(0, remainingLimit) : [];
475
+ if (filterQuery !== '') {
476
+ for (const item of group.items) {
477
+ if (itemsToTake.length >= remainingLimit) {
478
+ break;
479
+ }
480
+ if (filter(item, filterQuery, itemToStringLabel)) {
481
+ itemsToTake.push(item);
482
+ }
483
+ }
484
+ }
485
485
  if (itemsToTake.length > 0) {
486
486
  const newGroup = {
487
487
  ...group,
@@ -521,76 +521,85 @@ function AriaCombobox(props) {
521
521
  }
522
522
  return filteredItems;
523
523
  }, [filteredItems, isGrouped]);
524
- const store = useRenderElement.useRefWithInit(() => new useScrollLock.Store({
525
- id,
526
- labelId: undefined,
527
- selectedValue,
528
- open,
529
- filter,
530
- query,
531
- items,
532
- selectionMode,
533
- listRef,
534
- labelsRef,
535
- popupRef,
536
- emptyRef,
537
- inputRef,
538
- startDismissRef,
539
- endDismissRef,
540
- keyboardActiveRef,
541
- chipsContainerRef,
542
- clearRef,
543
- valuesRef,
544
- allValuesRef,
545
- selectionEventRef,
546
- name,
547
- form,
548
- disabled,
549
- readOnly,
550
- required,
551
- grid,
552
- isGrouped,
553
- virtualized,
554
- openOnInputClick,
555
- itemToStringLabel,
556
- isItemEqualToValue,
557
- modal,
558
- autoHighlight: autoHighlightMode,
559
- submitOnItemClick,
560
- hasInputValue,
561
- mounted: false,
562
- forceMounted: false,
563
- transitionStatus: 'idle',
564
- inline: inlineProp,
565
- activeIndex: null,
566
- selectedIndex: null,
567
- popupProps: {},
568
- inputProps: {},
569
- triggerProps: {},
570
- itemProps: useRenderElement.EMPTY_OBJECT,
571
- positionerElement: null,
572
- listElement: null,
573
- popupId: undefined,
574
- triggerElement: null,
575
- inputElement: null,
576
- inputGroupElement: null,
577
- popupSide: null,
578
- openMethod: null,
579
- inputInsidePopup: true,
580
- // Avoid duplicate names in the server HTML. Popup inputs aren't rendered
581
- // until after hydration, so the hidden input takes over then if needed.
582
- inputOwnsFormValue: selectionMode === 'none',
583
- onOpenChangeComplete: onOpenChangeCompleteProp || useRenderElement.NOOP,
584
- // Placeholder callbacks replaced on first render
585
- setOpen: useRenderElement.NOOP,
586
- setInputValue: useRenderElement.NOOP,
587
- setSelectedValue: useRenderElement.NOOP,
588
- setIndices: useRenderElement.NOOP,
589
- onItemHighlighted: useRenderElement.NOOP,
590
- handleSelection: useRenderElement.NOOP,
591
- forceMount: useRenderElement.NOOP,
592
- requestSubmit: useRenderElement.NOOP
593
- })).current;
524
+ const store = useRenderElement.useRefWithInit(() => {
525
+ // An inline list open on the first render never gets a closed pass of the closed-state
526
+ // sync effect below, and `items`-prop lists don't self-register their index the way
527
+ // individually rendered `<Combobox.Item>`s do, so the selected item was never highlighted.
528
+ // Seeding the index here lets list navigation highlight and scroll to the selection on
529
+ // mount. Computed once by construction, so a selection or list that resolves after mount
530
+ // doesn't move an existing highlight or scroll the list away.
531
+ let initialSelectedIndex = null;
532
+ if (inlineProp && open && hasItems && selectionMode !== 'none') {
533
+ initialSelectedIndex = useCompositeListItem.findSelectionIndex(flatFilteredItems, selectedValue, isItemEqualToValue, multiple);
534
+ }
535
+ return new useScrollLock.Store({
536
+ id,
537
+ labelId: undefined,
538
+ selectedValue,
539
+ open,
540
+ items,
541
+ selectionMode,
542
+ listRef,
543
+ labelsRef,
544
+ popupRef,
545
+ emptyRef,
546
+ inputRef,
547
+ startDismissRef,
548
+ endDismissRef,
549
+ keyboardActiveRef,
550
+ chipsContainerRef,
551
+ clearRef,
552
+ valuesRef,
553
+ pointerDownItemRef,
554
+ selectionEventRef,
555
+ name,
556
+ form,
557
+ disabled,
558
+ readOnly,
559
+ required,
560
+ grid,
561
+ virtualized,
562
+ openOnInputClick,
563
+ itemToStringLabel,
564
+ isItemEqualToValue,
565
+ modal,
566
+ autoHighlight: autoHighlightMode,
567
+ submitOnItemClick,
568
+ hasInputValue,
569
+ mounted: false,
570
+ forceMounted: false,
571
+ transitionStatus: 'idle',
572
+ inline: inlineProp,
573
+ activeIndex: null,
574
+ selectedIndex: initialSelectedIndex,
575
+ popupProps: {},
576
+ listProps: {},
577
+ inputProps: {},
578
+ triggerProps: {},
579
+ itemProps: useRenderElement.EMPTY_OBJECT,
580
+ positionerElement: null,
581
+ listElement: null,
582
+ popupId: undefined,
583
+ triggerElement: null,
584
+ inputElement: null,
585
+ inputGroupElement: null,
586
+ popupSide: null,
587
+ openMethod: null,
588
+ inputInsidePopup: true,
589
+ // Avoid duplicate names in the server HTML. Popup inputs aren't rendered
590
+ // until after hydration, so the hidden input takes over then if needed.
591
+ inputOwnsFormValue: selectionMode === 'none',
592
+ // Placeholder callbacks replaced on first render
593
+ onOpenChangeComplete: useRenderElement.NOOP,
594
+ setOpen: useRenderElement.NOOP,
595
+ setInputValue: useRenderElement.NOOP,
596
+ setSelectedValue: useRenderElement.NOOP,
597
+ setIndices: useRenderElement.NOOP,
598
+ handleSelection: useRenderElement.NOOP,
599
+ forceMount: useRenderElement.NOOP,
600
+ requestSubmit: useRenderElement.NOOP
601
+ });
602
+ }).current;
594
603
  const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;
595
604
  const fieldStringValue = React__namespace.useMemo(() => {
596
605
  if (selectionMode === 'none') {
@@ -633,35 +642,38 @@ function AriaCombobox(props) {
633
642
  store.set('forceMounted', true);
634
643
  }
635
644
  });
636
- const initialSelectedValueRef = React__namespace.useRef(selectedValue);
637
- useScrollLock.useIsoLayoutEffect(() => {
638
- // Ensure the values and labels are registered for programmatic value changes.
639
- if (selectedValue !== initialSelectedValueRef.current) {
640
- forceMount();
645
+
646
+ /**
647
+ * Emits `onItemHighlighted` for the item at `index`, or clears the highlight when `index` is `-1`
648
+ * (a no-op if nothing was highlighted). Keeps `lastHighlightRef` in sync with what was emitted.
649
+ */
650
+ const emitHighlight = useScrollLock.useStableCallback((value, index, type) => {
651
+ if (index === -1) {
652
+ if (lastHighlightRef.current === INITIAL_LAST_HIGHLIGHT) {
653
+ return;
654
+ }
655
+ lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
656
+ } else {
657
+ lastHighlightRef.current = {
658
+ value,
659
+ index
660
+ };
641
661
  }
642
- }, [forceMount, selectedValue]);
662
+ onItemHighlighted(value, useScrollLock.createGenericEventDetails(type, undefined, {
663
+ index
664
+ }));
665
+ });
643
666
  const setIndices = useScrollLock.useStableCallback(options => {
644
667
  store.update(options);
645
- const type = options.type || 'none';
646
- if (options.activeIndex === undefined) {
668
+ const activeIndexOption = options.activeIndex;
669
+ if (activeIndexOption === undefined) {
647
670
  return;
648
671
  }
649
- if (options.activeIndex === null) {
650
- if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
651
- lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
652
- onItemHighlighted(undefined, useScrollLock.createGenericEventDetails(type, undefined, {
653
- index: -1
654
- }));
655
- }
672
+ const type = options.type || useScrollLock.none;
673
+ if (activeIndexOption === null) {
674
+ emitHighlight(undefined, -1, type);
656
675
  } else {
657
- const activeValue = valuesRef.current[options.activeIndex];
658
- lastHighlightRef.current = {
659
- value: activeValue,
660
- index: options.activeIndex
661
- };
662
- onItemHighlighted(activeValue, useScrollLock.createGenericEventDetails(type, undefined, {
663
- index: options.activeIndex
664
- }));
676
+ emitHighlight(valuesRef.current[activeIndexOption], activeIndexOption, type);
665
677
  }
666
678
  });
667
679
  const setInputValue = useScrollLock.useStableCallback((next, eventDetails) => {
@@ -674,6 +686,11 @@ function AriaCombobox(props) {
674
686
  // If user is typing, ensure we don't auto-highlight on open due to a race
675
687
  // with the post-open effect that sets this flag.
676
688
  if (eventDetails.reason === useScrollLock.inputChange) {
689
+ // A controlled popup may ignore a close request. Resuming input proves the popup
690
+ // is remaining open, so release the query captured for an exit animation.
691
+ if (open && closeQuery !== null) {
692
+ setCloseQuery(null);
693
+ }
677
694
  const event = eventDetails.event;
678
695
  const inputType = event.inputType;
679
696
  // Treat composition commits as typed input; autofill may omit `inputType` or
@@ -689,10 +706,34 @@ function AriaCombobox(props) {
689
706
  pendingQueryHighlightRef.current = {
690
707
  hasQuery
691
708
  };
692
- if (hasQuery && autoHighlightMode && store.state.activeIndex == null) {
709
+
710
+ // Virtualized lists own their scroller. Reset regular lists directly so a stale
711
+ // composite registry cannot select a reordered item and scrolling cannot escape
712
+ // the popup.
713
+ const list = store.state.listElement;
714
+ if (!store.state.virtualized && list) {
715
+ const popup = popupRef.current;
716
+ for (const ancestor of useScrollLock.getOverflowAncestors(list.firstElementChild ?? list)) {
717
+ if (!useScrollLock.isHTMLElement(ancestor) || (popup ? !useScrollLock.contains(popup, ancestor) : ancestor.getAttribute('role') === 'dialog')) {
718
+ break;
719
+ }
720
+ if (scrollable.isScrollableY(ancestor)) {
721
+ ancestor.scrollTop = 0;
722
+ break;
723
+ }
724
+ }
725
+ }
726
+ if (hasQuery && autoHighlightMode && store.state.activeIndex == null && (open || inline)) {
693
727
  store.set('activeIndex', 0);
694
728
  }
695
729
  }
730
+ } else if (eventDetails.reason === useScrollLock.inputClear && next === '' && store.state.inputInsidePopup) {
731
+ // A programmatic clear of an active query (e.g. after selecting an item with the
732
+ // input inside the popup): restore the highlight to the selected item.
733
+ pendingQueryHighlightRef.current = {
734
+ hasQuery: false,
735
+ selection: true
736
+ };
696
737
  }
697
738
  setInputValueUnwrapped(next);
698
739
  });
@@ -704,7 +745,7 @@ function AriaCombobox(props) {
704
745
  // If the `Empty` component is not used, the positioner or popup should be hidden
705
746
  // with CSS. In this case, allow the Escape key to bubble to close a parent popup
706
747
  // if there are no items to show.
707
- if (eventDetails.reason === 'escape-key' && hasItems && flatFilteredItems.length === 0 && !store.state.emptyRef.current) {
748
+ if (eventDetails.reason === useScrollLock.escapeKey && hasItems && flatFilteredItems.length === 0 && !emptyRef.current) {
708
749
  eventDetails.allowPropagation();
709
750
  }
710
751
  props.onOpenChange?.(nextOpen, eventDetails);
@@ -714,10 +755,10 @@ function AriaCombobox(props) {
714
755
 
715
756
  // If reopening interrupts the close animation, handleUnmount won't run to clear the
716
757
  // frozen closeQuery and pending popup input.
717
- if (nextOpen && multiple && inputInsidePopup && !inline && closeQuery !== null) {
758
+ if (nextOpen && inputInsidePopup && !inline && closeQuery !== null) {
718
759
  setQueryChangedAfterOpen(false);
719
760
  setCloseQuery(null);
720
- if (inputValue !== '') {
761
+ if (inputValue !== '' && eventDetails.reason !== useScrollLock.inputChange) {
721
762
  setInputValue('', useScrollLock.createChangeEventDetails(useScrollLock.inputClear, eventDetails.event));
722
763
  }
723
764
  }
@@ -771,18 +812,8 @@ function AriaCombobox(props) {
771
812
  if (shouldFillInput) {
772
813
  setInputValue(useCompositeListItem.stringifyAsLabel(nextValue, itemToStringLabel), useScrollLock.createChangeEventDetails(eventDetails.reason, eventDetails.event));
773
814
  }
774
- if (single && nextValue != null && eventDetails.reason !== useScrollLock.inputChange && queryChangedAfterOpen && !inline) {
775
- setCloseQuery(query);
776
- }
777
815
  });
778
- const handleSelection = useScrollLock.useStableCallback((event, passedValue) => {
779
- let itemValue = passedValue;
780
- if (itemValue === undefined) {
781
- if (activeIndex === null) {
782
- return;
783
- }
784
- itemValue = valuesRef.current[activeIndex];
785
- }
816
+ const handleSelection = useScrollLock.useStableCallback((event, itemValue) => {
786
817
  const targetEl = useScrollLock.getTarget(event);
787
818
  const overrideEvent = selectionEventRef.current ?? event;
788
819
  selectionEventRef.current = null;
@@ -798,8 +829,8 @@ function AriaCombobox(props) {
798
829
  }
799
830
  if (multiple) {
800
831
  const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];
801
- const isCurrentlySelected = useCompositeListItem.selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
802
- const nextValue = isCurrentlySelected ? useCompositeListItem.removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
832
+ const isCurrentlySelected = useCompositeListItem.selectedValueIncludes(currentSelectedValue, itemValue, isItemEqualToValue);
833
+ const nextValue = isCurrentlySelected ? useCompositeListItem.removeItem(currentSelectedValue, itemValue, isItemEqualToValue) : [...currentSelectedValue, itemValue];
803
834
  setSelectedValue(nextValue, eventDetails);
804
835
  if (eventDetails.isCanceled) {
805
836
  return;
@@ -822,9 +853,6 @@ function AriaCombobox(props) {
822
853
  }
823
854
  });
824
855
  const requestSubmit = useScrollLock.useStableCallback(() => {
825
- if (!store.state.submitOnItemClick) {
826
- return;
827
- }
828
856
  const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
829
857
  if (formElement && typeof formElement.requestSubmit === 'function') {
830
858
  formElement.requestSubmit();
@@ -897,25 +925,29 @@ function AriaCombobox(props) {
897
925
  React__namespace.useImperativeHandle(props.actionsRef, () => ({
898
926
  unmount: handleUnmount
899
927
  }), [handleUnmount]);
900
- useScrollLock.useIsoLayoutEffect(function syncSelectedIndex() {
901
- if (open || selectionMode === 'none') {
928
+ useScrollLock.useIsoLayoutEffect(function syncClosedState() {
929
+ if (open) {
902
930
  return;
903
931
  }
904
- const registry = items ? flatItems : allValuesRef.current;
905
- if (multiple) {
906
- const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
907
- const lastValue = currentValue[currentValue.length - 1];
908
- const lastIndex = useCompositeListItem.findItemIndex(registry, lastValue, isItemEqualToValue);
909
- setIndices({
910
- selectedIndex: lastIndex === -1 ? null : lastIndex
911
- });
912
- } else {
913
- const index = useCompositeListItem.findItemIndex(registry, selectedValue, isItemEqualToValue);
914
- setIndices({
915
- selectedIndex: index === -1 ? null : index
916
- });
932
+
933
+ // State-driven (not tied to the internal event path) so controlled closes
934
+ // also clear a pointerdown that never received a matching item mouseup.
935
+ pointerDownItemRef.current = null;
936
+ if (selectionMode === 'none') {
937
+ return;
917
938
  }
918
- }, [open, selectedValue, items, selectionMode, flatItems, multiple, isItemEqualToValue, setIndices]);
939
+
940
+ // Without `items`, look the selection up in the live registry of mounted item
941
+ // values (the list stays mounted while closed when closed-state features need
942
+ // it — trigger interaction and rendered-label autofill force-mount it). Mounted
943
+ // items re-assert the index themselves when their registration moves; when
944
+ // nothing is mounted the lookup resolves to `null` and each item re-registers
945
+ // the index on the next open.
946
+ const registry = hasItems ? flatItems : valuesRef.current;
947
+ setIndices({
948
+ selectedIndex: useCompositeListItem.findSelectionIndex(registry, selectedValue, isItemEqualToValue, multiple)
949
+ });
950
+ }, [open, selectedValue, selectionMode, multiple, hasItems, flatItems, isItemEqualToValue, setIndices]);
919
951
  useScrollLock.useIsoLayoutEffect(() => {
920
952
  if (items) {
921
953
  valuesRef.current = flatFilteredItems;
@@ -925,14 +957,52 @@ function AriaCombobox(props) {
925
957
  useScrollLock.useIsoLayoutEffect(() => {
926
958
  const pendingHighlight = pendingQueryHighlightRef.current;
927
959
  if (pendingHighlight) {
960
+ // A directly rendered list remains visible when the popup state is closed, while a
961
+ // kept-mounted Positioner is hidden and should stay inert.
962
+ const listIsNavigable = open || inline || store.state.positionerElement?.hidden === false;
928
963
  if (pendingHighlight.hasQuery) {
929
- if (autoHighlightMode) {
964
+ if (autoHighlightMode && listIsNavigable) {
930
965
  store.set('activeIndex', 0);
931
966
  }
932
- } else if (autoHighlightMode === 'always') {
933
- store.set('activeIndex', 0);
967
+ pendingQueryHighlightRef.current = null;
968
+ } else if (String(inputValue).trim() === '') {
969
+ // Only handle the clear once it has committed (a controlled input may reject it),
970
+ // so a restore cannot fire while a query is still active.
971
+ pendingQueryHighlightRef.current = null;
972
+ if (listIsNavigable) {
973
+ const clearedBySelection = pendingHighlight.selection;
974
+ if (autoHighlightMode === 'always' && !clearedBySelection && store.state.selectionMode === 'none') {
975
+ // There is no selection to restore in Autocomplete. Keep the first-item reset
976
+ // synchronous so list navigation sees it before a directly rendered list closes.
977
+ store.set('activeIndex', 0);
978
+ }
979
+
980
+ // Items re-mounted by the clear publish their composite indices in a follow-up
981
+ // commit, so the item registries are mid-update here. Defer past React's cascade.
982
+ queueMicrotask(() => {
983
+ if (!store.state.open && !store.state.inline || inputRef.current && inputRef.current.value.trim() !== '') {
984
+ return;
985
+ }
986
+
987
+ // Return the highlight to the selected item, the same anchor the popup uses
988
+ // when it first opens. Read the selection through the store so consumers can
989
+ // pass an inline `isItemEqualToValue` or a fresh `selectedValue` array without
990
+ // re-running this effect on every render.
991
+ const currentSelectedValue = store.state.selectedValue;
992
+ const isMultiple = store.state.selectionMode === 'multiple';
993
+ const lastSelectedValue = isMultiple && Array.isArray(currentSelectedValue) ? currentSelectedValue[currentSelectedValue.length - 1] : currentSelectedValue;
994
+ const hasSelection = store.state.selectionMode !== 'none' && lastSelectedValue != null;
995
+ if (hasSelection || clearedBySelection) {
996
+ const registry = hasItems || hasFilteredItemsProp ? flatFilteredItems : valuesRef.current;
997
+ // A selection that is no longer in the list drops the highlight rather than
998
+ // leaving it on whichever item now occupies that index.
999
+ store.set('activeIndex', hasSelection ? useCompositeListItem.findSelectionIndex(registry, currentSelectedValue, store.state.isItemEqualToValue, isMultiple) : null);
1000
+ } else if (autoHighlightMode === 'always') {
1001
+ store.set('activeIndex', 0);
1002
+ }
1003
+ });
1004
+ }
934
1005
  }
935
- pendingQueryHighlightRef.current = null;
936
1006
  }
937
1007
  if (!open && !inline) {
938
1008
  return;
@@ -945,21 +1015,11 @@ function AriaCombobox(props) {
945
1015
  store.set('activeIndex', 0);
946
1016
  return;
947
1017
  }
948
- if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
949
- lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
950
- store.state.onItemHighlighted(undefined, useScrollLock.createGenericEventDetails(useScrollLock.none, undefined, {
951
- index: -1
952
- }));
953
- }
1018
+ emitHighlight(undefined, -1, useScrollLock.none);
954
1019
  return;
955
1020
  }
956
1021
  if (storeActiveIndex >= candidateItems.length) {
957
- if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
958
- lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
959
- store.state.onItemHighlighted(undefined, useScrollLock.createGenericEventDetails(useScrollLock.none, undefined, {
960
- index: -1
961
- }));
962
- }
1022
+ emitHighlight(undefined, -1, useScrollLock.none);
963
1023
  store.set('activeIndex', null);
964
1024
  return;
965
1025
  }
@@ -967,15 +1027,12 @@ function AriaCombobox(props) {
967
1027
  const previouslyHighlightedItemValue = lastHighlightRef.current.value;
968
1028
  const isSameItem = previouslyHighlightedItemValue !== NO_ACTIVE_VALUE && useCompositeListItem.compareItemEquality(itemValue, previouslyHighlightedItemValue, store.state.isItemEqualToValue);
969
1029
  if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {
970
- lastHighlightRef.current = {
971
- value: itemValue,
972
- index: storeActiveIndex
973
- };
974
- store.state.onItemHighlighted(itemValue, useScrollLock.createGenericEventDetails(useScrollLock.none, undefined, {
975
- index: storeActiveIndex
976
- }));
1030
+ emitHighlight(itemValue, storeActiveIndex, useScrollLock.none);
977
1031
  }
978
- }, [activeIndex, autoHighlightMode, hasFilteredItemsProp, hasItems, flatFilteredItems, inline, open, store]);
1032
+ }, [activeIndex, autoHighlightMode, emitHighlight, hasFilteredItemsProp, hasItems, flatFilteredItems, inline, open, store,
1033
+ // Reruns the effect when the query changes without affecting the deps above, such as
1034
+ // clearing the input when no items are filtered out (individually rendered items).
1035
+ inputValue]);
979
1036
  useScrollLock.useIsoLayoutEffect(() => {
980
1037
  if (selectionMode === 'none') {
981
1038
  setFilled(String(inputValue) !== '');
@@ -1006,6 +1063,12 @@ function AriaCombobox(props) {
1006
1063
  }
1007
1064
  setQueryChangedAfterOpen(true);
1008
1065
  });
1066
+ function syncInputToSelectedLabel() {
1067
+ const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1068
+ if (inputValue !== nextInputValue) {
1069
+ setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1070
+ }
1071
+ }
1009
1072
  useScrollLock.useValueChanged(selectedValue, () => {
1010
1073
  if (selectionMode === 'none') {
1011
1074
  return;
@@ -1014,10 +1077,7 @@ function AriaCombobox(props) {
1014
1077
  setDirty(isSelectedValueDirty(selectedValue));
1015
1078
  validation.change(selectedValue);
1016
1079
  if (single && !hasInputValue && !inputInsidePopup) {
1017
- const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1018
- if (inputValue !== nextInputValue) {
1019
- setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1020
- }
1080
+ syncInputToSelectedLabel();
1021
1081
  }
1022
1082
  });
1023
1083
  useScrollLock.useValueChanged(inputValue, () => {
@@ -1032,10 +1092,7 @@ function AriaCombobox(props) {
1032
1092
  if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
1033
1093
  return;
1034
1094
  }
1035
- const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1036
- if (inputValue !== nextInputValue) {
1037
- setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1038
- }
1095
+ syncInputToSelectedLabel();
1039
1096
  });
1040
1097
  const floatingRootContext = useCompositeListItem.useFloatingRootContext({
1041
1098
  open: inline ? true : open,
@@ -1045,18 +1102,19 @@ function AriaCombobox(props) {
1045
1102
  floating: positionerElement
1046
1103
  }
1047
1104
  });
1048
- let ariaHasPopup;
1049
- let ariaExpanded;
1050
- if (!inline) {
1051
- ariaHasPopup = grid ? 'grid' : 'listbox';
1052
- ariaExpanded = open ? 'true' : 'false';
1053
- }
1105
+ const ariaHasPopup = grid ? 'grid' : 'listbox';
1106
+ // An inline list isn't gated on `open`: it renders for as long as it's in the tree, so the
1107
+ // combobox is permanently expanded even while the internal open state is `false`.
1108
+ const expanded = open || inline;
1109
+ const ariaExpanded = expanded ? 'true' : 'false';
1054
1110
  const role = React__namespace.useMemo(() => {
1055
1111
  const isPlainInput = inputElement?.tagName === 'INPUT';
1056
1112
  // During SSR and initial hydration, the input ref is not available yet.
1057
1113
  // Assume an input-like control so combobox ARIA attributes are present.
1058
1114
  const shouldTreatAsInput = inputElement == null || isPlainInput;
1059
- const shouldApplyAria = shouldTreatAsInput || open;
1115
+ // A non-input control only takes on combobox semantics while the list is exposed, which for
1116
+ // an inline list is the whole time.
1117
+ const shouldApplyAria = shouldTreatAsInput || expanded;
1060
1118
  const reference = shouldTreatAsInput ? {
1061
1119
  autoComplete: 'off',
1062
1120
  spellCheck: 'false',
@@ -1067,7 +1125,7 @@ function AriaCombobox(props) {
1067
1125
  reference.role = 'combobox';
1068
1126
  reference['aria-expanded'] = ariaExpanded;
1069
1127
  reference['aria-haspopup'] = ariaHasPopup;
1070
- reference['aria-controls'] = open ? listElement?.id : undefined;
1128
+ reference['aria-controls'] = expanded ? listElement?.id : undefined;
1071
1129
  reference['aria-autocomplete'] = autoComplete;
1072
1130
  }
1073
1131
  return {
@@ -1076,7 +1134,7 @@ function AriaCombobox(props) {
1076
1134
  role: 'presentation'
1077
1135
  }
1078
1136
  };
1079
- }, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
1137
+ }, [inputElement, expanded, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
1080
1138
  const click = useScrollLock.useClick(floatingRootContext, {
1081
1139
  enabled: !readOnly && !disabled && openOnInputClick,
1082
1140
  event: 'mousedown-only',
@@ -1130,7 +1188,7 @@ function AriaCombobox(props) {
1130
1188
  } else {
1131
1189
  setIndices({
1132
1190
  activeIndex: nextActiveIndex,
1133
- type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
1191
+ type: keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer
1134
1192
  });
1135
1193
  }
1136
1194
  }
@@ -1145,7 +1203,8 @@ function AriaCombobox(props) {
1145
1203
  }
1146
1204
  }
1147
1205
  }, dismiss.reference, click.reference, role.reference), [listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store]);
1148
- const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, listNavigation.floating, dismiss.floating, role.floating), [listNavigation.floating, dismiss.floating, role.floating]);
1206
+ const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, dismiss.floating), [dismiss.floating]);
1207
+ const listProps = React__namespace.useMemo(() => useRenderElement.mergeProps(listNavigation.floating, role.floating), [listNavigation.floating, role.floating]);
1149
1208
  const itemProps = React__namespace.useMemo(() => {
1150
1209
  const listNavigationItemProps = listNavigation.item;
1151
1210
  if (!listNavigationItemProps) {
@@ -1159,10 +1218,11 @@ function AriaCombobox(props) {
1159
1218
  onFocus: undefined
1160
1219
  };
1161
1220
  }, [listNavigation.item]);
1162
- useScrollLock.useOnFirstRender(() => {
1221
+ useCompositeListItem.useOnFirstRender(() => {
1163
1222
  store.update({
1164
1223
  inline: inlineProp,
1165
1224
  popupProps,
1225
+ listProps,
1166
1226
  inputProps,
1167
1227
  triggerProps,
1168
1228
  itemProps,
@@ -1170,10 +1230,10 @@ function AriaCombobox(props) {
1170
1230
  setInputValue,
1171
1231
  setSelectedValue,
1172
1232
  setIndices,
1173
- onItemHighlighted,
1174
1233
  handleSelection,
1175
1234
  forceMount,
1176
- requestSubmit
1235
+ requestSubmit,
1236
+ onOpenChangeComplete
1177
1237
  });
1178
1238
  });
1179
1239
  useScrollLock.useIsoLayoutEffect(() => {
@@ -1186,6 +1246,7 @@ function AriaCombobox(props) {
1186
1246
  items,
1187
1247
  inline: inlineProp,
1188
1248
  popupProps,
1249
+ listProps,
1189
1250
  inputProps,
1190
1251
  triggerProps,
1191
1252
  openMethod,
@@ -1197,9 +1258,7 @@ function AriaCombobox(props) {
1197
1258
  readOnly,
1198
1259
  required,
1199
1260
  grid,
1200
- isGrouped,
1201
1261
  virtualized,
1202
- onOpenChangeComplete,
1203
1262
  openOnInputClick,
1204
1263
  itemToStringLabel,
1205
1264
  modal,
@@ -1207,10 +1266,9 @@ function AriaCombobox(props) {
1207
1266
  isItemEqualToValue,
1208
1267
  submitOnItemClick,
1209
1268
  hasInputValue,
1210
- requestSubmit,
1211
1269
  inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup)
1212
1270
  });
1213
- }, [store, id, selectedValue, open, mounted, transitionStatus, items, popupProps, inputProps, itemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, validation, grid, isGrouped, virtualized, onOpenChangeComplete, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, requestSubmit, autoHighlightMode, form]);
1271
+ }, [store, id, selectedValue, open, mounted, transitionStatus, items, popupProps, listProps, inputProps, itemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, grid, virtualized, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, autoHighlightMode, form]);
1214
1272
  const hiddenInputRef = useRenderElement.useMergedRefs(inputRefProp, validation.inputRef);
1215
1273
  const itemsContextValue = React__namespace.useMemo(() => ({
1216
1274
  query,
@@ -1254,7 +1312,7 @@ function AriaCombobox(props) {
1254
1312
  },
1255
1313
  // Handle browser autofill.
1256
1314
  onChange(event) {
1257
- // Workaround for https://github.com/facebook/react/issues/9023
1315
+ // Workaround for https://github.com/react/react/issues/9023
1258
1316
  if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
1259
1317
  return;
1260
1318
  }
@@ -1349,7 +1407,51 @@ const triggerStateAttributesMapping = {
1349
1407
  } : null
1350
1408
  };
1351
1409
 
1352
- const BOUNDARY_OFFSET = 2;
1410
+ /**
1411
+ * The popup side is only meaningful while the positioner is mounted, as the store retains the
1412
+ * last resolved side after the popup unmounts.
1413
+ */
1414
+ function usePopupSide(store) {
1415
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
1416
+ const popupSide = useScrollLock.useStore(store, selectors.popupSide);
1417
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1418
+ return mounted && positionerElement ? popupSide : null;
1419
+ }
1420
+
1421
+ /**
1422
+ * Whether the filtered list has no items to show.
1423
+ */
1424
+ function useListEmpty() {
1425
+ return useComboboxDerivedItemsContext().filteredItems.length === 0;
1426
+ }
1427
+
1428
+ /**
1429
+ * The arrow keys that move the chip highlight backwards and forwards, in that order.
1430
+ */
1431
+ function getChipNavigationKeys(direction) {
1432
+ return direction === 'rtl' ? ['ArrowRight', 'ArrowLeft'] : ['ArrowLeft', 'ArrowRight'];
1433
+ }
1434
+
1435
+ /**
1436
+ * Where the highlight lands once the chip at `index` is removed, or `undefined` for no highlight.
1437
+ */
1438
+ function getIndexAfterChipRemoval(index, chipCount) {
1439
+ const nextIndex = index >= chipCount - 1 ? chipCount - 2 : index;
1440
+ return nextIndex >= 0 ? nextIndex : undefined;
1441
+ }
1442
+
1443
+ /**
1444
+ * Commits the highlighted item by clicking it, tagging the originating event so the item's
1445
+ * handler can attribute the selection to it.
1446
+ */
1447
+ function clickHighlightedItem(store, activeIndex, nativeEvent) {
1448
+ const listItem = store.state.listRef.current[activeIndex];
1449
+ if (listItem) {
1450
+ store.state.selectionEventRef.current = nativeEvent;
1451
+ listItem.click();
1452
+ store.state.selectionEventRef.current = null;
1453
+ }
1454
+ }
1353
1455
 
1354
1456
  /**
1355
1457
  * A button that opens the popup.
@@ -1379,20 +1481,14 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1379
1481
  labelId: fieldLabelId
1380
1482
  } = useCompositeListItem.useLabelableContext();
1381
1483
  const store = useComboboxRootContext();
1382
- const {
1383
- filteredItems
1384
- } = useComboboxDerivedItemsContext();
1385
1484
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
1386
1485
  const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
1387
1486
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
1388
1487
  const required = useScrollLock.useStore(store, selectors.required);
1389
- const mounted = useScrollLock.useStore(store, selectors.mounted);
1390
- const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1391
1488
  const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1392
1489
  const listElement = useScrollLock.useStore(store, selectors.listElement);
1393
1490
  const storedPopupId = useScrollLock.useStore(store, selectors.popupId);
1394
1491
  const triggerProps = useScrollLock.useStore(store, selectors.triggerProps);
1395
- const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
1396
1492
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
1397
1493
  const rootId = useScrollLock.useStore(store, selectors.id);
1398
1494
  const comboboxLabelId = useScrollLock.useStore(store, selectors.labelId);
@@ -1405,8 +1501,8 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1405
1501
  const inputValue = useComboboxInputValueContext();
1406
1502
  const focusTimeout = useScrollLock.useTimeout();
1407
1503
  const disabled = fieldDisabled || comboboxDisabled || disabledProp;
1408
- const listEmpty = filteredItems.length === 0;
1409
- const popupSide = mounted && positionerElement ? popupSideValue : null;
1504
+ const listEmpty = useListEmpty();
1505
+ const popupSide = usePopupSide(store);
1410
1506
  useCompositeListItem.useLabelableId({
1411
1507
  id: inputInsidePopup ? idProp : undefined
1412
1508
  });
@@ -1424,18 +1520,6 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1424
1520
  function trackPointerType(event) {
1425
1521
  currentPointerTypeRef.current = event.pointerType;
1426
1522
  }
1427
- const domReference = floatingRootContext.useState('domReferenceElement');
1428
-
1429
- // Update the floating root context to use the trigger element when it differs from the current reference.
1430
- // This ensures useClick and useTypeahead attach handlers to the correct element.
1431
- React__namespace.useEffect(() => {
1432
- if (!inputInsidePopup) {
1433
- return;
1434
- }
1435
- if (triggerElement && triggerElement !== domReference) {
1436
- floatingRootContext.set('domReferenceElement', triggerElement);
1437
- }
1438
- }, [triggerElement, domReference, floatingRootContext, inputInsidePopup]);
1439
1523
  const {
1440
1524
  reference: triggerTypeaheadProps
1441
1525
  } = useCompositeListItem.useTypeahead(floatingRootContext, {
@@ -1446,7 +1530,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1446
1530
  onMatch(index) {
1447
1531
  const nextSelectedValue = store.state.valuesRef.current[index];
1448
1532
  if (nextSelectedValue !== undefined) {
1449
- store.state.setSelectedValue(nextSelectedValue, useScrollLock.createChangeEventDetails('none'));
1533
+ store.state.setSelectedValue(nextSelectedValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1450
1534
  }
1451
1535
  }
1452
1536
  });
@@ -1481,7 +1565,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1481
1565
  id,
1482
1566
  tabIndex: inputInsidePopup ? 0 : -1,
1483
1567
  role: inputInsidePopup ? 'combobox' : undefined,
1484
- 'aria-expanded': open ? 'true' : 'false',
1568
+ 'aria-expanded': open,
1485
1569
  'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
1486
1570
  'aria-controls': ariaControls,
1487
1571
  'aria-required': inputInsidePopup ? required || undefined : undefined,
@@ -1528,22 +1612,20 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1528
1612
  }
1529
1613
  const doc = useScrollLock.ownerDocument(event.currentTarget);
1530
1614
  function handleMouseUp(mouseEvent) {
1531
- if (!triggerElement) {
1615
+ const currentTriggerElement = store.state.triggerElement;
1616
+ if (!currentTriggerElement) {
1532
1617
  return;
1533
1618
  }
1534
1619
  const mouseUpTarget = useScrollLock.getTarget(mouseEvent);
1535
1620
  const positioner = store.state.positionerElement;
1536
1621
  const list = store.state.listElement;
1537
- if (useScrollLock.contains(triggerElement, mouseUpTarget) || useScrollLock.contains(positioner, mouseUpTarget) || useScrollLock.contains(list, mouseUpTarget) || mouseUpTarget === triggerElement) {
1622
+ if (useScrollLock.contains(currentTriggerElement, mouseUpTarget) || useScrollLock.contains(positioner, mouseUpTarget) || useScrollLock.contains(list, mouseUpTarget)) {
1538
1623
  return;
1539
1624
  }
1540
- const bounds = useCompositeListItem.getPseudoElementBounds(triggerElement);
1541
- const withinHorizontal = mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET;
1542
- const withinVertical = mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET;
1543
- if (withinHorizontal && withinVertical) {
1625
+ if (useCompositeListItem.isMouseWithinBounds(mouseEvent, currentTriggerElement)) {
1544
1626
  return;
1545
1627
  }
1546
- store.state.setOpen(false, useScrollLock.createChangeEventDetails('cancel-open', mouseEvent));
1628
+ store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.cancelOpen, mouseEvent));
1547
1629
  }
1548
1630
  if (inputInsidePopup) {
1549
1631
  doc.addEventListener('mouseup', handleMouseUp, {
@@ -1552,7 +1634,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1552
1634
  }
1553
1635
  },
1554
1636
  onKeyDown(event) {
1555
- if (disabled || readOnly) {
1637
+ if (readOnly) {
1556
1638
  return;
1557
1639
  }
1558
1640
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
@@ -1561,7 +1643,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1561
1643
  store.state.inputRef.current?.focus();
1562
1644
  }
1563
1645
  }
1564
- }, validation ? validation.getValidationProps(disabled, elementProps) : elementProps, getButtonProps],
1646
+ }, validation.getValidationProps(disabled, elementProps), getButtonProps],
1565
1647
  stateAttributesMapping: triggerStateAttributesMapping
1566
1648
  });
1567
1649
  return element;
@@ -1636,9 +1718,6 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1636
1718
  const positioning = useComboboxPositionerContext(true);
1637
1719
  const hasPositionerParent = Boolean(positioning);
1638
1720
  const store = useComboboxRootContext();
1639
- const {
1640
- filteredItems
1641
- } = useComboboxDerivedItemsContext();
1642
1721
  // `inputValue` can't be placed in the store.
1643
1722
  // https://github.com/mui/base-ui/issues/2703
1644
1723
  const inputValue = useComboboxInputValueContext();
@@ -1655,19 +1734,16 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1655
1734
  const open = useScrollLock.useStore(store, selectors.open);
1656
1735
  const mounted = useScrollLock.useStore(store, selectors.mounted);
1657
1736
  const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
1658
- const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1659
- const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1660
1737
  const rootId = useScrollLock.useStore(store, selectors.id);
1661
1738
  const inline = useScrollLock.useStore(store, selectors.inline);
1662
1739
  const modal = useScrollLock.useStore(store, selectors.modal);
1663
1740
  const autoHighlightEnabled = Boolean(autoHighlightMode);
1664
- const popupSide = mounted && positionerElement ? popupSideValue : null;
1741
+ const popupSide = usePopupSide(store);
1665
1742
  const disabled = fieldDisabled || comboboxDisabled || disabledProp;
1666
- const listEmpty = filteredItems.length === 0;
1743
+ const listEmpty = useListEmpty();
1667
1744
  const isInsidePopup = hasPositionerParent || inline;
1668
1745
  const focusManagerModal = !isInsidePopup || modal;
1669
1746
  const id = useScrollLock.useBaseUiId(idProp ?? (!isInsidePopup ? rootId : undefined));
1670
- const ariaLabelledBy = useCompositeListItem.resolveAriaLabelledBy(fieldLabelId, undefined);
1671
1747
  const fieldStateForInput = hasPositionerParent ? useCompositeListItem.DEFAULT_FIELD_STATE_ATTRIBUTES : fieldState;
1672
1748
  const [composingValue, setComposingValue] = React__namespace.useState(null);
1673
1749
  const isComposingRef = React__namespace.useRef(false);
@@ -1685,7 +1761,17 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1685
1761
  inputOwnsFormValue
1686
1762
  });
1687
1763
  });
1688
- const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(disabled, elementProps);
1764
+ const validationProps = hasPositionerParent ? elementProps : validation.getValidationProps(disabled, elementProps);
1765
+ function clearHighlight() {
1766
+ store.state.setIndices({
1767
+ activeIndex: null,
1768
+ selectedIndex: null,
1769
+ type: store.state.keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer
1770
+ });
1771
+ }
1772
+ function markPointerActive() {
1773
+ store.state.keyboardActiveRef.current = false;
1774
+ }
1689
1775
  const state = {
1690
1776
  ...fieldStateForInput,
1691
1777
  open,
@@ -1703,9 +1789,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1703
1789
  highlightedChipIndex
1704
1790
  } = comboboxChipsContext;
1705
1791
  const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
1706
- const isRtl = direction === 'rtl';
1707
- const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
1708
- const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
1792
+ const [previousChipKey, nextChipKey] = getChipNavigationKeys(direction);
1709
1793
  if (highlightedChipIndex !== undefined) {
1710
1794
  if (event.key === previousChipKey) {
1711
1795
  event.preventDefault();
@@ -1724,14 +1808,8 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1724
1808
  } else if (event.key === 'Backspace' || event.key === 'Delete') {
1725
1809
  event.preventDefault();
1726
1810
  // Move highlight appropriately after removal.
1727
- const computedNextIndex = highlightedChipIndex >= selectedValue.length - 1 ? selectedValue.length - 2 : highlightedChipIndex;
1728
- // If the computed index is negative, treat it as no highlight.
1729
- nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
1730
- store.state.setIndices({
1731
- activeIndex: null,
1732
- selectedIndex: null,
1733
- type: 'keyboard'
1734
- });
1811
+ nextIndex = getIndexAfterChipRemoval(highlightedChipIndex, selectedValue.length);
1812
+ clearHighlight();
1735
1813
  }
1736
1814
  return nextIndex;
1737
1815
  }
@@ -1740,13 +1818,6 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1740
1818
  if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
1741
1819
  event.preventDefault();
1742
1820
  nextIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : undefined;
1743
- } else if (event.key === 'Backspace' && event.currentTarget.value === '' && selectedValue.length > 0) {
1744
- store.state.setIndices({
1745
- activeIndex: null,
1746
- selectedIndex: null,
1747
- type: 'keyboard'
1748
- });
1749
- event.preventDefault();
1750
1821
  }
1751
1822
  return nextIndex;
1752
1823
  }
@@ -1754,11 +1825,10 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1754
1825
  state,
1755
1826
  ref: [forwardedRef, store.state.inputRef, setInputElement],
1756
1827
  props: [inputProps, triggerProps, {
1757
- type: 'text',
1758
- value: componentProps.value ?? composingValue ?? inputValue,
1828
+ value: composingValue ?? inputValue,
1759
1829
  'aria-readonly': readOnly || undefined,
1760
1830
  'aria-required': required || undefined,
1761
- 'aria-labelledby': ariaLabelledBy,
1831
+ 'aria-labelledby': fieldLabelId,
1762
1832
  disabled,
1763
1833
  readOnly,
1764
1834
  required: selectionMode === 'none' ? required : undefined,
@@ -1813,11 +1883,23 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1813
1883
  store.state.setInputValue(next, useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent));
1814
1884
  },
1815
1885
  onChange(event) {
1886
+ const nativeEvent = event.nativeEvent;
1816
1887
  // Autofill may not provide `inputType` (Chrome) or may report
1817
1888
  // `insertReplacementText` (Firefox).
1818
- const inputType = event.nativeEvent.inputType;
1889
+ const inputType = nativeEvent.inputType;
1819
1890
  const autofillLikeInput = !inputType || inputType === 'insertReplacementText';
1891
+ // During composition the input is always considered typed into.
1820
1892
  const shouldOpenOnInput = isComposingRef.current || !autofillLikeInput;
1893
+ function maybeOpenOnInput(trimmed) {
1894
+ if (readOnly || disabled || !trimmed || !shouldOpenOnInput) {
1895
+ return;
1896
+ }
1897
+ store.state.setOpen(true, useScrollLock.createChangeEventDetails(useScrollLock.inputChange, nativeEvent));
1898
+ // When autoHighlight is enabled, keep the highlight (will be set to 0 in root).
1899
+ if (!autoHighlightEnabled) {
1900
+ clearHighlight();
1901
+ }
1902
+ }
1821
1903
 
1822
1904
  // During IME composition, avoid propagating controlled updates to prevent
1823
1905
  // filtering the options prematurely so `Empty` won't show incorrectly.
@@ -1829,38 +1911,23 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1829
1911
  const nextVal = event.currentTarget.value;
1830
1912
  setComposingValue(nextVal);
1831
1913
  if (nextVal === '' && !store.state.openOnInputClick && !store.state.inputInsidePopup) {
1832
- store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.inputClear, event.nativeEvent));
1914
+ store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.inputClear, nativeEvent));
1833
1915
  }
1834
1916
  const trimmed = nextVal.trim();
1835
1917
  const shouldMaintainHighlight = autoHighlightEnabled && trimmed !== '';
1836
- if (!readOnly && !disabled && trimmed) {
1837
- if (shouldOpenOnInput) {
1838
- store.state.setOpen(true, useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent));
1839
- if (!autoHighlightEnabled) {
1840
- store.state.setIndices({
1841
- activeIndex: null,
1842
- selectedIndex: null,
1843
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1844
- });
1845
- }
1846
- }
1847
- }
1918
+ maybeOpenOnInput(trimmed);
1848
1919
  if (open && store.state.activeIndex !== null && !shouldMaintainHighlight) {
1849
- store.state.setIndices({
1850
- activeIndex: null,
1851
- selectedIndex: null,
1852
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1853
- });
1920
+ clearHighlight();
1854
1921
  }
1855
1922
  return;
1856
1923
  }
1857
- const inputChangeDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent);
1924
+ const inputChangeDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputChange, nativeEvent);
1858
1925
  store.state.setInputValue(event.currentTarget.value, inputChangeDetails);
1859
1926
  if (inputChangeDetails.isCanceled) {
1860
1927
  return;
1861
1928
  }
1862
1929
  const empty = event.currentTarget.value === '';
1863
- const clearDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputClear, event.nativeEvent);
1930
+ const clearDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputClear, nativeEvent);
1864
1931
  if (empty && !store.state.inputInsidePopup) {
1865
1932
  if (selectionMode === 'single') {
1866
1933
  store.state.setSelectedValue(null, clearDetails);
@@ -1869,30 +1936,13 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1869
1936
  store.state.setOpen(false, clearDetails);
1870
1937
  }
1871
1938
  }
1872
- const trimmed = event.currentTarget.value.trim();
1873
- if (!readOnly && !disabled && trimmed) {
1874
- if (shouldOpenOnInput) {
1875
- store.state.setOpen(true, useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent));
1876
- // When autoHighlight is enabled, keep the highlight (will be set to 0 in root).
1877
- if (!autoHighlightEnabled) {
1878
- store.state.setIndices({
1879
- activeIndex: null,
1880
- selectedIndex: null,
1881
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1882
- });
1883
- }
1884
- }
1885
- }
1939
+ maybeOpenOnInput(event.currentTarget.value.trim());
1886
1940
 
1887
1941
  // When the user types, ensure the list resets its highlight so that
1888
1942
  // virtual focus returns to the input (aria-activedescendant is
1889
1943
  // cleared).
1890
1944
  if (open && store.state.activeIndex !== null && !autoHighlightEnabled) {
1891
- store.state.setIndices({
1892
- activeIndex: null,
1893
- selectedIndex: null,
1894
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1895
- });
1945
+ clearHighlight();
1896
1946
  }
1897
1947
  },
1898
1948
  onKeyDown(event) {
@@ -1938,11 +1988,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1938
1988
  const removalIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : selectedValue.length - 1;
1939
1989
  const newValue = selectedValue.filter((_, index) => index !== removalIndex);
1940
1990
  // If the removed item was also the active (highlighted) item, clear highlight
1941
- store.state.setIndices({
1942
- activeIndex: null,
1943
- selectedIndex: null,
1944
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1945
- });
1991
+ clearHighlight();
1946
1992
  store.state.setSelectedValue(newValue, useScrollLock.createChangeEventDetails(useScrollLock.none, event.nativeEvent));
1947
1993
  return;
1948
1994
  }
@@ -1972,20 +2018,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1972
2018
  return;
1973
2019
  }
1974
2020
  useScrollLock.stopEvent(event);
1975
- const listItem = store.state.listRef.current[activeIndex];
1976
- if (listItem) {
1977
- store.state.selectionEventRef.current = nativeEvent;
1978
- listItem.click();
1979
- store.state.selectionEventRef.current = null;
1980
- }
2021
+ clickHighlightedItem(store, activeIndex, nativeEvent);
1981
2022
  }
1982
2023
  },
1983
- onPointerMove() {
1984
- store.state.keyboardActiveRef.current = false;
1985
- },
1986
- onPointerDown() {
1987
- store.state.keyboardActiveRef.current = false;
1988
- }
2024
+ onPointerMove: markPointerActive,
2025
+ onPointerDown: markPointerActive
1989
2026
  }, validationProps],
1990
2027
  stateAttributesMapping: triggerStateAttributesMapping
1991
2028
  });
@@ -2006,7 +2043,7 @@ function handleInputPress(event, store, disabled, readOnly, shouldIgnoreTarget)
2006
2043
  return;
2007
2044
  }
2008
2045
  const target = useScrollLock.getTarget(event.nativeEvent);
2009
- const targetElement = floatingUi_utils.isElement(target) ? target : null;
2046
+ const targetElement = useScrollLock.isElement(target) ? target : null;
2010
2047
  if (targetElement !== event.currentTarget && (shouldIgnoreTarget?.(targetElement) || useScrollLock.isInteractiveElement(targetElement))) {
2011
2048
  return;
2012
2049
  }
@@ -2037,20 +2074,14 @@ const ComboboxInputGroup = /*#__PURE__*/React__namespace.forwardRef(function Com
2037
2074
  state: fieldState
2038
2075
  } = useCompositeListItem.useFieldRootContext();
2039
2076
  const store = useComboboxRootContext();
2040
- const {
2041
- filteredItems
2042
- } = useComboboxDerivedItemsContext();
2043
2077
  const open = useScrollLock.useStore(store, selectors.open);
2044
- const mounted = useScrollLock.useStore(store, selectors.mounted);
2045
- const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
2046
- const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
2047
2078
  const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
2048
2079
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2049
2080
  const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
2050
2081
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2051
- const popupSide = mounted && positionerElement ? popupSideValue : null;
2082
+ const popupSide = usePopupSide(store);
2052
2083
  const disabled = comboboxDisabled;
2053
- const listEmpty = filteredItems.length === 0;
2084
+ const listEmpty = useListEmpty();
2054
2085
  const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
2055
2086
  const state = {
2056
2087
  ...fieldState,
@@ -2186,19 +2217,21 @@ const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2186
2217
  if (disabled || readOnly) {
2187
2218
  return;
2188
2219
  }
2189
- const keyboardActiveRef = store.state.keyboardActiveRef;
2220
+ const type = store.state.keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer;
2190
2221
  store.state.setInputValue('', useScrollLock.createChangeEventDetails(useScrollLock.clearPress, event.nativeEvent));
2191
2222
  if (selectionMode !== 'none') {
2192
2223
  store.state.setSelectedValue(Array.isArray(selectedValue) ? [] : null, useScrollLock.createChangeEventDetails(useScrollLock.clearPress, event.nativeEvent));
2224
+ // A distinct object shape: `Store.update` iterates own keys, so passing an explicit
2225
+ // `selectedIndex: undefined` would overwrite the state instead of leaving it alone.
2193
2226
  store.state.setIndices({
2194
2227
  activeIndex: null,
2195
2228
  selectedIndex: null,
2196
- type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
2229
+ type
2197
2230
  });
2198
2231
  } else {
2199
2232
  store.state.setIndices({
2200
2233
  activeIndex: null,
2201
- type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
2234
+ type
2202
2235
  });
2203
2236
  }
2204
2237
  store.state.inputRef.current?.focus();
@@ -2242,9 +2275,6 @@ function ComboboxCollection(props) {
2242
2275
  } = useComboboxDerivedItemsContext();
2243
2276
  const groupContext = useGroupCollectionContext();
2244
2277
  const itemsToRender = groupContext ? groupContext.items : filteredItems;
2245
- if (!itemsToRender) {
2246
- return null;
2247
- }
2248
2278
  return /*#__PURE__*/jsxRuntime.jsx(React__namespace.Fragment, {
2249
2279
  children: itemsToRender.map(children)
2250
2280
  });
@@ -2268,7 +2298,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2268
2298
  } = useComboboxDerivedItemsContext();
2269
2299
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2270
2300
  const grid = useScrollLock.useStore(store, selectors.grid);
2271
- const popupProps = useScrollLock.useStore(store, selectors.popupProps);
2301
+ const listProps = useScrollLock.useStore(store, selectors.listProps);
2272
2302
  const virtualized = useScrollLock.useStore(store, selectors.virtualized);
2273
2303
  const forceMounted = useScrollLock.useStore(store, selectors.forceMounted);
2274
2304
  const multiple = selectionMode === 'multiple';
@@ -2282,7 +2312,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2282
2312
 
2283
2313
  // Support "closed template" API: if children is a function, implicitly wrap it
2284
2314
  // with a Combobox.Collection that reads items from context/root.
2285
- // Ensures this component's `popupProps` subscription does not cause <Combobox.Item>
2315
+ // Ensures this component's `listProps` subscription does not cause <Combobox.Item>
2286
2316
  // to re-render on every active index change.
2287
2317
  const resolvedChildren = React__namespace.useMemo(() => {
2288
2318
  if (typeof children === 'function') {
@@ -2299,7 +2329,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2299
2329
  const element = useRenderElement.useRenderElement('div', componentProps, {
2300
2330
  state,
2301
2331
  ref: [forwardedRef, setListElement, hasPositionerContext ? null : setPositionerElement],
2302
- props: [popupProps, {
2332
+ props: [listProps, {
2303
2333
  children: resolvedChildren,
2304
2334
  tabIndex: -1,
2305
2335
  id: floatingId,
@@ -2316,13 +2346,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2316
2346
  return;
2317
2347
  }
2318
2348
  useScrollLock.stopEvent(event);
2319
- const nativeEvent = event.nativeEvent;
2320
- const listItem = store.state.listRef.current[activeIndex];
2321
- if (listItem) {
2322
- store.state.selectionEventRef.current = nativeEvent;
2323
- listItem.click();
2324
- store.state.selectionEventRef.current = null;
2325
- }
2349
+ clickHighlightedItem(store, activeIndex, event.nativeEvent);
2326
2350
  }
2327
2351
  },
2328
2352
  onKeyDownCapture() {
@@ -2381,7 +2405,7 @@ function useInitialLiveRegionTextMutation() {
2381
2405
  if (textNode == null) {
2382
2406
  return undefined;
2383
2407
  }
2384
- const originalValue = textNode.nodeValue ?? '';
2408
+ const originalValue = textNode.data;
2385
2409
  const markedValue = `${originalValue}${LIVE_REGION_MARKER}`;
2386
2410
  textNode.nodeValue = markedValue;
2387
2411
  timeout.start(INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY, () => {
@@ -2510,24 +2534,23 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2510
2534
  render,
2511
2535
  className,
2512
2536
  anchor,
2513
- positionMethod = 'absolute',
2514
- side = 'bottom',
2515
- align = 'center',
2516
- sideOffset = 0,
2517
- alignOffset = 0,
2537
+ // `useAnchorPositioning` applies the same defaults to the undefined values; the names
2538
+ // remain destructured to exclude the props from `elementProps`.
2539
+ positionMethod,
2540
+ side,
2541
+ align,
2542
+ sideOffset,
2543
+ alignOffset,
2518
2544
  collisionBoundary = 'clipping-ancestors',
2519
- collisionPadding = 5,
2520
- arrowPadding = 5,
2521
- sticky = false,
2545
+ collisionPadding,
2546
+ arrowPadding,
2547
+ sticky,
2522
2548
  disableAnchorTracking = false,
2523
2549
  collisionAvoidance = useScrollLock.DROPDOWN_COLLISION_AVOIDANCE,
2524
2550
  style: styleProp,
2525
2551
  ...elementProps
2526
2552
  } = componentProps;
2527
2553
  const store = useComboboxRootContext();
2528
- const {
2529
- filteredItems
2530
- } = useComboboxDerivedItemsContext();
2531
2554
  const floatingRootContext = useComboboxFloatingContext();
2532
2555
  const keepMounted = useComboboxPortalContext();
2533
2556
  const modal = useScrollLock.useStore(store, selectors.modal);
@@ -2540,7 +2563,7 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2540
2563
  const inputGroupElement = useScrollLock.useStore(store, selectors.inputGroupElement);
2541
2564
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2542
2565
  const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2543
- const empty = filteredItems.length === 0;
2566
+ const empty = useListEmpty();
2544
2567
  const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputGroupElement ?? inputElement);
2545
2568
  const positioning = useCompositeListItem.useAnchorPositioning({
2546
2569
  anchor: resolvedAnchor,
@@ -2615,18 +2638,16 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2615
2638
  const store = useComboboxRootContext();
2616
2639
  const positioning = useComboboxPositionerContext();
2617
2640
  const floatingRootContext = useComboboxFloatingContext();
2618
- const {
2619
- filteredItems
2620
- } = useComboboxDerivedItemsContext();
2621
2641
  const mounted = useScrollLock.useStore(store, selectors.mounted);
2622
2642
  const open = useScrollLock.useStore(store, selectors.open);
2623
2643
  const openMethod = useScrollLock.useStore(store, selectors.openMethod);
2644
+ const popupProps = useScrollLock.useStore(store, selectors.popupProps);
2624
2645
  const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2625
2646
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2626
2647
  const inputElement = useScrollLock.useStore(store, selectors.inputElement);
2627
2648
  const modal = useScrollLock.useStore(store, selectors.modal);
2628
2649
  const rootId = useScrollLock.useStore(store, selectors.id);
2629
- const empty = filteredItems.length === 0;
2650
+ const empty = useListEmpty();
2630
2651
  const popupId = elementProps.id ?? (inputInsidePopup ? getComboboxPopupId(rootId) : undefined);
2631
2652
  useScrollLock.useIsoLayoutEffect(() => {
2632
2653
  // Prefer the rendered DOM id, which a `render` prop element or function may override.
@@ -2655,10 +2676,9 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2655
2676
  const element = useRenderElement.useRenderElement('div', componentProps, {
2656
2677
  state,
2657
2678
  ref: [forwardedRef, store.state.popupRef],
2658
- props: [{
2679
+ props: [popupProps, {
2659
2680
  id: popupId,
2660
2681
  role: inputInsidePopup ? 'dialog' : 'presentation',
2661
- tabIndex: -1,
2662
2682
  onFocus(event) {
2663
2683
  const target = useScrollLock.getTarget(event.nativeEvent);
2664
2684
  if (openMethod !== 'touch' && (useScrollLock.contains(store.state.listElement, target) || target === event.currentTarget)) {
@@ -2805,7 +2825,7 @@ const ComboboxGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function Com
2805
2825
  useScrollLock.useIsoLayoutEffect(() => {
2806
2826
  setLabelId(id);
2807
2827
  return () => {
2808
- setLabelId(undefined);
2828
+ setLabelId(currentId => currentId === id ? undefined : currentId);
2809
2829
  };
2810
2830
  }, [id, setLabelId]);
2811
2831
  const element = useRenderElement.useRenderElement('div', componentProps, {
@@ -2847,27 +2867,27 @@ function ComboboxItemInner(props) {
2847
2867
  style,
2848
2868
  value: itemValue = null,
2849
2869
  index: indexProp,
2850
- disabled = false,
2870
+ disabled: disabledProp = false,
2851
2871
  nativeButton = false,
2852
2872
  ...elementProps
2853
2873
  } = componentProps;
2854
- const didPointerDownRef = React__namespace.useRef(false);
2855
2874
  const textRef = React__namespace.useRef(null);
2856
2875
  const listItem = useCompositeListItem.useCompositeListItem({
2876
+ guess: true,
2857
2877
  index: indexProp,
2858
- textRef,
2859
- indexGuessBehavior: useCompositeListItem.IndexGuessBehavior.GuessFromOrder
2878
+ textRef
2860
2879
  });
2861
2880
  const store = useComboboxRootContext();
2862
2881
  const isRow = useComboboxRowContext();
2863
2882
  const hasItems = useComboboxHasItemsContext();
2864
- const open = useScrollLock.useStore(store, selectors.open);
2865
2883
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2884
+ const rootDisabled = useScrollLock.useStore(store, selectors.disabled);
2866
2885
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2867
2886
  const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
2887
+ const disabled = rootDisabled || disabledProp;
2868
2888
  const selectable = selectionMode !== 'none';
2869
- const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
2870
- const hasRegistered = listItem.index !== -1;
2889
+ const index = indexProp ?? indexFromFilter ?? listItem.index;
2890
+ const hasRegistered = index !== -1;
2871
2891
  const rootId = useScrollLock.useStore(store, selectors.id);
2872
2892
  const highlighted = useScrollLock.useStore(store, selectors.isActive, index);
2873
2893
  const matchesSelectedValue = useScrollLock.useStore(store, selectors.isSelected, itemValue);
@@ -2892,31 +2912,24 @@ function ComboboxItemInner(props) {
2892
2912
  }
2893
2913
  const visibleMap = store.state.valuesRef.current;
2894
2914
  visibleMap[index] = itemValue;
2895
-
2896
- // Stable registry that doesn't depend on filtering. Assume that no
2897
- // filtering had occurred at this point; otherwise, an `items` prop is
2898
- // required.
2899
- if (selectionMode !== 'none') {
2900
- store.state.allValuesRef.current.push(itemValue);
2901
- }
2902
2915
  return () => {
2903
2916
  delete visibleMap[index];
2904
2917
  };
2905
- }, [hasRegistered, hasItems, index, itemValue, store, selectionMode]);
2918
+ }, [hasRegistered, hasItems, index, itemValue, store]);
2906
2919
  useScrollLock.useIsoLayoutEffect(() => {
2907
- if (!open) {
2908
- didPointerDownRef.current = false;
2909
- return;
2910
- }
2911
2920
  if (!hasRegistered || hasItems) {
2912
2921
  return;
2913
2922
  }
2923
+
2924
+ // Runs while closed as well (the list can stay mounted via `keepMounted` or a
2925
+ // force-mount) so the index tracks the item's composite position, keeping features
2926
+ // like closed-trigger typeahead in sync when the rendered order changes.
2914
2927
  const selectedValue = store.state.selectedValue;
2915
2928
  const lastSelectedValue = Array.isArray(selectedValue) ? selectedValue[selectedValue.length - 1] : selectedValue;
2916
2929
  if (useCompositeListItem.compareItemEquality(itemValue, lastSelectedValue, isItemEqualToValue)) {
2917
2930
  store.set('selectedIndex', index);
2918
2931
  }
2919
- }, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
2932
+ }, [hasRegistered, hasItems, store, index, itemValue, isItemEqualToValue]);
2920
2933
  const {
2921
2934
  getButtonProps,
2922
2935
  buttonRef
@@ -2951,7 +2964,12 @@ function ComboboxItemInner(props) {
2951
2964
  // as it should be a `<div>` instead.
2952
2965
  tabIndex: undefined,
2953
2966
  onPointerDownCapture(event) {
2954
- didPointerDownRef.current = true;
2967
+ // The compat `mouseup` only fires for the primary pointer, so a non-primary
2968
+ // touch must not overwrite the shared ref — a mismatch would make the primary
2969
+ // pointer's release read as a drag-select and commit a second time after `click`.
2970
+ if (event.isPrimary) {
2971
+ store.state.pointerDownItemRef.current = event.currentTarget;
2972
+ }
2955
2973
  event.preventDefault();
2956
2974
  },
2957
2975
  onMouseDown(event) {
@@ -2966,8 +2984,8 @@ function ComboboxItemInner(props) {
2966
2984
  commitSelection(event.nativeEvent);
2967
2985
  },
2968
2986
  onMouseUp(event) {
2969
- const pointerStartedOnItem = didPointerDownRef.current;
2970
- didPointerDownRef.current = false;
2987
+ const pointerStartedOnItem = store.state.pointerDownItemRef.current === event.currentTarget;
2988
+ store.state.pointerDownItemRef.current = null;
2971
2989
  if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) {
2972
2990
  return;
2973
2991
  }
@@ -3103,6 +3121,33 @@ const ComboboxEmpty = /*#__PURE__*/React__namespace.forwardRef(function Combobox
3103
3121
  });
3104
3122
  if (process.env.NODE_ENV !== "production") ComboboxEmpty.displayName = "ComboboxEmpty";
3105
3123
 
3124
+ /**
3125
+ * A visual separator between items.
3126
+ * Renders a `<div>` element.
3127
+ *
3128
+ * @internal
3129
+ */
3130
+ const ListboxSeparator = /*#__PURE__*/React__namespace.forwardRef(function ListboxSeparator(componentProps, forwardedRef) {
3131
+ const {
3132
+ className,
3133
+ render,
3134
+ orientation = 'horizontal',
3135
+ style,
3136
+ ...elementProps
3137
+ } = componentProps;
3138
+ const state = {
3139
+ orientation
3140
+ };
3141
+ return useRenderElement.useRenderElement('div', componentProps, {
3142
+ state,
3143
+ ref: forwardedRef,
3144
+ props: [{
3145
+ role: 'presentation'
3146
+ }, elementProps]
3147
+ });
3148
+ });
3149
+ if (process.env.NODE_ENV !== "production") ListboxSeparator.displayName = "ListboxSeparator";
3150
+
3106
3151
  /**
3107
3152
  * Returns the internally filtered items.
3108
3153
  */
@@ -3169,7 +3214,8 @@ const ComboboxLabel = /*#__PURE__*/React__namespace.forwardRef(function Combobox
3169
3214
  id: defaultLabelId,
3170
3215
  fallbackControlId: localControlId,
3171
3216
  setLabelId(nextLabelId) {
3172
- store.set('labelId', nextLabelId);
3217
+ const resolvedLabelId = typeof nextLabelId === 'function' ? nextLabelId(store.state.labelId) : nextLabelId;
3218
+ store.set('labelId', resolvedLabelId);
3173
3219
  }
3174
3220
  });
3175
3221
  return useRenderElement.useRenderElement('div', componentProps, {
@@ -3212,11 +3258,10 @@ function ComboboxValue(props) {
3212
3258
  }
3213
3259
 
3214
3260
  const ComboboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function ComboboxItemIndicator(componentProps, forwardedRef) {
3215
- const keepMounted = componentProps.keepMounted ?? false;
3216
3261
  const {
3217
3262
  selected
3218
3263
  } = useComboboxItemContext();
3219
- const shouldRender = keepMounted || selected;
3264
+ const shouldRender = componentProps.keepMounted || selected;
3220
3265
  if (!shouldRender) {
3221
3266
  return null;
3222
3267
  }
@@ -3346,9 +3391,7 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3346
3391
  } = useCompositeListItem.useCompositeListItem();
3347
3392
  function handleKeyDown(event) {
3348
3393
  let nextIndex = index;
3349
- const isRtl = direction === 'rtl';
3350
- const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
3351
- const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
3394
+ const [previousChipKey, nextChipKey] = getChipNavigationKeys(direction);
3352
3395
  if (event.key === previousChipKey) {
3353
3396
  event.preventDefault();
3354
3397
  if (index > 0) {
@@ -3364,13 +3407,12 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3364
3407
  nextIndex = undefined;
3365
3408
  }
3366
3409
  } else if (event.key === 'Backspace' || event.key === 'Delete') {
3367
- const computedNextIndex = index >= selectedValue.length - 1 ? selectedValue.length - 2 : index;
3368
- nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
3410
+ nextIndex = getIndexAfterChipRemoval(index, selectedValue.length);
3369
3411
  useScrollLock.stopEvent(event);
3370
3412
  store.state.setIndices({
3371
3413
  activeIndex: null,
3372
3414
  selectedIndex: null,
3373
- type: 'keyboard'
3415
+ type: useScrollLock.keyboard
3374
3416
  });
3375
3417
  store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), useScrollLock.createChangeEventDetails(useScrollLock.none, event.nativeEvent));
3376
3418
  } else if (event.key === 'Enter' || event.key === ' ') {
@@ -3470,7 +3512,7 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3470
3512
  if (removedIndex !== -1 && activeIndex === removedIndex) {
3471
3513
  store.state.setIndices({
3472
3514
  activeIndex: null,
3473
- type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
3515
+ type: store.state.keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer
3474
3516
  });
3475
3517
  }
3476
3518
  }
@@ -3491,18 +3533,12 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3491
3533
  event.preventDefault();
3492
3534
  },
3493
3535
  onClick(event) {
3494
- if (disabled || readOnly) {
3495
- return;
3496
- }
3497
3536
  const eventDetails = removeChip(event);
3498
3537
  if (!eventDetails.isPropagationAllowed) {
3499
3538
  event.stopPropagation();
3500
3539
  }
3501
3540
  },
3502
3541
  onKeyDown(event) {
3503
- if (disabled || readOnly) {
3504
- return;
3505
- }
3506
3542
  if (event.key === 'Enter' || event.key === ' ') {
3507
3543
  const eventDetails = removeChip(event);
3508
3544
  if (!eventDetails.isPropagationAllowed) {
@@ -3516,6 +3552,14 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3516
3552
  });
3517
3553
  if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
3518
3554
 
3555
+ /**
3556
+ * A visual separator between items or groups.
3557
+ * Renders a `<div>` element.
3558
+ *
3559
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
3560
+ */
3561
+ const ComboboxSeparator = ListboxSeparator;
3562
+
3519
3563
  exports.AriaCombobox = AriaCombobox;
3520
3564
  exports.ComboboxArrow = ComboboxArrow;
3521
3565
  exports.ComboboxBackdrop = ComboboxBackdrop;
@@ -3539,9 +3583,11 @@ exports.ComboboxPortal = ComboboxPortal;
3539
3583
  exports.ComboboxPositioner = ComboboxPositioner;
3540
3584
  exports.ComboboxRoot = ComboboxRoot;
3541
3585
  exports.ComboboxRow = ComboboxRow;
3586
+ exports.ComboboxSeparator = ComboboxSeparator;
3542
3587
  exports.ComboboxStatus = ComboboxStatus;
3543
3588
  exports.ComboboxTrigger = ComboboxTrigger;
3544
3589
  exports.ComboboxValue = ComboboxValue;
3590
+ exports.ListboxSeparator = ListboxSeparator;
3545
3591
  exports.useComboboxFilter = useComboboxFilter;
3546
3592
  exports.useComboboxInputValueContext = useComboboxInputValueContext;
3547
3593
  exports.useCoreFilter = useCoreFilter;