@jobber/components 8.23.0 → 8.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/Autocomplete/index.cjs +1 -1
  2. package/dist/Autocomplete/index.mjs +1 -1
  3. package/dist/Card/index.cjs +4 -4
  4. package/dist/Card/index.mjs +4 -4
  5. package/dist/Chip/index.cjs +1 -1
  6. package/dist/Chip/index.mjs +1 -1
  7. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
  8. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
  9. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  11. package/dist/Chips/index.cjs +1 -1
  12. package/dist/Chips/index.mjs +1 -1
  13. package/dist/ComboboxChipRemove-cjs.js +668 -379
  14. package/dist/ComboboxChipRemove-es.js +508 -219
  15. package/dist/ConfirmationModal/index.cjs +1 -1
  16. package/dist/ConfirmationModal/index.mjs +1 -1
  17. package/dist/DataDump/index.cjs +4 -4
  18. package/dist/DataDump/index.mjs +4 -4
  19. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  20. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  21. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  22. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  23. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
  24. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
  25. package/dist/DataList/components/DataListFilters/index.cjs +1 -1
  26. package/dist/DataList/components/DataListFilters/index.mjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  28. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  30. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  32. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  34. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  36. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  38. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  40. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  41. package/dist/DataList/index.cjs +1 -1
  42. package/dist/DataList/index.mjs +1 -1
  43. package/dist/DatePicker/index.cjs +1 -1
  44. package/dist/DatePicker/index.mjs +1 -1
  45. package/dist/Dialog/index.cjs +3 -3
  46. package/dist/Dialog/index.mjs +3 -3
  47. package/dist/DrawerDescription-cjs.js +32 -33
  48. package/dist/DrawerDescription-es.js +19 -20
  49. package/dist/DrawerTitle-cjs.js +7 -5
  50. package/dist/DrawerTitle-es.js +7 -5
  51. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
  52. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
  53. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
  54. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
  55. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
  56. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
  57. package/dist/FilterPicker/index.cjs +1 -1
  58. package/dist/FilterPicker/index.mjs +1 -1
  59. package/dist/FormFieldWrapper-cjs.js +2 -2
  60. package/dist/FormFieldWrapper-es.js +2 -2
  61. package/dist/FormatFile/index.cjs +1 -1
  62. package/dist/FormatFile/index.mjs +1 -1
  63. package/dist/Gallery/index.cjs +1 -1
  64. package/dist/Gallery/index.mjs +1 -1
  65. package/dist/InputDate/index.cjs +1 -1
  66. package/dist/InputDate/index.mjs +1 -1
  67. package/dist/InputNumberExperimental-cjs.js +319 -102
  68. package/dist/InputNumberExperimental-es.js +321 -104
  69. package/dist/LightBox/index.cjs +1 -1
  70. package/dist/LightBox/index.mjs +1 -1
  71. package/dist/Menu/index.cjs +4 -4
  72. package/dist/Menu/index.mjs +4 -4
  73. package/dist/MenuSubmenuTrigger-cjs.js +545 -498
  74. package/dist/MenuSubmenuTrigger-es.js +490 -443
  75. package/dist/Modal/index.cjs +1 -1
  76. package/dist/Modal/index.mjs +1 -1
  77. package/dist/NumberFieldInput-cjs.js +609 -496
  78. package/dist/NumberFieldInput-es.js +612 -498
  79. package/dist/Page/index.cjs +4 -4
  80. package/dist/Page/index.mjs +4 -4
  81. package/dist/Popover/index.cjs +1 -1
  82. package/dist/Popover/index.mjs +1 -1
  83. package/dist/ScrollAreaViewport-cjs.js +326 -601
  84. package/dist/ScrollAreaViewport-es.js +278 -548
  85. package/dist/SelectGroupLabel-cjs.js +494 -528
  86. package/dist/SelectGroupLabel-es.js +411 -445
  87. package/dist/Separator-cjs.js +1 -0
  88. package/dist/Separator-es.js +1 -0
  89. package/dist/Tooltip/index.cjs +1 -1
  90. package/dist/Tooltip/index.mjs +1 -1
  91. package/dist/docs/InputTime/InputTime.md +8 -0
  92. package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
  93. package/dist/floating-ui.react-cjs.js +586 -10
  94. package/dist/floating-ui.react-dom-cjs.js +92 -92
  95. package/dist/floating-ui.react-dom-es.js +1 -1
  96. package/dist/floating-ui.react-es.js +580 -4
  97. package/dist/floating-ui.utils-cjs.js +159 -0
  98. package/dist/floating-ui.utils-es.js +136 -0
  99. package/dist/index.cjs +4 -4
  100. package/dist/index.mjs +4 -4
  101. package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
  102. package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
  103. package/dist/primitives/BottomSheet/index.cjs +3 -3
  104. package/dist/primitives/BottomSheet/index.mjs +3 -3
  105. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
  106. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
  107. package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
  108. package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
  109. package/dist/primitives/SelectPrimitive/index.cjs +5 -5
  110. package/dist/primitives/SelectPrimitive/index.mjs +5 -5
  111. package/dist/primitives/index.cjs +4 -4
  112. package/dist/primitives/index.mjs +4 -4
  113. package/dist/resolveAriaLabelledBy-cjs.js +5 -0
  114. package/dist/resolveAriaLabelledBy-es.js +5 -1
  115. package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
  116. package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
  117. package/dist/styles.css +9 -3
  118. package/dist/unstyledPrimitives/index.cjs +1217 -639
  119. package/dist/unstyledPrimitives/index.mjs +1163 -585
  120. package/dist/useButton-cjs.js +283 -317
  121. package/dist/useButton-es.js +277 -298
  122. package/dist/useCompositeListItem-cjs.js +328 -279
  123. package/dist/useCompositeListItem-es.js +293 -246
  124. package/dist/useLabel-cjs.js +11 -1
  125. package/dist/useLabel-es.js +11 -2
  126. package/dist/useLabelableId-cjs.js +37 -91
  127. package/dist/useLabelableId-es.js +37 -90
  128. package/dist/useRenderElement-cjs.js +81 -26
  129. package/dist/useRenderElement-es.js +81 -27
  130. package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
  131. package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
  132. package/package.json +3 -3
  133. package/dist/index.esm-cjs.js +0 -738
  134. package/dist/index.esm-es.js +0 -712
@@ -6,10 +6,11 @@ var useRenderElement = require('./useRenderElement-cjs.js');
6
6
  var useLabelableId = require('./useLabelableId-cjs.js');
7
7
  var useCompositeListItem = require('./useCompositeListItem-cjs.js');
8
8
  var resolveAriaLabelledBy = require('./resolveAriaLabelledBy-cjs.js');
9
- var InternalBackdrop = require('./InternalBackdrop-cjs.js');
9
+ var useScrollLock = require('./useScrollLock-cjs.js');
10
10
  var jsxRuntime = require('react/jsx-runtime');
11
11
  var ReactDOM = require('react-dom');
12
12
  var useLabel = require('./useLabel-cjs.js');
13
+ var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
13
14
 
