@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.
- package/dist/Autocomplete/index.cjs +1 -1
- package/dist/Autocomplete/index.mjs +1 -1
- package/dist/Card/index.cjs +4 -4
- package/dist/Card/index.mjs +4 -4
- package/dist/Chip/index.cjs +1 -1
- package/dist/Chip/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
- package/dist/Chips/index.cjs +1 -1
- package/dist/Chips/index.mjs +1 -1
- package/dist/ComboboxChipRemove-cjs.js +668 -379
- package/dist/ComboboxChipRemove-es.js +508 -219
- package/dist/ConfirmationModal/index.cjs +1 -1
- package/dist/ConfirmationModal/index.mjs +1 -1
- package/dist/DataDump/index.cjs +4 -4
- package/dist/DataDump/index.mjs +4 -4
- package/dist/DataList/components/DataListActions/index.cjs +1 -1
- package/dist/DataList/components/DataListActions/index.mjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/index.mjs +1 -1
- package/dist/DataList/components/DataListHeader/index.cjs +1 -1
- package/dist/DataList/components/DataListHeader/index.mjs +1 -1
- package/dist/DataList/components/DataListItem/index.cjs +1 -1
- package/dist/DataList/components/DataListItem/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
- package/dist/DataList/components/DataListItems/index.cjs +1 -1
- package/dist/DataList/components/DataListItems/index.mjs +1 -1
- package/dist/DataList/components/DataListLayout/index.cjs +1 -1
- package/dist/DataList/components/DataListLayout/index.mjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
- package/dist/DataList/index.cjs +1 -1
- package/dist/DataList/index.mjs +1 -1
- package/dist/DatePicker/index.cjs +1 -1
- package/dist/DatePicker/index.mjs +1 -1
- package/dist/Dialog/index.cjs +3 -3
- package/dist/Dialog/index.mjs +3 -3
- package/dist/DrawerDescription-cjs.js +32 -33
- package/dist/DrawerDescription-es.js +19 -20
- package/dist/DrawerTitle-cjs.js +7 -5
- package/dist/DrawerTitle-es.js +7 -5
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
- package/dist/FilterPicker/index.cjs +1 -1
- package/dist/FilterPicker/index.mjs +1 -1
- package/dist/FormFieldWrapper-cjs.js +2 -2
- package/dist/FormFieldWrapper-es.js +2 -2
- package/dist/FormatFile/index.cjs +1 -1
- package/dist/FormatFile/index.mjs +1 -1
- package/dist/Gallery/index.cjs +1 -1
- package/dist/Gallery/index.mjs +1 -1
- package/dist/InputDate/index.cjs +1 -1
- package/dist/InputDate/index.mjs +1 -1
- package/dist/InputNumberExperimental-cjs.js +319 -102
- package/dist/InputNumberExperimental-es.js +321 -104
- package/dist/LightBox/index.cjs +1 -1
- package/dist/LightBox/index.mjs +1 -1
- package/dist/Menu/index.cjs +4 -4
- package/dist/Menu/index.mjs +4 -4
- package/dist/MenuSubmenuTrigger-cjs.js +545 -498
- package/dist/MenuSubmenuTrigger-es.js +490 -443
- package/dist/Modal/index.cjs +1 -1
- package/dist/Modal/index.mjs +1 -1
- package/dist/NumberFieldInput-cjs.js +609 -496
- package/dist/NumberFieldInput-es.js +612 -498
- package/dist/Page/index.cjs +4 -4
- package/dist/Page/index.mjs +4 -4
- package/dist/Popover/index.cjs +1 -1
- package/dist/Popover/index.mjs +1 -1
- package/dist/ScrollAreaViewport-cjs.js +326 -601
- package/dist/ScrollAreaViewport-es.js +278 -548
- package/dist/SelectGroupLabel-cjs.js +494 -528
- package/dist/SelectGroupLabel-es.js +411 -445
- package/dist/Separator-cjs.js +1 -0
- package/dist/Separator-es.js +1 -0
- package/dist/Tooltip/index.cjs +1 -1
- package/dist/Tooltip/index.mjs +1 -1
- package/dist/docs/InputTime/InputTime.md +8 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
- package/dist/floating-ui.react-cjs.js +586 -10
- package/dist/floating-ui.react-dom-cjs.js +92 -92
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +580 -4
- package/dist/floating-ui.utils-cjs.js +159 -0
- package/dist/floating-ui.utils-es.js +136 -0
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
- package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
- package/dist/primitives/BottomSheet/index.cjs +3 -3
- package/dist/primitives/BottomSheet/index.mjs +3 -3
- package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
- package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
- package/dist/primitives/SelectPrimitive/index.cjs +5 -5
- package/dist/primitives/SelectPrimitive/index.mjs +5 -5
- package/dist/primitives/index.cjs +4 -4
- package/dist/primitives/index.mjs +4 -4
- package/dist/resolveAriaLabelledBy-cjs.js +5 -0
- package/dist/resolveAriaLabelledBy-es.js +5 -1
- package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
- package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
- package/dist/styles.css +9 -3
- package/dist/unstyledPrimitives/index.cjs +1217 -639
- package/dist/unstyledPrimitives/index.mjs +1163 -585
- package/dist/useButton-cjs.js +283 -317
- package/dist/useButton-es.js +277 -298
- package/dist/useCompositeListItem-cjs.js +328 -279
- package/dist/useCompositeListItem-es.js +293 -246
- package/dist/useLabel-cjs.js +11 -1
- package/dist/useLabel-es.js +11 -2
- package/dist/useLabelableId-cjs.js +37 -91
- package/dist/useLabelableId-es.js +37 -90
- package/dist/useRenderElement-cjs.js +81 -26
- package/dist/useRenderElement-es.js +81 -27
- package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
- package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
- package/package.json +3 -3
- package/dist/index.esm-cjs.js +0 -738
- 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
|
|
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")
|
|
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:
|
|
71
|
-
labelId:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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:
|
|
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:
|
|
130
|
+
hasNullItemLabel: useScrollLock.createSelector((state, enabled) => {
|
|
93
131
|
return enabled ? resolveAriaLabelledBy.hasNullItemLabel(state.items) : false;
|
|
94
132
|
}),
|
|
95
|
-
open:
|
|
96
|
-
mounted:
|
|
97
|
-
forceMounted:
|
|
98
|
-
inline:
|
|
99
|
-
activeIndex:
|
|
100
|
-
selectedIndex:
|
|
101
|
-
isActive:
|
|
102
|
-
isSelected:
|
|
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:
|
|
111
|
-
popupProps:
|
|
112
|
-
inputProps:
|
|
113
|
-
triggerProps:
|
|
114
|
-
|
|
115
|
-
positionerElement:
|
|
116
|
-
listElement:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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:
|
|
416
|
-
default:
|
|
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
|
|
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 =
|
|
573
|
-
const selectedIndex =
|
|
574
|
-
const positionerElement =
|
|
575
|
-
const listElement =
|
|
576
|
-
const triggerElement =
|
|
577
|
-
const inputElement =
|
|
578
|
-
const inputGroupElement =
|
|
579
|
-
const inline =
|
|
580
|
-
const 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
|
-
} =
|
|
591
|
-
|
|
592
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
779
|
-
if (
|
|
780
|
-
|
|
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
|
|
958
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 :
|
|
1088
|
+
touchOpenDelay: inputInsidePopup ? 0 : 100,
|
|
1038
1089
|
reason: useButton.inputPress
|
|
1039
1090
|
});
|
|
1040
|
-
const dismiss =
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
|
1101
|
-
inputProps
|
|
1167
|
+
popupProps,
|
|
1168
|
+
inputProps,
|
|
1102
1169
|
triggerProps,
|
|
1103
|
-
|
|
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
|
|
1124
|
-
inputProps
|
|
1190
|
+
popupProps,
|
|
1191
|
+
inputProps,
|
|
1125
1192
|
triggerProps,
|
|
1126
1193
|
openMethod,
|
|
1127
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
-
|
|
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(
|
|
1248
|
-
value:
|
|
1249
|
-
children: /*#__PURE__*/jsxRuntime.jsx(
|
|
1250
|
-
value:
|
|
1251
|
-
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
|
-
...
|
|
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 =
|
|
1300
|
-
const comboboxDisabled =
|
|
1301
|
-
const readOnly =
|
|
1302
|
-
const required =
|
|
1303
|
-
const mounted =
|
|
1304
|
-
const popupSideValue =
|
|
1305
|
-
const positionerElement =
|
|
1306
|
-
const listElement =
|
|
1307
|
-
const
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1313
|
-
const
|
|
1314
|
-
const
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
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
|
-
} =
|
|
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':
|
|
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
|
-
|
|
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 =
|
|
1550
|
-
const required =
|
|
1551
|
-
const comboboxDisabled =
|
|
1552
|
-
const readOnly =
|
|
1553
|
-
const name =
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
const
|
|
1557
|
-
const
|
|
1558
|
-
const
|
|
1559
|
-
const
|
|
1560
|
-
const
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1563
|
-
const
|
|
1564
|
-
const
|
|
1565
|
-
const
|
|
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 ===
|
|
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 ===
|
|
1719
|
+
} else if (event.key === nextChipKey) {
|
|
1615
1720
|
event.preventDefault();
|
|
1616
|
-
if (highlightedChipIndex <
|
|
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 ===
|
|
1742
|
+
if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
|
|
1638
1743
|
event.preventDefault();
|
|
1639
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
}),
|
|
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 =
|
|
1910
|
-
const mounted =
|
|
1911
|
-
const popupSideValue =
|
|
1912
|
-
const positionerElement =
|
|
1913
|
-
const comboboxDisabled =
|
|
1914
|
-
const readOnly =
|
|
1915
|
-
const hasSelectedValue =
|
|
1916
|
-
const 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 =
|
|
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
|
-
...
|
|
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 =
|
|
1988
|
-
const comboboxDisabled =
|
|
1989
|
-
const readOnly =
|
|
1990
|
-
const open =
|
|
1991
|
-
const selectedValue =
|
|
1992
|
-
const 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
|
|
2122
|
-
const
|
|
2123
|
-
const
|
|
2124
|
-
const
|
|
2125
|
-
const
|
|
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:
|
|
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 =
|
|
2242
|
-
const 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(
|
|
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
|
-
...
|
|
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 =
|
|
2274
|
-
const mounted =
|
|
2275
|
-
const 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 =
|
|
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 =
|
|
2321
|
-
const open =
|
|
2322
|
-
const mounted =
|
|
2323
|
-
const openMethod =
|
|
2324
|
-
const
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2327
|
-
const
|
|
2328
|
-
const
|
|
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
|
-
|
|
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 =
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
props:
|
|
2389
|
-
|
|
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:
|
|
2393
|
-
children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(
|
|
2394
|
-
inert:
|
|
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
|
-
...
|
|
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 =
|
|
2425
|
-
const open =
|
|
2426
|
-
const openMethod =
|
|
2427
|
-
const transitionStatus =
|
|
2428
|
-
const inputInsidePopup =
|
|
2429
|
-
const inputElement =
|
|
2430
|
-
const 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(
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const
|
|
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 ?
|
|
2871
|
+
const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
|
|
2653
2872
|
const hasRegistered = listItem.index !== -1;
|
|
2654
|
-
const rootId =
|
|
2655
|
-
const highlighted =
|
|
2656
|
-
const matchesSelectedValue =
|
|
2657
|
-
const
|
|
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: [
|
|
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 =
|
|
2871
|
-
const triggerElement =
|
|
2872
|
-
const inputElement =
|
|
2873
|
-
const rootId =
|
|
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 =
|
|
2910
|
-
const selectedValue =
|
|
2911
|
-
const items =
|
|
2912
|
-
const multiple =
|
|
2913
|
-
const 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 =
|
|
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
|
-
|
|
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 =
|
|
3002
|
-
const 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,
|
|
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" ? '
|
|
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
|
|
3053
|
-
const
|
|
3054
|
-
const
|
|
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
|
-
|
|
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 ===
|
|
3361
|
+
} else if (event.key === nextChipKey) {
|
|
3069
3362
|
event.preventDefault();
|
|
3070
|
-
if (index <
|
|
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 =
|
|
3162
|
-
const readOnly =
|
|
3163
|
-
const selectedValue =
|
|
3164
|
-
const 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;
|