@jobber/components 9.6.1 → 9.6.2

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 +2 -0
  2. package/dist/Autocomplete/index.mjs +2 -0
  3. package/dist/Autocomplete-cjs.js +5 -4
  4. package/dist/Autocomplete-es.js +2 -1
  5. package/dist/Card/index.cjs +2 -1
  6. package/dist/Card/index.mjs +2 -1
  7. package/dist/Chip/index.cjs +2 -0
  8. package/dist/Chip/index.mjs +2 -0
  9. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +2 -0
  10. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +2 -0
  11. package/dist/Chips/InternalChipDismissible/index.cjs +2 -0
  12. package/dist/Chips/InternalChipDismissible/index.mjs +2 -0
  13. package/dist/Chips/index.cjs +2 -0
  14. package/dist/Chips/index.mjs +2 -0
  15. package/dist/Combobox/index.cjs +4 -2
  16. package/dist/Combobox/index.mjs +4 -2
  17. package/dist/{ComboboxSeparator-cjs.js → ComboboxChipRemove-cjs.js} +455 -501
  18. package/dist/{ComboboxSeparator-es.js → ComboboxChipRemove-es.js} +456 -500
  19. package/dist/ComboboxPrimitive-cjs.js +30 -29
  20. package/dist/ComboboxPrimitive-es.js +3 -2
  21. package/dist/ConfirmationModal/index.cjs +2 -0
  22. package/dist/ConfirmationModal/index.mjs +2 -0
  23. package/dist/DataDump/index.cjs +2 -1
  24. package/dist/DataDump/index.mjs +2 -1
  25. package/dist/DataList/components/DataListActions/index.cjs +2 -0
  26. package/dist/DataList/components/DataListActions/index.mjs +2 -0
  27. package/dist/DataList/components/DataListBulkActions/index.cjs +2 -0
  28. package/dist/DataList/components/DataListBulkActions/index.mjs +2 -0
  29. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +2 -0
  30. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +2 -0
  31. package/dist/DataList/components/DataListFilters/index.cjs +2 -0
  32. package/dist/DataList/components/DataListFilters/index.mjs +2 -0
  33. package/dist/DataList/components/DataListHeader/index.cjs +2 -0
  34. package/dist/DataList/components/DataListHeader/index.mjs +2 -0
  35. package/dist/DataList/components/DataListItem/index.cjs +2 -0
  36. package/dist/DataList/components/DataListItem/index.mjs +2 -0
  37. package/dist/DataList/components/DataListItemActions/index.cjs +2 -0
  38. package/dist/DataList/components/DataListItemActions/index.mjs +2 -0
  39. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +2 -0
  40. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +2 -0
  41. package/dist/DataList/components/DataListItems/index.cjs +2 -0
  42. package/dist/DataList/components/DataListItems/index.mjs +2 -0
  43. package/dist/DataList/components/DataListLayout/index.cjs +2 -0
  44. package/dist/DataList/components/DataListLayout/index.mjs +2 -0
  45. package/dist/DataList/components/DataListLayoutActions/index.cjs +2 -0
  46. package/dist/DataList/components/DataListLayoutActions/index.mjs +2 -0
  47. package/dist/DataList/index.cjs +2 -0
  48. package/dist/DataList/index.mjs +2 -0
  49. package/dist/DatePicker/index.cjs +2 -0
  50. package/dist/DatePicker/index.mjs +2 -0
  51. package/dist/DatePicker-cjs.js +5 -4
  52. package/dist/DatePicker-es.js +2 -1
  53. package/dist/Dialog/index.cjs +2 -1
  54. package/dist/Dialog/index.mjs +2 -1
  55. package/dist/DrawerDescription-cjs.js +99 -268
  56. package/dist/DrawerDescription-es.js +103 -270
  57. package/dist/DrawerTitle-cjs.js +6 -2
  58. package/dist/DrawerTitle-es.js +7 -3
  59. package/dist/FieldDescription-cjs.js +181 -184
  60. package/dist/FieldDescription-es.js +184 -187
  61. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +2 -0
  62. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +2 -0
  63. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +2 -0
  64. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +2 -0
  65. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +2 -0
  66. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +2 -0
  67. package/dist/FilterPicker/index.cjs +2 -0
  68. package/dist/FilterPicker/index.mjs +2 -0
  69. package/dist/FilterPickerContent-cjs.js +5 -4
  70. package/dist/FilterPickerContent-es.js +2 -1
  71. package/dist/FormatFile/index.cjs +2 -0
  72. package/dist/FormatFile/index.mjs +2 -0
  73. package/dist/Gallery/index.cjs +2 -0
  74. package/dist/Gallery/index.mjs +2 -0
  75. package/dist/InputDate/index.cjs +2 -0
  76. package/dist/InputDate/index.mjs +2 -0
  77. package/dist/InputNumber/index.cjs +2 -1
  78. package/dist/InputNumber/index.mjs +2 -1
  79. package/dist/LightBox/index.cjs +2 -0
  80. package/dist/LightBox/index.mjs +2 -0
  81. package/dist/Menu/index.cjs +2 -1
  82. package/dist/Menu/index.mjs +2 -1
  83. package/dist/Menu-cjs.js +5 -4
  84. package/dist/Menu-es.js +2 -1
  85. package/dist/MenuSubmenuTrigger-cjs.js +164 -206
  86. package/dist/MenuSubmenuTrigger-es.js +152 -194
  87. package/dist/Modal/index.cjs +2 -0
  88. package/dist/Modal/index.mjs +3 -1
  89. package/dist/NumberFieldInput-cjs.js +308 -187
  90. package/dist/NumberFieldInput-es.js +308 -187
  91. package/dist/Page/index.cjs +2 -1
  92. package/dist/Page/index.mjs +2 -1
  93. package/dist/Popover/index.cjs +2 -0
  94. package/dist/Popover/index.mjs +2 -0
  95. package/dist/Popover-cjs.js +8 -7
  96. package/dist/Popover-es.js +2 -1
  97. package/dist/ScrollAreaViewport-cjs.js +1093 -747
  98. package/dist/ScrollAreaViewport-es.js +1081 -734
  99. package/dist/Select/index.cjs +2 -1
  100. package/dist/Select/index.mjs +2 -1
  101. package/dist/SelectGroupLabel-cjs.js +306 -207
  102. package/dist/SelectGroupLabel-es.js +302 -203
  103. package/dist/Tooltip/index.cjs +2 -0
  104. package/dist/Tooltip/index.mjs +2 -0
  105. package/dist/Tooltip-cjs.js +7 -6
  106. package/dist/Tooltip-es.js +2 -1
  107. package/dist/dialogReturnFocus-cjs.js +1 -1
  108. package/dist/dialogReturnFocus-es.js +2 -2
  109. package/dist/floating-ui.react-cjs.js +192 -2564
  110. package/dist/floating-ui.react-dom-cjs.js +2103 -0
  111. package/dist/floating-ui.react-dom-es.js +2072 -0
  112. package/dist/floating-ui.react-es.js +156 -2519
  113. package/dist/floating-ui.utils-cjs.js +342 -0
  114. package/dist/floating-ui.utils-es.js +300 -0
  115. package/dist/index.cjs +4 -3
  116. package/dist/index.mjs +4 -3
  117. package/dist/primitives/BottomSheet/index.cjs +1 -1
  118. package/dist/primitives/BottomSheet/index.mjs +1 -1
  119. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -2
  120. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -2
  121. package/dist/primitives/SelectPrimitive/index.cjs +2 -0
  122. package/dist/primitives/SelectPrimitive/index.mjs +2 -0
  123. package/dist/primitives/index.cjs +3 -2
  124. package/dist/primitives/index.mjs +3 -2
  125. package/dist/scrollEdges-cjs.js +11 -1
  126. package/dist/scrollEdges-es.js +11 -1
  127. package/dist/unstyledPrimitives/index.cjs +478 -720
  128. package/dist/unstyledPrimitives/index.mjs +421 -663
  129. package/dist/useCompositeListItem-cjs.js +361 -2108
  130. package/dist/useCompositeListItem-es.js +331 -2076
  131. package/dist/useLabel-cjs.js +3 -2
  132. package/dist/useLabel-es.js +3 -2
  133. package/dist/useRenderElement-cjs.js +7 -9
  134. package/dist/useRenderElement-es.js +7 -9
  135. package/dist/useScrollLock-cjs.js +344 -819
  136. package/dist/useScrollLock-es.js +296 -732
  137. package/dist/useScrollToActive-cjs.js +5 -4
  138. package/dist/useScrollToActive-es.js +2 -1
  139. package/package.json +3 -3
  140. package/dist/scrollable-cjs.js +0 -58
  141. package/dist/scrollable-es.js +0 -54
