@jobber/components 9.6.0 → 9.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActivityIndicator-cjs.js +1 -0
- package/dist/ActivityIndicator-es.js +1 -0
- package/dist/Autocomplete/index.cjs +2 -0
- package/dist/Autocomplete/index.mjs +2 -0
- package/dist/Autocomplete-cjs.js +6 -4
- package/dist/Autocomplete-es.js +3 -1
- package/dist/Banner-cjs.js +1 -0
- package/dist/Banner-es.js +1 -0
- package/dist/Card/index.cjs +2 -1
- package/dist/Card/index.mjs +2 -1
- package/dist/Chip/index.cjs +2 -0
- package/dist/Chip/index.mjs +2 -0
- package/dist/Chip-cjs.js +1 -0
- package/dist/Chip-es.js +1 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +2 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +2 -0
- package/dist/Chips/InternalChipDismissible/index.cjs +2 -0
- package/dist/Chips/InternalChipDismissible/index.mjs +2 -0
- package/dist/Chips/index.cjs +2 -0
- package/dist/Chips/index.mjs +2 -0
- package/dist/Combobox/index.cjs +4 -2
- package/dist/Combobox/index.mjs +4 -2
- package/dist/{ComboboxSeparator-cjs.js → ComboboxChipRemove-cjs.js} +455 -501
- package/dist/{ComboboxSeparator-es.js → ComboboxChipRemove-es.js} +456 -500
- package/dist/ComboboxPrimitive-cjs.js +30 -29
- package/dist/ComboboxPrimitive-es.js +3 -2
- package/dist/ConfirmationModal/index.cjs +2 -0
- package/dist/ConfirmationModal/index.mjs +2 -0
- package/dist/ConfirmationModal-cjs.js +1 -0
- package/dist/ConfirmationModal-es.js +1 -0
- package/dist/DataDump/index.cjs +2 -1
- package/dist/DataDump/index.mjs +2 -1
- package/dist/DataDump-cjs.js +1 -0
- package/dist/DataDump-es.js +1 -0
- package/dist/DataList/components/DataListActions/index.cjs +2 -0
- package/dist/DataList/components/DataListActions/index.mjs +2 -0
- package/dist/DataList/components/DataListBulkActions/index.cjs +2 -0
- package/dist/DataList/components/DataListBulkActions/index.mjs +2 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +2 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +2 -0
- package/dist/DataList/components/DataListFilters/index.cjs +2 -0
- package/dist/DataList/components/DataListFilters/index.mjs +2 -0
- package/dist/DataList/components/DataListHeader/index.cjs +2 -0
- package/dist/DataList/components/DataListHeader/index.mjs +2 -0
- package/dist/DataList/components/DataListItem/index.cjs +2 -0
- package/dist/DataList/components/DataListItem/index.mjs +2 -0
- package/dist/DataList/components/DataListItemActions/index.cjs +2 -0
- package/dist/DataList/components/DataListItemActions/index.mjs +2 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +2 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +2 -0
- package/dist/DataList/components/DataListItems/index.cjs +2 -0
- package/dist/DataList/components/DataListItems/index.mjs +2 -0
- package/dist/DataList/components/DataListLayout/index.cjs +2 -0
- package/dist/DataList/components/DataListLayout/index.mjs +2 -0
- package/dist/DataList/components/DataListLayoutActions/index.cjs +2 -0
- package/dist/DataList/components/DataListLayoutActions/index.mjs +2 -0
- package/dist/DataList/index.cjs +2 -0
- package/dist/DataList/index.mjs +2 -0
- package/dist/DataTable-cjs.js +1 -0
- package/dist/DataTable-es.js +1 -0
- package/dist/DatePicker/index.cjs +2 -0
- package/dist/DatePicker/index.mjs +2 -0
- package/dist/DatePicker-cjs.js +8 -7
- package/dist/DatePicker-es.js +6 -5
- package/dist/Dialog/index.cjs +2 -1
- package/dist/Dialog/index.mjs +2 -1
- package/dist/DrawerDescription-cjs.js +99 -268
- package/dist/DrawerDescription-es.js +103 -270
- package/dist/DrawerGrid-cjs.js +1 -0
- package/dist/DrawerGrid-es.js +1 -0
- package/dist/DrawerTitle-cjs.js +6 -2
- package/dist/DrawerTitle-es.js +7 -3
- package/dist/FeatureSwitch-cjs.js +1 -0
- package/dist/FeatureSwitch-es.js +1 -0
- package/dist/FieldDescription-cjs.js +181 -184
- package/dist/FieldDescription-es.js +184 -187
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +2 -0
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +2 -0
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +2 -0
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +2 -0
- package/dist/FilterPicker/index.cjs +2 -0
- package/dist/FilterPicker/index.mjs +2 -0
- package/dist/FilterPickerContent-cjs.js +5 -4
- package/dist/FilterPickerContent-es.js +2 -1
- package/dist/FormFieldWrapper-cjs.js +1 -0
- package/dist/FormFieldWrapper-es.js +1 -0
- package/dist/FormatFile/index.cjs +2 -0
- package/dist/FormatFile/index.mjs +2 -0
- package/dist/FormatFile-cjs.js +1 -0
- package/dist/FormatFile-es.js +1 -0
- package/dist/Gallery/index.cjs +2 -0
- package/dist/Gallery/index.mjs +2 -0
- package/dist/InputAvatar-cjs.js +1 -0
- package/dist/InputAvatar-es.js +1 -0
- package/dist/InputDate/index.cjs +2 -0
- package/dist/InputDate/index.mjs +2 -0
- package/dist/InputDate-cjs.js +1 -0
- package/dist/InputDate-es.js +1 -0
- package/dist/InputNumber/index.cjs +2 -1
- package/dist/InputNumber/index.mjs +2 -1
- package/dist/InternalChipDismissible-cjs.js +1 -0
- package/dist/InternalChipDismissible-es.js +1 -0
- package/dist/LightBox/index.cjs +2 -0
- package/dist/LightBox/index.mjs +2 -0
- package/dist/LightBox-cjs.js +1 -0
- package/dist/LightBox-es.js +1 -0
- package/dist/Menu/index.cjs +2 -1
- package/dist/Menu/index.mjs +2 -1
- package/dist/Menu-cjs.js +6 -4
- package/dist/Menu-es.js +3 -1
- package/dist/MenuSubmenuTrigger-cjs.js +164 -206
- package/dist/MenuSubmenuTrigger-es.js +152 -194
- package/dist/Modal/index.cjs +3 -1
- package/dist/Modal/index.mjs +4 -2
- package/dist/NumberFieldInput-cjs.js +308 -187
- package/dist/NumberFieldInput-es.js +308 -187
- package/dist/Page/index.cjs +2 -1
- package/dist/Page/index.mjs +2 -1
- package/dist/Page-cjs.js +1 -0
- package/dist/Page-es.js +1 -0
- package/dist/Popover/index.cjs +2 -0
- package/dist/Popover/index.mjs +2 -0
- package/dist/Popover-cjs.js +9 -7
- package/dist/Popover-es.js +3 -1
- package/dist/ProgressBar-cjs.js +1 -0
- package/dist/ProgressBar-es.js +1 -0
- package/dist/ScrollAreaViewport-cjs.js +1093 -747
- package/dist/ScrollAreaViewport-es.js +1081 -734
- package/dist/Select/index.cjs +2 -1
- package/dist/Select/index.mjs +2 -1
- package/dist/SelectGroupLabel-cjs.js +306 -207
- package/dist/SelectGroupLabel-es.js +302 -203
- package/dist/SideDrawer-cjs.js +1 -0
- package/dist/SideDrawer-es.js +1 -0
- package/dist/Spinner-cjs.js +1 -0
- package/dist/Spinner-es.js +1 -0
- package/dist/Tooltip/index.cjs +2 -0
- package/dist/Tooltip/index.mjs +2 -0
- package/dist/Tooltip-cjs.js +7 -6
- package/dist/Tooltip-es.js +2 -1
- package/dist/dialogReturnFocus-cjs.js +1 -1
- package/dist/dialogReturnFocus-es.js +2 -2
- package/dist/floating-ui.react-cjs.js +192 -2564
- package/dist/floating-ui.react-dom-cjs.js +2103 -0
- package/dist/floating-ui.react-dom-es.js +2072 -0
- package/dist/floating-ui.react-es.js +156 -2519
- package/dist/floating-ui.utils-cjs.js +342 -0
- package/dist/floating-ui.utils-es.js +300 -0
- package/dist/index.cjs +4 -3
- package/dist/index.mjs +4 -3
- package/dist/primitives/BottomSheet/index.cjs +1 -1
- package/dist/primitives/BottomSheet/index.mjs +1 -1
- package/dist/primitives/ComboboxPrimitive/index.cjs +4 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +4 -2
- package/dist/primitives/SelectPrimitive/index.cjs +2 -0
- package/dist/primitives/SelectPrimitive/index.mjs +2 -0
- package/dist/primitives/index.cjs +3 -2
- package/dist/primitives/index.mjs +3 -2
- package/dist/scrollEdges-cjs.js +11 -1
- package/dist/scrollEdges-es.js +11 -1
- package/dist/sharedHelpers/i18n/index.cjs +1 -0
- package/dist/sharedHelpers/i18n/index.d.ts +1 -0
- package/dist/sharedHelpers/i18n/index.mjs +1 -1
- package/dist/sharedHelpers/i18n/releasedLanguages.d.ts +12 -0
- package/dist/sharedHelpers/i18n/useReleasedLocale.d.ts +6 -0
- package/dist/showToast-cjs.js +1 -1
- package/dist/showToast-es.js +1 -1
- package/dist/testUtils/releasedLanguages.d.ts +16 -0
- package/dist/unstyledPrimitives/index.cjs +478 -720
- package/dist/unstyledPrimitives/index.mjs +421 -663
- package/dist/useAtlantisI18n-cjs.js +44 -11
- package/dist/useAtlantisI18n-es.js +44 -12
- package/dist/useCompositeListItem-cjs.js +361 -2108
- package/dist/useCompositeListItem-es.js +331 -2076
- package/dist/useLabel-cjs.js +3 -2
- package/dist/useLabel-es.js +3 -2
- package/dist/useRenderElement-cjs.js +7 -9
- package/dist/useRenderElement-es.js +7 -9
- package/dist/useScrollLock-cjs.js +344 -819
- package/dist/useScrollLock-es.js +296 -732
- package/dist/useScrollToActive-cjs.js +5 -4
- package/dist/useScrollToActive-es.js +2 -1
- package/package.json +3 -3
- package/dist/scrollable-cjs.js +0 -58
- package/dist/scrollable-es.js +0 -54
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { bi as getGridNavigatedIndex, ag as isIndexOutOfListBounds, N as createSelector, a4 as useDirection, n as useControlled, V as Store, b as useStableCallback, P as useStore, q as useValueAsRef, m as useTransitionStatus, O as useOpenInteractionType, a as useIsoLayoutEffect, E as createGenericEventDetails, z as inputClear, y as inputChange, d as createChangeEventDetails, W as focusOut, X as outsidePress, g as getTarget, a9 as itemPress, f as useOpenChangeComplete, K as useValueChanged, v as none, Q as useClick, bj as inputPress, R as useDismiss, _ as contains, F as FOCUSABLE_POPUP_PROPS, U as useOnFirstRender, w as visuallyHiddenInput, x as visuallyHidden, Y as pressableTriggerOpenStateMapping, k as useTimeout, c as useButton, o as ownerDocument, L as stopEvent, aj as listNavigation, e as closePress, u as useBaseUiId, bd as android, bk as gecko, aF as escapeKey, aG as isInteractiveElement, bl as clearPress, t as transitionStatusMapping, b8 as triggerOpenStateMapping, r as ios, a0 as FloatingPortal, p as popupStateMapping, a1 as InternalBackdrop, a2 as inertValue, a3 as DROPDOWN_COLLISION_AVOIDANCE, i as FloatingFocusManager, S as SafeReact, l as error, bm as chipRemovePress } from './useScrollLock-es.js';
|
|
3
3
|
import { f as formatErrorMessage, c as EMPTY_ARRAY, a as useRefWithInit, E as EMPTY_OBJECT, N as NOOP, m as mergeProps, b as useMergedRefs, u as useRenderElement } from './useRenderElement-es.js';
|
|
4
|
-
import { h as hasNullItemLabel, e as compareItemEquality,
|
|
4
|
+
import { h as hasNullItemLabel, e as compareItemEquality, m as stringifyAsLabel, a as useFormContext, b as useFieldRootContext, c as useLabelableId, E as isGroupedItems, s as stringifyAsValue, d as useRegisterFieldControl, x as selectedValueIncludes, A as removeItem, g as findItemIndex, i as useFloatingRootContext, j as useListNavigation, l as defaultItemEquality, n as areArraysEqual, f as fieldValidityMapping, u as useLabelableContext, k as useTypeahead, r as resolveAriaLabelledBy, o as getPseudoElementBounds, F as FieldRootContext, G as DEFAULT_FIELD_ROOT_CONTEXT, H as DEFAULT_FIELD_STATE_ATTRIBUTES, C as CompositeList, v as useAnchorPositioning, t as useAnchoredPopupScrollLock, w as usePositioner, y as getDisabledMountTransitionStyles, z as useCompositeListItem, I as IndexGuessBehavior, J as getDefaultLabelId, p as resolveMultipleLabels, q as resolveSelectedLabel } from './useCompositeListItem-es.js';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
import * as ReactDOM from 'react-dom';
|
|
7
7
|
import { s as stringifyLocale } from './stringifyLocale-es.js';
|
|
8
|
-
import { i as isScrollableY } from './scrollable-es.js';
|
|
9
8
|
import { u as useLabel } from './useLabel-es.js';
|
|
9
|
+
import { b as isElement } from './floating-ui.utils-es.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Positional arguments are deliberate: property names of an options object
|
|
@@ -82,15 +82,15 @@ function useComboboxHasItemsContext() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const selectors = {
|
|
85
|
-
id: state => state.id,
|
|
86
|
-
labelId: state => state.labelId,
|
|
87
|
-
items: state => state.items,
|
|
88
|
-
selectedValue: state => state.selectedValue,
|
|
89
|
-
hasSelectionChips: state => {
|
|
85
|
+
id: createSelector(state => state.id),
|
|
86
|
+
labelId: createSelector(state => state.labelId),
|
|
87
|
+
items: createSelector(state => state.items),
|
|
88
|
+
selectedValue: createSelector(state => state.selectedValue),
|
|
89
|
+
hasSelectionChips: createSelector(state => {
|
|
90
90
|
const selectedValue = state.selectedValue;
|
|
91
91
|
return Array.isArray(selectedValue) && selectedValue.length > 0;
|
|
92
|
-
},
|
|
93
|
-
hasSelectedValue: state => {
|
|
92
|
+
}),
|
|
93
|
+
hasSelectedValue: createSelector(state => {
|
|
94
94
|
const {
|
|
95
95
|
selectedValue,
|
|
96
96
|
selectionMode
|
|
@@ -102,53 +102,53 @@ const selectors = {
|
|
|
102
102
|
return selectedValue.length > 0;
|
|
103
103
|
}
|
|
104
104
|
return true;
|
|
105
|
-
},
|
|
106
|
-
hasNullItemLabel: (state, enabled) => {
|
|
105
|
+
}),
|
|
106
|
+
hasNullItemLabel: createSelector((state, enabled) => {
|
|
107
107
|
return enabled ? hasNullItemLabel(state.items) : false;
|
|
108
|
-
},
|
|
109
|
-
open: state => state.open,
|
|
110
|
-
mounted: state => state.mounted,
|
|
111
|
-
forceMounted: state => state.forceMounted,
|
|
112
|
-
inline: state => state.inline,
|
|
113
|
-
activeIndex: state => state.activeIndex,
|
|
114
|
-
selectedIndex: state => state.selectedIndex,
|
|
115
|
-
isActive: (state, index) => state.activeIndex === index,
|
|
116
|
-
isSelected: (state, itemValue) => {
|
|
108
|
+
}),
|
|
109
|
+
open: createSelector(state => state.open),
|
|
110
|
+
mounted: createSelector(state => state.mounted),
|
|
111
|
+
forceMounted: createSelector(state => state.forceMounted),
|
|
112
|
+
inline: createSelector(state => state.inline),
|
|
113
|
+
activeIndex: createSelector(state => state.activeIndex),
|
|
114
|
+
selectedIndex: createSelector(state => state.selectedIndex),
|
|
115
|
+
isActive: createSelector((state, index) => state.activeIndex === index),
|
|
116
|
+
isSelected: createSelector((state, itemValue) => {
|
|
117
117
|
const comparer = state.isItemEqualToValue;
|
|
118
118
|
const selectedValue = state.selectedValue;
|
|
119
119
|
if (Array.isArray(selectedValue)) {
|
|
120
120
|
return selectedValue.some(selectedItem => compareItemEquality(itemValue, selectedItem, comparer));
|
|
121
121
|
}
|
|
122
122
|
return compareItemEquality(itemValue, selectedValue, comparer);
|
|
123
|
-
},
|
|
124
|
-
transitionStatus: state => state.transitionStatus,
|
|
125
|
-
popupProps: state => state.popupProps,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
123
|
+
}),
|
|
124
|
+
transitionStatus: createSelector(state => state.transitionStatus),
|
|
125
|
+
popupProps: createSelector(state => state.popupProps),
|
|
126
|
+
inputProps: createSelector(state => state.inputProps),
|
|
127
|
+
triggerProps: createSelector(state => state.triggerProps),
|
|
128
|
+
itemProps: createSelector(state => state.itemProps),
|
|
129
|
+
positionerElement: createSelector(state => state.positionerElement),
|
|
130
|
+
listElement: createSelector(state => state.listElement),
|
|
131
|
+
popupId: createSelector(state => state.popupId),
|
|
132
|
+
triggerElement: createSelector(state => state.triggerElement),
|
|
133
|
+
inputElement: createSelector(state => state.inputElement),
|
|
134
|
+
inputGroupElement: createSelector(state => state.inputGroupElement),
|
|
135
|
+
popupSide: createSelector(state => state.popupSide),
|
|
136
|
+
openMethod: createSelector(state => state.openMethod),
|
|
137
|
+
inputInsidePopup: createSelector(state => state.inputInsidePopup),
|
|
138
|
+
inputOwnsFormValue: createSelector(state => state.inputOwnsFormValue),
|
|
139
|
+
selectionMode: createSelector(state => state.selectionMode),
|
|
140
|
+
name: createSelector(state => state.name),
|
|
141
|
+
form: createSelector(state => state.form),
|
|
142
|
+
disabled: createSelector(state => state.disabled),
|
|
143
|
+
readOnly: createSelector(state => state.readOnly),
|
|
144
|
+
required: createSelector(state => state.required),
|
|
145
|
+
grid: createSelector(state => state.grid),
|
|
146
|
+
virtualized: createSelector(state => state.virtualized),
|
|
147
|
+
itemToStringLabel: createSelector(state => state.itemToStringLabel),
|
|
148
|
+
isItemEqualToValue: createSelector(state => state.isItemEqualToValue),
|
|
149
|
+
modal: createSelector(state => state.modal),
|
|
150
|
+
autoHighlight: createSelector(state => state.autoHighlight),
|
|
151
|
+
submitOnItemClick: createSelector(state => state.submitOnItemClick)
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
/**
|
|
@@ -171,7 +171,8 @@ function createCollatorItemFilter(collatorFilter, itemToStringLabel) {
|
|
|
171
171
|
if (item == null) {
|
|
172
172
|
return false;
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
const itemString = stringifyAsLabel(item, itemToStringLabel);
|
|
175
|
+
return collatorFilter.contains(itemString, query);
|
|
175
176
|
};
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -187,13 +188,14 @@ function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel,
|
|
|
187
188
|
if (!query) {
|
|
188
189
|
return true;
|
|
189
190
|
}
|
|
191
|
+
const itemString = stringifyAsLabel(item, itemToStringLabel);
|
|
190
192
|
const selectedString = selectedValue != null ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';
|
|
191
193
|
|
|
192
194
|
// Handle case-insensitive matching consistently
|
|
193
195
|
if (selectedString && collatorFilter.contains(selectedString, query) && selectedString.length === query.length) {
|
|
194
196
|
return true;
|
|
195
197
|
}
|
|
196
|
-
return collatorFilter.contains(
|
|
198
|
+
return collatorFilter.contains(itemString, query);
|
|
197
199
|
};
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -265,8 +267,9 @@ function useComboboxFilter(options = {}) {
|
|
|
265
267
|
return createSingleSelectionCollatorFilter(coreFilter, itemToString, value)(item, query);
|
|
266
268
|
}, [coreFilter, value, multiple]);
|
|
267
269
|
return React.useMemo(() => ({
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
contains,
|
|
271
|
+
startsWith: coreFilter.startsWith,
|
|
272
|
+
endsWith: coreFilter.endsWith
|
|
270
273
|
}), [contains, coreFilter]);
|
|
271
274
|
}
|
|
272
275
|
|
|
@@ -287,7 +290,7 @@ function AriaCombobox(props) {
|
|
|
287
290
|
inputValue: inputValueProp,
|
|
288
291
|
open: openProp,
|
|
289
292
|
defaultOpen = false,
|
|
290
|
-
selectionMode,
|
|
293
|
+
selectionMode = 'none',
|
|
291
294
|
onItemHighlighted: onItemHighlightedProp,
|
|
292
295
|
name: nameProp,
|
|
293
296
|
form,
|
|
@@ -360,10 +363,12 @@ function AriaCombobox(props) {
|
|
|
360
363
|
*/
|
|
361
364
|
const valuesRef = React.useRef([]);
|
|
362
365
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
366
|
+
* Contains all item values in a stable, unfiltered order.
|
|
367
|
+
* This is only used when `items` prop is not provided.
|
|
368
|
+
* It accumulates values on first mount and does not remove them on unmount due to
|
|
369
|
+
* filtering, providing a stable index for selected value tracking.
|
|
365
370
|
*/
|
|
366
|
-
const
|
|
371
|
+
const allValuesRef = React.useRef([]);
|
|
367
372
|
const disabled = fieldDisabled || disabledProp;
|
|
368
373
|
const name = fieldName ?? nameProp;
|
|
369
374
|
const multiple = selectionMode === 'multiple';
|
|
@@ -390,10 +395,11 @@ function AriaCombobox(props) {
|
|
|
390
395
|
if (filterProp !== undefined) {
|
|
391
396
|
return filterProp;
|
|
392
397
|
}
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
if (single && !queryChangedAfterOpen) {
|
|
399
|
+
return createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue);
|
|
400
|
+
}
|
|
395
401
|
return createCollatorItemFilter(collatorFilter, itemToStringLabel);
|
|
396
|
-
}, [filterProp, collatorFilter, itemToStringLabel]);
|
|
402
|
+
}, [filterProp, single, selectedValue, queryChangedAfterOpen, collatorFilter, itemToStringLabel]);
|
|
397
403
|
|
|
398
404
|
// If neither inputValue nor defaultInputValue are provided, derive it from the
|
|
399
405
|
// selected value for single mode so the input reflects the selection on mount.
|
|
@@ -419,9 +425,9 @@ function AriaCombobox(props) {
|
|
|
419
425
|
state: 'open'
|
|
420
426
|
});
|
|
421
427
|
const isGrouped = isGroupedItems(items);
|
|
422
|
-
const query = closeQuery ?? String(inputValue).trim();
|
|
428
|
+
const query = closeQuery ?? (inputValue === '' ? '' : String(inputValue).trim());
|
|
423
429
|
const selectedLabelString = single ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';
|
|
424
|
-
const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
|
|
430
|
+
const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
|
|
425
431
|
const filterQuery = shouldBypassFiltering ? '' : query;
|
|
426
432
|
const shouldIgnoreExternalFiltering = hasItems && hasFilteredItemsProp && shouldBypassFiltering;
|
|
427
433
|
const flatItems = React.useMemo(() => {
|
|
@@ -448,18 +454,12 @@ function AriaCombobox(props) {
|
|
|
448
454
|
if (limit > -1 && currentCount >= limit) {
|
|
449
455
|
break;
|
|
450
456
|
}
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
for (const item of group.items) {
|
|
455
|
-
if (itemsToTake.length >= remainingLimit) {
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
if (filter(item, filterQuery, itemToStringLabel)) {
|
|
459
|
-
itemsToTake.push(item);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
457
|
+
const candidateItems = filterQuery === '' ? group.items : group.items.filter(item => filter(item, filterQuery, itemToStringLabel));
|
|
458
|
+
if (candidateItems.length === 0) {
|
|
459
|
+
continue;
|
|
462
460
|
}
|
|
461
|
+
const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
|
|
462
|
+
const itemsToTake = candidateItems.slice(0, remainingLimit);
|
|
463
463
|
if (itemsToTake.length > 0) {
|
|
464
464
|
const newGroup = {
|
|
465
465
|
...group,
|
|
@@ -499,85 +499,76 @@ function AriaCombobox(props) {
|
|
|
499
499
|
}
|
|
500
500
|
return filteredItems;
|
|
501
501
|
}, [filteredItems, isGrouped]);
|
|
502
|
-
const store = useRefWithInit(() => {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
setOpen: NOOP,
|
|
573
|
-
setInputValue: NOOP,
|
|
574
|
-
setSelectedValue: NOOP,
|
|
575
|
-
setIndices: NOOP,
|
|
576
|
-
handleSelection: NOOP,
|
|
577
|
-
forceMount: NOOP,
|
|
578
|
-
requestSubmit: NOOP
|
|
579
|
-
});
|
|
580
|
-
}).current;
|
|
502
|
+
const store = useRefWithInit(() => new Store({
|
|
503
|
+
id,
|
|
504
|
+
labelId: undefined,
|
|
505
|
+
selectedValue,
|
|
506
|
+
open,
|
|
507
|
+
filter,
|
|
508
|
+
query,
|
|
509
|
+
items,
|
|
510
|
+
selectionMode,
|
|
511
|
+
listRef,
|
|
512
|
+
labelsRef,
|
|
513
|
+
popupRef,
|
|
514
|
+
emptyRef,
|
|
515
|
+
inputRef,
|
|
516
|
+
startDismissRef,
|
|
517
|
+
endDismissRef,
|
|
518
|
+
keyboardActiveRef,
|
|
519
|
+
chipsContainerRef,
|
|
520
|
+
clearRef,
|
|
521
|
+
valuesRef,
|
|
522
|
+
allValuesRef,
|
|
523
|
+
selectionEventRef,
|
|
524
|
+
name,
|
|
525
|
+
form,
|
|
526
|
+
disabled,
|
|
527
|
+
readOnly,
|
|
528
|
+
required,
|
|
529
|
+
grid,
|
|
530
|
+
isGrouped,
|
|
531
|
+
virtualized,
|
|
532
|
+
openOnInputClick,
|
|
533
|
+
itemToStringLabel,
|
|
534
|
+
isItemEqualToValue,
|
|
535
|
+
modal,
|
|
536
|
+
autoHighlight: autoHighlightMode,
|
|
537
|
+
submitOnItemClick,
|
|
538
|
+
hasInputValue,
|
|
539
|
+
mounted: false,
|
|
540
|
+
forceMounted: false,
|
|
541
|
+
transitionStatus: 'idle',
|
|
542
|
+
inline: inlineProp,
|
|
543
|
+
activeIndex: null,
|
|
544
|
+
selectedIndex: null,
|
|
545
|
+
popupProps: {},
|
|
546
|
+
inputProps: {},
|
|
547
|
+
triggerProps: {},
|
|
548
|
+
itemProps: EMPTY_OBJECT,
|
|
549
|
+
positionerElement: null,
|
|
550
|
+
listElement: null,
|
|
551
|
+
popupId: undefined,
|
|
552
|
+
triggerElement: null,
|
|
553
|
+
inputElement: null,
|
|
554
|
+
inputGroupElement: null,
|
|
555
|
+
popupSide: null,
|
|
556
|
+
openMethod: null,
|
|
557
|
+
inputInsidePopup: true,
|
|
558
|
+
// Avoid duplicate names in the server HTML. Popup inputs aren't rendered
|
|
559
|
+
// until after hydration, so the hidden input takes over then if needed.
|
|
560
|
+
inputOwnsFormValue: selectionMode === 'none',
|
|
561
|
+
onOpenChangeComplete: onOpenChangeCompleteProp || NOOP,
|
|
562
|
+
// Placeholder callbacks replaced on first render
|
|
563
|
+
setOpen: NOOP,
|
|
564
|
+
setInputValue: NOOP,
|
|
565
|
+
setSelectedValue: NOOP,
|
|
566
|
+
setIndices: NOOP,
|
|
567
|
+
onItemHighlighted: NOOP,
|
|
568
|
+
handleSelection: NOOP,
|
|
569
|
+
forceMount: NOOP,
|
|
570
|
+
requestSubmit: NOOP
|
|
571
|
+
})).current;
|
|
581
572
|
const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;
|
|
582
573
|
const fieldStringValue = React.useMemo(() => {
|
|
583
574
|
if (selectionMode === 'none') {
|
|
@@ -620,38 +611,35 @@ function AriaCombobox(props) {
|
|
|
620
611
|
store.set('forceMounted', true);
|
|
621
612
|
}
|
|
622
613
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const emitHighlight = useStableCallback((value, index, type) => {
|
|
629
|
-
if (index === -1) {
|
|
630
|
-
if (lastHighlightRef.current === INITIAL_LAST_HIGHLIGHT) {
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
634
|
-
} else {
|
|
635
|
-
lastHighlightRef.current = {
|
|
636
|
-
value,
|
|
637
|
-
index
|
|
638
|
-
};
|
|
614
|
+
const initialSelectedValueRef = React.useRef(selectedValue);
|
|
615
|
+
useIsoLayoutEffect(() => {
|
|
616
|
+
// Ensure the values and labels are registered for programmatic value changes.
|
|
617
|
+
if (selectedValue !== initialSelectedValueRef.current) {
|
|
618
|
+
forceMount();
|
|
639
619
|
}
|
|
640
|
-
|
|
641
|
-
index
|
|
642
|
-
}));
|
|
643
|
-
});
|
|
620
|
+
}, [forceMount, selectedValue]);
|
|
644
621
|
const setIndices = useStableCallback(options => {
|
|
645
622
|
store.update(options);
|
|
646
|
-
const
|
|
647
|
-
if (
|
|
623
|
+
const type = options.type || 'none';
|
|
624
|
+
if (options.activeIndex === undefined) {
|
|
648
625
|
return;
|
|
649
626
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
627
|
+
if (options.activeIndex === null) {
|
|
628
|
+
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
629
|
+
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
630
|
+
onItemHighlighted(undefined, createGenericEventDetails(type, undefined, {
|
|
631
|
+
index: -1
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
653
634
|
} else {
|
|
654
|
-
|
|
635
|
+
const activeValue = valuesRef.current[options.activeIndex];
|
|
636
|
+
lastHighlightRef.current = {
|
|
637
|
+
value: activeValue,
|
|
638
|
+
index: options.activeIndex
|
|
639
|
+
};
|
|
640
|
+
onItemHighlighted(activeValue, createGenericEventDetails(type, undefined, {
|
|
641
|
+
index: options.activeIndex
|
|
642
|
+
}));
|
|
655
643
|
}
|
|
656
644
|
});
|
|
657
645
|
const setInputValue = useStableCallback((next, eventDetails) => {
|
|
@@ -664,11 +652,6 @@ function AriaCombobox(props) {
|
|
|
664
652
|
// If user is typing, ensure we don't auto-highlight on open due to a race
|
|
665
653
|
// with the post-open effect that sets this flag.
|
|
666
654
|
if (eventDetails.reason === inputChange) {
|
|
667
|
-
// A controlled popup may ignore a close request. Resuming input proves the popup
|
|
668
|
-
// is remaining open, so release the query captured for an exit animation.
|
|
669
|
-
if (open && closeQuery !== null) {
|
|
670
|
-
setCloseQuery(null);
|
|
671
|
-
}
|
|
672
655
|
const event = eventDetails.event;
|
|
673
656
|
const inputType = event.inputType;
|
|
674
657
|
// Treat composition commits as typed input; autofill may omit `inputType` or
|
|
@@ -684,34 +667,10 @@ function AriaCombobox(props) {
|
|
|
684
667
|
pendingQueryHighlightRef.current = {
|
|
685
668
|
hasQuery
|
|
686
669
|
};
|
|
687
|
-
|
|
688
|
-
// Virtualized lists own their scroller. Reset regular lists directly so a stale
|
|
689
|
-
// composite registry cannot select a reordered item and scrolling cannot escape
|
|
690
|
-
// the popup.
|
|
691
|
-
const list = store.state.listElement;
|
|
692
|
-
if (!store.state.virtualized && list) {
|
|
693
|
-
const popup = popupRef.current;
|
|
694
|
-
for (const ancestor of getOverflowAncestors(list.firstElementChild ?? list)) {
|
|
695
|
-
if (!isHTMLElement(ancestor) || (popup ? !contains(popup, ancestor) : ancestor.getAttribute('role') === 'dialog')) {
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
if (isScrollableY(ancestor)) {
|
|
699
|
-
ancestor.scrollTop = 0;
|
|
700
|
-
break;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
if (hasQuery && autoHighlightMode && store.state.activeIndex == null && (open || inline)) {
|
|
670
|
+
if (hasQuery && autoHighlightMode && store.state.activeIndex == null) {
|
|
705
671
|
store.set('activeIndex', 0);
|
|
706
672
|
}
|
|
707
673
|
}
|
|
708
|
-
} else if (eventDetails.reason === inputClear && next === '' && store.state.inputInsidePopup) {
|
|
709
|
-
// A programmatic clear of an active query (e.g. after selecting an item with the
|
|
710
|
-
// input inside the popup): restore the highlight to the selected item.
|
|
711
|
-
pendingQueryHighlightRef.current = {
|
|
712
|
-
hasQuery: false,
|
|
713
|
-
selection: true
|
|
714
|
-
};
|
|
715
674
|
}
|
|
716
675
|
setInputValueUnwrapped(next);
|
|
717
676
|
});
|
|
@@ -723,7 +682,7 @@ function AriaCombobox(props) {
|
|
|
723
682
|
// If the `Empty` component is not used, the positioner or popup should be hidden
|
|
724
683
|
// with CSS. In this case, allow the Escape key to bubble to close a parent popup
|
|
725
684
|
// if there are no items to show.
|
|
726
|
-
if (eventDetails.reason ===
|
|
685
|
+
if (eventDetails.reason === 'escape-key' && hasItems && flatFilteredItems.length === 0 && !store.state.emptyRef.current) {
|
|
727
686
|
eventDetails.allowPropagation();
|
|
728
687
|
}
|
|
729
688
|
props.onOpenChange?.(nextOpen, eventDetails);
|
|
@@ -733,10 +692,10 @@ function AriaCombobox(props) {
|
|
|
733
692
|
|
|
734
693
|
// If reopening interrupts the close animation, handleUnmount won't run to clear the
|
|
735
694
|
// frozen closeQuery and pending popup input.
|
|
736
|
-
if (nextOpen && inputInsidePopup && !inline && closeQuery !== null) {
|
|
695
|
+
if (nextOpen && multiple && inputInsidePopup && !inline && closeQuery !== null) {
|
|
737
696
|
setQueryChangedAfterOpen(false);
|
|
738
697
|
setCloseQuery(null);
|
|
739
|
-
if (inputValue !== ''
|
|
698
|
+
if (inputValue !== '') {
|
|
740
699
|
setInputValue('', createChangeEventDetails(inputClear, eventDetails.event));
|
|
741
700
|
}
|
|
742
701
|
}
|
|
@@ -790,8 +749,18 @@ function AriaCombobox(props) {
|
|
|
790
749
|
if (shouldFillInput) {
|
|
791
750
|
setInputValue(stringifyAsLabel(nextValue, itemToStringLabel), createChangeEventDetails(eventDetails.reason, eventDetails.event));
|
|
792
751
|
}
|
|
752
|
+
if (single && nextValue != null && eventDetails.reason !== inputChange && queryChangedAfterOpen && !inline) {
|
|
753
|
+
setCloseQuery(query);
|
|
754
|
+
}
|
|
793
755
|
});
|
|
794
|
-
const handleSelection = useStableCallback((event,
|
|
756
|
+
const handleSelection = useStableCallback((event, passedValue) => {
|
|
757
|
+
let itemValue = passedValue;
|
|
758
|
+
if (itemValue === undefined) {
|
|
759
|
+
if (activeIndex === null) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
itemValue = valuesRef.current[activeIndex];
|
|
763
|
+
}
|
|
795
764
|
const targetEl = getTarget(event);
|
|
796
765
|
const overrideEvent = selectionEventRef.current ?? event;
|
|
797
766
|
selectionEventRef.current = null;
|
|
@@ -807,8 +776,8 @@ function AriaCombobox(props) {
|
|
|
807
776
|
}
|
|
808
777
|
if (multiple) {
|
|
809
778
|
const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
810
|
-
const isCurrentlySelected = selectedValueIncludes(currentSelectedValue, itemValue, isItemEqualToValue);
|
|
811
|
-
const nextValue = isCurrentlySelected ? removeItem(currentSelectedValue, itemValue, isItemEqualToValue) : [...currentSelectedValue, itemValue];
|
|
779
|
+
const isCurrentlySelected = selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
|
|
780
|
+
const nextValue = isCurrentlySelected ? removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
|
|
812
781
|
setSelectedValue(nextValue, eventDetails);
|
|
813
782
|
if (eventDetails.isCanceled) {
|
|
814
783
|
return;
|
|
@@ -831,6 +800,9 @@ function AriaCombobox(props) {
|
|
|
831
800
|
}
|
|
832
801
|
});
|
|
833
802
|
const requestSubmit = useStableCallback(() => {
|
|
803
|
+
if (!store.state.submitOnItemClick) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
834
806
|
const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
|
|
835
807
|
if (formElement && typeof formElement.requestSubmit === 'function') {
|
|
836
808
|
formElement.requestSubmit();
|
|
@@ -903,29 +875,25 @@ function AriaCombobox(props) {
|
|
|
903
875
|
React.useImperativeHandle(props.actionsRef, () => ({
|
|
904
876
|
unmount: handleUnmount
|
|
905
877
|
}), [handleUnmount]);
|
|
906
|
-
useIsoLayoutEffect(function
|
|
907
|
-
if (open) {
|
|
878
|
+
useIsoLayoutEffect(function syncSelectedIndex() {
|
|
879
|
+
if (open || selectionMode === 'none') {
|
|
908
880
|
return;
|
|
909
881
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
882
|
+
const registry = items ? flatItems : allValuesRef.current;
|
|
883
|
+
if (multiple) {
|
|
884
|
+
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
885
|
+
const lastValue = currentValue[currentValue.length - 1];
|
|
886
|
+
const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);
|
|
887
|
+
setIndices({
|
|
888
|
+
selectedIndex: lastIndex === -1 ? null : lastIndex
|
|
889
|
+
});
|
|
890
|
+
} else {
|
|
891
|
+
const index = findItemIndex(registry, selectedValue, isItemEqualToValue);
|
|
892
|
+
setIndices({
|
|
893
|
+
selectedIndex: index === -1 ? null : index
|
|
894
|
+
});
|
|
916
895
|
}
|
|
917
|
-
|
|
918
|
-
// Without `items`, look the selection up in the live registry of mounted item
|
|
919
|
-
// values (the list stays mounted while closed when closed-state features need
|
|
920
|
-
// it — trigger interaction and rendered-label autofill force-mount it). Mounted
|
|
921
|
-
// items re-assert the index themselves when their registration moves; when
|
|
922
|
-
// nothing is mounted the lookup resolves to `null` and each item re-registers
|
|
923
|
-
// the index on the next open.
|
|
924
|
-
const registry = hasItems ? flatItems : valuesRef.current;
|
|
925
|
-
setIndices({
|
|
926
|
-
selectedIndex: findSelectionIndex(registry, selectedValue, isItemEqualToValue, multiple)
|
|
927
|
-
});
|
|
928
|
-
}, [open, selectedValue, selectionMode, multiple, hasItems, flatItems, isItemEqualToValue, setIndices]);
|
|
896
|
+
}, [open, selectedValue, items, selectionMode, flatItems, multiple, isItemEqualToValue, setIndices]);
|
|
929
897
|
useIsoLayoutEffect(() => {
|
|
930
898
|
if (items) {
|
|
931
899
|
valuesRef.current = flatFilteredItems;
|
|
@@ -935,52 +903,14 @@ function AriaCombobox(props) {
|
|
|
935
903
|
useIsoLayoutEffect(() => {
|
|
936
904
|
const pendingHighlight = pendingQueryHighlightRef.current;
|
|
937
905
|
if (pendingHighlight) {
|
|
938
|
-
// A directly rendered list remains visible when the popup state is closed, while a
|
|
939
|
-
// kept-mounted Positioner is hidden and should stay inert.
|
|
940
|
-
const listIsNavigable = open || inline || store.state.positionerElement?.hidden === false;
|
|
941
906
|
if (pendingHighlight.hasQuery) {
|
|
942
|
-
if (autoHighlightMode
|
|
907
|
+
if (autoHighlightMode) {
|
|
943
908
|
store.set('activeIndex', 0);
|
|
944
909
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
// Only handle the clear once it has committed (a controlled input may reject it),
|
|
948
|
-
// so a restore cannot fire while a query is still active.
|
|
949
|
-
pendingQueryHighlightRef.current = null;
|
|
950
|
-
if (listIsNavigable) {
|
|
951
|
-
const clearedBySelection = pendingHighlight.selection;
|
|
952
|
-
if (autoHighlightMode === 'always' && !clearedBySelection && store.state.selectionMode === 'none') {
|
|
953
|
-
// There is no selection to restore in Autocomplete. Keep the first-item reset
|
|
954
|
-
// synchronous so list navigation sees it before a directly rendered list closes.
|
|
955
|
-
store.set('activeIndex', 0);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// Items re-mounted by the clear publish their composite indices in a follow-up
|
|
959
|
-
// commit, so the item registries are mid-update here. Defer past React's cascade.
|
|
960
|
-
queueMicrotask(() => {
|
|
961
|
-
if (!store.state.open && !store.state.inline || inputRef.current && inputRef.current.value.trim() !== '') {
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// Return the highlight to the selected item, the same anchor the popup uses
|
|
966
|
-
// when it first opens. Read the selection through the store so consumers can
|
|
967
|
-
// pass an inline `isItemEqualToValue` or a fresh `selectedValue` array without
|
|
968
|
-
// re-running this effect on every render.
|
|
969
|
-
const currentSelectedValue = store.state.selectedValue;
|
|
970
|
-
const isMultiple = store.state.selectionMode === 'multiple';
|
|
971
|
-
const lastSelectedValue = isMultiple && Array.isArray(currentSelectedValue) ? currentSelectedValue[currentSelectedValue.length - 1] : currentSelectedValue;
|
|
972
|
-
const hasSelection = store.state.selectionMode !== 'none' && lastSelectedValue != null;
|
|
973
|
-
if (hasSelection || clearedBySelection) {
|
|
974
|
-
const registry = hasItems || hasFilteredItemsProp ? flatFilteredItems : valuesRef.current;
|
|
975
|
-
// A selection that is no longer in the list drops the highlight rather than
|
|
976
|
-
// leaving it on whichever item now occupies that index.
|
|
977
|
-
store.set('activeIndex', hasSelection ? findSelectionIndex(registry, currentSelectedValue, store.state.isItemEqualToValue, isMultiple) : null);
|
|
978
|
-
} else if (autoHighlightMode === 'always') {
|
|
979
|
-
store.set('activeIndex', 0);
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
}
|
|
910
|
+
} else if (autoHighlightMode === 'always') {
|
|
911
|
+
store.set('activeIndex', 0);
|
|
983
912
|
}
|
|
913
|
+
pendingQueryHighlightRef.current = null;
|
|
984
914
|
}
|
|
985
915
|
if (!open && !inline) {
|
|
986
916
|
return;
|
|
@@ -993,11 +923,21 @@ function AriaCombobox(props) {
|
|
|
993
923
|
store.set('activeIndex', 0);
|
|
994
924
|
return;
|
|
995
925
|
}
|
|
996
|
-
|
|
926
|
+
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
927
|
+
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
928
|
+
store.state.onItemHighlighted(undefined, createGenericEventDetails(none, undefined, {
|
|
929
|
+
index: -1
|
|
930
|
+
}));
|
|
931
|
+
}
|
|
997
932
|
return;
|
|
998
933
|
}
|
|
999
934
|
if (storeActiveIndex >= candidateItems.length) {
|
|
1000
|
-
|
|
935
|
+
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
936
|
+
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
937
|
+
store.state.onItemHighlighted(undefined, createGenericEventDetails(none, undefined, {
|
|
938
|
+
index: -1
|
|
939
|
+
}));
|
|
940
|
+
}
|
|
1001
941
|
store.set('activeIndex', null);
|
|
1002
942
|
return;
|
|
1003
943
|
}
|
|
@@ -1005,12 +945,15 @@ function AriaCombobox(props) {
|
|
|
1005
945
|
const previouslyHighlightedItemValue = lastHighlightRef.current.value;
|
|
1006
946
|
const isSameItem = previouslyHighlightedItemValue !== NO_ACTIVE_VALUE && compareItemEquality(itemValue, previouslyHighlightedItemValue, store.state.isItemEqualToValue);
|
|
1007
947
|
if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {
|
|
1008
|
-
|
|
948
|
+
lastHighlightRef.current = {
|
|
949
|
+
value: itemValue,
|
|
950
|
+
index: storeActiveIndex
|
|
951
|
+
};
|
|
952
|
+
store.state.onItemHighlighted(itemValue, createGenericEventDetails(none, undefined, {
|
|
953
|
+
index: storeActiveIndex
|
|
954
|
+
}));
|
|
1009
955
|
}
|
|
1010
|
-
}, [activeIndex, autoHighlightMode,
|
|
1011
|
-
// Reruns the effect when the query changes without affecting the deps above, such as
|
|
1012
|
-
// clearing the input when no items are filtered out (individually rendered items).
|
|
1013
|
-
inputValue]);
|
|
956
|
+
}, [activeIndex, autoHighlightMode, hasFilteredItemsProp, hasItems, flatFilteredItems, inline, open, store]);
|
|
1014
957
|
useIsoLayoutEffect(() => {
|
|
1015
958
|
if (selectionMode === 'none') {
|
|
1016
959
|
setFilled(String(inputValue) !== '');
|
|
@@ -1041,12 +984,6 @@ function AriaCombobox(props) {
|
|
|
1041
984
|
}
|
|
1042
985
|
setQueryChangedAfterOpen(true);
|
|
1043
986
|
});
|
|
1044
|
-
function syncInputToSelectedLabel() {
|
|
1045
|
-
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
1046
|
-
if (inputValue !== nextInputValue) {
|
|
1047
|
-
setInputValue(nextInputValue, createChangeEventDetails(none));
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
987
|
useValueChanged(selectedValue, () => {
|
|
1051
988
|
if (selectionMode === 'none') {
|
|
1052
989
|
return;
|
|
@@ -1055,7 +992,10 @@ function AriaCombobox(props) {
|
|
|
1055
992
|
setDirty(isSelectedValueDirty(selectedValue));
|
|
1056
993
|
validation.change(selectedValue);
|
|
1057
994
|
if (single && !hasInputValue && !inputInsidePopup) {
|
|
1058
|
-
|
|
995
|
+
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
996
|
+
if (inputValue !== nextInputValue) {
|
|
997
|
+
setInputValue(nextInputValue, createChangeEventDetails(none));
|
|
998
|
+
}
|
|
1059
999
|
}
|
|
1060
1000
|
});
|
|
1061
1001
|
useValueChanged(inputValue, () => {
|
|
@@ -1070,7 +1010,10 @@ function AriaCombobox(props) {
|
|
|
1070
1010
|
if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
|
|
1071
1011
|
return;
|
|
1072
1012
|
}
|
|
1073
|
-
|
|
1013
|
+
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
1014
|
+
if (inputValue !== nextInputValue) {
|
|
1015
|
+
setInputValue(nextInputValue, createChangeEventDetails(none));
|
|
1016
|
+
}
|
|
1074
1017
|
});
|
|
1075
1018
|
const floatingRootContext = useFloatingRootContext({
|
|
1076
1019
|
open: inline ? true : open,
|
|
@@ -1080,19 +1023,18 @@ function AriaCombobox(props) {
|
|
|
1080
1023
|
floating: positionerElement
|
|
1081
1024
|
}
|
|
1082
1025
|
});
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1026
|
+
let ariaHasPopup;
|
|
1027
|
+
let ariaExpanded;
|
|
1028
|
+
if (!inline) {
|
|
1029
|
+
ariaHasPopup = grid ? 'grid' : 'listbox';
|
|
1030
|
+
ariaExpanded = open ? 'true' : 'false';
|
|
1031
|
+
}
|
|
1088
1032
|
const role = React.useMemo(() => {
|
|
1089
1033
|
const isPlainInput = inputElement?.tagName === 'INPUT';
|
|
1090
1034
|
// During SSR and initial hydration, the input ref is not available yet.
|
|
1091
1035
|
// Assume an input-like control so combobox ARIA attributes are present.
|
|
1092
1036
|
const shouldTreatAsInput = inputElement == null || isPlainInput;
|
|
1093
|
-
|
|
1094
|
-
// an inline list is the whole time.
|
|
1095
|
-
const shouldApplyAria = shouldTreatAsInput || expanded;
|
|
1037
|
+
const shouldApplyAria = shouldTreatAsInput || open;
|
|
1096
1038
|
const reference = shouldTreatAsInput ? {
|
|
1097
1039
|
autoComplete: 'off',
|
|
1098
1040
|
spellCheck: 'false',
|
|
@@ -1103,7 +1045,7 @@ function AriaCombobox(props) {
|
|
|
1103
1045
|
reference.role = 'combobox';
|
|
1104
1046
|
reference['aria-expanded'] = ariaExpanded;
|
|
1105
1047
|
reference['aria-haspopup'] = ariaHasPopup;
|
|
1106
|
-
reference['aria-controls'] =
|
|
1048
|
+
reference['aria-controls'] = open ? listElement?.id : undefined;
|
|
1107
1049
|
reference['aria-autocomplete'] = autoComplete;
|
|
1108
1050
|
}
|
|
1109
1051
|
return {
|
|
@@ -1112,7 +1054,7 @@ function AriaCombobox(props) {
|
|
|
1112
1054
|
role: 'presentation'
|
|
1113
1055
|
}
|
|
1114
1056
|
};
|
|
1115
|
-
}, [inputElement,
|
|
1057
|
+
}, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
|
|
1116
1058
|
const click = useClick(floatingRootContext, {
|
|
1117
1059
|
enabled: !readOnly && !disabled && openOnInputClick,
|
|
1118
1060
|
event: 'mousedown-only',
|
|
@@ -1166,7 +1108,7 @@ function AriaCombobox(props) {
|
|
|
1166
1108
|
} else {
|
|
1167
1109
|
setIndices({
|
|
1168
1110
|
activeIndex: nextActiveIndex,
|
|
1169
|
-
type: keyboardActiveRef.current ? keyboard : pointer
|
|
1111
|
+
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1170
1112
|
});
|
|
1171
1113
|
}
|
|
1172
1114
|
}
|
|
@@ -1181,8 +1123,7 @@ function AriaCombobox(props) {
|
|
|
1181
1123
|
}
|
|
1182
1124
|
}
|
|
1183
1125
|
}, dismiss.reference, click.reference, role.reference), [listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store]);
|
|
1184
|
-
const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, dismiss.floating), [dismiss.floating]);
|
|
1185
|
-
const listProps = React.useMemo(() => mergeProps(listNavigation.floating, role.floating), [listNavigation.floating, role.floating]);
|
|
1126
|
+
const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, listNavigation.floating, dismiss.floating, role.floating), [listNavigation.floating, dismiss.floating, role.floating]);
|
|
1186
1127
|
const itemProps = React.useMemo(() => {
|
|
1187
1128
|
const listNavigationItemProps = listNavigation.item;
|
|
1188
1129
|
if (!listNavigationItemProps) {
|
|
@@ -1200,7 +1141,6 @@ function AriaCombobox(props) {
|
|
|
1200
1141
|
store.update({
|
|
1201
1142
|
inline: inlineProp,
|
|
1202
1143
|
popupProps,
|
|
1203
|
-
listProps,
|
|
1204
1144
|
inputProps,
|
|
1205
1145
|
triggerProps,
|
|
1206
1146
|
itemProps,
|
|
@@ -1208,10 +1148,10 @@ function AriaCombobox(props) {
|
|
|
1208
1148
|
setInputValue,
|
|
1209
1149
|
setSelectedValue,
|
|
1210
1150
|
setIndices,
|
|
1151
|
+
onItemHighlighted,
|
|
1211
1152
|
handleSelection,
|
|
1212
1153
|
forceMount,
|
|
1213
|
-
requestSubmit
|
|
1214
|
-
onOpenChangeComplete
|
|
1154
|
+
requestSubmit
|
|
1215
1155
|
});
|
|
1216
1156
|
});
|
|
1217
1157
|
useIsoLayoutEffect(() => {
|
|
@@ -1224,7 +1164,6 @@ function AriaCombobox(props) {
|
|
|
1224
1164
|
items,
|
|
1225
1165
|
inline: inlineProp,
|
|
1226
1166
|
popupProps,
|
|
1227
|
-
listProps,
|
|
1228
1167
|
inputProps,
|
|
1229
1168
|
triggerProps,
|
|
1230
1169
|
openMethod,
|
|
@@ -1236,7 +1175,9 @@ function AriaCombobox(props) {
|
|
|
1236
1175
|
readOnly,
|
|
1237
1176
|
required,
|
|
1238
1177
|
grid,
|
|
1178
|
+
isGrouped,
|
|
1239
1179
|
virtualized,
|
|
1180
|
+
onOpenChangeComplete,
|
|
1240
1181
|
openOnInputClick,
|
|
1241
1182
|
itemToStringLabel,
|
|
1242
1183
|
modal,
|
|
@@ -1244,9 +1185,10 @@ function AriaCombobox(props) {
|
|
|
1244
1185
|
isItemEqualToValue,
|
|
1245
1186
|
submitOnItemClick,
|
|
1246
1187
|
hasInputValue,
|
|
1188
|
+
requestSubmit,
|
|
1247
1189
|
inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup)
|
|
1248
1190
|
});
|
|
1249
|
-
}, [store, id, selectedValue, open, mounted, transitionStatus, items, popupProps,
|
|
1191
|
+
}, [store, id, selectedValue, open, mounted, transitionStatus, items, popupProps, inputProps, itemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, validation, grid, isGrouped, virtualized, onOpenChangeComplete, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, requestSubmit, autoHighlightMode, form]);
|
|
1250
1192
|
const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);
|
|
1251
1193
|
const itemsContextValue = React.useMemo(() => ({
|
|
1252
1194
|
query,
|
|
@@ -1290,7 +1232,7 @@ function AriaCombobox(props) {
|
|
|
1290
1232
|
},
|
|
1291
1233
|
// Handle browser autofill.
|
|
1292
1234
|
onChange(event) {
|
|
1293
|
-
// Workaround for https://github.com/
|
|
1235
|
+
// Workaround for https://github.com/facebook/react/issues/9023
|
|
1294
1236
|
if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
|
|
1295
1237
|
return;
|
|
1296
1238
|
}
|
|
@@ -1385,51 +1327,7 @@ const triggerStateAttributesMapping = {
|
|
|
1385
1327
|
} : null
|
|
1386
1328
|
};
|
|
1387
1329
|
|
|
1388
|
-
|
|
1389
|
-
* The popup side is only meaningful while the positioner is mounted, as the store retains the
|
|
1390
|
-
* last resolved side after the popup unmounts.
|
|
1391
|
-
*/
|
|
1392
|
-
function usePopupSide(store) {
|
|
1393
|
-
const mounted = useStore(store, selectors.mounted);
|
|
1394
|
-
const popupSide = useStore(store, selectors.popupSide);
|
|
1395
|
-
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1396
|
-
return mounted && positionerElement ? popupSide : null;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Whether the filtered list has no items to show.
|
|
1401
|
-
*/
|
|
1402
|
-
function useListEmpty() {
|
|
1403
|
-
return useComboboxDerivedItemsContext().filteredItems.length === 0;
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
/**
|
|
1407
|
-
* The arrow keys that move the chip highlight backwards and forwards, in that order.
|
|
1408
|
-
*/
|
|
1409
|
-
function getChipNavigationKeys(direction) {
|
|
1410
|
-
return direction === 'rtl' ? ['ArrowRight', 'ArrowLeft'] : ['ArrowLeft', 'ArrowRight'];
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
/**
|
|
1414
|
-
* Where the highlight lands once the chip at `index` is removed, or `undefined` for no highlight.
|
|
1415
|
-
*/
|
|
1416
|
-
function getIndexAfterChipRemoval(index, chipCount) {
|
|
1417
|
-
const nextIndex = index >= chipCount - 1 ? chipCount - 2 : index;
|
|
1418
|
-
return nextIndex >= 0 ? nextIndex : undefined;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
/**
|
|
1422
|
-
* Commits the highlighted item by clicking it, tagging the originating event so the item's
|
|
1423
|
-
* handler can attribute the selection to it.
|
|
1424
|
-
*/
|
|
1425
|
-
function clickHighlightedItem(store, activeIndex, nativeEvent) {
|
|
1426
|
-
const listItem = store.state.listRef.current[activeIndex];
|
|
1427
|
-
if (listItem) {
|
|
1428
|
-
store.state.selectionEventRef.current = nativeEvent;
|
|
1429
|
-
listItem.click();
|
|
1430
|
-
store.state.selectionEventRef.current = null;
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1330
|
+
const BOUNDARY_OFFSET = 2;
|
|
1433
1331
|
|
|
1434
1332
|
/**
|
|
1435
1333
|
* A button that opens the popup.
|
|
@@ -1459,14 +1357,20 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1459
1357
|
labelId: fieldLabelId
|
|
1460
1358
|
} = useLabelableContext();
|
|
1461
1359
|
const store = useComboboxRootContext();
|
|
1360
|
+
const {
|
|
1361
|
+
filteredItems
|
|
1362
|
+
} = useComboboxDerivedItemsContext();
|
|
1462
1363
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
1463
1364
|
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
1464
1365
|
const readOnly = useStore(store, selectors.readOnly);
|
|
1465
1366
|
const required = useStore(store, selectors.required);
|
|
1367
|
+
const mounted = useStore(store, selectors.mounted);
|
|
1368
|
+
const popupSideValue = useStore(store, selectors.popupSide);
|
|
1466
1369
|
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1467
1370
|
const listElement = useStore(store, selectors.listElement);
|
|
1468
1371
|
const storedPopupId = useStore(store, selectors.popupId);
|
|
1469
1372
|
const triggerProps = useStore(store, selectors.triggerProps);
|
|
1373
|
+
const triggerElement = useStore(store, selectors.triggerElement);
|
|
1470
1374
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
1471
1375
|
const rootId = useStore(store, selectors.id);
|
|
1472
1376
|
const comboboxLabelId = useStore(store, selectors.labelId);
|
|
@@ -1479,8 +1383,8 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1479
1383
|
const inputValue = useComboboxInputValueContext();
|
|
1480
1384
|
const focusTimeout = useTimeout();
|
|
1481
1385
|
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
|
|
1482
|
-
const listEmpty =
|
|
1483
|
-
const popupSide =
|
|
1386
|
+
const listEmpty = filteredItems.length === 0;
|
|
1387
|
+
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
1484
1388
|
useLabelableId({
|
|
1485
1389
|
id: inputInsidePopup ? idProp : undefined
|
|
1486
1390
|
});
|
|
@@ -1498,6 +1402,18 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1498
1402
|
function trackPointerType(event) {
|
|
1499
1403
|
currentPointerTypeRef.current = event.pointerType;
|
|
1500
1404
|
}
|
|
1405
|
+
const domReference = floatingRootContext.useState('domReferenceElement');
|
|
1406
|
+
|
|
1407
|
+
// Update the floating root context to use the trigger element when it differs from the current reference.
|
|
1408
|
+
// This ensures useClick and useTypeahead attach handlers to the correct element.
|
|
1409
|
+
React.useEffect(() => {
|
|
1410
|
+
if (!inputInsidePopup) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (triggerElement && triggerElement !== domReference) {
|
|
1414
|
+
floatingRootContext.set('domReferenceElement', triggerElement);
|
|
1415
|
+
}
|
|
1416
|
+
}, [triggerElement, domReference, floatingRootContext, inputInsidePopup]);
|
|
1501
1417
|
const {
|
|
1502
1418
|
reference: triggerTypeaheadProps
|
|
1503
1419
|
} = useTypeahead(floatingRootContext, {
|
|
@@ -1508,7 +1424,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1508
1424
|
onMatch(index) {
|
|
1509
1425
|
const nextSelectedValue = store.state.valuesRef.current[index];
|
|
1510
1426
|
if (nextSelectedValue !== undefined) {
|
|
1511
|
-
store.state.setSelectedValue(nextSelectedValue, createChangeEventDetails(none));
|
|
1427
|
+
store.state.setSelectedValue(nextSelectedValue, createChangeEventDetails('none'));
|
|
1512
1428
|
}
|
|
1513
1429
|
}
|
|
1514
1430
|
});
|
|
@@ -1543,7 +1459,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1543
1459
|
id,
|
|
1544
1460
|
tabIndex: inputInsidePopup ? 0 : -1,
|
|
1545
1461
|
role: inputInsidePopup ? 'combobox' : undefined,
|
|
1546
|
-
'aria-expanded': open,
|
|
1462
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1547
1463
|
'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
|
|
1548
1464
|
'aria-controls': ariaControls,
|
|
1549
1465
|
'aria-required': inputInsidePopup ? required || undefined : undefined,
|
|
@@ -1590,20 +1506,22 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1590
1506
|
}
|
|
1591
1507
|
const doc = ownerDocument(event.currentTarget);
|
|
1592
1508
|
function handleMouseUp(mouseEvent) {
|
|
1593
|
-
|
|
1594
|
-
if (!currentTriggerElement) {
|
|
1509
|
+
if (!triggerElement) {
|
|
1595
1510
|
return;
|
|
1596
1511
|
}
|
|
1597
1512
|
const mouseUpTarget = getTarget(mouseEvent);
|
|
1598
1513
|
const positioner = store.state.positionerElement;
|
|
1599
1514
|
const list = store.state.listElement;
|
|
1600
|
-
if (contains(
|
|
1515
|
+
if (contains(triggerElement, mouseUpTarget) || contains(positioner, mouseUpTarget) || contains(list, mouseUpTarget) || mouseUpTarget === triggerElement) {
|
|
1601
1516
|
return;
|
|
1602
1517
|
}
|
|
1603
|
-
|
|
1518
|
+
const bounds = getPseudoElementBounds(triggerElement);
|
|
1519
|
+
const withinHorizontal = mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET;
|
|
1520
|
+
const withinVertical = mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET;
|
|
1521
|
+
if (withinHorizontal && withinVertical) {
|
|
1604
1522
|
return;
|
|
1605
1523
|
}
|
|
1606
|
-
store.state.setOpen(false, createChangeEventDetails(
|
|
1524
|
+
store.state.setOpen(false, createChangeEventDetails('cancel-open', mouseEvent));
|
|
1607
1525
|
}
|
|
1608
1526
|
if (inputInsidePopup) {
|
|
1609
1527
|
doc.addEventListener('mouseup', handleMouseUp, {
|
|
@@ -1612,7 +1530,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1612
1530
|
}
|
|
1613
1531
|
},
|
|
1614
1532
|
onKeyDown(event) {
|
|
1615
|
-
if (readOnly) {
|
|
1533
|
+
if (disabled || readOnly) {
|
|
1616
1534
|
return;
|
|
1617
1535
|
}
|
|
1618
1536
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
@@ -1621,7 +1539,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1621
1539
|
store.state.inputRef.current?.focus();
|
|
1622
1540
|
}
|
|
1623
1541
|
}
|
|
1624
|
-
}, validation.getValidationProps(disabled, elementProps), getButtonProps],
|
|
1542
|
+
}, validation ? validation.getValidationProps(disabled, elementProps) : elementProps, getButtonProps],
|
|
1625
1543
|
stateAttributesMapping: triggerStateAttributesMapping
|
|
1626
1544
|
});
|
|
1627
1545
|
return element;
|
|
@@ -1696,6 +1614,9 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1696
1614
|
const positioning = useComboboxPositionerContext(true);
|
|
1697
1615
|
const hasPositionerParent = Boolean(positioning);
|
|
1698
1616
|
const store = useComboboxRootContext();
|
|
1617
|
+
const {
|
|
1618
|
+
filteredItems
|
|
1619
|
+
} = useComboboxDerivedItemsContext();
|
|
1699
1620
|
// `inputValue` can't be placed in the store.
|
|
1700
1621
|
// https://github.com/mui/base-ui/issues/2703
|
|
1701
1622
|
const inputValue = useComboboxInputValueContext();
|
|
@@ -1712,16 +1633,19 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1712
1633
|
const open = useStore(store, selectors.open);
|
|
1713
1634
|
const mounted = useStore(store, selectors.mounted);
|
|
1714
1635
|
const selectedValue = useStore(store, selectors.selectedValue);
|
|
1636
|
+
const popupSideValue = useStore(store, selectors.popupSide);
|
|
1637
|
+
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1715
1638
|
const rootId = useStore(store, selectors.id);
|
|
1716
1639
|
const inline = useStore(store, selectors.inline);
|
|
1717
1640
|
const modal = useStore(store, selectors.modal);
|
|
1718
1641
|
const autoHighlightEnabled = Boolean(autoHighlightMode);
|
|
1719
|
-
const popupSide =
|
|
1642
|
+
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
1720
1643
|
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
|
|
1721
|
-
const listEmpty =
|
|
1644
|
+
const listEmpty = filteredItems.length === 0;
|
|
1722
1645
|
const isInsidePopup = hasPositionerParent || inline;
|
|
1723
1646
|
const focusManagerModal = !isInsidePopup || modal;
|
|
1724
1647
|
const id = useBaseUiId(idProp ?? (!isInsidePopup ? rootId : undefined));
|
|
1648
|
+
const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, undefined);
|
|
1725
1649
|
const fieldStateForInput = hasPositionerParent ? DEFAULT_FIELD_STATE_ATTRIBUTES : fieldState;
|
|
1726
1650
|
const [composingValue, setComposingValue] = React.useState(null);
|
|
1727
1651
|
const isComposingRef = React.useRef(false);
|
|
@@ -1739,17 +1663,7 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1739
1663
|
inputOwnsFormValue
|
|
1740
1664
|
});
|
|
1741
1665
|
});
|
|
1742
|
-
const validationProps = hasPositionerParent ? elementProps : validation.getValidationProps(disabled, elementProps);
|
|
1743
|
-
function clearHighlight() {
|
|
1744
|
-
store.state.setIndices({
|
|
1745
|
-
activeIndex: null,
|
|
1746
|
-
selectedIndex: null,
|
|
1747
|
-
type: store.state.keyboardActiveRef.current ? keyboard : pointer
|
|
1748
|
-
});
|
|
1749
|
-
}
|
|
1750
|
-
function markPointerActive() {
|
|
1751
|
-
store.state.keyboardActiveRef.current = false;
|
|
1752
|
-
}
|
|
1666
|
+
const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(disabled, elementProps);
|
|
1753
1667
|
const state = {
|
|
1754
1668
|
...fieldStateForInput,
|
|
1755
1669
|
open,
|
|
@@ -1767,7 +1681,9 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1767
1681
|
highlightedChipIndex
|
|
1768
1682
|
} = comboboxChipsContext;
|
|
1769
1683
|
const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
|
|
1770
|
-
const
|
|
1684
|
+
const isRtl = direction === 'rtl';
|
|
1685
|
+
const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
1686
|
+
const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
1771
1687
|
if (highlightedChipIndex !== undefined) {
|
|
1772
1688
|
if (event.key === previousChipKey) {
|
|
1773
1689
|
event.preventDefault();
|
|
@@ -1786,8 +1702,14 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1786
1702
|
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
|
1787
1703
|
event.preventDefault();
|
|
1788
1704
|
// Move highlight appropriately after removal.
|
|
1789
|
-
|
|
1790
|
-
|
|
1705
|
+
const computedNextIndex = highlightedChipIndex >= selectedValue.length - 1 ? selectedValue.length - 2 : highlightedChipIndex;
|
|
1706
|
+
// If the computed index is negative, treat it as no highlight.
|
|
1707
|
+
nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
|
|
1708
|
+
store.state.setIndices({
|
|
1709
|
+
activeIndex: null,
|
|
1710
|
+
selectedIndex: null,
|
|
1711
|
+
type: 'keyboard'
|
|
1712
|
+
});
|
|
1791
1713
|
}
|
|
1792
1714
|
return nextIndex;
|
|
1793
1715
|
}
|
|
@@ -1796,6 +1718,13 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1796
1718
|
if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
|
|
1797
1719
|
event.preventDefault();
|
|
1798
1720
|
nextIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : undefined;
|
|
1721
|
+
} else if (event.key === 'Backspace' && event.currentTarget.value === '' && selectedValue.length > 0) {
|
|
1722
|
+
store.state.setIndices({
|
|
1723
|
+
activeIndex: null,
|
|
1724
|
+
selectedIndex: null,
|
|
1725
|
+
type: 'keyboard'
|
|
1726
|
+
});
|
|
1727
|
+
event.preventDefault();
|
|
1799
1728
|
}
|
|
1800
1729
|
return nextIndex;
|
|
1801
1730
|
}
|
|
@@ -1803,10 +1732,11 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1803
1732
|
state,
|
|
1804
1733
|
ref: [forwardedRef, store.state.inputRef, setInputElement],
|
|
1805
1734
|
props: [inputProps, triggerProps, {
|
|
1806
|
-
|
|
1735
|
+
type: 'text',
|
|
1736
|
+
value: componentProps.value ?? composingValue ?? inputValue,
|
|
1807
1737
|
'aria-readonly': readOnly || undefined,
|
|
1808
1738
|
'aria-required': required || undefined,
|
|
1809
|
-
'aria-labelledby':
|
|
1739
|
+
'aria-labelledby': ariaLabelledBy,
|
|
1810
1740
|
disabled,
|
|
1811
1741
|
readOnly,
|
|
1812
1742
|
required: selectionMode === 'none' ? required : undefined,
|
|
@@ -1861,23 +1791,11 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1861
1791
|
store.state.setInputValue(next, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
1862
1792
|
},
|
|
1863
1793
|
onChange(event) {
|
|
1864
|
-
const nativeEvent = event.nativeEvent;
|
|
1865
1794
|
// Autofill may not provide `inputType` (Chrome) or may report
|
|
1866
1795
|
// `insertReplacementText` (Firefox).
|
|
1867
|
-
const inputType = nativeEvent.inputType;
|
|
1796
|
+
const inputType = event.nativeEvent.inputType;
|
|
1868
1797
|
const autofillLikeInput = !inputType || inputType === 'insertReplacementText';
|
|
1869
|
-
// During composition the input is always considered typed into.
|
|
1870
1798
|
const shouldOpenOnInput = isComposingRef.current || !autofillLikeInput;
|
|
1871
|
-
function maybeOpenOnInput(trimmed) {
|
|
1872
|
-
if (readOnly || disabled || !trimmed || !shouldOpenOnInput) {
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
store.state.setOpen(true, createChangeEventDetails(inputChange, nativeEvent));
|
|
1876
|
-
// When autoHighlight is enabled, keep the highlight (will be set to 0 in root).
|
|
1877
|
-
if (!autoHighlightEnabled) {
|
|
1878
|
-
clearHighlight();
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
1799
|
|
|
1882
1800
|
// During IME composition, avoid propagating controlled updates to prevent
|
|
1883
1801
|
// filtering the options prematurely so `Empty` won't show incorrectly.
|
|
@@ -1889,23 +1807,38 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1889
1807
|
const nextVal = event.currentTarget.value;
|
|
1890
1808
|
setComposingValue(nextVal);
|
|
1891
1809
|
if (nextVal === '' && !store.state.openOnInputClick && !store.state.inputInsidePopup) {
|
|
1892
|
-
store.state.setOpen(false, createChangeEventDetails(inputClear, nativeEvent));
|
|
1810
|
+
store.state.setOpen(false, createChangeEventDetails(inputClear, event.nativeEvent));
|
|
1893
1811
|
}
|
|
1894
1812
|
const trimmed = nextVal.trim();
|
|
1895
1813
|
const shouldMaintainHighlight = autoHighlightEnabled && trimmed !== '';
|
|
1896
|
-
|
|
1814
|
+
if (!readOnly && !disabled && trimmed) {
|
|
1815
|
+
if (shouldOpenOnInput) {
|
|
1816
|
+
store.state.setOpen(true, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
1817
|
+
if (!autoHighlightEnabled) {
|
|
1818
|
+
store.state.setIndices({
|
|
1819
|
+
activeIndex: null,
|
|
1820
|
+
selectedIndex: null,
|
|
1821
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1897
1826
|
if (open && store.state.activeIndex !== null && !shouldMaintainHighlight) {
|
|
1898
|
-
|
|
1827
|
+
store.state.setIndices({
|
|
1828
|
+
activeIndex: null,
|
|
1829
|
+
selectedIndex: null,
|
|
1830
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1831
|
+
});
|
|
1899
1832
|
}
|
|
1900
1833
|
return;
|
|
1901
1834
|
}
|
|
1902
|
-
const inputChangeDetails = createChangeEventDetails(inputChange, nativeEvent);
|
|
1835
|
+
const inputChangeDetails = createChangeEventDetails(inputChange, event.nativeEvent);
|
|
1903
1836
|
store.state.setInputValue(event.currentTarget.value, inputChangeDetails);
|
|
1904
1837
|
if (inputChangeDetails.isCanceled) {
|
|
1905
1838
|
return;
|
|
1906
1839
|
}
|
|
1907
1840
|
const empty = event.currentTarget.value === '';
|
|
1908
|
-
const clearDetails = createChangeEventDetails(inputClear, nativeEvent);
|
|
1841
|
+
const clearDetails = createChangeEventDetails(inputClear, event.nativeEvent);
|
|
1909
1842
|
if (empty && !store.state.inputInsidePopup) {
|
|
1910
1843
|
if (selectionMode === 'single') {
|
|
1911
1844
|
store.state.setSelectedValue(null, clearDetails);
|
|
@@ -1914,13 +1847,30 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1914
1847
|
store.state.setOpen(false, clearDetails);
|
|
1915
1848
|
}
|
|
1916
1849
|
}
|
|
1917
|
-
|
|
1850
|
+
const trimmed = event.currentTarget.value.trim();
|
|
1851
|
+
if (!readOnly && !disabled && trimmed) {
|
|
1852
|
+
if (shouldOpenOnInput) {
|
|
1853
|
+
store.state.setOpen(true, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
1854
|
+
// When autoHighlight is enabled, keep the highlight (will be set to 0 in root).
|
|
1855
|
+
if (!autoHighlightEnabled) {
|
|
1856
|
+
store.state.setIndices({
|
|
1857
|
+
activeIndex: null,
|
|
1858
|
+
selectedIndex: null,
|
|
1859
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1918
1864
|
|
|
1919
1865
|
// When the user types, ensure the list resets its highlight so that
|
|
1920
1866
|
// virtual focus returns to the input (aria-activedescendant is
|
|
1921
1867
|
// cleared).
|
|
1922
1868
|
if (open && store.state.activeIndex !== null && !autoHighlightEnabled) {
|
|
1923
|
-
|
|
1869
|
+
store.state.setIndices({
|
|
1870
|
+
activeIndex: null,
|
|
1871
|
+
selectedIndex: null,
|
|
1872
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1873
|
+
});
|
|
1924
1874
|
}
|
|
1925
1875
|
},
|
|
1926
1876
|
onKeyDown(event) {
|
|
@@ -1966,7 +1916,11 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1966
1916
|
const removalIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : selectedValue.length - 1;
|
|
1967
1917
|
const newValue = selectedValue.filter((_, index) => index !== removalIndex);
|
|
1968
1918
|
// If the removed item was also the active (highlighted) item, clear highlight
|
|
1969
|
-
|
|
1919
|
+
store.state.setIndices({
|
|
1920
|
+
activeIndex: null,
|
|
1921
|
+
selectedIndex: null,
|
|
1922
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1923
|
+
});
|
|
1970
1924
|
store.state.setSelectedValue(newValue, createChangeEventDetails(none, event.nativeEvent));
|
|
1971
1925
|
return;
|
|
1972
1926
|
}
|
|
@@ -1996,11 +1950,20 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1996
1950
|
return;
|
|
1997
1951
|
}
|
|
1998
1952
|
stopEvent(event);
|
|
1999
|
-
|
|
1953
|
+
const listItem = store.state.listRef.current[activeIndex];
|
|
1954
|
+
if (listItem) {
|
|
1955
|
+
store.state.selectionEventRef.current = nativeEvent;
|
|
1956
|
+
listItem.click();
|
|
1957
|
+
store.state.selectionEventRef.current = null;
|
|
1958
|
+
}
|
|
2000
1959
|
}
|
|
2001
1960
|
},
|
|
2002
|
-
onPointerMove
|
|
2003
|
-
|
|
1961
|
+
onPointerMove() {
|
|
1962
|
+
store.state.keyboardActiveRef.current = false;
|
|
1963
|
+
},
|
|
1964
|
+
onPointerDown() {
|
|
1965
|
+
store.state.keyboardActiveRef.current = false;
|
|
1966
|
+
}
|
|
2004
1967
|
}, validationProps],
|
|
2005
1968
|
stateAttributesMapping: triggerStateAttributesMapping
|
|
2006
1969
|
});
|
|
@@ -2052,14 +2015,20 @@ const ComboboxInputGroup = /*#__PURE__*/React.forwardRef(function ComboboxInputG
|
|
|
2052
2015
|
state: fieldState
|
|
2053
2016
|
} = useFieldRootContext();
|
|
2054
2017
|
const store = useComboboxRootContext();
|
|
2018
|
+
const {
|
|
2019
|
+
filteredItems
|
|
2020
|
+
} = useComboboxDerivedItemsContext();
|
|
2055
2021
|
const open = useStore(store, selectors.open);
|
|
2022
|
+
const mounted = useStore(store, selectors.mounted);
|
|
2023
|
+
const popupSideValue = useStore(store, selectors.popupSide);
|
|
2024
|
+
const positionerElement = useStore(store, selectors.positionerElement);
|
|
2056
2025
|
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
2057
2026
|
const readOnly = useStore(store, selectors.readOnly);
|
|
2058
2027
|
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
2059
2028
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2060
|
-
const popupSide =
|
|
2029
|
+
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
2061
2030
|
const disabled = comboboxDisabled;
|
|
2062
|
-
const listEmpty =
|
|
2031
|
+
const listEmpty = filteredItems.length === 0;
|
|
2063
2032
|
const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
|
|
2064
2033
|
const state = {
|
|
2065
2034
|
...fieldState,
|
|
@@ -2195,21 +2164,19 @@ const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(compo
|
|
|
2195
2164
|
if (disabled || readOnly) {
|
|
2196
2165
|
return;
|
|
2197
2166
|
}
|
|
2198
|
-
const
|
|
2167
|
+
const keyboardActiveRef = store.state.keyboardActiveRef;
|
|
2199
2168
|
store.state.setInputValue('', createChangeEventDetails(clearPress, event.nativeEvent));
|
|
2200
2169
|
if (selectionMode !== 'none') {
|
|
2201
2170
|
store.state.setSelectedValue(Array.isArray(selectedValue) ? [] : null, createChangeEventDetails(clearPress, event.nativeEvent));
|
|
2202
|
-
// A distinct object shape: `Store.update` iterates own keys, so passing an explicit
|
|
2203
|
-
// `selectedIndex: undefined` would overwrite the state instead of leaving it alone.
|
|
2204
2171
|
store.state.setIndices({
|
|
2205
2172
|
activeIndex: null,
|
|
2206
2173
|
selectedIndex: null,
|
|
2207
|
-
type
|
|
2174
|
+
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2208
2175
|
});
|
|
2209
2176
|
} else {
|
|
2210
2177
|
store.state.setIndices({
|
|
2211
2178
|
activeIndex: null,
|
|
2212
|
-
type
|
|
2179
|
+
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2213
2180
|
});
|
|
2214
2181
|
}
|
|
2215
2182
|
store.state.inputRef.current?.focus();
|
|
@@ -2253,6 +2220,9 @@ function ComboboxCollection(props) {
|
|
|
2253
2220
|
} = useComboboxDerivedItemsContext();
|
|
2254
2221
|
const groupContext = useGroupCollectionContext();
|
|
2255
2222
|
const itemsToRender = groupContext ? groupContext.items : filteredItems;
|
|
2223
|
+
if (!itemsToRender) {
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2256
2226
|
return /*#__PURE__*/jsx(React.Fragment, {
|
|
2257
2227
|
children: itemsToRender.map(children)
|
|
2258
2228
|
});
|
|
@@ -2276,7 +2246,7 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2276
2246
|
} = useComboboxDerivedItemsContext();
|
|
2277
2247
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2278
2248
|
const grid = useStore(store, selectors.grid);
|
|
2279
|
-
const
|
|
2249
|
+
const popupProps = useStore(store, selectors.popupProps);
|
|
2280
2250
|
const virtualized = useStore(store, selectors.virtualized);
|
|
2281
2251
|
const forceMounted = useStore(store, selectors.forceMounted);
|
|
2282
2252
|
const multiple = selectionMode === 'multiple';
|
|
@@ -2290,7 +2260,7 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2290
2260
|
|
|
2291
2261
|
// Support "closed template" API: if children is a function, implicitly wrap it
|
|
2292
2262
|
// with a Combobox.Collection that reads items from context/root.
|
|
2293
|
-
// Ensures this component's `
|
|
2263
|
+
// Ensures this component's `popupProps` subscription does not cause <Combobox.Item>
|
|
2294
2264
|
// to re-render on every active index change.
|
|
2295
2265
|
const resolvedChildren = React.useMemo(() => {
|
|
2296
2266
|
if (typeof children === 'function') {
|
|
@@ -2307,7 +2277,7 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2307
2277
|
const element = useRenderElement('div', componentProps, {
|
|
2308
2278
|
state,
|
|
2309
2279
|
ref: [forwardedRef, setListElement, hasPositionerContext ? null : setPositionerElement],
|
|
2310
|
-
props: [
|
|
2280
|
+
props: [popupProps, {
|
|
2311
2281
|
children: resolvedChildren,
|
|
2312
2282
|
tabIndex: -1,
|
|
2313
2283
|
id: floatingId,
|
|
@@ -2324,7 +2294,13 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2324
2294
|
return;
|
|
2325
2295
|
}
|
|
2326
2296
|
stopEvent(event);
|
|
2327
|
-
|
|
2297
|
+
const nativeEvent = event.nativeEvent;
|
|
2298
|
+
const listItem = store.state.listRef.current[activeIndex];
|
|
2299
|
+
if (listItem) {
|
|
2300
|
+
store.state.selectionEventRef.current = nativeEvent;
|
|
2301
|
+
listItem.click();
|
|
2302
|
+
store.state.selectionEventRef.current = null;
|
|
2303
|
+
}
|
|
2328
2304
|
}
|
|
2329
2305
|
},
|
|
2330
2306
|
onKeyDownCapture() {
|
|
@@ -2383,7 +2359,7 @@ function useInitialLiveRegionTextMutation() {
|
|
|
2383
2359
|
if (textNode == null) {
|
|
2384
2360
|
return undefined;
|
|
2385
2361
|
}
|
|
2386
|
-
const originalValue = textNode.
|
|
2362
|
+
const originalValue = textNode.nodeValue ?? '';
|
|
2387
2363
|
const markedValue = `${originalValue}${LIVE_REGION_MARKER}`;
|
|
2388
2364
|
textNode.nodeValue = markedValue;
|
|
2389
2365
|
timeout.start(INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY, () => {
|
|
@@ -2512,23 +2488,24 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2512
2488
|
render,
|
|
2513
2489
|
className,
|
|
2514
2490
|
anchor,
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
sideOffset,
|
|
2521
|
-
alignOffset,
|
|
2491
|
+
positionMethod = 'absolute',
|
|
2492
|
+
side = 'bottom',
|
|
2493
|
+
align = 'center',
|
|
2494
|
+
sideOffset = 0,
|
|
2495
|
+
alignOffset = 0,
|
|
2522
2496
|
collisionBoundary = 'clipping-ancestors',
|
|
2523
|
-
collisionPadding,
|
|
2524
|
-
arrowPadding,
|
|
2525
|
-
sticky,
|
|
2497
|
+
collisionPadding = 5,
|
|
2498
|
+
arrowPadding = 5,
|
|
2499
|
+
sticky = false,
|
|
2526
2500
|
disableAnchorTracking = false,
|
|
2527
2501
|
collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
|
|
2528
2502
|
style: styleProp,
|
|
2529
2503
|
...elementProps
|
|
2530
2504
|
} = componentProps;
|
|
2531
2505
|
const store = useComboboxRootContext();
|
|
2506
|
+
const {
|
|
2507
|
+
filteredItems
|
|
2508
|
+
} = useComboboxDerivedItemsContext();
|
|
2532
2509
|
const floatingRootContext = useComboboxFloatingContext();
|
|
2533
2510
|
const keepMounted = useComboboxPortalContext();
|
|
2534
2511
|
const modal = useStore(store, selectors.modal);
|
|
@@ -2541,7 +2518,7 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2541
2518
|
const inputGroupElement = useStore(store, selectors.inputGroupElement);
|
|
2542
2519
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
2543
2520
|
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
2544
|
-
const empty =
|
|
2521
|
+
const empty = filteredItems.length === 0;
|
|
2545
2522
|
const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputGroupElement ?? inputElement);
|
|
2546
2523
|
const positioning = useAnchorPositioning({
|
|
2547
2524
|
anchor: resolvedAnchor,
|
|
@@ -2616,16 +2593,18 @@ const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(compo
|
|
|
2616
2593
|
const store = useComboboxRootContext();
|
|
2617
2594
|
const positioning = useComboboxPositionerContext();
|
|
2618
2595
|
const floatingRootContext = useComboboxFloatingContext();
|
|
2596
|
+
const {
|
|
2597
|
+
filteredItems
|
|
2598
|
+
} = useComboboxDerivedItemsContext();
|
|
2619
2599
|
const mounted = useStore(store, selectors.mounted);
|
|
2620
2600
|
const open = useStore(store, selectors.open);
|
|
2621
2601
|
const openMethod = useStore(store, selectors.openMethod);
|
|
2622
|
-
const popupProps = useStore(store, selectors.popupProps);
|
|
2623
2602
|
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
2624
2603
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
2625
2604
|
const inputElement = useStore(store, selectors.inputElement);
|
|
2626
2605
|
const modal = useStore(store, selectors.modal);
|
|
2627
2606
|
const rootId = useStore(store, selectors.id);
|
|
2628
|
-
const empty =
|
|
2607
|
+
const empty = filteredItems.length === 0;
|
|
2629
2608
|
const popupId = elementProps.id ?? (inputInsidePopup ? getComboboxPopupId(rootId) : undefined);
|
|
2630
2609
|
useIsoLayoutEffect(() => {
|
|
2631
2610
|
// Prefer the rendered DOM id, which a `render` prop element or function may override.
|
|
@@ -2654,9 +2633,10 @@ const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(compo
|
|
|
2654
2633
|
const element = useRenderElement('div', componentProps, {
|
|
2655
2634
|
state,
|
|
2656
2635
|
ref: [forwardedRef, store.state.popupRef],
|
|
2657
|
-
props: [
|
|
2636
|
+
props: [{
|
|
2658
2637
|
id: popupId,
|
|
2659
2638
|
role: inputInsidePopup ? 'dialog' : 'presentation',
|
|
2639
|
+
tabIndex: -1,
|
|
2660
2640
|
onFocus(event) {
|
|
2661
2641
|
const target = getTarget(event.nativeEvent);
|
|
2662
2642
|
if (openMethod !== 'touch' && (contains(store.state.listElement, target) || target === event.currentTarget)) {
|
|
@@ -2803,7 +2783,7 @@ const ComboboxGroupLabel = /*#__PURE__*/React.forwardRef(function ComboboxGroupL
|
|
|
2803
2783
|
useIsoLayoutEffect(() => {
|
|
2804
2784
|
setLabelId(id);
|
|
2805
2785
|
return () => {
|
|
2806
|
-
setLabelId(
|
|
2786
|
+
setLabelId(undefined);
|
|
2807
2787
|
};
|
|
2808
2788
|
}, [id, setLabelId]);
|
|
2809
2789
|
const element = useRenderElement('div', componentProps, {
|
|
@@ -2845,27 +2825,27 @@ function ComboboxItemInner(props) {
|
|
|
2845
2825
|
style,
|
|
2846
2826
|
value: itemValue = null,
|
|
2847
2827
|
index: indexProp,
|
|
2848
|
-
disabled
|
|
2828
|
+
disabled = false,
|
|
2849
2829
|
nativeButton = false,
|
|
2850
2830
|
...elementProps
|
|
2851
2831
|
} = componentProps;
|
|
2832
|
+
const didPointerDownRef = React.useRef(false);
|
|
2852
2833
|
const textRef = React.useRef(null);
|
|
2853
2834
|
const listItem = useCompositeListItem({
|
|
2854
|
-
guess: true,
|
|
2855
2835
|
index: indexProp,
|
|
2856
|
-
textRef
|
|
2836
|
+
textRef,
|
|
2837
|
+
indexGuessBehavior: IndexGuessBehavior.GuessFromOrder
|
|
2857
2838
|
});
|
|
2858
2839
|
const store = useComboboxRootContext();
|
|
2859
2840
|
const isRow = useComboboxRowContext();
|
|
2860
2841
|
const hasItems = useComboboxHasItemsContext();
|
|
2842
|
+
const open = useStore(store, selectors.open);
|
|
2861
2843
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2862
|
-
const rootDisabled = useStore(store, selectors.disabled);
|
|
2863
2844
|
const readOnly = useStore(store, selectors.readOnly);
|
|
2864
2845
|
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
2865
|
-
const disabled = rootDisabled || disabledProp;
|
|
2866
2846
|
const selectable = selectionMode !== 'none';
|
|
2867
|
-
const index = indexProp ?? indexFromFilter ?? listItem.index;
|
|
2868
|
-
const hasRegistered = index !== -1;
|
|
2847
|
+
const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
|
|
2848
|
+
const hasRegistered = listItem.index !== -1;
|
|
2869
2849
|
const rootId = useStore(store, selectors.id);
|
|
2870
2850
|
const highlighted = useStore(store, selectors.isActive, index);
|
|
2871
2851
|
const matchesSelectedValue = useStore(store, selectors.isSelected, itemValue);
|
|
@@ -2890,24 +2870,31 @@ function ComboboxItemInner(props) {
|
|
|
2890
2870
|
}
|
|
2891
2871
|
const visibleMap = store.state.valuesRef.current;
|
|
2892
2872
|
visibleMap[index] = itemValue;
|
|
2873
|
+
|
|
2874
|
+
// Stable registry that doesn't depend on filtering. Assume that no
|
|
2875
|
+
// filtering had occurred at this point; otherwise, an `items` prop is
|
|
2876
|
+
// required.
|
|
2877
|
+
if (selectionMode !== 'none') {
|
|
2878
|
+
store.state.allValuesRef.current.push(itemValue);
|
|
2879
|
+
}
|
|
2893
2880
|
return () => {
|
|
2894
2881
|
delete visibleMap[index];
|
|
2895
2882
|
};
|
|
2896
|
-
}, [hasRegistered, hasItems, index, itemValue, store]);
|
|
2883
|
+
}, [hasRegistered, hasItems, index, itemValue, store, selectionMode]);
|
|
2897
2884
|
useIsoLayoutEffect(() => {
|
|
2885
|
+
if (!open) {
|
|
2886
|
+
didPointerDownRef.current = false;
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2898
2889
|
if (!hasRegistered || hasItems) {
|
|
2899
2890
|
return;
|
|
2900
2891
|
}
|
|
2901
|
-
|
|
2902
|
-
// Runs while closed as well (the list can stay mounted via `keepMounted` or a
|
|
2903
|
-
// force-mount) so the index tracks the item's composite position, keeping features
|
|
2904
|
-
// like closed-trigger typeahead in sync when the rendered order changes.
|
|
2905
2892
|
const selectedValue = store.state.selectedValue;
|
|
2906
2893
|
const lastSelectedValue = Array.isArray(selectedValue) ? selectedValue[selectedValue.length - 1] : selectedValue;
|
|
2907
2894
|
if (compareItemEquality(itemValue, lastSelectedValue, isItemEqualToValue)) {
|
|
2908
2895
|
store.set('selectedIndex', index);
|
|
2909
2896
|
}
|
|
2910
|
-
}, [hasRegistered, hasItems, store, index, itemValue, isItemEqualToValue]);
|
|
2897
|
+
}, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
|
|
2911
2898
|
const {
|
|
2912
2899
|
getButtonProps,
|
|
2913
2900
|
buttonRef
|
|
@@ -2942,12 +2929,7 @@ function ComboboxItemInner(props) {
|
|
|
2942
2929
|
// as it should be a `<div>` instead.
|
|
2943
2930
|
tabIndex: undefined,
|
|
2944
2931
|
onPointerDownCapture(event) {
|
|
2945
|
-
|
|
2946
|
-
// touch must not overwrite the shared ref — a mismatch would make the primary
|
|
2947
|
-
// pointer's release read as a drag-select and commit a second time after `click`.
|
|
2948
|
-
if (event.isPrimary) {
|
|
2949
|
-
store.state.pointerDownItemRef.current = event.currentTarget;
|
|
2950
|
-
}
|
|
2932
|
+
didPointerDownRef.current = true;
|
|
2951
2933
|
event.preventDefault();
|
|
2952
2934
|
},
|
|
2953
2935
|
onMouseDown(event) {
|
|
@@ -2962,8 +2944,8 @@ function ComboboxItemInner(props) {
|
|
|
2962
2944
|
commitSelection(event.nativeEvent);
|
|
2963
2945
|
},
|
|
2964
2946
|
onMouseUp(event) {
|
|
2965
|
-
const pointerStartedOnItem =
|
|
2966
|
-
|
|
2947
|
+
const pointerStartedOnItem = didPointerDownRef.current;
|
|
2948
|
+
didPointerDownRef.current = false;
|
|
2967
2949
|
if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) {
|
|
2968
2950
|
return;
|
|
2969
2951
|
}
|
|
@@ -3099,33 +3081,6 @@ const ComboboxEmpty = /*#__PURE__*/React.forwardRef(function ComboboxEmpty(compo
|
|
|
3099
3081
|
});
|
|
3100
3082
|
if (process.env.NODE_ENV !== "production") ComboboxEmpty.displayName = "ComboboxEmpty";
|
|
3101
3083
|
|
|
3102
|
-
/**
|
|
3103
|
-
* A visual separator between items.
|
|
3104
|
-
* Renders a `<div>` element.
|
|
3105
|
-
*
|
|
3106
|
-
* @internal
|
|
3107
|
-
*/
|
|
3108
|
-
const ListboxSeparator = /*#__PURE__*/React.forwardRef(function ListboxSeparator(componentProps, forwardedRef) {
|
|
3109
|
-
const {
|
|
3110
|
-
className,
|
|
3111
|
-
render,
|
|
3112
|
-
orientation = 'horizontal',
|
|
3113
|
-
style,
|
|
3114
|
-
...elementProps
|
|
3115
|
-
} = componentProps;
|
|
3116
|
-
const state = {
|
|
3117
|
-
orientation
|
|
3118
|
-
};
|
|
3119
|
-
return useRenderElement('div', componentProps, {
|
|
3120
|
-
state,
|
|
3121
|
-
ref: forwardedRef,
|
|
3122
|
-
props: [{
|
|
3123
|
-
role: 'presentation'
|
|
3124
|
-
}, elementProps]
|
|
3125
|
-
});
|
|
3126
|
-
});
|
|
3127
|
-
if (process.env.NODE_ENV !== "production") ListboxSeparator.displayName = "ListboxSeparator";
|
|
3128
|
-
|
|
3129
3084
|
/**
|
|
3130
3085
|
* Returns the internally filtered items.
|
|
3131
3086
|
*/
|
|
@@ -3192,8 +3147,7 @@ const ComboboxLabel = /*#__PURE__*/React.forwardRef(function ComboboxLabel(compo
|
|
|
3192
3147
|
id: defaultLabelId,
|
|
3193
3148
|
fallbackControlId: localControlId,
|
|
3194
3149
|
setLabelId(nextLabelId) {
|
|
3195
|
-
|
|
3196
|
-
store.set('labelId', resolvedLabelId);
|
|
3150
|
+
store.set('labelId', nextLabelId);
|
|
3197
3151
|
}
|
|
3198
3152
|
});
|
|
3199
3153
|
return useRenderElement('div', componentProps, {
|
|
@@ -3236,10 +3190,11 @@ function ComboboxValue(props) {
|
|
|
3236
3190
|
}
|
|
3237
3191
|
|
|
3238
3192
|
const ComboboxItemIndicator = /*#__PURE__*/React.forwardRef(function ComboboxItemIndicator(componentProps, forwardedRef) {
|
|
3193
|
+
const keepMounted = componentProps.keepMounted ?? false;
|
|
3239
3194
|
const {
|
|
3240
3195
|
selected
|
|
3241
3196
|
} = useComboboxItemContext();
|
|
3242
|
-
const shouldRender =
|
|
3197
|
+
const shouldRender = keepMounted || selected;
|
|
3243
3198
|
if (!shouldRender) {
|
|
3244
3199
|
return null;
|
|
3245
3200
|
}
|
|
@@ -3369,7 +3324,9 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3369
3324
|
} = useCompositeListItem();
|
|
3370
3325
|
function handleKeyDown(event) {
|
|
3371
3326
|
let nextIndex = index;
|
|
3372
|
-
const
|
|
3327
|
+
const isRtl = direction === 'rtl';
|
|
3328
|
+
const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
3329
|
+
const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
3373
3330
|
if (event.key === previousChipKey) {
|
|
3374
3331
|
event.preventDefault();
|
|
3375
3332
|
if (index > 0) {
|
|
@@ -3385,12 +3342,13 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3385
3342
|
nextIndex = undefined;
|
|
3386
3343
|
}
|
|
3387
3344
|
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
|
3388
|
-
|
|
3345
|
+
const computedNextIndex = index >= selectedValue.length - 1 ? selectedValue.length - 2 : index;
|
|
3346
|
+
nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
|
|
3389
3347
|
stopEvent(event);
|
|
3390
3348
|
store.state.setIndices({
|
|
3391
3349
|
activeIndex: null,
|
|
3392
3350
|
selectedIndex: null,
|
|
3393
|
-
type: keyboard
|
|
3351
|
+
type: 'keyboard'
|
|
3394
3352
|
});
|
|
3395
3353
|
store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), createChangeEventDetails(none, event.nativeEvent));
|
|
3396
3354
|
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
@@ -3490,7 +3448,7 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
|
|
|
3490
3448
|
if (removedIndex !== -1 && activeIndex === removedIndex) {
|
|
3491
3449
|
store.state.setIndices({
|
|
3492
3450
|
activeIndex: null,
|
|
3493
|
-
type: store.state.keyboardActiveRef.current ? keyboard : pointer
|
|
3451
|
+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
3494
3452
|
});
|
|
3495
3453
|
}
|
|
3496
3454
|
}
|
|
@@ -3511,12 +3469,18 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
|
|
|
3511
3469
|
event.preventDefault();
|
|
3512
3470
|
},
|
|
3513
3471
|
onClick(event) {
|
|
3472
|
+
if (disabled || readOnly) {
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3514
3475
|
const eventDetails = removeChip(event);
|
|
3515
3476
|
if (!eventDetails.isPropagationAllowed) {
|
|
3516
3477
|
event.stopPropagation();
|
|
3517
3478
|
}
|
|
3518
3479
|
},
|
|
3519
3480
|
onKeyDown(event) {
|
|
3481
|
+
if (disabled || readOnly) {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3520
3484
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
3521
3485
|
const eventDetails = removeChip(event);
|
|
3522
3486
|
if (!eventDetails.isPropagationAllowed) {
|
|
@@ -3530,12 +3494,4 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
|
|
|
3530
3494
|
});
|
|
3531
3495
|
if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
|
|
3532
3496
|
|
|
3533
|
-
|
|
3534
|
-
* A visual separator between items or groups.
|
|
3535
|
-
* Renders a `<div>` element.
|
|
3536
|
-
*
|
|
3537
|
-
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
3538
|
-
*/
|
|
3539
|
-
const ComboboxSeparator = ListboxSeparator;
|
|
3540
|
-
|
|
3541
|
-
export { useFilteredItems as A, useCoreFilter as B, ComboboxRoot as C, AriaCombobox as D, useComboboxInputValueContext as E, ListboxSeparator as L, ComboboxLabel as a, ComboboxValue as b, ComboboxInput as c, ComboboxInputGroup as d, ComboboxTrigger as e, ComboboxList as f, ComboboxStatus as g, ComboboxPortal as h, ComboboxBackdrop as i, ComboboxPositioner as j, ComboboxPopup as k, ComboboxArrow as l, ComboboxIcon as m, ComboboxGroup as n, ComboboxGroupLabel as o, ComboboxItem as p, ComboboxItemIndicator as q, ComboboxChips as r, ComboboxChip as s, ComboboxChipRemove as t, ComboboxRow as u, ComboboxCollection as v, ComboboxEmpty as w, ComboboxClear as x, ComboboxSeparator as y, useComboboxFilter as z };
|
|
3497
|
+
export { useCoreFilter as A, AriaCombobox as B, ComboboxRoot as C, useComboboxInputValueContext as D, ComboboxLabel as a, ComboboxValue as b, ComboboxInput as c, ComboboxInputGroup as d, ComboboxTrigger as e, ComboboxList as f, ComboboxStatus as g, ComboboxPortal as h, ComboboxBackdrop as i, ComboboxPositioner as j, ComboboxPopup as k, ComboboxArrow as l, ComboboxIcon as m, ComboboxGroup as n, ComboboxGroupLabel as o, ComboboxItem as p, ComboboxItemIndicator as q, ComboboxChips as r, ComboboxChip as s, ComboboxChipRemove as t, ComboboxRow as u, ComboboxCollection as v, ComboboxEmpty as w, ComboboxClear as x, useComboboxFilter as y, useFilteredItems as z };
|