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