@@ -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');
11
10
  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: state => state.id,
108
- labelId: state => state.labelId,
109
- items: state => state.items,
110
- selectedValue: state => state.selectedValue,
111
- hasSelectionChips: state => {
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 => {
112
112
  const selectedValue = state.selectedValue;
113
113
  return Array.isArray(selectedValue) && selectedValue.length > 0;
114
- },
115
- hasSelectedValue: state => {
114
+ }),
115
+ hasSelectedValue: useScrollLock.createSelector(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: (state, enabled) => {
127
+ }),
128
+ hasNullItemLabel: useScrollLock.createSelector((state, enabled) => {
129
129
  return enabled ? useCompositeListItem.hasNullItemLabel(state.items) : false;
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) => {
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) => {
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: 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
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)
174
174
  };
175
175
 
176
176
  /**
@@ -193,7 +193,8 @@ function createCollatorItemFilter(collatorFilter, itemToStringLabel) {
193
193
  if (item == null) {
194
194
  return false;
195
195
  }
196
- return collatorFilter.contains(item, query, itemToStringLabel);
196
+ const itemString = useCompositeListItem.stringifyAsLabel(item, itemToStringLabel);
197
+ return collatorFilter.contains(itemString, query);
197
198
  };
198
199
  }
199
200
 
@@ -209,13 +210,14 @@ function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel,
209
210
  if (!query) {
210
211
  return true;
211
212
  }
213
+ const itemString = useCompositeListItem.stringifyAsLabel(item, itemToStringLabel);
212
214
  const selectedString = selectedValue != null ? useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel) : '';
213
215
 
214
216
  // Handle case-insensitive matching consistently
215
217
  if (selectedString && collatorFilter.contains(selectedString, query) && selectedString.length === query.length) {
216
218
  return true;
217
219
  }
218
- return collatorFilter.contains(item, query, itemToStringLabel);
220
+ return collatorFilter.contains(itemString, query);
219
221
  };
220
222
  }
221
223
 
@@ -287,8 +289,9 @@ function useComboboxFilter(options = {}) {
287
289
  return createSingleSelectionCollatorFilter(coreFilter, itemToString, value)(item, query);
288
290
  }, [coreFilter, value, multiple]);
289
291
  return React__namespace.useMemo(() => ({
290
- ...coreFilter,
291
- contains
292
+ contains,
293
+ startsWith: coreFilter.startsWith,
294
+ endsWith: coreFilter.endsWith
292
295
  }), [contains, coreFilter]);
293
296
  }
294
297
 
@@ -309,7 +312,7 @@ function AriaCombobox(props) {
309
312
  inputValue: inputValueProp,
310
313
  open: openProp,
311
314
  defaultOpen = false,
312
- selectionMode,
315
+ selectionMode = 'none',
313
316
  onItemHighlighted: onItemHighlightedProp,
314
317
  name: nameProp,
315
318
  form,
@@ -382,10 +385,12 @@ function AriaCombobox(props) {
382
385
  */
383
386
  const valuesRef = React__namespace.useRef([]);
384
387
  /**
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.
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.
387
392
  */
388
- const pointerDownItemRef = React__namespace.useRef(null);
393
+ const allValuesRef = React__namespace.useRef([]);
389
394
  const disabled = fieldDisabled || disabledProp;
390
395
  const name = fieldName ?? nameProp;
391
396
  const multiple = selectionMode === 'multiple';
@@ -412,10 +417,11 @@ function AriaCombobox(props) {
412
417
  if (filterProp !== undefined) {
413
418
  return filterProp;
414
419
  }
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.
420
+ if (single && !queryChangedAfterOpen) {
421
+ return createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue);
422
+ }
417
423
  return createCollatorItemFilter(collatorFilter, itemToStringLabel);
418
- }, [filterProp, collatorFilter, itemToStringLabel]);
424
+ }, [filterProp, single, selectedValue, queryChangedAfterOpen, collatorFilter, itemToStringLabel]);
419
425
 
420
426
  // If neither inputValue nor defaultInputValue are provided, derive it from the
421
427
  // selected value for single mode so the input reflects the selection on mount.
@@ -441,9 +447,9 @@ function AriaCombobox(props) {
441
447
  state: 'open'
442
448
  });
443
449
  const isGrouped = useCompositeListItem.isGroupedItems(items);
444
- const query = closeQuery ?? String(inputValue).trim();
450
+ const query = closeQuery ?? (inputValue === '' ? '' : String(inputValue).trim());
445
451
  const selectedLabelString = single ? useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel) : '';
446
- const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
452
+ const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
447
453
  const filterQuery = shouldBypassFiltering ? '' : query;
448
454
  const shouldIgnoreExternalFiltering = hasItems && hasFilteredItemsProp && shouldBypassFiltering;
449
455
  const flatItems = React__namespace.useMemo(() => {
@@ -470,18 +476,12 @@ function AriaCombobox(props) {
470
476
  if (limit > -1 && currentCount >= limit) {
471
477
  break;
472
478
  }
473
- const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
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
- }
479
+ const candidateItems = filterQuery === '' ? group.items : group.items.filter(item => filter(item, filterQuery, itemToStringLabel));
480
+ if (candidateItems.length === 0) {
481
+ continue;
484
482
  }
483
+ const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
484
+ const itemsToTake = candidateItems.slice(0, remainingLimit);
485
485
  if (itemsToTake.length > 0) {
486
486
  const newGroup = {
487
487
  ...group,
@@ -521,85 +521,76 @@ function AriaCombobox(props) {
521
521
  }
522
522
  return filteredItems;
523
523
  }, [filteredItems, isGrouped]);
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;
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;
603
594
  const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;
604
595
  const fieldStringValue = React__namespace.useMemo(() => {
605
596
  if (selectionMode === 'none') {
@@ -642,38 +633,35 @@ function AriaCombobox(props) {
642
633
  store.set('forceMounted', true);
643
634
  }
644
635
  });
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
- };
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();
661
641
  }
662
- onItemHighlighted(value, useScrollLock.createGenericEventDetails(type, undefined, {
663
- index
664
- }));
665
- });
642
+ }, [forceMount, selectedValue]);
666
643
  const setIndices = useScrollLock.useStableCallback(options => {
667
644
  store.update(options);
668
- const activeIndexOption = options.activeIndex;
669
- if (activeIndexOption === undefined) {
645
+ const type = options.type || 'none';
646
+ if (options.activeIndex === undefined) {
670
647
  return;
671
648
  }
672
- const type = options.type || useScrollLock.none;
673
- if (activeIndexOption === null) {
674
- emitHighlight(undefined, -1, type);
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
+ }
675
656
  } else {
676
- emitHighlight(valuesRef.current[activeIndexOption], activeIndexOption, type);
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
+ }));
677
665
  }
678
666
  });
679
667
  const setInputValue = useScrollLock.useStableCallback((next, eventDetails) => {
@@ -686,11 +674,6 @@ function AriaCombobox(props) {
686
674
  // If user is typing, ensure we don't auto-highlight on open due to a race
687
675
  // with the post-open effect that sets this flag.
688
676
  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
- }
694
677
  const event = eventDetails.event;
695
678
  const inputType = event.inputType;
696
679
  // Treat composition commits as typed input; autofill may omit `inputType` or
@@ -706,34 +689,10 @@ function AriaCombobox(props) {
706
689
  pendingQueryHighlightRef.current = {
707
690
  hasQuery
708
691
  };
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)) {
692
+ if (hasQuery && autoHighlightMode && store.state.activeIndex == null) {
727
693
  store.set('activeIndex', 0);
728
694
  }
729
695
  }
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
- };
737
696
  }