14
15
  function _interopNamespaceDefault(e) {
15
16
  var n = Object.create(null);
@@ -31,14 +32,49 @@ function _interopNamespaceDefault(e) {
31
32
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
32
33
  var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM);
33
34
 
35
+ /**
36
+ * Positional arguments are deliberate: property names of an options object
37
+ * don't minify, and the signature is locked to the caller via `typeof` on the
38
+ * `grid` option of `useListNavigation`.
39
+ *
40
+ * The injected grid navigator only ever operates on a uniform 1x1 grid (sizes are
41
+ * always `1x1` and packing is never dense), so the cell-map machinery that supports
42
+ * multi-cell items collapses to an identity transform over the item list. Calling
43
+ * `getGridNavigatedIndex` directly keeps the cell-map helpers
44
+ * (`createGridCellMap`/`getGridCellIndexOfCorner`/`getGridCellIndices`) out of
45
+ * grid-combobox bundles.
46
+ */
47
+ function gridNavigation(event, prevIndex, listRef, orientation, loopFocus, rtl, disabledIndices, minIndex, maxIndex, cols = 2) {
48
+ const nextIndex = useScrollLock.getGridNavigatedIndex(listRef.current, {
49
+ event,
50
+ orientation,
51
+ loopFocus,
52
+ rtl,
53
+ cols,
54
+ disabledIndices,
55
+ minIndex,
56
+ maxIndex,
57
+ // An out-of-range previous index falls back to the first enabled item.
58
+ prevIndex: prevIndex > maxIndex ? minIndex : prevIndex,
59
+ stopEvent: true
60
+ });
61
+
62
+ // `getGridNavigatedIndex` can return an out-of-bounds sentinel (e.g. `-1` when there is no
63
+ // previous item to move from); surface that as `undefined` so the caller treats it as
64
+ // "no navigation" rather than highlighting index `-1`.
65
+ return useScrollLock.isIndexOutOfListBounds(listRef.current, nextIndex) ? undefined : nextIndex;
66
+ }
67
+
34
68
  const ComboboxRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
35
69
  if (process.env.NODE_ENV !== "production") ComboboxRootContext.displayName = "ComboboxRootContext";
36
70
  const ComboboxFloatingContext = /*#__PURE__*/React__namespace.createContext(undefined);
37
71
  if (process.env.NODE_ENV !== "production") ComboboxFloatingContext.displayName = "ComboboxFloatingContext";
38
72
  const ComboboxDerivedItemsContext = /*#__PURE__*/React__namespace.createContext(undefined);
73
+ if (process.env.NODE_ENV !== "production") ComboboxDerivedItemsContext.displayName = "ComboboxDerivedItemsContext";
74
+ const ComboboxHasItemsContext = /*#__PURE__*/React__namespace.createContext(false);
39
75
  // `inputValue` can't be placed in the store.
40
76
  // https://github.com/mui/base-ui/issues/2703
41
- if (process.env.NODE_ENV !== "production") ComboboxDerivedItemsContext.displayName = "ComboboxDerivedItemsContext";
77
+ if (process.env.NODE_ENV !== "production") ComboboxHasItemsContext.displayName = "ComboboxHasItemsContext";
42
78
  const ComboboxInputValueContext = /*#__PURE__*/React__namespace.createContext('');
43
79
  if (process.env.NODE_ENV !== "production") ComboboxInputValueContext.displayName = "ComboboxInputValueContext";
44
80
  function useComboboxRootContext() {
@@ -65,18 +101,20 @@ function useComboboxDerivedItemsContext() {
65
101
  function useComboboxInputValueContext() {
66
102
  return React__namespace.useContext(ComboboxInputValueContext);
67
103
  }
104
+ function useComboboxHasItemsContext() {
105
+ return React__namespace.useContext(ComboboxHasItemsContext);
106
+ }
68
107
 
69
108
  const selectors = {
70
- id: InternalBackdrop.createSelector(state => state.id),
71
- labelId: InternalBackdrop.createSelector(state => state.labelId),
72
- query: InternalBackdrop.createSelector(state => state.query),
73
- items: InternalBackdrop.createSelector(state => state.items),
74
- selectedValue: InternalBackdrop.createSelector(state => state.selectedValue),
75
- hasSelectionChips: InternalBackdrop.createSelector(state => {
109
+ id: useScrollLock.createSelector(state => state.id),
110
+ labelId: useScrollLock.createSelector(state => state.labelId),
111
+ items: useScrollLock.createSelector(state => state.items),
112
+ selectedValue: useScrollLock.createSelector(state => state.selectedValue),
113
+ hasSelectionChips: useScrollLock.createSelector(state => {
76
114
  const selectedValue = state.selectedValue;
77
115
  return Array.isArray(selectedValue) && selectedValue.length > 0;
78
116
  }),
79
- hasSelectedValue: InternalBackdrop.createSelector(state => {
117
+ hasSelectedValue: useScrollLock.createSelector(state => {
80
118
  const {
81
119
  selectedValue,
82
120
  selectionMode
@@ -89,17 +127,17 @@ const selectors = {
89
127
  }
90
128
  return true;
91
129
  }),
92
- hasNullItemLabel: InternalBackdrop.createSelector((state, enabled) => {
130
+ hasNullItemLabel: useScrollLock.createSelector((state, enabled) => {
93
131
  return enabled ? resolveAriaLabelledBy.hasNullItemLabel(state.items) : false;
94
132
  }),
95
- open: InternalBackdrop.createSelector(state => state.open),
96
- mounted: InternalBackdrop.createSelector(state => state.mounted),
97
- forceMounted: InternalBackdrop.createSelector(state => state.forceMounted),
98
- inline: InternalBackdrop.createSelector(state => state.inline),
99
- activeIndex: InternalBackdrop.createSelector(state => state.activeIndex),
100
- selectedIndex: InternalBackdrop.createSelector(state => state.selectedIndex),
101
- isActive: InternalBackdrop.createSelector((state, index) => state.activeIndex === index),
102
- isSelected: InternalBackdrop.createSelector((state, itemValue) => {
133
+ open: useScrollLock.createSelector(state => state.open),
134
+ mounted: useScrollLock.createSelector(state => state.mounted),
135
+ forceMounted: useScrollLock.createSelector(state => state.forceMounted),
136
+ inline: useScrollLock.createSelector(state => state.inline),
137
+ activeIndex: useScrollLock.createSelector(state => state.activeIndex),
138
+ selectedIndex: useScrollLock.createSelector(state => state.selectedIndex),
139
+ isActive: useScrollLock.createSelector((state, index) => state.activeIndex === index),
140
+ isSelected: useScrollLock.createSelector((state, itemValue) => {
103
141
  const comparer = state.isItemEqualToValue;
104
142
  const selectedValue = state.selectedValue;
105
143
  if (Array.isArray(selectedValue)) {
@@ -107,46 +145,45 @@ const selectors = {
107
145
  }
108
146
  return resolveAriaLabelledBy.compareItemEquality(itemValue, selectedValue, comparer);
109
147
  }),
110
- transitionStatus: InternalBackdrop.createSelector(state => state.transitionStatus),
111
- popupProps: InternalBackdrop.createSelector(state => state.popupProps),
112
- inputProps: InternalBackdrop.createSelector(state => state.inputProps),
113
- triggerProps: InternalBackdrop.createSelector(state => state.triggerProps),
114
- getItemProps: InternalBackdrop.createSelector(state => state.getItemProps),
115
- positionerElement: InternalBackdrop.createSelector(state => state.positionerElement),
116
- listElement: InternalBackdrop.createSelector(state => state.listElement),
117
- triggerElement: InternalBackdrop.createSelector(state => state.triggerElement),
118
- inputElement: InternalBackdrop.createSelector(state => state.inputElement),
119
- inputGroupElement: InternalBackdrop.createSelector(state => state.inputGroupElement),
120
- popupSide: InternalBackdrop.createSelector(state => state.popupSide),
121
- openMethod: InternalBackdrop.createSelector(state => state.openMethod),
122
- inputInsidePopup: InternalBackdrop.createSelector(state => state.inputInsidePopup),
123
- selectionMode: InternalBackdrop.createSelector(state => state.selectionMode),
124
- listRef: InternalBackdrop.createSelector(state => state.listRef),
125
- labelsRef: InternalBackdrop.createSelector(state => state.labelsRef),
126
- popupRef: InternalBackdrop.createSelector(state => state.popupRef),
127
- emptyRef: InternalBackdrop.createSelector(state => state.emptyRef),
128
- inputRef: InternalBackdrop.createSelector(state => state.inputRef),
129
- keyboardActiveRef: InternalBackdrop.createSelector(state => state.keyboardActiveRef),
130
- chipsContainerRef: InternalBackdrop.createSelector(state => state.chipsContainerRef),
131
- clearRef: InternalBackdrop.createSelector(state => state.clearRef),
132
- valuesRef: InternalBackdrop.createSelector(state => state.valuesRef),
133
- allValuesRef: InternalBackdrop.createSelector(state => state.allValuesRef),
134
- name: InternalBackdrop.createSelector(state => state.name),
135
- disabled: InternalBackdrop.createSelector(state => state.disabled),
136
- readOnly: InternalBackdrop.createSelector(state => state.readOnly),
137
- required: InternalBackdrop.createSelector(state => state.required),
138
- grid: InternalBackdrop.createSelector(state => state.grid),
139
- isGrouped: InternalBackdrop.createSelector(state => state.isGrouped),
140
- virtualized: InternalBackdrop.createSelector(state => state.virtualized),
141
- onOpenChangeComplete: InternalBackdrop.createSelector(state => state.onOpenChangeComplete),
142
- openOnInputClick: InternalBackdrop.createSelector(state => state.openOnInputClick),
143
- itemToStringLabel: InternalBackdrop.createSelector(state => state.itemToStringLabel),
144
- isItemEqualToValue: InternalBackdrop.createSelector(state => state.isItemEqualToValue),
145
- modal: InternalBackdrop.createSelector(state => state.modal),
146
- autoHighlight: InternalBackdrop.createSelector(state => state.autoHighlight),
147
- submitOnItemClick: InternalBackdrop.createSelector(state => state.submitOnItemClick)
148
+ transitionStatus: useScrollLock.createSelector(state => state.transitionStatus),
149
+ popupProps: useScrollLock.createSelector(state => state.popupProps),
150
+ inputProps: useScrollLock.createSelector(state => state.inputProps),
151
+ triggerProps: useScrollLock.createSelector(state => state.triggerProps),
152
+ itemProps: useScrollLock.createSelector(state => state.itemProps),
153
+ positionerElement: useScrollLock.createSelector(state => state.positionerElement),
154
+ listElement: useScrollLock.createSelector(state => state.listElement),
155
+ popupId: useScrollLock.createSelector(state => state.popupId),
156
+ triggerElement: useScrollLock.createSelector(state => state.triggerElement),
157
+ inputElement: useScrollLock.createSelector(state => state.inputElement),
158
+ inputGroupElement: useScrollLock.createSelector(state => state.inputGroupElement),
159
+ popupSide: useScrollLock.createSelector(state => state.popupSide),
160
+ openMethod: useScrollLock.createSelector(state => state.openMethod),
161
+ inputInsidePopup: useScrollLock.createSelector(state => state.inputInsidePopup),
162
+ inputOwnsFormValue: useScrollLock.createSelector(state => state.inputOwnsFormValue),
163
+ selectionMode: useScrollLock.createSelector(state => state.selectionMode),
164
+ name: useScrollLock.createSelector(state => state.name),
165
+ form: useScrollLock.createSelector(state => state.form),
166
+ disabled: useScrollLock.createSelector(state => state.disabled),
167
+ readOnly: useScrollLock.createSelector(state => state.readOnly),
168
+ required: useScrollLock.createSelector(state => state.required),
169
+ grid: useScrollLock.createSelector(state => state.grid),
170
+ virtualized: useScrollLock.createSelector(state => state.virtualized),
171
+ itemToStringLabel: useScrollLock.createSelector(state => state.itemToStringLabel),
172
+ isItemEqualToValue: useScrollLock.createSelector(state => state.isItemEqualToValue),
173
+ modal: useScrollLock.createSelector(state => state.modal),
174
+ autoHighlight: useScrollLock.createSelector(state => state.autoHighlight),
175
+ submitOnItemClick: useScrollLock.createSelector(state => state.submitOnItemClick)
148
176
  };
149
177
 
178
+ /**
179
+ * Derives the default id assigned to `Combobox.Popup` when the input is rendered inside it.
180
+ * Shared by the popup (which applies it) and the trigger (which references it via `aria-controls`)
181
+ * so the convention only lives in one place.
182
+ */
183
+ function getComboboxPopupId(rootId) {
184
+ return rootId == null ? undefined : `${rootId}-popup`;
185
+ }
186
+
150
187
  /**
151
188
  * Enhanced filter using Intl.Collator for more robust string matching.
152
189
  * Uses the provided `itemToStringLabel` function if available, otherwise falls back to:
@@ -187,15 +224,6 @@ function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel,
187
224
  }
188
225
 
189
226
  const filterCache = new Map();
190
- function stringifyLocale(locale) {
191
- if (Array.isArray(locale)) {
192
- return locale.map(value => stringifyLocale(value)).join(',');
193
- }
194
- if (locale == null) {
195
- return '';
196
- }
197
- return String(locale);
198
- }
199
227
  function getFilter(options = {}) {
200
228
  const mergedOptions = {
201
229
  usage: 'search',
@@ -203,7 +231,7 @@ function getFilter(options = {}) {
203
231
  ignorePunctuation: true,
204
232
  ...options
205
233
  };
206
- const cacheKey = `${stringifyLocale(options.locale)}|${JSON.stringify(mergedOptions)}`;
234
+ const cacheKey = `${useLabel.stringifyLocale(options.locale)}|${JSON.stringify(mergedOptions)}`;
207
235
  const cachedFilter = filterCache.get(cacheKey);
208
236
  if (cachedFilter) {
209
237
  return cachedFilter;
@@ -284,9 +312,12 @@ function AriaCombobox(props) {
284
312
  onSelectedValueChange,
285
313
  defaultInputValue: defaultInputValueProp,
286
314
  inputValue: inputValueProp,
315
+ open: openProp,
316
+ defaultOpen = false,
287
317
  selectionMode = 'none',
288
318
  onItemHighlighted: onItemHighlightedProp,
289
319
  name: nameProp,
320
+ form,
290
321
  disabled: disabledProp = false,
291
322
  readOnly = false,
292
323
  required = false,
@@ -319,7 +350,6 @@ function AriaCombobox(props) {
319
350
  const {
320
351
  setDirty,
321
352
  validityData,
322
- shouldValidateOnChange,
323
353
  setFilled,
324
354
  name: fieldName,
325
355
  disabled: fieldDisabled,
@@ -328,6 +358,7 @@ function AriaCombobox(props) {
328
358
  validationMode,
329
359
  validation
330
360
  } = useLabelableId.useFieldRootContext();
361
+ const direction = useScrollLock.useDirection();
331
362
  const id = useLabelableId.useLabelableId({
332
363
  id: idProp
333
364
  });
@@ -412,8 +443,8 @@ function AriaCombobox(props) {
412
443
  state: 'inputValue'
413
444
  });
414
445
  const [open, setOpenUnwrapped] = useButton.useControlled({
415
- controlled: props.open,
416
- default: props.defaultOpen,
446
+ controlled: openProp,
447
+ default: defaultOpen,
417
448
  name: 'Combobox',
418
449
  state: 'open'
419
450
  });
@@ -492,7 +523,7 @@ function AriaCombobox(props) {
492
523
  }
493
524
  return filteredItems;
494
525
  }, [filteredItems, isGrouped]);
495
- const store = useRenderElement.useRefWithInit(() => new InternalBackdrop.Store({
526
+ const store = useRenderElement.useRefWithInit(() => new useScrollLock.Store({
496
527
  id,
497
528
  labelId: undefined,
498
529
  selectedValue,
@@ -515,6 +546,7 @@ function AriaCombobox(props) {
515
546
  allValuesRef,
516
547
  selectionEventRef,
517
548
  name,
549
+ form,
518
550
  disabled,
519
551
  readOnly,
520
552
  required,
@@ -537,14 +569,19 @@ function AriaCombobox(props) {
537
569
  popupProps: {},
538
570
  inputProps: {},
539
571
  triggerProps: {},
572
+ itemProps: useRenderElement.EMPTY_OBJECT,
540
573
  positionerElement: null,
541
574
  listElement: null,
575
+ popupId: undefined,
542
576
  triggerElement: null,
543
577
  inputElement: null,
544
578
  inputGroupElement: null,
545
579
  popupSide: null,
546
580
  openMethod: null,
547
581
  inputInsidePopup: true,
582
+ // Avoid duplicate names in the server HTML. Popup inputs aren't rendered
583
+ // until after hydration, so the hidden input takes over then if needed.
584
+ inputOwnsFormValue: selectionMode === 'none',
548
585
  onOpenChangeComplete: onOpenChangeCompleteProp || useRenderElement.NOOP,
549
586
  // Placeholder callbacks replaced on first render
550
587
  setOpen: useRenderElement.NOOP,
@@ -553,7 +590,6 @@ function AriaCombobox(props) {
553
590
  setIndices: useRenderElement.NOOP,
554
591
  onItemHighlighted: useRenderElement.NOOP,
555
592
  handleSelection: useRenderElement.NOOP,
556
- getItemProps: () => useRenderElement.EMPTY_OBJECT,
557
593
  forceMount: useRenderElement.NOOP,
558
594
  requestSubmit: useRenderElement.NOOP
559
595
  })).current;
@@ -569,15 +605,16 @@ function AriaCombobox(props) {
569
605
  }, [fieldRawValue, itemToStringValue, selectionMode, selectedValue]);
570
606
  const onItemHighlighted = useButton.useStableCallback(onItemHighlightedProp);
571
607
  const onOpenChangeComplete = useButton.useStableCallback(onOpenChangeCompleteProp);
572
- const activeIndex = InternalBackdrop.useStore(store, selectors.activeIndex);
573
- const selectedIndex = InternalBackdrop.useStore(store, selectors.selectedIndex);
574
- const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
575
- const listElement = InternalBackdrop.useStore(store, selectors.listElement);
576
- const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
577
- const inputElement = InternalBackdrop.useStore(store, selectors.inputElement);
578
- const inputGroupElement = InternalBackdrop.useStore(store, selectors.inputGroupElement);
579
- const inline = InternalBackdrop.useStore(store, selectors.inline);
580
- const inputInsidePopup = InternalBackdrop.useStore(store, selectors.inputInsidePopup);
608
+ const activeIndex = useScrollLock.useStore(store, selectors.activeIndex);
609
+ const selectedIndex = useScrollLock.useStore(store, selectors.selectedIndex);
610
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
611
+ const listElement = useScrollLock.useStore(store, selectors.listElement);
612
+ const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
613
+ const inputElement = useScrollLock.useStore(store, selectors.inputElement);
614
+ const inputGroupElement = useScrollLock.useStore(store, selectors.inputGroupElement);
615
+ const inline = useScrollLock.useStore(store, selectors.inline);
616
+ const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
617
+ const inputOwnsFormValue = useScrollLock.useStore(store, selectors.inputOwnsFormValue);
581
618
  const triggerRef = useButton.useValueAsRef(triggerElement);
582
619
  const {
583
620
  mounted,
@@ -587,15 +624,9 @@ function AriaCombobox(props) {
587
624
  const {
588
625
  openMethod,
589
626
  triggerProps
590
- } = InternalBackdrop.useOpenInteractionType(open);
591
- useLabelableId.useField({
592
- id,
593
- name,
594
- commit: validation.commit,
595
- value: fieldRawValue,
596
- controlRef: inputInsidePopup ? triggerRef : inputRef,
597
- getValue: () => fieldStringValue
598
- });
627
+ } = useScrollLock.useOpenInteractionType(open);
628
+ const getStringifiedValueForForm = useButton.useStableCallback(() => fieldStringValue);
629
+ useLabelableId.useRegisterFieldControl(inputInsidePopup ? triggerRef : inputRef, id, fieldRawValue, getStringifiedValueForForm, !disabled, nameProp);
599
630
  const forceMount = useButton.useStableCallback(() => {
600
631
  if (items) {
601
632
  // Ensure typeahead works on a closed list.
@@ -682,6 +713,16 @@ function AriaCombobox(props) {
682
713
  if (eventDetails.isCanceled) {
683
714
  return;
684
715
  }
716
+
717
+ // If reopening interrupts the close animation, handleUnmount won't run to clear the
718
+ // frozen closeQuery and pending popup input.
719
+ if (nextOpen && multiple && inputInsidePopup && !inline && closeQuery !== null) {
720
+ setQueryChangedAfterOpen(false);
721
+ setCloseQuery(null);
722
+ if (inputValue !== '') {
723
+ setInputValue('', useButton.createChangeEventDetails(useButton.inputClear, eventDetails.event));
724
+ }
725
+ }
685
726
  if (!nextOpen && queryChangedAfterOpen) {
686
727
  if (single) {
687
728
  if (!inline) {
@@ -692,17 +733,22 @@ function AriaCombobox(props) {
692
733
  setQueryChangedAfterOpen(false);
693
734
  }
694
735
  } else if (multiple) {
695
- if (inline || inputInsidePopup) {
736
+ if (!inline) {
737
+ // Freeze the current query so filtering remains stable while exiting.
738
+ setCloseQuery(query);
739
+ }
740
+ if (inputInsidePopup) {
696
741
  setIndices({
697
742
  activeIndex: null
698
743
  });
699
- } else {
700
- // Freeze the current query so filtering remains stable while exiting.
701
- setCloseQuery(query);
702
744
  }
745
+
703
746
  // Clear the input immediately on close while retaining filtering via closeQuery for exit animations
704
- // if the input is outside the popup.
705
- setInputValue('', useButton.createChangeEventDetails(useButton.inputClear, eventDetails.event));
747
+ // if the input is outside the popup. When the input is inside the popup, defer the clear until
748
+ // unmount so the filtered list doesn't flash to unfiltered during the exit animation.
749
+ if (!inputInsidePopup || inline) {
750
+ setInputValue('', useButton.createChangeEventDetails(useButton.inputClear, eventDetails.event));
751
+ }
706
752
  }
707
753
  }
708
754
  setOpenUnwrapped(nextOpen);
@@ -757,6 +803,9 @@ function AriaCombobox(props) {
757
803
  const isCurrentlySelected = resolveAriaLabelledBy.selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
758
804
  const nextValue = isCurrentlySelected ? resolveAriaLabelledBy.removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
759
805
  setSelectedValue(nextValue, eventDetails);
806
+ if (eventDetails.isCanceled) {
807
+ return;
808
+ }
760
809
  const wasFiltering = inputRef.current ? inputRef.current.value.trim() !== '' : false;
761
810
  if (!wasFiltering) {
762
811
  return;
@@ -768,6 +817,9 @@ function AriaCombobox(props) {
768
817
  }
769
818
  } else {
770
819
  setSelectedValue(itemValue, eventDetails);
820
+ if (eventDetails.isCanceled) {
821
+ return;
822
+ }
771
823
  setOpen(false, eventDetails);
772
824
  }
773
825
  });
@@ -775,9 +827,9 @@ function AriaCombobox(props) {
775
827
  if (!store.state.submitOnItemClick) {
776
828
  return;
777
829
  }
778
- const form = store.state.inputElement?.form;
779
- if (form && typeof form.requestSubmit === 'function') {
780
- form.requestSubmit();
830
+ const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
831
+ if (formElement && typeof formElement.requestSubmit === 'function') {
832
+ formElement.requestSubmit();
781
833
  }
782
834
  });
783
835
  const handleUnmount = useButton.useStableCallback(() => {
@@ -943,6 +995,13 @@ function AriaCombobox(props) {
943
995
  });
944
996
  }
945
997
  }, [hasItems, autoHighlightMode, flatFilteredItems.length, setIndices]);
998
+ function isSelectedValueDirty(value) {
999
+ const initialValue = validityData.initialValue;
1000
+ if (Array.isArray(value) && Array.isArray(initialValue)) {
1001
+ return !resolveAriaLabelledBy.areArraysEqual(value, initialValue, (itemValue, initialItemValue) => resolveAriaLabelledBy.compareItemEquality(itemValue, initialItemValue, isItemEqualToValue));
1002
+ }
1003
+ return value !== initialValue;
1004
+ }
946
1005
  useButton.useValueChanged(query, () => {
947
1006
  if (!open || query === '' || query === String(initialDefaultInputValue)) {
948
1007
  return;
@@ -954,12 +1013,8 @@ function AriaCombobox(props) {
954
1013
  return;
955
1014
  }
956
1015
  clearErrors(name);
957
- setDirty(selectedValue !== validityData.initialValue);
958
- if (shouldValidateOnChange()) {
959
- validation.commit(selectedValue);
960
- } else {
961
- validation.commit(selectedValue, true);
962
- }
1016
+ setDirty(isSelectedValueDirty(selectedValue));
1017
+ validation.change(selectedValue);
963
1018
  if (single && !hasInputValue && !inputInsidePopup) {
964
1019
  const nextInputValue = resolveAriaLabelledBy.stringifyAsLabel(selectedValue, itemToStringLabel);
965
1020
  if (inputValue !== nextInputValue) {
@@ -973,11 +1028,7 @@ function AriaCombobox(props) {
973
1028
  }
974
1029
  clearErrors(name);
975
1030
  setDirty(inputValue !== validityData.initialValue);
976
- if (shouldValidateOnChange()) {
977
- validation.commit(inputValue);
978
- } else {
979
- validation.commit(inputValue, true);
980
- }
1031
+ validation.change(inputValue);
981
1032
  });
982
1033
  useButton.useValueChanged(items, () => {
983
1034
  if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
@@ -1028,16 +1079,16 @@ function AriaCombobox(props) {
1028
1079
  }
1029
1080
  };
1030
1081
  }, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
1031
- const click = InternalBackdrop.useClick(floatingRootContext, {
1082
+ const click = useScrollLock.useClick(floatingRootContext, {
1032
1083
  enabled: !readOnly && !disabled && openOnInputClick,
1033
1084
  event: 'mousedown-only',
1034
1085
  toggle: false,
1035
- // Apply a small delay for touch to let iOS viewport centering settle.
1086
+ // Apply a small delay for touch to let mobile viewport/keyboard positioning settle.
1036
1087
  // This avoids top-bottom flip flickers if the preferred position is "top" when first tapping.
1037
- touchOpenDelay: inputInsidePopup ? 0 : 50,
1088
+ touchOpenDelay: inputInsidePopup ? 0 : 100,
1038
1089
  reason: useButton.inputPress
1039
1090
  });
1040
- const dismiss = InternalBackdrop.useDismiss(floatingRootContext, {
1091
+ const dismiss = useScrollLock.useDismiss(floatingRootContext, {
1041
1092
  enabled: !readOnly && !disabled && !inline,
1042
1093
  outsidePressEvent: {
1043
1094
  mouse: 'sloppy',
@@ -1065,13 +1116,10 @@ function AriaCombobox(props) {
1065
1116
  focusItemOnOpen: queryChangedAfterOpen || selectionMode === 'none' && !autoHighlightMode ? false : 'auto',
1066
1117
  focusItemOnHover: highlightItemOnHover,
1067
1118
  resetOnPointerLeave: !keepHighlight,
1068
- // `cols` > 1 enables grid navigation.
1069
- // Since <Combobox.Row> infers column sizes (and is required when building a grid),
1070
- // it works correctly even with a value of `2`.
1071
- // Floating UI tests don't require `role="row"` wrappers, so retains the number API.
1072
- cols: grid ? 2 : 1,
1073
1119
  orientation: grid ? 'horizontal' : undefined,
1120
+ rtl: direction === 'rtl',
1074
1121
  disabledIndices: useRenderElement.EMPTY_ARRAY,
1122
+ grid: grid ? gridNavigation : undefined,
1075
1123
  onNavigate(nextActiveIndex, event) {
1076
1124
  // Retain the highlight only while actually transitioning out or closed.
1077
1125
  if (!event && !open || transitionStatus === 'ending') {
@@ -1089,18 +1137,37 @@ function AriaCombobox(props) {
1089
1137
  }
1090
1138
  }
1091
1139
  });
1092
- const {
1093
- getReferenceProps,
1094
- getFloatingProps,
1095
- getItemProps
1096
- } = InternalBackdrop.useInteractions([role, click, dismiss, listNavigation]);
1097
- InternalBackdrop.useOnFirstRender(() => {
1140
+ const inputProps = React__namespace.useMemo(() => useRenderElement.mergeProps(listNavigation.reference, {
1141
+ onKeyDown(event) {
1142
+ // In grid mode the navigation hook treats ArrowLeft/ArrowRight as horizontal
1143
+ // grid movement. When the input has focus and no item is highlighted the user
1144
+ // is still editing the query, so let the input keep its native caret behavior.
1145
+ if (grid && store.state.activeIndex == null && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
1146
+ event.preventBaseUIHandler();
1147
+ }
1148
+ }
1149
+ }, dismiss.reference, click.reference, role.reference), [listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store]);
1150
+ const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, listNavigation.floating, dismiss.floating, role.floating), [listNavigation.floating, dismiss.floating, role.floating]);
1151
+ const itemProps = React__namespace.useMemo(() => {
1152
+ const listNavigationItemProps = listNavigation.item;
1153
+ if (!listNavigationItemProps) {
1154
+ return useRenderElement.EMPTY_OBJECT;
1155
+ }
1156
+
1157
+ // Combobox keeps focus on the input; item focus would incorrectly sync
1158
+ // list navigation state from DOM focus.
1159
+ return {
1160
+ ...listNavigationItemProps,
1161
+ onFocus: undefined
1162
+ };
1163
+ }, [listNavigation.item]);
1164
+ useScrollLock.useOnFirstRender(() => {
1098
1165
  store.update({
1099
1166
  inline: inlineProp,
1100
- popupProps: getFloatingProps(),
1101
- inputProps: getReferenceProps(),
1167
+ popupProps,
1168
+ inputProps,
1102
1169
  triggerProps,
1103
- getItemProps,
1170
+ itemProps,
1104
1171
  setOpen,
1105
1172
  setInputValue,
1106
1173
  setSelectedValue,
@@ -1120,13 +1187,14 @@ function AriaCombobox(props) {
1120
1187
  transitionStatus,
1121
1188
  items,
1122
1189
  inline: inlineProp,
1123
- popupProps: getFloatingProps(),
1124
- inputProps: getReferenceProps(),
1190
+ popupProps,
1191
+ inputProps,
1125
1192
  triggerProps,
1126
1193
  openMethod,
1127
- getItemProps,
1194
+ itemProps,
1128
1195
  selectionMode,
1129
1196
  name,
1197
+ form,
1130
1198
  disabled,
1131
1199
  readOnly,
1132
1200
  required,
@@ -1141,9 +1209,10 @@ function AriaCombobox(props) {
1141
1209
  isItemEqualToValue,
1142
1210
  submitOnItemClick,
1143
1211
  hasInputValue,
1144
- requestSubmit
1212
+ requestSubmit,
1213
+ inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup)
1145
1214
  });
1146
- }, [store, id, selectedValue, open, mounted, transitionStatus, items, getFloatingProps, getReferenceProps, getItemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, validation, grid, isGrouped, virtualized, onOpenChangeComplete, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, requestSubmit, autoHighlightMode]);
1215
+ }, [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]);
1147
1216
  const hiddenInputRef = useRenderElement.useMergedRefs(inputRefProp, validation.inputRef);
1148
1217
  const itemsContextValue = React__namespace.useMemo(() => ({
1149
1218
  query,
@@ -1158,7 +1227,7 @@ function AriaCombobox(props) {
1158
1227
  return resolveAriaLabelledBy.stringifyAsValue(fieldRawValue, itemToStringValue);
1159
1228
  }, [fieldRawValue, itemToStringValue]);
1160
1229
  const hasMultipleSelection = multiple && Array.isArray(selectedValue) && selectedValue.length > 0;
1161
- const hiddenInputName = multiple || selectionMode === 'none' ? undefined : name;
1230
+ const hiddenInputName = multiple || selectionMode === 'none' && inputOwnsFormValue ? undefined : name;
1162
1231
  const hiddenInputs = React__namespace.useMemo(() => {
1163
1232
  if (!multiple || !Array.isArray(selectedValue) || !name) {
1164
1233
  return null;
@@ -1167,14 +1236,16 @@ function AriaCombobox(props) {
1167
1236
  const currentSerializedValue = resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue);
1168
1237
  return /*#__PURE__*/jsxRuntime.jsx("input", {
1169
1238
  type: "hidden",
1239
+ form: form,
1170
1240
  name: name,
1171
- value: currentSerializedValue
1241
+ value: currentSerializedValue,
1242
+ disabled: disabled
1172
1243
  }, currentSerializedValue);
1173
1244
  });
1174
- }, [multiple, selectedValue, name, itemToStringValue]);
1245
+ }, [multiple, selectedValue, form, name, itemToStringValue, disabled]);
1175
1246
  const children = /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
1176
1247
  children: [props.children, /*#__PURE__*/jsxRuntime.jsx("input", {
1177
- ...validation.getInputValidationProps({
1248
+ ...validation.getValidationProps(disabled, {
1178
1249
  // Move focus when the hidden input is focused.
1179
1250
  onFocus() {
1180
1251
  if (inputInsidePopup) {
@@ -1186,48 +1257,58 @@ function AriaCombobox(props) {
1186
1257
  // Handle browser autofill.
1187
1258
  onChange(event) {
1188
1259
  // Workaround for https://github.com/facebook/react/issues/9023
1189
- if (event.nativeEvent.defaultPrevented) {
1260
+ if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
1190
1261
  return;
1191
1262
  }
1192
- const nextValue = event.target.value;
1263
+ const nextValue = event.currentTarget.value;
1264
+ const nextValueLower = nextValue.toLowerCase();
1193
1265
  const details = useButton.createChangeEventDetails(useButton.none, event.nativeEvent);
1266
+ const findSerializedMatchIndex = () => valuesRef.current.findIndex(candidate => resolveAriaLabelledBy.stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || resolveAriaLabelledBy.stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
1194
1267
  function handleChange() {
1195
1268
  // Browser autofill only writes a single scalar value.
1196
1269
  if (multiple) {
1197
1270
  return;
1198
1271
  }
1199
1272
  if (selectionMode === 'none') {
1200
- setDirty(nextValue !== validityData.initialValue);
1201
1273
  setInputValue(nextValue, details);
1202
- if (shouldValidateOnChange()) {
1203
- validation.commit(nextValue);
1204
- }
1205
1274
  return;
1206
1275
  }
1207
- const matchingValue = valuesRef.current.find(v => {
1208
- const candidate = resolveAriaLabelledBy.stringifyAsValue(v, itemToStringValue);
1209
- if (candidate.toLowerCase() === nextValue.toLowerCase()) {
1210
- return true;
1211
- }
1212
- return false;
1213
- });
1276
+
1277
+ // Preserve the original serialized matching, then fall back to rendered text,
1278
+ // which browsers can autofill for primitive values like `value="US">United States`.
1279
+ let matchingIndex = findSerializedMatchIndex();
1280
+ if (matchingIndex === -1) {
1281
+ matchingIndex = valuesRef.current.findIndex((_, index) => {
1282
+ const renderedLabel = labelsRef.current[index];
1283
+ return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
1284
+ });
1285
+ }
1286
+ const matchingValue = matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
1214
1287
  if (matchingValue != null) {
1215
- setDirty(matchingValue !== validityData.initialValue);
1288
+ // `setSelectedValue` may be canceled by `onValueChange`; rely on
1289
+ // `useValueChanged` to mark the field dirty and run validation only
1290
+ // when the value actually changes.
1216
1291
  setSelectedValue?.(matchingValue, details);
1217
- if (shouldValidateOnChange()) {
1218
- validation.commit(matchingValue);
1219
- }
1220
1292
  }
1221
1293
  }
1222
- if (items) {
1223
- handleChange();
1224
- } else {
1294
+
1295
+ // Only single-selection autofill matches against the registered values/labels.
1296
+ // `multiple` ignores autofill and `none` just writes the input value, so avoid the
1297
+ // sticky `forceMounted` mount (which never resets) for those modes.
1298
+ if (single) {
1225
1299
  forceMount();
1226
- queueMicrotask(handleChange);
1300
+ if (items && findSerializedMatchIndex() === -1) {
1301
+ // `forceMount` only refreshes the derived labels for the `items` prop. When
1302
+ // serialized matching misses, also mount the list so rendered labels (which can
1303
+ // differ from the serialized values) are registered for autofill matching.
1304
+ store.set('forceMounted', true);
1305
+ }
1227
1306
  }
1307
+ queueMicrotask(handleChange);
1228
1308
  }
1229
1309
  }),
1230
1310
  id: id && hiddenInputName == null ? `${id}-hidden-input` : undefined,
1311
+ form: form,
1231
1312
  name: hiddenInputName,
1232
1313
  autoComplete: formAutoComplete,
1233
1314
  disabled: disabled,
@@ -1237,18 +1318,22 @@ function AriaCombobox(props) {
1237
1318
  ref: hiddenInputRef,
1238
1319
  style: hiddenInputName ? useButton.visuallyHiddenInput : useButton.visuallyHidden,
1239
1320
  tabIndex: -1,
1240
- "aria-hidden": true
1321
+ "aria-hidden": true,
1322
+ suppressHydrationWarning: true
1241
1323
  }), hiddenInputs]
1242
1324
  });
1243
1325
  return /*#__PURE__*/jsxRuntime.jsx(ComboboxRootContext.Provider, {
1244
1326
  value: store,
1245
1327
  children: /*#__PURE__*/jsxRuntime.jsx(ComboboxFloatingContext.Provider, {
1246
1328
  value: floatingRootContext,
1247
- children: /*#__PURE__*/jsxRuntime.jsx(ComboboxDerivedItemsContext.Provider, {
1248
- value: itemsContextValue,
1249
- children: /*#__PURE__*/jsxRuntime.jsx(ComboboxInputValueContext.Provider, {
1250
- value: inputValue,
1251
- children: children
1329
+ children: /*#__PURE__*/jsxRuntime.jsx(ComboboxHasItemsContext.Provider, {
1330
+ value: hasItems,
1331
+ children: /*#__PURE__*/jsxRuntime.jsx(ComboboxDerivedItemsContext.Provider, {
1332
+ value: itemsContextValue,
1333
+ children: /*#__PURE__*/jsxRuntime.jsx(ComboboxInputValueContext.Provider, {
1334
+ value: inputValue,
1335
+ children: children
1336
+ })
1252
1337
  })
1253
1338
  })
1254
1339
  })
@@ -1256,7 +1341,7 @@ function AriaCombobox(props) {
1256
1341
  }
1257
1342
 
1258
1343
  const triggerStateAttributesMapping = {
1259
- ...InternalBackdrop.pressableTriggerOpenStateMapping,
1344
+ ...useScrollLock.pressableTriggerOpenStateMapping,
1260
1345
  ...useLabelableId.fieldValidityMapping,
1261
1346
  popupSide: side => side ? {
1262
1347
  'data-popup-side': side
@@ -1271,6 +1356,8 @@ const BOUNDARY_OFFSET = 2;
1271
1356
  /**
1272
1357
  * A button that opens the popup.
1273
1358
  * Renders a `<button>` element.
1359
+ *
1360
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
1274
1361
  */
1275
1362
  const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function ComboboxTrigger(componentProps, forwardedRef) {
1276
1363
  const {
@@ -1279,6 +1366,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1279
1366
  nativeButton = true,
1280
1367
  disabled: disabledProp = false,
1281
1368
  id: idProp,
1369
+ style,
1282
1370
  ...elementProps
1283
1371
  } = componentProps;
1284
1372
  const {
@@ -1296,24 +1384,25 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1296
1384
  const {
1297
1385
  filteredItems
1298
1386
  } = useComboboxDerivedItemsContext();
1299
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
1300
- const comboboxDisabled = InternalBackdrop.useStore(store, selectors.disabled);
1301
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
1302
- const required = InternalBackdrop.useStore(store, selectors.required);
1303
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
1304
- const popupSideValue = InternalBackdrop.useStore(store, selectors.popupSide);
1305
- const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
1306
- const listElement = InternalBackdrop.useStore(store, selectors.listElement);
1307
- const triggerProps = InternalBackdrop.useStore(store, selectors.triggerProps);
1308
- const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
1309
- const inputInsidePopup = InternalBackdrop.useStore(store, selectors.inputInsidePopup);
1310
- const rootId = InternalBackdrop.useStore(store, selectors.id);
1311
- const comboboxLabelId = InternalBackdrop.useStore(store, selectors.labelId);
1312
- const open = InternalBackdrop.useStore(store, selectors.open);
1313
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
1314
- const activeIndex = InternalBackdrop.useStore(store, selectors.activeIndex);
1315
- const selectedIndex = InternalBackdrop.useStore(store, selectors.selectedIndex);
1316
- const hasSelectedValue = InternalBackdrop.useStore(store, selectors.hasSelectedValue);
1387
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
1388
+ const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
1389
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
1390
+ const required = useScrollLock.useStore(store, selectors.required);
1391
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
1392
+ const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1393
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1394
+ const listElement = useScrollLock.useStore(store, selectors.listElement);
1395
+ const storedPopupId = useScrollLock.useStore(store, selectors.popupId);
1396
+ const triggerProps = useScrollLock.useStore(store, selectors.triggerProps);
1397
+ const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
1398
+ const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
1399
+ const rootId = useScrollLock.useStore(store, selectors.id);
1400
+ const comboboxLabelId = useScrollLock.useStore(store, selectors.labelId);
1401
+ const open = useScrollLock.useStore(store, selectors.open);
1402
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
1403
+ const activeIndex = useScrollLock.useStore(store, selectors.activeIndex);
1404
+ const selectedIndex = useScrollLock.useStore(store, selectors.selectedIndex);
1405
+ const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
1317
1406
  const floatingRootContext = useComboboxFloatingContext();
1318
1407
  const inputValue = useComboboxInputValueContext();
1319
1408
  const focusTimeout = useButton.useTimeout();
@@ -1325,6 +1414,14 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1325
1414
  });
1326
1415
  const id = inputInsidePopup ? idProp ?? rootId : idProp;
1327
1416
  const ariaLabelledBy = resolveAriaLabelledBy.resolveAriaLabelledBy(fieldLabelId, comboboxLabelId);
1417
+ let ariaControls;
1418
+ if (open && inputInsidePopup) {
1419
+ // Fall back to the default id while the popup registers its own (custom ids are stored once the
1420
+ // popup mounts), so `aria-controls` is set on the same commit `open` becomes `true`.
1421
+ ariaControls = storedPopupId ?? getComboboxPopupId(rootId);
1422
+ } else if (open) {
1423
+ ariaControls = listElement?.id;
1424
+ }
1328
1425
  const currentPointerTypeRef = React__namespace.useRef('');
1329
1426
  function trackPointerType(event) {
1330
1427
  currentPointerTypeRef.current = event.pointerType;
@@ -1357,7 +1454,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1357
1454
  });
1358
1455
  const {
1359
1456
  reference: triggerClickProps
1360
- } = InternalBackdrop.useClick(floatingRootContext, {
1457
+ } = useScrollLock.useClick(floatingRootContext, {
1361
1458
  enabled: !readOnly && !comboboxDisabled,
1362
1459
  event: 'mousedown'
1363
1460
  });
@@ -1374,7 +1471,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1374
1471
  disabled,
1375
1472
  popupSide,
1376
1473
  listEmpty,
1377
- placeholder: !hasSelectedValue
1474
+ placeholder: selectionMode === 'none' ? false : !hasSelectedValue
1378
1475
  };
1379
1476
  const setTriggerElement = useButton.useStableCallback(element => {
1380
1477
  store.set('triggerElement', element);
@@ -1388,7 +1485,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1388
1485
  role: inputInsidePopup ? 'combobox' : undefined,
1389
1486
  'aria-expanded': open ? 'true' : 'false',
1390
1487
  'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
1391
- 'aria-controls': open ? listElement?.id : undefined,
1488
+ 'aria-controls': ariaControls,
1392
1489
  'aria-required': inputInsidePopup ? required || undefined : undefined,
1393
1490
  'aria-labelledby': ariaLabelledBy,
1394
1491
  onPointerDown: trackPointerType,
@@ -1466,7 +1563,7 @@ const ComboboxTrigger = /*#__PURE__*/React__namespace.forwardRef(function Combob
1466
1563
  store.state.inputRef.current?.focus();
1467
1564
  }
1468
1565
  }
1469
- }, validation ? validation.getValidationProps(elementProps) : elementProps, getButtonProps],
1566
+ }, validation ? validation.getValidationProps(disabled, elementProps) : elementProps, getButtonProps],
1470
1567
  stateAttributesMapping: triggerStateAttributesMapping
1471
1568
  });
1472
1569
  return element;
@@ -1501,9 +1598,9 @@ const ComboboxInternalDismissButton = /*#__PURE__*/React__namespace.forwardRef(f
1501
1598
  native: false
1502
1599
  });
1503
1600
  const mergedRef = useRenderElement.useMergedRefs(forwardedRef, buttonRef);
1504
- const handleDismiss = useButton.useStableCallback(event => {
1601
+ function handleDismiss(event) {
1505
1602
  store.state.setOpen(false, useButton.createChangeEventDetails(useButton.closePress, event.nativeEvent, event.currentTarget));
1506
- });
1603
+ }
1507
1604
  const dismissProps = getButtonProps({
1508
1605
  onClick: handleDismiss
1509
1606
  });
@@ -1523,6 +1620,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1523
1620
  className,
1524
1621
  disabled: disabledProp = false,
1525
1622
  id: idProp,
1623
+ style,
1526
1624
  ...elementProps
1527
1625
  } = componentProps;
1528
1626
  const {
@@ -1546,23 +1644,24 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1546
1644
  // `inputValue` can't be placed in the store.
1547
1645
  // https://github.com/mui/base-ui/issues/2703
1548
1646
  const inputValue = useComboboxInputValueContext();
1549
- const direction = InternalBackdrop.useDirection();
1550
- const required = InternalBackdrop.useStore(store, selectors.required);
1551
- const comboboxDisabled = InternalBackdrop.useStore(store, selectors.disabled);
1552
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
1553
- const name = InternalBackdrop.useStore(store, selectors.name);
1554
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
1555
- const autoHighlightMode = InternalBackdrop.useStore(store, selectors.autoHighlight);
1556
- const inputProps = InternalBackdrop.useStore(store, selectors.inputProps);
1557
- const triggerProps = InternalBackdrop.useStore(store, selectors.triggerProps);
1558
- const open = InternalBackdrop.useStore(store, selectors.open);
1559
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
1560
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
1561
- const popupSideValue = InternalBackdrop.useStore(store, selectors.popupSide);
1562
- const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
1563
- const rootId = InternalBackdrop.useStore(store, selectors.id);
1564
- const inline = InternalBackdrop.useStore(store, selectors.inline);
1565
- const modal = InternalBackdrop.useStore(store, selectors.modal);
1647
+ const direction = useScrollLock.useDirection();
1648
+ const required = useScrollLock.useStore(store, selectors.required);
1649
+ const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
1650
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
1651
+ const name = useScrollLock.useStore(store, selectors.name);
1652
+ const form = useScrollLock.useStore(store, selectors.form);
1653
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
1654
+ const autoHighlightMode = useScrollLock.useStore(store, selectors.autoHighlight);
1655
+ const inputProps = useScrollLock.useStore(store, selectors.inputProps);
1656
+ const triggerProps = useScrollLock.useStore(store, selectors.triggerProps);
1657
+ const open = useScrollLock.useStore(store, selectors.open);
1658
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
1659
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
1660
+ const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
1661
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
1662
+ const rootId = useScrollLock.useStore(store, selectors.id);
1663
+ const inline = useScrollLock.useStore(store, selectors.inline);
1664
+ const modal = useScrollLock.useStore(store, selectors.modal);
1566
1665
  const autoHighlightEnabled = Boolean(autoHighlightMode);
1567
1666
  const popupSide = mounted && positionerElement ? popupSideValue : null;
1568
1667
  const disabled = fieldDisabled || comboboxDisabled || disabledProp;
@@ -1576,6 +1675,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1576
1675
  const isComposingRef = React__namespace.useRef(false);
1577
1676
  const lastActiveIndexRef = React__namespace.useRef(null);
1578
1677
  const shouldRestoreActiveIndexRef = React__namespace.useRef(false);
1678
+ const inputOwnsFormValue = selectionMode === 'none' && !hasPositionerParent;
1579
1679
  const setInputElement = useButton.useStableCallback(element => {
1580
1680
  const nextIsInsidePopup = hasPositionerParent || store.state.inline;
1581
1681
  if (nextIsInsidePopup && !store.state.hasInputValue) {
@@ -1583,10 +1683,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1583
1683
  }
1584
1684
  store.update({
1585
1685
  inputElement: element,
1586
- inputInsidePopup: nextIsInsidePopup
1686
+ inputInsidePopup: nextIsInsidePopup,
1687
+ inputOwnsFormValue
1587
1688
  });
1588
1689
  });
1589
- const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(elementProps);
1690
+ const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(disabled, elementProps);
1590
1691
  const state = {
1591
1692
  ...fieldStateForInput,
1592
1693
  open,
@@ -1603,17 +1704,21 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1603
1704
  const {
1604
1705
  highlightedChipIndex
1605
1706
  } = comboboxChipsContext;
1707
+ const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
1708
+ const isRtl = direction === 'rtl';
1709
+ const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
1710
+ const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
1606
1711
  if (highlightedChipIndex !== undefined) {
1607
- if (event.key === 'ArrowLeft') {
1712
+ if (event.key === previousChipKey) {
1608
1713
  event.preventDefault();
1609
1714
  if (highlightedChipIndex > 0) {
1610
1715
  nextIndex = highlightedChipIndex - 1;
1611
1716
  } else {
1612
1717
  nextIndex = undefined;
1613
1718
  }
1614
- } else if (event.key === 'ArrowRight') {
1719
+ } else if (event.key === nextChipKey) {
1615
1720
  event.preventDefault();
1616
- if (highlightedChipIndex < selectedValue.length - 1) {
1721
+ if (highlightedChipIndex < renderedChipsCount - 1) {
1617
1722
  nextIndex = highlightedChipIndex + 1;
1618
1723
  } else {
1619
1724
  nextIndex = undefined;
@@ -1634,10 +1739,9 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1634
1739
  }
1635
1740
 
1636
1741
  // Handle navigation when no chip is highlighted
1637
- if (event.key === 'ArrowLeft' && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
1742
+ if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
1638
1743
  event.preventDefault();
1639
- const lastChipIndex = Math.max(selectedValue.length - 1, 0);
1640
- nextIndex = lastChipIndex;
1744
+ nextIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : undefined;
1641
1745
  } else if (event.key === 'Backspace' && event.currentTarget.value === '' && selectedValue.length > 0) {
1642
1746
  store.state.setIndices({
1643
1747
  activeIndex: null,
@@ -1660,7 +1764,8 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1660
1764
  disabled,
1661
1765
  readOnly,
1662
1766
  required: selectionMode === 'none' ? required : undefined,
1663
- ...(selectionMode === 'none' && name && {
1767
+ form,
1768
+ ...(inputOwnsFormValue && name && {
1664
1769
  name
1665
1770
  }),
1666
1771
  id,
@@ -1697,7 +1802,7 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1697
1802
  }
1698
1803
  },
1699
1804
  onCompositionStart(event) {
1700
- if (useButton.isAndroid) {
1805
+ if (useButton.android) {
1701
1806
  return;
1702
1807
  }
1703
1808
  isComposingRef.current = true;
@@ -1751,7 +1856,11 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1751
1856
  }
1752
1857
  return;
1753
1858
  }
1754
- store.state.setInputValue(event.currentTarget.value, useButton.createChangeEventDetails(useButton.inputChange, event.nativeEvent));
1859
+ const inputChangeDetails = useButton.createChangeEventDetails(useButton.inputChange, event.nativeEvent);
1860
+ store.state.setInputValue(event.currentTarget.value, inputChangeDetails);
1861
+ if (inputChangeDetails.isCanceled) {
1862
+ return;
1863
+ }
1755
1864
  const empty = event.currentTarget.value === '';
1756
1865
  const clearDetails = useButton.createChangeEventDetails(useButton.inputClear, event.nativeEvent);
1757
1866
  if (empty && !store.state.inputInsidePopup) {
@@ -1801,14 +1910,14 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1801
1910
  const isRTL = direction === 'rtl';
1802
1911
  if (event.key === 'Home') {
1803
1912
  useButton.stopEvent(event);
1804
- const cursor = useButton.isFirefox && isRTL ? input.value.length : 0;
1913
+ const cursor = useScrollLock.gecko && isRTL ? input.value.length : 0;
1805
1914
  input.setSelectionRange(cursor, cursor);
1806
1915
  input.scrollLeft = 0;
1807
1916
  return;
1808
1917
  }
1809
1918
  if (event.key === 'End') {
1810
1919
  useButton.stopEvent(event);
1811
- const cursor = useButton.isFirefox && isRTL ? 0 : input.value.length;
1920
+ const cursor = useScrollLock.gecko && isRTL ? 0 : input.value.length;
1812
1921
  input.setSelectionRange(cursor, cursor);
1813
1922
  input.scrollLeft = isRTL ? -scrollAmount : scrollAmount;
1814
1923
  return;
@@ -1827,7 +1936,9 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1827
1936
 
1828
1937
  // Handle deletion when no chip is highlighted and the input is empty.
1829
1938
  if (comboboxChipsContext && event.key === 'Backspace' && input.value === '' && comboboxChipsContext.highlightedChipIndex === undefined && Array.isArray(selectedValue) && selectedValue.length > 0) {
1830
- const newValue = selectedValue.slice(0, -1);
1939
+ const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
1940
+ const removalIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : selectedValue.length - 1;
1941
+ const newValue = selectedValue.filter((_, index) => index !== removalIndex);
1831
1942
  // If the removed item was also the active (highlighted) item, clear highlight
1832
1943
  store.state.setIndices({
1833
1944
  activeIndex: null,
@@ -1880,42 +1991,67 @@ const ComboboxInput = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1880
1991
  }, validationProps],
1881
1992
  stateAttributesMapping: triggerStateAttributesMapping
1882
1993
  });
1994
+ const renderedInput = hasPositionerParent ? /*#__PURE__*/jsxRuntime.jsx(useLabelableId.FieldRootContext.Provider, {
1995
+ value: useLabelableId.DEFAULT_FIELD_ROOT_CONTEXT,
1996
+ children: element
1997
+ }) : element;
1883
1998
  return /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
1884
1999
  children: [open && focusManagerModal && /*#__PURE__*/jsxRuntime.jsx(ComboboxInternalDismissButton, {
1885
2000
  ref: store.state.startDismissRef
1886
- }), element]
2001
+ }), renderedInput]
1887
2002
  });
1888
2003
  });
1889
2004
  if (process.env.NODE_ENV !== "production") ComboboxInput.displayName = "ComboboxInput";
1890
2005
 
2006
+ function handleInputPress(event, store, disabled, readOnly, shouldIgnoreTarget) {
2007
+ if (event.baseUIHandlerPrevented || readOnly) {
2008
+ return;
2009
+ }
2010
+ const target = useButton.getTarget(event.nativeEvent);
2011
+ const targetElement = floatingUi_utils_dom.isElement(target) ? target : null;
2012
+ if (targetElement !== event.currentTarget && (shouldIgnoreTarget?.(targetElement) || useScrollLock.isInteractiveElement(targetElement))) {
2013
+ return;
2014
+ }
2015
+ event.preventDefault();
2016
+ if (disabled) {
2017
+ return;
2018
+ }
2019
+ store.state.inputRef.current?.focus();
2020
+ if (store.state.openOnInputClick) {
2021
+ store.state.setOpen(true, useButton.createChangeEventDetails(useButton.inputPress, event.nativeEvent));
2022
+ }
2023
+ }
2024
+
1891
2025
  /**
1892
2026
  * A wrapper for the input and its associated controls.
1893
2027
  * Renders a `<div>` element.
2028
+ *
2029
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
1894
2030
  */
1895
2031
  const ComboboxInputGroup = /*#__PURE__*/React__namespace.forwardRef(function ComboboxInputGroup(componentProps, forwardedRef) {
1896
2032
  const {
1897
2033
  render,
1898
2034
  className,
2035
+ style,
1899
2036
  ...elementProps
1900
2037
  } = componentProps;
1901
2038
  const {
1902
- state: fieldState,
1903
- disabled: fieldDisabled
2039
+ state: fieldState
1904
2040
  } = useLabelableId.useFieldRootContext();
1905
2041
  const store = useComboboxRootContext();
1906
2042
  const {
1907
2043
  filteredItems
1908
2044
  } = useComboboxDerivedItemsContext();
1909
- const open = InternalBackdrop.useStore(store, selectors.open);
1910
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
1911
- const popupSideValue = InternalBackdrop.useStore(store, selectors.popupSide);
1912
- const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
1913
- const comboboxDisabled = InternalBackdrop.useStore(store, selectors.disabled);
1914
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
1915
- const hasSelectedValue = InternalBackdrop.useStore(store, selectors.hasSelectedValue);
1916
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
2045
+ const open = useScrollLock.useStore(store, selectors.open);
2046
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
2047
+ const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
2048
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
2049
+ const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
2050
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2051
+ const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
2052
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
1917
2053
  const popupSide = mounted && positionerElement ? popupSideValue : null;
1918
- const disabled = fieldDisabled || comboboxDisabled;
2054
+ const disabled = comboboxDisabled;
1919
2055
  const listEmpty = filteredItems.length === 0;
1920
2056
  const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
1921
2057
  const state = {
@@ -1933,7 +2069,12 @@ const ComboboxInputGroup = /*#__PURE__*/React__namespace.forwardRef(function Com
1933
2069
  return useRenderElement.useRenderElement('div', componentProps, {
1934
2070
  ref: [forwardedRef, setInputGroupElement],
1935
2071
  props: [{
1936
- role: 'group'
2072
+ role: 'group',
2073
+ onMouseDown(event) {
2074
+ handleInputPress(event, store, disabled, readOnly, target => {
2075
+ return useButton.contains(store.state.chipsContainerRef.current, target);
2076
+ });
2077
+ }
1937
2078
  }, elementProps],
1938
2079
  state,
1939
2080
  stateAttributesMapping: triggerStateAttributesMapping
@@ -1944,11 +2085,14 @@ if (process.env.NODE_ENV !== "production") ComboboxInputGroup.displayName = "Com
1944
2085
  /**
1945
2086
  * An icon that indicates that the trigger button opens the popup.
1946
2087
  * Renders a `<span>` element.
2088
+ *
2089
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
1947
2090
  */
1948
2091
  const ComboboxIcon = /*#__PURE__*/React__namespace.forwardRef(function ComboboxIcon(componentProps, forwardedRef) {
1949
2092
  const {
1950
- className,
1951
2093
  render,
2094
+ className,
2095
+ style,
1952
2096
  ...elementProps
1953
2097
  } = componentProps;
1954
2098
  const element = useRenderElement.useRenderElement('span', componentProps, {
@@ -1964,12 +2108,14 @@ if (process.env.NODE_ENV !== "production") ComboboxIcon.displayName = "ComboboxI
1964
2108
 
1965
2109
  const stateAttributesMapping$2 = {
1966
2110
  ...useButton.transitionStatusMapping,
1967
- ...InternalBackdrop.triggerOpenStateMapping
2111
+ ...useScrollLock.triggerOpenStateMapping
1968
2112
  };
1969
2113
 
1970
2114
  /**
1971
2115
  * Clears the value when clicked.
1972
2116
  * Renders a `<button>` element.
2117
+ *
2118
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
1973
2119
  */
1974
2120
  const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function ComboboxClear(componentProps, forwardedRef) {
1975
2121
  const {
@@ -1978,18 +2124,19 @@ const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function Combobox
1978
2124
  disabled: disabledProp = false,
1979
2125
  nativeButton = true,
1980
2126
  keepMounted = false,
2127
+ style,
1981
2128
  ...elementProps
1982
2129
  } = componentProps;
1983
2130
  const {
1984
2131
  disabled: fieldDisabled
1985
2132
  } = useLabelableId.useFieldRootContext();
1986
2133
  const store = useComboboxRootContext();
1987
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
1988
- const comboboxDisabled = InternalBackdrop.useStore(store, selectors.disabled);
1989
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
1990
- const open = InternalBackdrop.useStore(store, selectors.open);
1991
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
1992
- const hasSelectionChips = InternalBackdrop.useStore(store, selectors.hasSelectionChips);
2134
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2135
+ const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
2136
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2137
+ const open = useScrollLock.useStore(store, selectors.open);
2138
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
2139
+ const hasSelectionChips = useScrollLock.useStore(store, selectors.hasSelectionChips);
1993
2140
  const inputValue = useComboboxInputValueContext();
1994
2141
  let visible = false;
1995
2142
  if (selectionMode === 'none') {
@@ -2014,6 +2161,7 @@ const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2014
2161
  } = useButton.useTransitionStatus(visible);
2015
2162
  const state = {
2016
2163
  disabled,
2164
+ visible,
2017
2165
  open,
2018
2166
  transitionStatus
2019
2167
  };
@@ -2109,6 +2257,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2109
2257
  const {
2110
2258
  render,
2111
2259
  className,
2260
+ style,
2112
2261
  children,
2113
2262
  ...elementProps
2114
2263
  } = componentProps;
@@ -2116,17 +2265,14 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2116
2265
  const floatingRootContext = useComboboxFloatingContext();
2117
2266
  const hasPositionerContext = Boolean(useComboboxPositionerContext(true));
2118
2267
  const {
2119
- filteredItems
2268
+ filteredItems,
2269
+ hasItems
2120
2270
  } = useComboboxDerivedItemsContext();
2121
- const items = InternalBackdrop.useStore(store, selectors.items);
2122
- const labelsRef = InternalBackdrop.useStore(store, selectors.labelsRef);
2123
- const listRef = InternalBackdrop.useStore(store, selectors.listRef);
2124
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
2125
- const grid = InternalBackdrop.useStore(store, selectors.grid);
2126
- const popupProps = InternalBackdrop.useStore(store, selectors.popupProps);
2127
- const disabled = InternalBackdrop.useStore(store, selectors.disabled);
2128
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
2129
- const virtualized = InternalBackdrop.useStore(store, selectors.virtualized);
2271
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2272
+ const grid = useScrollLock.useStore(store, selectors.grid);
2273
+ const popupProps = useScrollLock.useStore(store, selectors.popupProps);
2274
+ const virtualized = useScrollLock.useStore(store, selectors.virtualized);
2275
+ const forceMounted = useScrollLock.useStore(store, selectors.forceMounted);
2130
2276
  const multiple = selectionMode === 'multiple';
2131
2277
  const empty = filteredItems.length === 0;
2132
2278
  const setPositionerElement = useButton.useStableCallback(element => {
@@ -2162,7 +2308,7 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2162
2308
  role: grid ? 'grid' : 'listbox',
2163
2309
  'aria-multiselectable': multiple ? 'true' : undefined,
2164
2310
  onKeyDown(event) {
2165
- if (disabled || readOnly) {
2311
+ if (store.state.disabled || store.state.readOnly) {
2166
2312
  return;
2167
2313
  }
2168
2314
  if (event.key === 'Enter') {
@@ -2192,28 +2338,93 @@ const ComboboxList = /*#__PURE__*/React__namespace.forwardRef(function ComboboxL
2192
2338
  if (virtualized) {
2193
2339
  return element;
2194
2340
  }
2341
+
2342
+ // With the `items` prop, typeahead labels are derived from the items so they survive the list
2343
+ // unmounting (unmounting clears the registered labels). Rendered labels only need to be
2344
+ // registered when the list is force-mounted to match browser autofill against rendered text.
2345
+ const labelsRef = hasItems && !forceMounted ? undefined : store.state.labelsRef;
2195
2346
  return /*#__PURE__*/jsxRuntime.jsx(useCompositeListItem.CompositeList, {
2196
- elementsRef: listRef,
2197
- labelsRef: items ? undefined : labelsRef,
2347
+ elementsRef: store.state.listRef,
2348
+ labelsRef: labelsRef,
2198
2349
  children: element
2199
2350
  });
2200
2351
  });
2201
2352
  if (process.env.NODE_ENV !== "production") ComboboxList.displayName = "ComboboxList";
2202
2353
 
2354
+ // Word Joiner is invisible and zero-width, so it forces a text mutation without shifting layout.
2355
+ const LIVE_REGION_MARKER = '\u2060';
2356
+ // Safari VoiceOver needed roughly 200ms to reliably notice the initial polite live-region change.
2357
+ const INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY = 200;
2358
+ function findLastTextNode(root) {
2359
+ const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
2360
+ let lastTextNode = null;
2361
+ while (walker.nextNode()) {
2362
+ const textNode = walker.currentNode;
2363
+ if (textNode.nodeValue !== '') {
2364
+ lastTextNode = textNode;
2365
+ }
2366
+ }
2367
+ return lastTextNode;
2368
+ }
2369
+ function useInitialLiveRegionTextMutation() {
2370
+ const timeout = useButton.useTimeout();
2371
+ const rootRef = React__namespace.useRef(null);
2372
+
2373
+ // Only the initial mounted announcement needs the marker; later text updates announce naturally.
2374
+ React__namespace.useEffect(() => {
2375
+ if (useButton.ios) {
2376
+ return undefined;
2377
+ }
2378
+ const root = rootRef.current;
2379
+ if (root == null) {
2380
+ return undefined;
2381
+ }
2382
+ const textNode = findLastTextNode(root);
2383
+ if (textNode == null) {
2384
+ return undefined;
2385
+ }
2386
+ const originalValue = textNode.nodeValue ?? '';
2387
+ const markedValue = `${originalValue}${LIVE_REGION_MARKER}`;
2388
+ textNode.nodeValue = markedValue;
2389
+ timeout.start(INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY, () => {
2390
+ if (textNode.nodeValue === markedValue) {
2391
+ textNode.nodeValue = originalValue;
2392
+ }
2393
+ });
2394
+ return () => {
2395
+ timeout.clear();
2396
+ if (textNode.nodeValue === markedValue) {
2397
+ textNode.nodeValue = originalValue;
2398
+ }
2399
+ };
2400
+ }, [rootRef, timeout]);
2401
+ return rootRef;
2402
+ }
2403
+
2203
2404
  /**
2204
2405
  * Displays a status message whose content changes are announced politely to screen readers.
2205
2406
  * Useful for conveying the status of an asynchronously loaded list.
2407
+ * This component's root element must remain mounted in the DOM to announce
2408
+ * changes consistently across screen readers. Avoid hiding or removing the
2409
+ * component itself with `display: none`, `hidden`, `aria-hidden`, or conditional
2410
+ * rendering. Prefer updating or conditionally rendering its children instead.
2206
2411
  * Renders a `<div>` element.
2412
+ *
2413
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2207
2414
  */
2208
2415
  const ComboboxStatus = /*#__PURE__*/React__namespace.forwardRef(function ComboboxStatus(componentProps, forwardedRef) {
2209
2416
  const {
2210
2417
  render,
2211
2418
  className,
2419
+ style,
2420
+ children: childrenProp,
2212
2421
  ...elementProps
2213
2422
  } = componentProps;
2423
+ const statusRef = useInitialLiveRegionTextMutation();
2214
2424
  return useRenderElement.useRenderElement('div', componentProps, {
2215
- ref: forwardedRef,
2425
+ ref: [forwardedRef, statusRef],
2216
2426
  props: [{
2427
+ children: childrenProp,
2217
2428
  role: 'status',
2218
2429
  'aria-live': 'polite',
2219
2430
  'aria-atomic': true
@@ -2238,15 +2449,15 @@ const ComboboxPortal = /*#__PURE__*/React__namespace.forwardRef(function Combobo
2238
2449
  ...portalProps
2239
2450
  } = props;
2240
2451
  const store = useComboboxRootContext();
2241
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
2242
- const forceMounted = InternalBackdrop.useStore(store, selectors.forceMounted);
2452
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
2453
+ const forceMounted = useScrollLock.useStore(store, selectors.forceMounted);
2243
2454
  const shouldRender = mounted || keepMounted || forceMounted;
2244
2455
  if (!shouldRender) {
2245
2456
  return null;
2246
2457
  }
2247
2458
  return /*#__PURE__*/jsxRuntime.jsx(ComboboxPortalContext.Provider, {
2248
2459
  value: keepMounted,
2249
- children: /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingPortal, {
2460
+ children: /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingPortal, {
2250
2461
  ref: forwardedRef,
2251
2462
  ...portalProps
2252
2463
  })
@@ -2255,24 +2466,27 @@ const ComboboxPortal = /*#__PURE__*/React__namespace.forwardRef(function Combobo
2255
2466
  if (process.env.NODE_ENV !== "production") ComboboxPortal.displayName = "ComboboxPortal";
2256
2467
 
2257
2468
  const stateAttributesMapping$1 = {
2258
- ...InternalBackdrop.popupStateMapping,
2469
+ ...useScrollLock.popupStateMapping,
2259
2470
  ...useButton.transitionStatusMapping
2260
2471
  };
2261
2472
 
2262
2473
  /**
2263
2474
  * An overlay displayed beneath the popup.
2264
2475
  * Renders a `<div>` element.
2476
+ *
2477
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2265
2478
  */
2266
2479
  const ComboboxBackdrop = /*#__PURE__*/React__namespace.forwardRef(function ComboboxBackdrop(componentProps, forwardedRef) {
2267
2480
  const {
2268
- className,
2269
2481
  render,
2482
+ className,
2483
+ style,
2270
2484
  ...elementProps
2271
2485
  } = componentProps;
2272
2486
  const store = useComboboxRootContext();
2273
- const open = InternalBackdrop.useStore(store, selectors.open);
2274
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
2275
- const transitionStatus = InternalBackdrop.useStore(store, selectors.transitionStatus);
2487
+ const open = useScrollLock.useStore(store, selectors.open);
2488
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
2489
+ const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2276
2490
  const state = {
2277
2491
  open,
2278
2492
  transitionStatus
@@ -2308,7 +2522,8 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2308
2522
  arrowPadding = 5,
2309
2523
  sticky = false,
2310
2524
  disableAnchorTracking = false,
2311
- collisionAvoidance = InternalBackdrop.DROPDOWN_COLLISION_AVOIDANCE,
2525
+ collisionAvoidance = useScrollLock.DROPDOWN_COLLISION_AVOIDANCE,
2526
+ style: styleProp,
2312
2527
  ...elementProps
2313
2528
  } = componentProps;
2314
2529
  const store = useComboboxRootContext();
@@ -2317,15 +2532,16 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2317
2532
  } = useComboboxDerivedItemsContext();
2318
2533
  const floatingRootContext = useComboboxFloatingContext();
2319
2534
  const keepMounted = useComboboxPortalContext();
2320
- const modal = InternalBackdrop.useStore(store, selectors.modal);
2321
- const open = InternalBackdrop.useStore(store, selectors.open);
2322
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
2323
- const openMethod = InternalBackdrop.useStore(store, selectors.openMethod);
2324
- const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
2325
- const inputElement = InternalBackdrop.useStore(store, selectors.inputElement);
2326
- const inputGroupElement = InternalBackdrop.useStore(store, selectors.inputGroupElement);
2327
- const inputInsidePopup = InternalBackdrop.useStore(store, selectors.inputInsidePopup);
2328
- const transitionStatus = InternalBackdrop.useStore(store, selectors.transitionStatus);
2535
+ const modal = useScrollLock.useStore(store, selectors.modal);
2536
+ const open = useScrollLock.useStore(store, selectors.open);
2537
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
2538
+ const openMethod = useScrollLock.useStore(store, selectors.openMethod);
2539
+ const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
2540
+ const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
2541
+ const inputElement = useScrollLock.useStore(store, selectors.inputElement);
2542
+ const inputGroupElement = useScrollLock.useStore(store, selectors.inputGroupElement);
2543
+ const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2544
+ const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2329
2545
  const empty = filteredItems.length === 0;
2330
2546
  const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputGroupElement ?? inputElement);
2331
2547
  const positioning = useCompositeListItem.useAnchorPositioning({
@@ -2346,20 +2562,7 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2346
2562
  collisionAvoidance,
2347
2563
  lazyFlip: true
2348
2564
  });
2349
- InternalBackdrop.useScrollLock(open && modal && openMethod !== 'touch', triggerElement);
2350
- const defaultProps = React__namespace.useMemo(() => {
2351
- const style = {
2352
- ...positioning.positionerStyles
2353
- };
2354
- if (!open) {
2355
- style.pointerEvents = 'none';
2356
- }
2357
- return {
2358
- role: 'presentation',
2359
- hidden: !mounted,
2360
- style
2361
- };
2362
- }, [open, mounted, positioning.positionerStyles]);
2565
+ useCompositeListItem.useAnchoredPopupScrollLock(open && modal, openMethod === 'touch', positionerElement, triggerElement);
2363
2566
  const state = {
2364
2567
  open,
2365
2568
  side: positioning.side,
@@ -2370,28 +2573,21 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2370
2573
  useButton.useIsoLayoutEffect(() => {
2371
2574
  store.set('popupSide', positioning.side);
2372
2575
  }, [store, positioning.side]);
2373
- const contextValue = React__namespace.useMemo(() => ({
2374
- side: positioning.side,
2375
- align: positioning.align,
2376
- arrowRef: positioning.arrowRef,
2377
- arrowUncentered: positioning.arrowUncentered,
2378
- arrowStyles: positioning.arrowStyles,
2379
- anchorHidden: positioning.anchorHidden,
2380
- isPositioned: positioning.isPositioned
2381
- }), [positioning.side, positioning.align, positioning.arrowRef, positioning.arrowUncentered, positioning.arrowStyles, positioning.anchorHidden, positioning.isPositioned]);
2382
2576
  const setPositionerElement = useButton.useStableCallback(element => {
2383
2577
  store.set('positionerElement', element);
2384
2578
  });
2385
- const element = useRenderElement.useRenderElement('div', componentProps, {
2386
- state,
2387
- ref: [forwardedRef, setPositionerElement],
2388
- props: [defaultProps, useCompositeListItem.getDisabledMountTransitionStyles(transitionStatus), elementProps],
2389
- stateAttributesMapping: InternalBackdrop.popupStateMapping
2579
+ const element = useCompositeListItem.usePositioner(componentProps, state, {
2580
+ styles: positioning.positionerStyles,
2581
+ transitionStatus,
2582
+ props: elementProps,
2583
+ refs: [forwardedRef, setPositionerElement],
2584
+ hidden: !mounted,
2585
+ inert: !open
2390
2586
  });
2391
2587
  return /*#__PURE__*/jsxRuntime.jsxs(ComboboxPositionerContext.Provider, {
2392
- value: contextValue,
2393
- children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.InternalBackdrop, {
2394
- inert: InternalBackdrop.inertValue(!open),
2588
+ value: positioning,
2589
+ children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(useScrollLock.InternalBackdrop, {
2590
+ inert: useScrollLock.inertValue(!open),
2395
2591
  cutout: inputGroupElement ?? inputElement ?? triggerElement
2396
2592
  }), element]
2397
2593
  });
@@ -2399,18 +2595,21 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
2399
2595
  if (process.env.NODE_ENV !== "production") ComboboxPositioner.displayName = "ComboboxPositioner";
2400
2596
 
2401
2597
  const stateAttributesMapping = {
2402
- ...InternalBackdrop.popupStateMapping,
2598
+ ...useScrollLock.popupStateMapping,
2403
2599
  ...useButton.transitionStatusMapping
2404
2600
  };
2405
2601
 
2406
2602
  /**
2407
2603
  * A container for the list.
2408
2604
  * Renders a `<div>` element.
2605
+ *
2606
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2409
2607
  */
2410
2608
  const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function ComboboxPopup(componentProps, forwardedRef) {
2411
2609
  const {
2412
2610
  render,
2413
2611
  className,
2612
+ style,
2414
2613
  initialFocus,
2415
2614
  finalFocus,
2416
2615
  ...elementProps
@@ -2421,14 +2620,23 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2421
2620
  const {
2422
2621
  filteredItems
2423
2622
  } = useComboboxDerivedItemsContext();
2424
- const mounted = InternalBackdrop.useStore(store, selectors.mounted);
2425
- const open = InternalBackdrop.useStore(store, selectors.open);
2426
- const openMethod = InternalBackdrop.useStore(store, selectors.openMethod);
2427
- const transitionStatus = InternalBackdrop.useStore(store, selectors.transitionStatus);
2428
- const inputInsidePopup = InternalBackdrop.useStore(store, selectors.inputInsidePopup);
2429
- const inputElement = InternalBackdrop.useStore(store, selectors.inputElement);
2430
- const modal = InternalBackdrop.useStore(store, selectors.modal);
2623
+ const mounted = useScrollLock.useStore(store, selectors.mounted);
2624
+ const open = useScrollLock.useStore(store, selectors.open);
2625
+ const openMethod = useScrollLock.useStore(store, selectors.openMethod);
2626
+ const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
2627
+ const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
2628
+ const inputElement = useScrollLock.useStore(store, selectors.inputElement);
2629
+ const modal = useScrollLock.useStore(store, selectors.modal);
2630
+ const rootId = useScrollLock.useStore(store, selectors.id);
2431
2631
  const empty = filteredItems.length === 0;
2632
+ const popupId = elementProps.id ?? (inputInsidePopup ? getComboboxPopupId(rootId) : undefined);
2633
+ useButton.useIsoLayoutEffect(() => {
2634
+ // Prefer the rendered DOM id, which a `render` prop element or function may override.
2635
+ store.set('popupId', store.state.popupRef.current?.id || popupId);
2636
+ return () => {
2637
+ store.set('popupId', undefined);
2638
+ };
2639
+ }, [store, popupId]);
2432
2640
  useButton.useOpenChangeComplete({
2433
2641
  open,
2434
2642
  ref: store.state.popupRef,
@@ -2450,6 +2658,7 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2450
2658
  state,
2451
2659
  ref: [forwardedRef, store.state.popupRef],
2452
2660
  props: [{
2661
+ id: popupId,
2453
2662
  role: inputInsidePopup ? 'dialog' : 'presentation',
2454
2663
  tabIndex: -1,
2455
2664
  onFocus(event) {
@@ -2474,7 +2683,7 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2474
2683
  resolvedFinalFocus = inputInsidePopup ? undefined : false;
2475
2684
  }
2476
2685
  const focusManagerModal = !inputInsidePopup || modal;
2477
- return /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingFocusManager, {
2686
+ return /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingFocusManager, {
2478
2687
  context: floatingRootContext,
2479
2688
  disabled: !mounted,
2480
2689
  modal: focusManagerModal,
@@ -2494,11 +2703,14 @@ if (process.env.NODE_ENV !== "production") ComboboxPopup.displayName = "Combobox
2494
2703
  /**
2495
2704
  * Displays an element positioned against the anchor.
2496
2705
  * Renders a `<div>` element.
2706
+ *
2707
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2497
2708
  */
2498
2709
  const ComboboxArrow = /*#__PURE__*/React__namespace.forwardRef(function ComboboxArrow(componentProps, forwardedRef) {
2499
2710
  const {
2500
- className,
2501
2711
  render,
2712
+ className,
2713
+ style,
2502
2714
  ...elementProps
2503
2715
  } = componentProps;
2504
2716
  const store = useComboboxRootContext();
@@ -2509,7 +2721,7 @@ const ComboboxArrow = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2509
2721
  arrowUncentered,
2510
2722
  arrowStyles
2511
2723
  } = useComboboxPositionerContext();
2512
- const open = InternalBackdrop.useStore(store, selectors.open);
2724
+ const open = useScrollLock.useStore(store, selectors.open);
2513
2725
  const state = {
2514
2726
  open,
2515
2727
  side,
@@ -2518,7 +2730,7 @@ const ComboboxArrow = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2518
2730
  };
2519
2731
  return useRenderElement.useRenderElement('div', componentProps, {
2520
2732
  ref: [arrowRef, forwardedRef],
2521
- stateAttributesMapping: InternalBackdrop.popupStateMapping,
2733
+ stateAttributesMapping: useScrollLock.popupStateMapping,
2522
2734
  state,
2523
2735
  props: {
2524
2736
  style: arrowStyles,
@@ -2543,6 +2755,7 @@ const ComboboxGroup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2543
2755
  const {
2544
2756
  render,
2545
2757
  className,
2758
+ style,
2546
2759
  items,
2547
2760
  ...elementProps
2548
2761
  } = componentProps;
@@ -2576,11 +2789,14 @@ if (process.env.NODE_ENV !== "production") ComboboxGroup.displayName = "Combobox
2576
2789
  /**
2577
2790
  * An accessible label that is automatically associated with its parent group.
2578
2791
  * Renders a `<div>` element.
2792
+ *
2793
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2579
2794
  */
2580
2795
  const ComboboxGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function ComboboxGroupLabel(componentProps, forwardedRef) {
2581
2796
  const {
2582
2797
  render,
2583
2798
  className,
2799
+ style,
2584
2800
  id: idProp,
2585
2801
  ...elementProps
2586
2802
  } = componentProps;
@@ -2620,10 +2836,17 @@ function useComboboxRowContext() {
2620
2836
  return React__namespace.useContext(ComboboxRowContext);
2621
2837
  }
2622
2838
 
2623
- const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef(function ComboboxItem(componentProps, forwardedRef) {
2839
+ function ComboboxItemInner(props) {
2840
+ const {
2841
+ componentProps,
2842
+ forwardedRef,
2843
+ virtualized,
2844
+ indexFromFilter
2845
+ } = props;
2624
2846
  const {
2625
2847
  render,
2626
2848
  className,
2849
+ style,
2627
2850
  value: itemValue = null,
2628
2851
  index: indexProp,
2629
2852
  disabled = false,
@@ -2639,22 +2862,18 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2639
2862
  });
2640
2863
  const store = useComboboxRootContext();
2641
2864
  const isRow = useComboboxRowContext();
2642
- const {
2643
- flatFilteredItems,
2644
- hasItems
2645
- } = useComboboxDerivedItemsContext();
2646
- const open = InternalBackdrop.useStore(store, selectors.open);
2647
- const selectionMode = InternalBackdrop.useStore(store, selectors.selectionMode);
2648
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
2649
- const virtualized = InternalBackdrop.useStore(store, selectors.virtualized);
2650
- const isItemEqualToValue = InternalBackdrop.useStore(store, selectors.isItemEqualToValue);
2865
+ const hasItems = useComboboxHasItemsContext();
2866
+ const open = useScrollLock.useStore(store, selectors.open);
2867
+ const selectionMode = useScrollLock.useStore(store, selectors.selectionMode);
2868
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
2869
+ const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
2651
2870
  const selectable = selectionMode !== 'none';
2652
- const index = indexProp ?? (virtualized ? resolveAriaLabelledBy.findItemIndex(flatFilteredItems, itemValue, isItemEqualToValue) : listItem.index);
2871
+ const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
2653
2872
  const hasRegistered = listItem.index !== -1;
2654
- const rootId = InternalBackdrop.useStore(store, selectors.id);
2655
- const highlighted = InternalBackdrop.useStore(store, selectors.isActive, index);
2656
- const matchesSelectedValue = InternalBackdrop.useStore(store, selectors.isSelected, itemValue);
2657
- const getItemProps = InternalBackdrop.useStore(store, selectors.getItemProps);
2873
+ const rootId = useScrollLock.useStore(store, selectors.id);
2874
+ const highlighted = useScrollLock.useStore(store, selectors.isActive, index);
2875
+ const matchesSelectedValue = useScrollLock.useStore(store, selectors.isSelected, itemValue);
2876
+ const itemProps = useScrollLock.useStore(store, selectors.itemProps);
2658
2877
  const itemRef = React__namespace.useRef(null);
2659
2878
  const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined;
2660
2879
  const selected = matchesSelectedValue && selectable;
@@ -2700,17 +2919,6 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2700
2919
  store.set('selectedIndex', index);
2701
2920
  }
2702
2921
  }, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
2703
- const state = {
2704
- disabled,
2705
- selected,
2706
- highlighted
2707
- };
2708
- const rootProps = getItemProps({
2709
- active: highlighted,
2710
- selected
2711
- });
2712
- rootProps.id = undefined;
2713
- rootProps.onFocus = undefined;
2714
2922
  const {
2715
2923
  getButtonProps,
2716
2924
  buttonRef
@@ -2720,6 +2928,11 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2720
2928
  native: nativeButton,
2721
2929
  composite: true
2722
2930
  });
2931
+ const state = {
2932
+ disabled,
2933
+ selected,
2934
+ highlighted
2935
+ };
2723
2936
  function commitSelection(nativeEvent) {
2724
2937
  function selectItem() {
2725
2938
  store.state.handleSelection(nativeEvent, itemValue);
@@ -2743,6 +2956,11 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2743
2956
  didPointerDownRef.current = true;
2744
2957
  event.preventDefault();
2745
2958
  },
2959
+ onMouseDown(event) {
2960
+ // iOS Safari can emit a synthetic mousedown for touch taps without a preceding
2961
+ // pointerdown. Prevent default here too so tapping an item does not blur the input.
2962
+ event.preventDefault();
2963
+ },
2746
2964
  onClick(event) {
2747
2965
  if (disabled || readOnly) {
2748
2966
  return;
@@ -2761,7 +2979,7 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2761
2979
  const element = useRenderElement.useRenderElement('div', componentProps, {
2762
2980
  ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
2763
2981
  state,
2764
- props: [rootProps, defaultProps, elementProps, getButtonProps]
2982
+ props: [itemProps, defaultProps, elementProps, getButtonProps]
2765
2983
  });
2766
2984
  const contextValue = React__namespace.useMemo(() => ({
2767
2985
  selected,
@@ -2771,6 +2989,61 @@ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__name
2771
2989
  value: contextValue,
2772
2990
  children: element
2773
2991
  });
2992
+ }
2993
+
2994
+ /**
2995
+ * Resolves the index from the filtered items for the virtualized fallback (no `index` prop).
2996
+ * Isolated here so that this per-keystroke subscription to the derived-items context is only
2997
+ * paid by virtualized items. Those re-render on every input change anyway — the parent
2998
+ * virtualizer re-windows the list as the filtered set changes — so the extra subscription costs
2999
+ * them nothing, while it keeps every non-virtualized item off that context.
3000
+ */
3001
+ function ComboboxItemVirtualizedIndex(props) {
3002
+ const {
3003
+ componentProps,
3004
+ forwardedRef
3005
+ } = props;
3006
+ const store = useComboboxRootContext();
3007
+ const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
3008
+ const {
3009
+ flatFilteredItems
3010
+ } = useComboboxDerivedItemsContext();
3011
+ const indexFromFilter = resolveAriaLabelledBy.findItemIndex(flatFilteredItems, componentProps.value ?? null, isItemEqualToValue);
3012
+
3013
+ // Only reached when `virtualized` is true (see the wrapper below).
3014
+ return /*#__PURE__*/jsxRuntime.jsx(ComboboxItemInner, {
3015
+ componentProps: componentProps,
3016
+ forwardedRef: forwardedRef,
3017
+ virtualized: true,
3018
+ indexFromFilter: indexFromFilter
3019
+ });
3020
+ }
3021
+
3022
+ /**
3023
+ * An individual item in the list.
3024
+ * Renders a `<div>` element.
3025
+ *
3026
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
3027
+ */
3028
+ const ComboboxItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef(function ComboboxItem(componentProps, forwardedRef) {
3029
+ const store = useComboboxRootContext();
3030
+ const virtualized = useScrollLock.useStore(store, selectors.virtualized);
3031
+
3032
+ // `virtualized` (and whether an item provides an explicit `index`) must be stable for an
3033
+ // item's lifetime: the two branches return different component types, so flipping it at
3034
+ // runtime remounts the item and resets its refs and effects.
3035
+ if (virtualized && componentProps.index == null) {
3036
+ return /*#__PURE__*/jsxRuntime.jsx(ComboboxItemVirtualizedIndex, {
3037
+ componentProps: componentProps,
3038
+ forwardedRef: forwardedRef
3039
+ });
3040
+ }
3041
+ return /*#__PURE__*/jsxRuntime.jsx(ComboboxItemInner, {
3042
+ componentProps: componentProps,
3043
+ forwardedRef: forwardedRef,
3044
+ virtualized: virtualized,
3045
+ indexFromFilter: undefined
3046
+ });
2774
3047
  }));
2775
3048
  if (process.env.NODE_ENV !== "production") ComboboxItem.displayName = "ComboboxItem";
2776
3049
 
@@ -2778,6 +3051,7 @@ const ComboboxRow = /*#__PURE__*/React__namespace.forwardRef(function ComboboxRo
2778
3051
  const {
2779
3052
  render,
2780
3053
  className,
3054
+ style,
2781
3055
  ...elementProps
2782
3056
  } = componentProps;
2783
3057
  const element = useRenderElement.useRenderElement('div', componentProps, {
@@ -2797,12 +3071,19 @@ if (process.env.NODE_ENV !== "production") ComboboxRow.displayName = "ComboboxRo
2797
3071
  * Renders its children only when the list is empty.
2798
3072
  * Requires the `items` prop on the root component.
2799
3073
  * Announces changes politely to screen readers.
3074
+ * This component's root element must remain mounted in the DOM to announce
3075
+ * changes consistently across screen readers. Avoid hiding or removing the
3076
+ * component itself with `display: none`, `hidden`, `aria-hidden`, or conditional
3077
+ * rendering. Prefer updating or conditionally rendering its children instead.
2800
3078
  * Renders a `<div>` element.
3079
+ *
3080
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
2801
3081
  */
2802
3082
  const ComboboxEmpty = /*#__PURE__*/React__namespace.forwardRef(function ComboboxEmpty(componentProps, forwardedRef) {
2803
3083
  const {
2804
3084
  render,
2805
3085
  className,
3086
+ style,
2806
3087
  children: childrenProp,
2807
3088
  ...elementProps
2808
3089
  } = componentProps;
@@ -2810,9 +3091,10 @@ const ComboboxEmpty = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2810
3091
  filteredItems
2811
3092
  } = useComboboxDerivedItemsContext();
2812
3093
  const store = useComboboxRootContext();
3094
+ const emptyRef = useInitialLiveRegionTextMutation();
2813
3095
  const children = filteredItems.length === 0 ? childrenProp : null;
2814
3096
  return useRenderElement.useRenderElement('div', componentProps, {
2815
- ref: [forwardedRef, store.state.emptyRef],
3097
+ ref: [forwardedRef, store.state.emptyRef, emptyRef],
2816
3098
  props: [{
2817
3099
  children,
2818
3100
  role: 'status',
@@ -2860,6 +3142,7 @@ const ComboboxLabel = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2860
3142
  const {
2861
3143
  render,
2862
3144
  className,
3145
+ style,
2863
3146
  ...elementProps
2864
3147
  } = componentProps;
2865
3148
  // Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
@@ -2867,10 +3150,10 @@ const ComboboxLabel = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2867
3150
  delete elementPropsWithoutId.id;
2868
3151
  const fieldRootContext = useLabelableId.useFieldRootContext();
2869
3152
  const store = useComboboxRootContext();
2870
- const inputInsidePopup = InternalBackdrop.useStore(store, selectors.inputInsidePopup);
2871
- const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
2872
- const inputElement = InternalBackdrop.useStore(store, selectors.inputElement);
2873
- const rootId = InternalBackdrop.useStore(store, selectors.id);
3153
+ const inputInsidePopup = useScrollLock.useStore(store, selectors.inputInsidePopup);
3154
+ const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
3155
+ const inputElement = useScrollLock.useStore(store, selectors.inputElement);
3156
+ const rootId = useScrollLock.useStore(store, selectors.id);
2874
3157
  const defaultLabelId = resolveAriaLabelledBy.getDefaultLabelId(rootId);
2875
3158
  const localControlId = triggerElement?.id ?? (inputInsidePopup ? rootId : undefined);
2876
3159
  if (process.env.NODE_ENV !== 'production') {
@@ -2906,13 +3189,13 @@ function ComboboxValue(props) {
2906
3189
  placeholder
2907
3190
  } = props;
2908
3191
  const store = useComboboxRootContext();
2909
- const itemToStringLabel = InternalBackdrop.useStore(store, selectors.itemToStringLabel);
2910
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
2911
- const items = InternalBackdrop.useStore(store, selectors.items);
2912
- const multiple = InternalBackdrop.useStore(store, selectors.selectionMode) === 'multiple';
2913
- const hasSelectedValue = InternalBackdrop.useStore(store, selectors.hasSelectedValue);
3192
+ const itemToStringLabel = useScrollLock.useStore(store, selectors.itemToStringLabel);
3193
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
3194
+ const items = useScrollLock.useStore(store, selectors.items);
3195
+ const multiple = useScrollLock.useStore(store, selectors.selectionMode) === 'multiple';
3196
+ const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
2914
3197
  const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;
2915
- const hasNullLabel = InternalBackdrop.useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
3198
+ const hasNullLabel = useScrollLock.useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
2916
3199
  let children = null;
2917
3200
  if (typeof childrenProp === 'function') {
2918
3201
  children = childrenProp(selectedValue);
@@ -2947,13 +3230,13 @@ const ComboboxItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function
2947
3230
  });
2948
3231
  });
2949
3232
 
2950
- /** The core implementation of the indicator is split here to avoid paying the hooks
2951
- * costs unless the element needs to be mounted. */
3233
+ // Split the core implementation to avoid paying the hook costs unless the element needs to mount.
2952
3234
  if (process.env.NODE_ENV !== "production") ComboboxItemIndicator.displayName = "ComboboxItemIndicator";
2953
3235
  const Inner = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef((componentProps, forwardedRef) => {
2954
3236
  const {
2955
3237
  render,
2956
3238
  className,
3239
+ style,
2957
3240
  keepMounted,
2958
3241
  ...elementProps
2959
3242
  } = componentProps;
@@ -2995,11 +3278,12 @@ const ComboboxChips = /*#__PURE__*/React__namespace.forwardRef(function Combobox
2995
3278
  const {
2996
3279
  render,
2997
3280
  className,
3281
+ style,
2998
3282
  ...elementProps
2999
3283
  } = componentProps;
3000
3284
  const store = useComboboxRootContext();
3001
- const open = InternalBackdrop.useStore(store, selectors.open);
3002
- const hasSelectionChips = InternalBackdrop.useStore(store, selectors.hasSelectionChips);
3285
+ const open = useScrollLock.useStore(store, selectors.open);
3286
+ const hasSelectionChips = useScrollLock.useStore(store, selectors.hasSelectionChips);
3003
3287
  const [highlightedChipIndex, setHighlightedChipIndex] = React__namespace.useState(undefined);
3004
3288
  if (open && highlightedChipIndex !== undefined) {
3005
3289
  setHighlightedChipIndex(undefined);
@@ -3011,7 +3295,11 @@ const ComboboxChips = /*#__PURE__*/React__namespace.forwardRef(function Combobox
3011
3295
  // arrow keys inside a container unless it has a toolbar role.
3012
3296
  props: [hasSelectionChips ? {
3013
3297
  role: 'toolbar'
3014
- } : useRenderElement.EMPTY_OBJECT, elementProps]
3298
+ } : useRenderElement.EMPTY_OBJECT, {
3299
+ onMouseDown(event) {
3300
+ handleInputPress(event, store, store.state.disabled, store.state.readOnly);
3301
+ }
3302
+ }, elementProps]
3015
3303
  });
3016
3304
  const contextValue = React__namespace.useMemo(() => ({
3017
3305
  highlightedChipIndex,
@@ -3033,7 +3321,7 @@ if (process.env.NODE_ENV !== "production") ComboboxChipContext.displayName = "Co
3033
3321
  function useComboboxChipContext() {
3034
3322
  const context = React__namespace.useContext(ComboboxChipContext);
3035
3323
  if (!context) {
3036
- throw new Error(process.env.NODE_ENV !== "production" ? 'useComboboxChipContext must be used within a ComboboxChip' : useRenderElement.formatErrorMessage(17));
3324
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxChipContext is missing. ComboboxChip parts must be placed within <Combobox.Chip>.' : useRenderElement.formatErrorMessage(17));
3037
3325
  }
3038
3326
  return context;
3039
3327
  }
@@ -3042,6 +3330,7 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3042
3330
  const {
3043
3331
  render,
3044
3332
  className,
3333
+ style,
3045
3334
  ...elementProps
3046
3335
  } = componentProps;
3047
3336
  const store = useComboboxRootContext();
@@ -3049,25 +3338,29 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3049
3338
  setHighlightedChipIndex,
3050
3339
  chipsRef
3051
3340
  } = useComboboxChipsContext();
3052
- const disabled = InternalBackdrop.useStore(store, selectors.disabled);
3053
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
3054
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
3341
+ const direction = useScrollLock.useDirection();
3342
+ const disabled = useScrollLock.useStore(store, selectors.disabled);
3343
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
3344
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
3055
3345
  const {
3056
3346
  ref,
3057
3347
  index
3058
3348
  } = useCompositeListItem.useCompositeListItem();
3059
3349
  function handleKeyDown(event) {
3060
3350
  let nextIndex = index;
3061
- if (event.key === 'ArrowLeft') {
3351
+ const isRtl = direction === 'rtl';
3352
+ const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
3353
+ const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
3354
+ if (event.key === previousChipKey) {
3062
3355
  event.preventDefault();
3063
3356
  if (index > 0) {
3064
3357
  nextIndex = index - 1;
3065
3358
  } else {
3066
3359
  nextIndex = undefined;
3067
3360
  }
3068
- } else if (event.key === 'ArrowRight') {
3361
+ } else if (event.key === nextChipKey) {
3069
3362
  event.preventDefault();
3070
- if (index < selectedValue.length - 1) {
3363
+ if (index < chipsRef.current.length - 1) {
3071
3364
  nextIndex = index + 1;
3072
3365
  } else {
3073
3366
  nextIndex = undefined;
@@ -3119,16 +3412,6 @@ const ComboboxChip = /*#__PURE__*/React__namespace.forwardRef(function ComboboxC
3119
3412
  } else {
3120
3413
  chipsRef.current[nextIndex]?.focus();
3121
3414
  }
3122
- },
3123
- onMouseDown(event) {
3124
- if (readOnly) {
3125
- return;
3126
- }
3127
- event.preventDefault();
3128
- if (disabled) {
3129
- return;
3130
- }
3131
- store.state.inputRef.current?.focus();
3132
3415
  }
3133
3416
  }, elementProps]
3134
3417
  });
@@ -3145,6 +3428,8 @@ if (process.env.NODE_ENV !== "production") ComboboxChip.displayName = "ComboboxC
3145
3428
  /**
3146
3429
  * A button to remove a chip.
3147
3430
  * Renders a `<button>` element.
3431
+ *
3432
+ * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
3148
3433
  */
3149
3434
  const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function ComboboxChipRemove(componentProps, forwardedRef) {
3150
3435
  const {
@@ -3152,16 +3437,17 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3152
3437
  className,
3153
3438
  disabled: disabledProp = false,
3154
3439
  nativeButton = true,
3440
+ style,
3155
3441
  ...elementProps
3156
3442
  } = componentProps;
3157
3443
  const store = useComboboxRootContext();
3158
3444
  const {
3159
3445
  index
3160
3446
  } = useComboboxChipContext();
3161
- const comboboxDisabled = InternalBackdrop.useStore(store, selectors.disabled);
3162
- const readOnly = InternalBackdrop.useStore(store, selectors.readOnly);
3163
- const selectedValue = InternalBackdrop.useStore(store, selectors.selectedValue);
3164
- const isItemEqualToValue = InternalBackdrop.useStore(store, selectors.isItemEqualToValue);
3447
+ const comboboxDisabled = useScrollLock.useStore(store, selectors.disabled);
3448
+ const readOnly = useScrollLock.useStore(store, selectors.readOnly);
3449
+ const selectedValue = useScrollLock.useStore(store, selectors.selectedValue);
3450
+ const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
3165
3451
  const disabled = comboboxDisabled || disabledProp;
3166
3452
  const {
3167
3453
  buttonRef,
@@ -3203,6 +3489,9 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
3203
3489
  state,
3204
3490
  props: [{
3205
3491
  tabIndex: -1,
3492
+ onMouseDown(event) {
3493
+ event.preventDefault();
3494
+ },
3206
3495
  onClick(event) {
3207
3496
  if (disabled || readOnly) {
3208
3497
  return;