738
697
  setInputValueUnwrapped(next);
739
698
  });
@@ -745,7 +704,7 @@ function AriaCombobox(props) {
745
704
  // If the `Empty` component is not used, the positioner or popup should be hidden
746
705
  // with CSS. In this case, allow the Escape key to bubble to close a parent popup
747
706
  // if there are no items to show.
748
- if (eventDetails.reason === useScrollLock.escapeKey && hasItems && flatFilteredItems.length === 0 && !emptyRef.current) {
707
+ if (eventDetails.reason === 'escape-key' && hasItems && flatFilteredItems.length === 0 && !store.state.emptyRef.current) {
749
708
  eventDetails.allowPropagation();
750
709
  }
751
710
  props.onOpenChange?.(nextOpen, eventDetails);
@@ -755,10 +714,10 @@ function AriaCombobox(props) {
755
714
 
756
715
  // If reopening interrupts the close animation, handleUnmount won't run to clear the
757
716
  // frozen closeQuery and pending popup input.
758
- if (nextOpen && inputInsidePopup && !inline && closeQuery !== null) {
717
+ if (nextOpen && multiple && inputInsidePopup && !inline && closeQuery !== null) {
759
718
  setQueryChangedAfterOpen(false);
760
719
  setCloseQuery(null);
761
- if (inputValue !== '' && eventDetails.reason !== useScrollLock.inputChange) {
720
+ if (inputValue !== '') {
762
721
  setInputValue('', useScrollLock.createChangeEventDetails(useScrollLock.inputClear, eventDetails.event));
763
722
  }
764
723
  }
@@ -812,8 +771,18 @@ function AriaCombobox(props) {
812
771
  if (shouldFillInput) {
813
772
  setInputValue(useCompositeListItem.stringifyAsLabel(nextValue, itemToStringLabel), useScrollLock.createChangeEventDetails(eventDetails.reason, eventDetails.event));
814
773
  }
774
+ if (single && nextValue != null && eventDetails.reason !== useScrollLock.inputChange && queryChangedAfterOpen && !inline) {
775
+ setCloseQuery(query);
776
+ }
815
777
  });
816
- const handleSelection = useScrollLock.useStableCallback((event, itemValue) => {
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
+ }
817
786
  const targetEl = useScrollLock.getTarget(event);
818
787
  const overrideEvent = selectionEventRef.current ?? event;
819
788
  selectionEventRef.current = null;
@@ -829,8 +798,8 @@ function AriaCombobox(props) {
829
798
  }
830
799
  if (multiple) {
831
800
  const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];
832
- const isCurrentlySelected = useCompositeListItem.selectedValueIncludes(currentSelectedValue, itemValue, isItemEqualToValue);
833
- const nextValue = isCurrentlySelected ? useCompositeListItem.removeItem(currentSelectedValue, itemValue, isItemEqualToValue) : [...currentSelectedValue, itemValue];
801
+ const isCurrentlySelected = useCompositeListItem.selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
802
+ const nextValue = isCurrentlySelected ? useCompositeListItem.removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
834
803
  setSelectedValue(nextValue, eventDetails);
835
804
  if (eventDetails.isCanceled) {
836
805
  return;
@@ -853,6 +822,9 @@ function AriaCombobox(props) {
853
822
  }
854
823
  });
855
824
  const requestSubmit = useScrollLock.useStableCallback(() => {
825
+ if (!store.state.submitOnItemClick) {
826
+ return;
827
+ }
856
828
  const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
857
829
  if (formElement && typeof formElement.requestSubmit === 'function') {
858
830
  formElement.requestSubmit();
@@ -925,29 +897,25 @@ function AriaCombobox(props) {
925
897
  React__namespace.useImperativeHandle(props.actionsRef, () => ({
926
898
  unmount: handleUnmount
927
899
  }), [handleUnmount]);
928
- useScrollLock.useIsoLayoutEffect(function syncClosedState() {
929
- if (open) {
900
+ useScrollLock.useIsoLayoutEffect(function syncSelectedIndex() {
901
+ if (open || selectionMode === 'none') {
930
902
  return;
931
903
  }
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;
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
+ });
938
917
  }
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]);
918
+ }, [open, selectedValue, items, selectionMode, flatItems, multiple, isItemEqualToValue, setIndices]);
951
919
  useScrollLock.useIsoLayoutEffect(() => {
952
920
  if (items) {
953
921
  valuesRef.current = flatFilteredItems;
@@ -957,52 +925,14 @@ function AriaCombobox(props) {
957
925
  useScrollLock.useIsoLayoutEffect(() => {
958
926
  const pendingHighlight = pendingQueryHighlightRef.current;
959
927
  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;
963
928
  if (pendingHighlight.hasQuery) {
964
- if (autoHighlightMode && listIsNavigable) {
929
+ if (autoHighlightMode) {
965
930
  store.set('activeIndex', 0);
966
931
  }
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
- }
932
+ } else if (autoHighlightMode === 'always') {
933
+ store.set('activeIndex', 0);
1005
934
  }
935
+ pendingQueryHighlightRef.current = null;
1006
936
  }
1007
937
  if (!open && !inline) {
1008
938
  return;
@@ -1015,11 +945,21 @@ function AriaCombobox(props) {
1015
945
  store.set('activeIndex', 0);
1016
946
  return;
1017
947
  }
1018
- emitHighlight(undefined, -1, useScrollLock.none);
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
+ }
1019
954
  return;
1020
955
  }
1021
956
  if (storeActiveIndex >= candidateItems.length) {
1022
- emitHighlight(undefined, -1, useScrollLock.none);
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
+ }
1023
963
  store.set('activeIndex', null);
1024
964
  return;
1025
965
  }
@@ -1027,12 +967,15 @@ function AriaCombobox(props) {
1027
967
  const previouslyHighlightedItemValue = lastHighlightRef.current.value;
1028
968
  const isSameItem = previouslyHighlightedItemValue !== NO_ACTIVE_VALUE && useCompositeListItem.compareItemEquality(itemValue, previouslyHighlightedItemValue, store.state.isItemEqualToValue);
1029
969
  if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {
1030
- emitHighlight(itemValue, storeActiveIndex, useScrollLock.none);
970
+ lastHighlightRef.current = {
971
+ value: itemValue,
972
+ index: storeActiveIndex
973
+ };
974
+ store.state.onItemHighlighted(itemValue, useScrollLock.createGenericEventDetails(useScrollLock.none, undefined, {
975
+ index: storeActiveIndex
976
+ }));
1031
977
  }
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]);
978
+ }, [activeIndex, autoHighlightMode, hasFilteredItemsProp, hasItems, flatFilteredItems, inline, open, store]);
1036
979
  useScrollLock.useIsoLayoutEffect(() => {
1037
980
  if (selectionMode === 'none') {
1038
981
  setFilled(String(inputValue) !== '');
@@ -1063,12 +1006,6 @@ function AriaCombobox(props) {
1063
1006
  }
1064
1007
  setQueryChangedAfterOpen(true);
1065
1008
  });
1066
- function syncInputToSelectedLabel() {
1067
- const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1068
- if (inputValue !== nextInputValue) {
1069
- setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1070
- }
1071
- }
1072
1009
  useScrollLock.useValueChanged(selectedValue, () => {
1073
1010
  if (selectionMode === 'none') {
1074
1011
  return;
@@ -1077,7 +1014,10 @@ function AriaCombobox(props) {
1077
1014
  setDirty(isSelectedValueDirty(selectedValue));
1078
1015
  validation.change(selectedValue);
1079
1016
  if (single && !hasInputValue && !inputInsidePopup) {
1080
- syncInputToSelectedLabel();
1017
+ const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1018
+ if (inputValue !== nextInputValue) {
1019
+ setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1020
+ }
1081
1021
  }
1082
1022
  });
1083
1023
  useScrollLock.useValueChanged(inputValue, () => {
@@ -1092,7 +1032,10 @@ function AriaCombobox(props) {
1092
1032
  if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
1093
1033
  return;
1094
1034
  }
1095
- syncInputToSelectedLabel();
1035
+ const nextInputValue = useCompositeListItem.stringifyAsLabel(selectedValue, itemToStringLabel);
1036
+ if (inputValue !== nextInputValue) {
1037
+ setInputValue(nextInputValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1038
+ }
1096
1039
  });
1097
1040
  const floatingRootContext = useCompositeListItem.useFloatingRootContext({
1098
1041
  open: inline ? true : open,
@@ -1102,19 +1045,18 @@ function AriaCombobox(props) {
1102
1045
  floating: positionerElement
1103
1046
  }
1104
1047
  });
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';
1048
+ let ariaHasPopup;
1049
+ let ariaExpanded;
1050
+ if (!inline) {
1051
+ ariaHasPopup = grid ? 'grid' : 'listbox';
1052
+ ariaExpanded = open ? 'true' : 'false';
1053
+ }
1110
1054
  const role = React__namespace.useMemo(() => {
1111
1055
  const isPlainInput = inputElement?.tagName === 'INPUT';
1112
1056
  // During SSR and initial hydration, the input ref is not available yet.
1113
1057
  // Assume an input-like control so combobox ARIA attributes are present.
1114
1058
  const shouldTreatAsInput = inputElement == null || isPlainInput;
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;
1059
+ const shouldApplyAria = shouldTreatAsInput || open;
1118
1060
  const reference = shouldTreatAsInput ? {
1119
1061
  autoComplete: 'off',
1120
1062
  spellCheck: 'false',
@@ -1125,7 +1067,7 @@ function AriaCombobox(props) {
1125
1067
  reference.role = 'combobox';
1126
1068
  reference['aria-expanded'] = ariaExpanded;
1127
1069
  reference['aria-haspopup'] = ariaHasPopup;
1128
- reference['aria-controls'] = expanded ? listElement?.id : undefined;
1070
+ reference['aria-controls'] = open ? listElement?.id : undefined;
1129
1071
  reference['aria-autocomplete'] = autoComplete;
1130
1072
  }
1131
1073
  return {
@@ -1134,7 +1076,7 @@ function AriaCombobox(props) {
1134
1076
  role: 'presentation'
1135
1077
  }
1136
1078
  };
1137
- }, [inputElement, expanded, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
1079
+ }, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
1138
1080
  const click = useScrollLock.useClick(floatingRootContext, {
1139
1081
  enabled: !readOnly && !disabled && openOnInputClick,
1140
1082
  event: 'mousedown-only',
@@ -1188,7 +1130,7 @@ function AriaCombobox(props) {
1188
1130
  } else {
1189
1131
  setIndices({
1190
1132
  activeIndex: nextActiveIndex,
1191
- type: keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer
1133
+ type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
1192
1134
  });
1193
1135
  }
1194
1136
  }
@@ -1203,8 +1145,7 @@ function AriaCombobox(props) {
1203
1145
  }
1204
1146
  }
1205
1147
  }, dismiss.reference, click.reference, role.reference), [listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store]);
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]);
1148
+ const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, listNavigation.floating, dismiss.floating, role.floating), [listNavigation.floating, dismiss.floating, role.floating]);
1208
1149
  const itemProps = React__namespace.useMemo(() => {
1209
1150
  const listNavigationItemProps = listNavigation.item;
1210
1151
  if (!listNavigationItemProps) {
@@ -1218,11 +1159,10 @@ function AriaCombobox(props) {
1218
1159
  onFocus: undefined
1219
1160
  };
1220
1161
  }, [listNavigation.item]);
1221
- useCompositeListItem.useOnFirstRender(() => {
1162
+ useScrollLock.useOnFirstRender(() => {
1222
1163
  store.update({
1223
1164
  inline: inlineProp,
1224
1165
  popupProps,
1225
- listProps,
1226
1166
  inputProps,
1227
1167
  triggerProps,
1228
1168
  itemProps,
@@ -1230,10 +1170,10 @@ function AriaCombobox(props) {
1230
1170
  setInputValue,
1231
1171
  setSelectedValue,
1232
1172
  setIndices,
1173
+ onItemHighlighted,
1233
1174
  handleSelection,
1234
1175
  forceMount,
1235
- requestSubmit,
1236
- onOpenChangeComplete
1176
+ requestSubmit
1237
1177
  });
1238
1178
  });
1239
1179
  useScrollLock.useIsoLayoutEffect(() => {
@@ -1246,7 +1186,6 @@ function AriaCombobox(props) {
1246
1186
  items,
1247
1187
  inline: inlineProp,
1248
1188
  popupProps,
1249
- listProps,
1250
1189
  inputProps,
1251
1190
  triggerProps,
1252
1191
  openMethod,
@@ -1258,7 +1197,9 @@ function AriaCombobox(props) {
1258
1197
  readOnly,
1259
1198
  required,
1260
1199
  grid,
1200
+ isGrouped,
1261
1201
  virtualized,
1202
+ onOpenChangeComplete,
1262
1203
  openOnInputClick,
1263
1204
  itemToStringLabel,
1264
1205
  modal,
@@ -1266,9 +1207,10 @@ function AriaCombobox(props) {
1266
1207
  isItemEqualToValue,
1267
1208
  submitOnItemClick,
1268
1209
  hasInputValue,
1210
+ requestSubmit,
1269
1211
  inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup)
1270
1212
  });
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]);
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]);
1272
1214
  const hiddenInputRef = useRenderElement.useMergedRefs(inputRefProp, validation.inputRef);
1273
1215
  const itemsContextValue = React__namespace.useMemo(() => ({
1274
1216
  query,
@@ -1312,7 +1254,7 @@ function AriaCombobox(props) {
1312
1254
  },
1313
1255
  // Handle browser autofill.
1314
1256
  onChange(event) {
1315
- // Workaround for https://github.com/react/react/issues/9023
1257
+ // Workaround for https://github.com/facebook/react/issues/9023
1316
1258
  if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
1317
1259
  return;
1318
1260
  }
@@ -1407,51 +1349,7 @@ const triggerStateAttributesMapping = {
1407
1349
  } : null
1408
1350
  };
1409
1351
 
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
- }
1352
+ const BOUNDARY_OFFSET = 2;
1455
1353
 
1456
1354
  /**
1457
1355
  * A button that opens the popup.
@@ -1481,14 +1379,20 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1481
1379
  labelId: fieldLabelId
1482
1380
  } = useCompositeListItem.useLabelableContext();
1483
1381
  const store = useComboboxRootContext();
1382
+ const {
1383
+ filteredItems
1384
+ } = useComboboxDerivedItemsContext();
1484
1385
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
1485
1386
  const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
1486
1387
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
1487
1388
  const required = useScrollLock.useStore(store, selectors.required);
1389
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
1390
+ const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1488
1391
  const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1489
1392
  const listElement = useScrollLock.useStore(store, selectors.listElement);
1490
1393
  const storedPopupId = useScrollLock.useStore(store, selectors.popupId);
1491
1394
  const triggerProps = useScrollLock.useStore(store, selectors.triggerProps);
1395
+ const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
1492
1396
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
1493
1397
  const rootId = useScrollLock.useStore(store, selectors.id);
1494
1398
  const comboboxLabelId = useScrollLock.useStore(store, selectors.labelId);
@@ -1501,8 +1405,8 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1501
1405
  const inputValue = useComboboxInputValueContext();
1502
1406
  const focusTimeout = useScrollLock.useTimeout();
1503
1407
  const disabled = fieldDisabled || comboboxDisabled || disabledProp;
1504
- const listEmpty = useListEmpty();
1505
- const popupSide = usePopupSide(store);
1408
+ const listEmpty = filteredItems.length === 0;
1409
+ const popupSide = mounted && positionerElement ? popupSideValue : null;
1506
1410
  useCompositeListItem.useLabelableId({
1507
1411
  id: inputInsidePopup ? idProp : undefined
1508
1412
  });
@@ -1520,6 +1424,18 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1520
1424
  function trackPointerType(event) {
1521
1425
  currentPointerTypeRef.current = event.pointerType;
1522
1426
  }
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]);
1523
1439
  const {
1524
1440
  reference: triggerTypeaheadProps
1525
1441
  } = useCompositeListItem.useTypeahead(floatingRootContext, {
@@ -1530,7 +1446,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1530
1446
  onMatch(index) {
1531
1447
  const nextSelectedValue = store.state.valuesRef.current[index];
1532
1448
  if (nextSelectedValue !== undefined) {
1533
- store.state.setSelectedValue(nextSelectedValue, useScrollLock.createChangeEventDetails(useScrollLock.none));
1449
+ store.state.setSelectedValue(nextSelectedValue, useScrollLock.createChangeEventDetails('none'));
1534
1450
  }
1535
1451
  }
1536
1452
  });
@@ -1565,7 +1481,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1565
1481
  id,
1566
1482
  tabIndex: inputInsidePopup ? 0 : -1,
1567
1483
  role: inputInsidePopup ? 'combobox' : undefined,
1568
- 'aria-expanded': open,
1484
+ 'aria-expanded': open ? 'true' : 'false',
1569
1485
  'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
1570
1486
  'aria-controls': ariaControls,
1571
1487
  'aria-required': inputInsidePopup ? required || undefined : undefined,
@@ -1612,20 +1528,22 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1612
1528
  }
1613
1529
  const doc = useScrollLock.ownerDocument(event.currentTarget);
1614
1530
  function handleMouseUp(mouseEvent) {
1615
- const currentTriggerElement = store.state.triggerElement;
1616
- if (!currentTriggerElement) {
1531
+ if (!triggerElement) {
1617
1532
  return;
1618
1533
  }
1619
1534
  const mouseUpTarget = useScrollLock.getTarget(mouseEvent);
1620
1535
  const positioner = store.state.positionerElement;
1621
1536
  const list = store.state.listElement;
1622
- if (useScrollLock.contains(currentTriggerElement, mouseUpTarget) || useScrollLock.contains(positioner, mouseUpTarget) || useScrollLock.contains(list, mouseUpTarget)) {
1537
+ if (useScrollLock.contains(triggerElement, mouseUpTarget) || useScrollLock.contains(positioner, mouseUpTarget) || useScrollLock.contains(list, mouseUpTarget) || mouseUpTarget === triggerElement) {
1623
1538
  return;
1624
1539
  }
1625
- if (useCompositeListItem.isMouseWithinBounds(mouseEvent, currentTriggerElement)) {
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) {
1626
1544
  return;
1627
1545
  }
1628
- store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.cancelOpen, mouseEvent));
1546
+ store.state.setOpen(false, useScrollLock.createChangeEventDetails('cancel-open', mouseEvent));
1629
1547
  }
1630
1548
  if (inputInsidePopup) {
1631
1549
  doc.addEventListener('mouseup', handleMouseUp, {
@@ -1634,7 +1552,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1634
1552
  }
1635
1553
  },
1636
1554
  onKeyDown(event) {
1637
- if (readOnly) {
1555
+ if (disabled || readOnly) {
1638
1556
  return;
1639
1557
  }
1640
1558
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
@@ -1643,7 +1561,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1643
1561
  store.state.inputRef.current?.focus();
1644
1562
  }
1645
1563
  }
1646
- }, validation.getValidationProps(disabled, elementProps), getButtonProps],
1564
+ }, validation ? validation.getValidationProps(disabled, elementProps) : elementProps, getButtonProps],
1647
1565
  stateAttributesMapping: triggerStateAttributesMapping
1648
1566
  });
1649
1567
  return element;
@@ -1718,6 +1636,9 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1718
1636
  const positioning = useComboboxPositionerContext(true);
1719
1637
  const hasPositionerParent = Boolean(positioning);
1720
1638
  const store = useComboboxRootContext();
1639
+ const {
1640
+ filteredItems
1641
+ } = useComboboxDerivedItemsContext();
1721
1642
  // `inputValue` can't be placed in the store.
1722
1643
  // https://github.com/mui/base-ui/issues/2703
1723
1644
  const inputValue = useComboboxInputValueContext();
@@ -1734,16 +1655,19 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1734
1655
  const open = useScrollLock.useStore(store, selectors.open);
1735
1656
  const mounted = useScrollLock.useStore(store, selectors.mounted);
1736
1657
  const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
1658
+ const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1659
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1737
1660
  const rootId = useScrollLock.useStore(store, selectors.id);
1738
1661
  const inline = useScrollLock.useStore(store, selectors.inline);
1739
1662
  const modal = useScrollLock.useStore(store, selectors.modal);
1740
1663
  const autoHighlightEnabled = Boolean(autoHighlightMode);
1741
- const popupSide = usePopupSide(store);
1664
+ const popupSide = mounted && positionerElement ? popupSideValue : null;
1742
1665
  const disabled = fieldDisabled || comboboxDisabled || disabledProp;
1743
- const listEmpty = useListEmpty();
1666
+ const listEmpty = filteredItems.length === 0;
1744
1667
  const isInsidePopup = hasPositionerParent || inline;
1745
1668
  const focusManagerModal = !isInsidePopup || modal;
1746
1669
  const id = useScrollLock.useBaseUiId(idProp ?? (!isInsidePopup ? rootId : undefined));
1670
+ const ariaLabelledBy = useCompositeListItem.resolveAriaLabelledBy(fieldLabelId, undefined);
1747
1671
  const fieldStateForInput = hasPositionerParent ? useCompositeListItem.DEFAULT_FIELD_STATE_ATTRIBUTES : fieldState;
1748
1672
  const [composingValue, setComposingValue] = React__namespace.useState(null);
1749
1673
  const isComposingRef = React__namespace.useRef(false);
@@ -1761,17 +1685,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1761
1685
  inputOwnsFormValue
1762
1686
  });
1763
1687
  });
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
- }
1688
+ const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(disabled, elementProps);
1775
1689
  const state = {
1776
1690
  ...fieldStateForInput,
1777
1691
  open,
@@ -1789,7 +1703,9 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1789
1703
  highlightedChipIndex
1790
1704
  } = comboboxChipsContext;
1791
1705
  const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
1792
- const [previousChipKey, nextChipKey] = getChipNavigationKeys(direction);
1706
+ const isRtl = direction === 'rtl';
1707
+ const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
1708
+ const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
1793
1709
  if (highlightedChipIndex !== undefined) {
1794
1710
  if (event.key === previousChipKey) {
1795
1711
  event.preventDefault();
@@ -1808,8 +1724,14 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1808
1724
  } else if (event.key === 'Backspace' || event.key === 'Delete') {
1809
1725
  event.preventDefault();
1810
1726
  // Move highlight appropriately after removal.
1811
- nextIndex = getIndexAfterChipRemoval(highlightedChipIndex, selectedValue.length);
1812
- clearHighlight();
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
+ });
1813
1735
  }
1814
1736
  return nextIndex;
1815
1737
  }
@@ -1818,6 +1740,13 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1818
1740
  if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
1819
1741
  event.preventDefault();
1820
1742
  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();
1821
1750
  }
1822
1751
  return nextIndex;
1823
1752
  }
@@ -1825,10 +1754,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1825
1754
  state,
1826
1755
  ref: [forwardedRef, store.state.inputRef, setInputElement],
1827
1756
  props: [inputProps, triggerProps, {
1828
- value: composingValue ?? inputValue,
1757
+ type: 'text',
1758
+ value: componentProps.value ?? composingValue ?? inputValue,
1829
1759
  'aria-readonly': readOnly || undefined,
1830
1760
  'aria-required': required || undefined,
1831
- 'aria-labelledby': fieldLabelId,
1761
+ 'aria-labelledby': ariaLabelledBy,
1832
1762
  disabled,
1833
1763
  readOnly,
1834
1764
  required: selectionMode === 'none' ? required : undefined,
@@ -1883,23 +1813,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1883
1813
  store.state.setInputValue(next, useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent));
1884
1814
  },
1885
1815
  onChange(event) {
1886
- const nativeEvent = event.nativeEvent;
1887
1816
  // Autofill may not provide `inputType` (Chrome) or may report
1888
1817
  // `insertReplacementText` (Firefox).
1889
- const inputType = nativeEvent.inputType;
1818
+ const inputType = event.nativeEvent.inputType;
1890
1819
  const autofillLikeInput = !inputType || inputType === 'insertReplacementText';
1891
- // During composition the input is always considered typed into.
1892
1820
  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
- }
1903
1821
 
1904
1822
  // During IME composition, avoid propagating controlled updates to prevent
1905
1823
  // filtering the options prematurely so `Empty` won't show incorrectly.
@@ -1911,23 +1829,38 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1911
1829
  const nextVal = event.currentTarget.value;
1912
1830
  setComposingValue(nextVal);
1913
1831
  if (nextVal === '' && !store.state.openOnInputClick && !store.state.inputInsidePopup) {
1914
- store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.inputClear, nativeEvent));
1832
+ store.state.setOpen(false, useScrollLock.createChangeEventDetails(useScrollLock.inputClear, event.nativeEvent));
1915
1833
  }
1916
1834
  const trimmed = nextVal.trim();
1917
1835
  const shouldMaintainHighlight = autoHighlightEnabled && trimmed !== '';
1918
- maybeOpenOnInput(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
+ }
1919
1848
  if (open && store.state.activeIndex !== null && !shouldMaintainHighlight) {
1920
- clearHighlight();
1849
+ store.state.setIndices({
1850
+ activeIndex: null,
1851
+ selectedIndex: null,
1852
+ type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1853
+ });
1921
1854
  }
1922
1855
  return;
1923
1856
  }
1924
- const inputChangeDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputChange, nativeEvent);
1857
+ const inputChangeDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputChange, event.nativeEvent);
1925
1858
  store.state.setInputValue(event.currentTarget.value, inputChangeDetails);
1926
1859
  if (inputChangeDetails.isCanceled) {
1927
1860
  return;
1928
1861
  }
1929
1862
  const empty = event.currentTarget.value === '';
1930
- const clearDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputClear, nativeEvent);
1863
+ const clearDetails = useScrollLock.createChangeEventDetails(useScrollLock.inputClear, event.nativeEvent);
1931
1864
  if (empty && !store.state.inputInsidePopup) {
1932
1865
  if (selectionMode === 'single') {
1933
1866
  store.state.setSelectedValue(null, clearDetails);
@@ -1936,13 +1869,30 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1936
1869
  store.state.setOpen(false, clearDetails);
1937
1870
  }
1938
1871
  }
1939
- maybeOpenOnInput(event.currentTarget.value.trim());
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
+ }
1940
1886
 
1941
1887
  // When the user types, ensure the list resets its highlight so that
1942
1888
  // virtual focus returns to the input (aria-activedescendant is
1943
1889
  // cleared).
1944
1890
  if (open && store.state.activeIndex !== null && !autoHighlightEnabled) {
1945
- clearHighlight();
1891
+ store.state.setIndices({
1892
+ activeIndex: null,
1893
+ selectedIndex: null,
1894
+ type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1895
+ });
1946
1896
  }
1947
1897
  },
1948
1898
  onKeyDown(event) {
@@ -1988,7 +1938,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1988
1938
  const removalIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : selectedValue.length - 1;
1989
1939
  const newValue = selectedValue.filter((_, index) => index !== removalIndex);
1990
1940
  // If the removed item was also the active (highlighted) item, clear highlight
1991
- clearHighlight();
1941
+ store.state.setIndices({
1942
+ activeIndex: null,
1943
+ selectedIndex: null,
1944
+ type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
1945
+ });
1992
1946
  store.state.setSelectedValue(newValue, useScrollLock.createChangeEventDetails(useScrollLock.none, event.nativeEvent));
1993
1947
  return;
1994
1948
  }
@@ -2018,11 +1972,20 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2018
1972
  return;
2019
1973
  }
2020
1974
  useScrollLock.stopEvent(event);
2021
- clickHighlightedItem(store, activeIndex, nativeEvent);
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
+ }
2022
1981
  }
2023
1982
  },
2024
- onPointerMove: markPointerActive,
2025
- onPointerDown: markPointerActive
1983
+ onPointerMove() {
1984
+ store.state.keyboardActiveRef.current = false;
1985
+ },
1986
+ onPointerDown() {
1987
+ store.state.keyboardActiveRef.current = false;
1988
+ }
2026
1989
  }, validationProps],
2027
1990
  stateAttributesMapping: triggerStateAttributesMapping
2028
1991
  });
@@ -2043,7 +2006,7 @@ function handleInputPress(event, store, disabled, readOnly, shouldIgnoreTarget)
2043
2006
  return;
2044
2007
  }
2045
2008
  const target = useScrollLock.getTarget(event.nativeEvent);
2046
- const targetElement = useScrollLock.isElement(target) ? target : null;
2009
+ const targetElement = floatingUi_utils.isElement(target) ? target : null;
2047
2010
  if (targetElement !== event.currentTarget && (shouldIgnoreTarget?.(targetElement) || useScrollLock.isInteractiveElement(targetElement))) {
2048
2011
  return;
2049
2012
  }
@@ -2074,14 +2037,20 @@ const ComboboxInputGroup = /*#__PURE__*/React__namespace.forwardRef(function Com
2074
2037
  state: fieldState
2075
2038
  } = useCompositeListItem.useFieldRootContext();
2076
2039
  const store = useComboboxRootContext();
2040
+ const {
2041
+ filteredItems
2042
+ } = useComboboxDerivedItemsContext();
2077
2043
  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);
2078
2047
  const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
2079
2048
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2080
2049
  const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
2081
2050
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2082
- const popupSide = usePopupSide(store);
2051
+ const popupSide = mounted && positionerElement ? popupSideValue : null;
2083
2052
  const disabled = comboboxDisabled;
2084
- const listEmpty = useListEmpty();
2053
+ const listEmpty = filteredItems.length === 0;
2085
2054
  const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
2086
2055
  const state = {
2087
2056
  ...fieldState,
@@ -2217,21 +2186,19 @@ const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2217
2186
  if (disabled || readOnly) {
2218
2187
  return;
2219
2188
  }
2220
- const type = store.state.keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer;
2189
+ const keyboardActiveRef = store.state.keyboardActiveRef;
2221
2190
  store.state.setInputValue('', useScrollLock.createChangeEventDetails(useScrollLock.clearPress, event.nativeEvent));
2222
2191
  if (selectionMode !== 'none') {
2223
2192
  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.
2226
2193
  store.state.setIndices({
2227
2194
  activeIndex: null,
2228
2195
  selectedIndex: null,
2229
- type
2196
+ type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
2230
2197
  });
2231
2198
  } else {
2232
2199
  store.state.setIndices({
2233
2200
  activeIndex: null,
2234
- type
2201
+ type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
2235
2202
  });
2236
2203
  }
2237
2204
  store.state.inputRef.current?.focus();
@@ -2275,6 +2242,9 @@ function ComboboxCollection(props) {
2275
2242
  } = useComboboxDerivedItemsContext();
2276
2243
  const groupContext = useGroupCollectionContext();
2277
2244
  const itemsToRender = groupContext ? groupContext.items : filteredItems;
2245
+ if (!itemsToRender) {
2246
+ return null;
2247
+ }
2278
2248
  return /*#__PURE__*/jsxRuntime.jsx(React__namespace.Fragment, {
2279
2249
  children: itemsToRender.map(children)
2280
2250
  });
@@ -2298,7 +2268,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2298
2268
  } = useComboboxDerivedItemsContext();
2299
2269
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2300
2270
  const grid = useScrollLock.useStore(store, selectors.grid);
2301
- const listProps = useScrollLock.useStore(store, selectors.listProps);
2271
+ const popupProps = useScrollLock.useStore(store, selectors.popupProps);
2302
2272
  const virtualized = useScrollLock.useStore(store, selectors.virtualized);
2303
2273
  const forceMounted = useScrollLock.useStore(store, selectors.forceMounted);
2304
2274
  const multiple = selectionMode === 'multiple';
@@ -2312,7 +2282,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2312
2282
 
2313
2283
  // Support "closed template" API: if children is a function, implicitly wrap it
2314
2284
  // with a Combobox.Collection that reads items from context/root.
2315
- // Ensures this component's `listProps` subscription does not cause <Combobox.Item>
2285
+ // Ensures this component's `popupProps` subscription does not cause <Combobox.Item>
2316
2286
  // to re-render on every active index change.
2317
2287
  const resolvedChildren = React__namespace.useMemo(() => {
2318
2288
  if (typeof children === 'function') {
@@ -2329,7 +2299,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2329
2299
  const element = useRenderElement.useRenderElement('div', componentProps, {
2330
2300
  state,
2331
2301
  ref: [forwardedRef, setListElement, hasPositionerContext ? null : setPositionerElement],
2332
- props: [listProps, {
2302
+ props: [popupProps, {
2333
2303
  children: resolvedChildren,
2334
2304
  tabIndex: -1,
2335
2305
  id: floatingId,
@@ -2346,7 +2316,13 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2346
2316
  return;
2347
2317
  }
2348
2318
  useScrollLock.stopEvent(event);
2349
- clickHighlightedItem(store, activeIndex, event.nativeEvent);
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
+ }
2350
2326
  }
2351
2327
  },
2352
2328
  onKeyDownCapture() {
@@ -2405,7 +2381,7 @@ function useInitialLiveRegionTextMutation() {
2405
2381
  if (textNode == null) {
2406
2382
  return undefined;
2407
2383
  }
2408
- const originalValue = textNode.data;
2384
+ const originalValue = textNode.nodeValue ?? '';
2409
2385
  const markedValue = `${originalValue}${LIVE_REGION_MARKER}`;
2410
2386
  textNode.nodeValue = markedValue;
2411
2387
  timeout.start(INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY, () => {
@@ -2534,23 +2510,24 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2534
2510
  render,
2535
2511
  className,
2536
2512
  anchor,
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,
2513
+ positionMethod = 'absolute',
2514
+ side = 'bottom',
2515
+ align = 'center',
2516
+ sideOffset = 0,
2517
+ alignOffset = 0,
2544
2518
  collisionBoundary = 'clipping-ancestors',
2545
- collisionPadding,
2546
- arrowPadding,
2547
- sticky,
2519
+ collisionPadding = 5,
2520
+ arrowPadding = 5,
2521
+ sticky = false,
2548
2522
  disableAnchorTracking = false,
2549
2523
  collisionAvoidance = useScrollLock.DROPDOWN_COLLISION_AVOIDANCE,
2550
2524
  style: styleProp,
2551
2525
  ...elementProps
2552
2526
  } = componentProps;
2553
2527
  const store = useComboboxRootContext();
2528
+ const {
2529
+ filteredItems
2530
+ } = useComboboxDerivedItemsContext();
2554
2531
  const floatingRootContext = useComboboxFloatingContext();
2555
2532
  const keepMounted = useComboboxPortalContext();
2556
2533
  const modal = useScrollLock.useStore(store, selectors.modal);
@@ -2563,7 +2540,7 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2563
2540
  const inputGroupElement = useScrollLock.useStore(store, selectors.inputGroupElement);
2564
2541
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2565
2542
  const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2566
- const empty = useListEmpty();
2543
+ const empty = filteredItems.length === 0;
2567
2544
  const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputGroupElement ?? inputElement);
2568
2545
  const positioning = useCompositeListItem.useAnchorPositioning({
2569
2546
  anchor: resolvedAnchor,
@@ -2638,16 +2615,18 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2638
2615
  const store = useComboboxRootContext();
2639
2616
  const positioning = useComboboxPositionerContext();
2640
2617
  const floatingRootContext = useComboboxFloatingContext();
2618
+ const {
2619
+ filteredItems
2620
+ } = useComboboxDerivedItemsContext();
2641
2621
  const mounted = useScrollLock.useStore(store, selectors.mounted);
2642
2622
  const open = useScrollLock.useStore(store, selectors.open);
2643
2623
  const openMethod = useScrollLock.useStore(store, selectors.openMethod);
2644
- const popupProps = useScrollLock.useStore(store, selectors.popupProps);
2645
2624
  const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2646
2625
  const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2647
2626
  const inputElement = useScrollLock.useStore(store, selectors.inputElement);
2648
2627
  const modal = useScrollLock.useStore(store, selectors.modal);
2649
2628
  const rootId = useScrollLock.useStore(store, selectors.id);
2650
- const empty = useListEmpty();
2629
+ const empty = filteredItems.length === 0;
2651
2630
  const popupId = elementProps.id ?? (inputInsidePopup ? getComboboxPopupId(rootId) : undefined);
2652
2631
  useScrollLock.useIsoLayoutEffect(() => {
2653
2632
  // Prefer the rendered DOM id, which a `render` prop element or function may override.
@@ -2676,9 +2655,10 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2676
2655
  const element = useRenderElement.useRenderElement('div', componentProps, {
2677
2656
  state,
2678
2657
  ref: [forwardedRef, store.state.popupRef],
2679
- props: [popupProps, {
2658
+ props: [{
2680
2659
  id: popupId,
2681
2660
  role: inputInsidePopup ? 'dialog' : 'presentation',
2661
+ tabIndex: -1,
2682
2662
  onFocus(event) {
2683
2663
  const target = useScrollLock.getTarget(event.nativeEvent);
2684
2664
  if (openMethod !== 'touch' && (useScrollLock.contains(store.state.listElement, target) || target === event.currentTarget)) {
@@ -2825,7 +2805,7 @@ const ComboboxGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function Com
2825
2805
  useScrollLock.useIsoLayoutEffect(() => {
2826
2806
  setLabelId(id);
2827
2807
  return () => {
2828
- setLabelId(currentId => currentId === id ? undefined : currentId);
2808
+ setLabelId(undefined);
2829
2809
  };
2830
2810
  }, [id, setLabelId]);
2831
2811
  const element = useRenderElement.useRenderElement('div', componentProps, {
@@ -2867,27 +2847,27 @@ function ComboboxItemInner(props) {
2867
2847
  style,
2868
2848
  value: itemValue = null,
2869
2849
  index: indexProp,
2870
- disabled: disabledProp = false,
2850
+ disabled = false,
2871
2851
  nativeButton = false,
2872
2852
  ...elementProps
2873
2853
  } = componentProps;
2854
+ const didPointerDownRef = React__namespace.useRef(false);
2874
2855
  const textRef = React__namespace.useRef(null);
2875
2856
  const listItem = useCompositeListItem.useCompositeListItem({
2876
- guess: true,
2877
2857
  index: indexProp,
2878
- textRef
2858
+ textRef,
2859
+ indexGuessBehavior: useCompositeListItem.IndexGuessBehavior.GuessFromOrder
2879
2860
  });
2880
2861
  const store = useComboboxRootContext();
2881
2862
  const isRow = useComboboxRowContext();
2882
2863
  const hasItems = useComboboxHasItemsContext();
2864
+ const open = useScrollLock.useStore(store, selectors.open);
2883
2865
  const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2884
- const rootDisabled = useScrollLock.useStore(store, selectors.disabled);
2885
2866
  const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2886
2867
  const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
2887
- const disabled = rootDisabled || disabledProp;
2888
2868
  const selectable = selectionMode !== 'none';
2889
- const index = indexProp ?? indexFromFilter ?? listItem.index;
2890
- const hasRegistered = index !== -1;
2869
+ const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
2870
+ const hasRegistered = listItem.index !== -1;
2891
2871
  const rootId = useScrollLock.useStore(store, selectors.id);
2892
2872
  const highlighted = useScrollLock.useStore(store, selectors.isActive, index);
2893
2873
  const matchesSelectedValue = useScrollLock.useStore(store, selectors.isSelected, itemValue);
@@ -2912,24 +2892,31 @@ function ComboboxItemInner(props) {
2912
2892
  }
2913
2893
  const visibleMap = store.state.valuesRef.current;
2914
2894
  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
+ }
2915
2902
  return () => {
2916
2903
  delete visibleMap[index];
2917
2904
  };
2918
- }, [hasRegistered, hasItems, index, itemValue, store]);
2905
+ }, [hasRegistered, hasItems, index, itemValue, store, selectionMode]);
2919
2906
  useScrollLock.useIsoLayoutEffect(() => {
2907
+ if (!open) {
2908
+ didPointerDownRef.current = false;
2909
+ return;
2910
+ }
2920
2911
  if (!hasRegistered || hasItems) {
2921
2912
  return;
2922
2913
  }
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.
2927
2914
  const selectedValue = store.state.selectedValue;
2928
2915
  const lastSelectedValue = Array.isArray(selectedValue) ? selectedValue[selectedValue.length - 1] : selectedValue;
2929
2916
  if (useCompositeListItem.compareItemEquality(itemValue, lastSelectedValue, isItemEqualToValue)) {
2930
2917
  store.set('selectedIndex', index);
2931
2918
  }
2932
- }, [hasRegistered, hasItems, store, index, itemValue, isItemEqualToValue]);
2919
+ }, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
2933
2920
  const {
2934
2921
  getButtonProps,
2935
2922
  buttonRef
@@ -2964,12 +2951,7 @@ function ComboboxItemInner(props) {
2964
2951
  // as it should be a `<div>` instead.
2965
2952
  tabIndex: undefined,
2966
2953
  onPointerDownCapture(event) {
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
- }
2954
+ didPointerDownRef.current = true;
2973
2955
  event.preventDefault();
2974
2956
  },
2975
2957
  onMouseDown(event) {
@@ -2984,8 +2966,8 @@ function ComboboxItemInner(props) {
2984
2966
  commitSelection(event.nativeEvent);
2985
2967
  },
2986
2968
  onMouseUp(event) {
2987
- const pointerStartedOnItem = store.state.pointerDownItemRef.current === event.currentTarget;
2988
- store.state.pointerDownItemRef.current = null;
2969
+ const pointerStartedOnItem = didPointerDownRef.current;
2970
+ didPointerDownRef.current = false;
2989
2971
  if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) {
2990
2972
  return;
2991
2973
  }
@@ -3121,33 +3103,6 @@ const ComboboxEmpty = /*#__PURE__*/React__namespace.forwardRef(function Combobox
3121
3103
  });
3122
3104
  if (process.env.NODE_ENV !== "production") ComboboxEmpty.displayName = "ComboboxEmpty";
3123
3105
 
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
-
3151
3106
  /**
3152
3107
  * Returns the internally filtered items.
3153
3108
  */
@@ -3214,8 +3169,7 @@ const ComboboxLabel = /*#__PURE__*/React__namespace.forwardRef(function Combobox
3214
3169
  id: defaultLabelId,
3215
3170
  fallbackControlId: localControlId,
3216
3171
  setLabelId(nextLabelId) {
3217
- const resolvedLabelId = typeof nextLabelId === 'function' ? nextLabelId(store.state.labelId) : nextLabelId;
3218
- store.set('labelId', resolvedLabelId);
3172
+ store.set('labelId', nextLabelId);
3219
3173
  }
3220
3174
  });
3221
3175
  return useRenderElement.useRenderElement('div', componentProps, {
@@ -3258,10 +3212,11 @@ function ComboboxValue(props) {
3258
3212
  }
3259
3213
 
3260
3214
  const ComboboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function ComboboxItemIndicator(componentProps, forwardedRef) {
3215
+ const keepMounted = componentProps.keepMounted ?? false;
3261
3216
  const {
3262
3217
  selected
3263
3218
  } = useComboboxItemContext();
3264
- const shouldRender = componentProps.keepMounted || selected;
3219
+ const shouldRender = keepMounted || selected;
3265
3220
  if (!shouldRender) {
3266
3221
  return null;
3267
3222
  }
@@ -3391,7 +3346,9 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3391
3346
  } = useCompositeListItem.useCompositeListItem();
3392
3347
  function handleKeyDown(event) {
3393
3348
  let nextIndex = index;
3394
- const [previousChipKey, nextChipKey] = getChipNavigationKeys(direction);
3349
+ const isRtl = direction === 'rtl';
3350
+ const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
3351
+ const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
3395
3352
  if (event.key === previousChipKey) {
3396
3353
  event.preventDefault();
3397
3354
  if (index > 0) {
@@ -3407,12 +3364,13 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3407
3364
  nextIndex = undefined;
3408
3365
  }
3409
3366
  } else if (event.key === 'Backspace' || event.key === 'Delete') {
3410
- nextIndex = getIndexAfterChipRemoval(index, selectedValue.length);
3367
+ const computedNextIndex = index >= selectedValue.length - 1 ? selectedValue.length - 2 : index;
3368
+ nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
3411
3369
  useScrollLock.stopEvent(event);
3412
3370
  store.state.setIndices({
3413
3371
  activeIndex: null,
3414
3372
  selectedIndex: null,
3415
- type: useScrollLock.keyboard
3373
+ type: 'keyboard'
3416
3374
  });
3417
3375
  store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), useScrollLock.createChangeEventDetails(useScrollLock.none, event.nativeEvent));
3418
3376
  } else if (event.key === 'Enter' || event.key === ' ') {
@@ -3512,7 +3470,7 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3512
3470
  if (removedIndex !== -1 && activeIndex === removedIndex) {
3513
3471
  store.state.setIndices({
3514
3472
  activeIndex: null,
3515
- type: store.state.keyboardActiveRef.current ? useScrollLock.keyboard : useScrollLock.pointer
3473
+ type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
3516
3474
  });
3517
3475
  }
3518
3476
  }
@@ -3533,12 +3491,18 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3533
3491
  event.preventDefault();
3534
3492
  },
3535
3493
  onClick(event) {
3494
+ if (disabled || readOnly) {
3495
+ return;
3496
+ }
3536
3497
  const eventDetails = removeChip(event);
3537
3498
  if (!eventDetails.isPropagationAllowed) {
3538
3499
  event.stopPropagation();
3539
3500
  }
3540
3501
  },
3541
3502
  onKeyDown(event) {
3503
+ if (disabled || readOnly) {
3504
+ return;
3505
+ }
3542
3506
  if (event.key === 'Enter' || event.key === ' ') {
3543
3507
  const eventDetails = removeChip(event);
3544
3508
  if (!eventDetails.isPropagationAllowed) {
@@ -3552,14 +3516,6 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3552
3516
  });
3553
3517
  if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
3554
3518
 
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
-
3563
3519
  exports.AriaCombobox = AriaCombobox;
3564
3520
  exports.ComboboxArrow = ComboboxArrow;
3565
3521
  exports.ComboboxBackdrop = ComboboxBackdrop;
@@ -3583,11 +3539,9 @@ exports.ComboboxPortal = ComboboxPortal;
3583
3539
  exports.ComboboxPositioner = ComboboxPositioner;
3584
3540
  exports.ComboboxRoot = ComboboxRoot;
3585
3541
  exports.ComboboxRow = ComboboxRow;
3586
- exports.ComboboxSeparator = ComboboxSeparator;
3587
3542
  exports.ComboboxStatus = ComboboxStatus;
3588
3543
  exports.ComboboxTrigger = ComboboxTrigger;
3589
3544
  exports.ComboboxValue = ComboboxValue;
3590
- exports.ListboxSeparator = ListboxSeparator;
3591
3545
  exports.useComboboxFilter = useComboboxFilter;
3592
3546
  exports.useComboboxInputValueContext = useComboboxInputValueContext;
3593
3547
  exports.useCoreFilter = useCoreFilter;