@jobber/components 8.20.2 → 8.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BottomSheet-cjs.js +9 -58
- package/dist/BottomSheet-es.js +2 -47
- package/dist/Card/index.cjs +4 -0
- package/dist/Card/index.mjs +4 -0
- package/dist/ComboboxChipRemove-cjs.js +3418 -0
- package/dist/ComboboxChipRemove-es.js +3366 -0
- package/dist/ComboboxPrimitive-cjs.js +170 -0
- package/dist/ComboboxPrimitive-es.js +168 -0
- package/dist/DataDump/index.cjs +4 -0
- package/dist/DataDump/index.mjs +4 -0
- package/dist/InputNumberExperimental-cjs.js +45 -44
- package/dist/InputNumberExperimental-es.js +3 -2
- package/dist/InternalBackdrop-cjs.js +4278 -0
- package/dist/InternalBackdrop-es.js +4204 -0
- package/dist/Menu/index.cjs +4 -0
- package/dist/Menu/index.mjs +4 -0
- package/dist/Menu-cjs.js +9 -8
- package/dist/Menu-es.js +2 -1
- package/dist/MenuSubmenuTrigger-cjs.js +208 -1969
- package/dist/MenuSubmenuTrigger-es.js +7 -1759
- package/dist/NumberFieldInput-cjs.js +56 -439
- package/dist/NumberFieldInput-es.js +6 -376
- package/dist/Page/index.cjs +4 -0
- package/dist/Page/index.mjs +4 -0
- package/dist/ScrollAreaViewport-cjs.js +108 -4356
- package/dist/ScrollAreaViewport-es.js +6 -4201
- package/dist/buttonRenderAdapter-cjs.js +56 -0
- package/dist/buttonRenderAdapter-es.js +51 -0
- package/dist/clamp-cjs.js +0 -1194
- package/dist/clamp-es.js +1 -1091
- package/dist/docs/empty-states/empty-states.md +29 -0
- package/dist/index.cjs +4 -0
- package/dist/index.mjs +4 -0
- package/dist/primitives/BottomSheet/index.cjs +5 -2
- package/dist/primitives/BottomSheet/index.mjs +5 -2
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +32 -0
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.types.d.ts +30 -0
- package/dist/primitives/ComboboxPrimitive/index.cjs +28 -0
- package/dist/primitives/ComboboxPrimitive/index.d.ts +2 -0
- package/dist/primitives/ComboboxPrimitive/index.mjs +22 -0
- package/dist/primitives/InputNumberExperimental/index.cjs +4 -2
- package/dist/primitives/InputNumberExperimental/index.mjs +4 -2
- package/dist/primitives/index.cjs +11 -2
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.mjs +10 -2
- package/dist/styles.css +767 -0
- package/dist/unstyledPrimitives/index.cjs +237 -3588
- package/dist/unstyledPrimitives/index.mjs +96 -3447
- package/dist/useButton-cjs.js +1196 -0
- package/dist/useButton-es.js +1091 -0
- package/dist/useCompositeListItem-cjs.js +1792 -0
- package/dist/useCompositeListItem-es.js +1762 -0
- package/dist/useLabel-cjs.js +411 -0
- package/dist/useLabel-es.js +378 -0
- package/package.json +2 -2
|
@@ -1,3081 +1,128 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { f as formatErrorMessage, d as EMPTY_ARRAY, a as useRefWithInit, b as useMergedRefs, N as NOOP, E as EMPTY_OBJECT, u as useRenderElement, m as mergeProps } from '../useRenderElement-es.js';
|
|
2
|
+
import { a as useStableCallback, ar as pointer, u as useBaseUiId, d as useIsoLayoutEffect, t as transitionStatusMapping, n as createChangeEventDetails, Q as imperativeAction, o as ownerDocument, m as contains, g as getTarget, b as useTimeout, as as swipe, ac as useAnimationFrame, al as TransitionStatusDataAttributes, a4 as triggerHover, B as useControlled, a6 as itemPress, c as useTransitionStatus, f as useOpenChangeComplete, a8 as useAnimationsFinished, ao as isFirefox, at as scrub, K as createGenericEventDetails, V as isWebKit } from '../useButton-es.js';
|
|
3
|
+
import { A as useCoreFilter, B as AriaCombobox, D as stringifyAsLabel, E as useComboboxInputValueContext, l as ComboboxArrow, i as ComboboxBackdrop, x as ComboboxClear, v as ComboboxCollection, w as ComboboxEmpty, n as ComboboxGroup, o as ComboboxGroupLabel, m as ComboboxIcon, c as ComboboxInput, d as ComboboxInputGroup, p as ComboboxItem, f as ComboboxList, k as ComboboxPopup, h as ComboboxPortal, j as ComboboxPositioner, u as ComboboxRow, g as ComboboxStatus, e as ComboboxTrigger, z as useFilteredItems, s as ComboboxChip, t as ComboboxChipRemove, r as ComboboxChips, q as ComboboxItemIndicator, a as ComboboxLabel, C as ComboboxRoot, b as ComboboxValue, y as useComboboxFilter } from '../ComboboxChipRemove-es.js';
|
|
5
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
-
import { a as useFormContext, b as useFieldRootContext, j as useLabelableId, k as useField, f as fieldValidityMapping, u as useLabelableContext, l as DEFAULT_FIELD_STATE_ATTRIBUTES, c as useLabel, m as useNumberFieldRootContext, n as DEFAULT_STEP, s as stateAttributesMapping$9, i as NumberFieldDecrement, d as NumberFieldGroup, h as NumberFieldIncrement, e as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
|
|
7
|
-
import { u as useFloatingRootContext, o as useListNavigation, p as useTypeahead, q as getPseudoElementBounds, C as CompositeList, r as useAnchorPositioning, s as getDisabledMountTransitionStyles, t as useCompositeListItem, I as IndexGuessBehavior, a as DrawerClose, D as DrawerTitle, v as useMenuRootContext, w as useMenuPositionerContext, x as useContextMenuRootContext, y as useMenuItem, R as REGULAR_ITEM, z as itemMapping, A as MenuStore, l as MenuGroup, n as MenuGroupLabel, c as MenuItem, b as MenuLinkItem, j as MenuPopup, h as MenuPortal, i as MenuPositioner, k as MenuRadioGroup, d as MenuRadioItem, e as MenuRadioItemIndicator, g as MenuRoot, m as MenuSubmenuRoot, f as MenuSubmenuTrigger, M as MenuTrigger } from '../MenuSubmenuTrigger-es.js';
|
|
8
|
-
import * as ReactDOM from 'react-dom';
|
|
9
5
|
import { S as Separator } from '../Separator-es.js';
|
|
6
|
+
import { u as useDialogRootContext, n as useDrawerProviderContext, o as DrawerBackdropCssVars, q as DrawerPopupCssVars, r as DrawerProviderContext, s as useDialogPortalContext, t as DialogStore, v as useDrawerRootContext, m as useTriggerRegistration, w as DrawerPopupDataAttributes, x as useDrawerSnapPoints, y as DrawerViewportContext, z as DRAWER_CONTENT_ATTRIBUTE, c as DrawerBackdrop, f as DrawerContent, d as DrawerPopup, b as DrawerPortal, a as DrawerRoot, D as DrawerTrigger, A as useScrollAreaRootContext, B as getOffset, E as ScrollAreaRootCssVars, F as scrollAreaStateAttributesMapping, G as useScrollAreaViewportContext, S as ScrollAreaRoot, e as ScrollAreaViewport } from '../ScrollAreaViewport-es.js';
|
|
7
|
+
import { a as DrawerClose, D as DrawerTitle, u as useMenuRootContext, o as useMenuPositionerContext, p as useContextMenuRootContext, q as useMenuItem, R as REGULAR_ITEM, r as itemMapping, s as MenuStore, l as MenuGroup, n as MenuGroupLabel, c as MenuItem, b as MenuLinkItem, j as MenuPopup, h as MenuPortal, i as MenuPositioner, k as MenuRadioGroup, d as MenuRadioItem, e as MenuRadioItemIndicator, g as MenuRoot, m as MenuSubmenuRoot, f as MenuSubmenuTrigger, M as MenuTrigger } from '../MenuSubmenuTrigger-es.js';
|
|
8
|
+
import { u as useRenderElement, m as mergeProps, f as formatErrorMessage, N as NOOP, E as EMPTY_OBJECT } from '../useRenderElement-es.js';
|
|
10
9
|
import { a as isHTMLElement, f as getComputedStyle, b as isElement, k as getWindow } from '../floating-ui.utils.dom-es.js';
|
|
10
|
+
import { c as clamp } from '../clamp-es.js';
|
|
11
|
+
import { C as CommonPopupDataAttributes, p as popupStateMapping, $ as BASE_UI_SWIPE_IGNORE_SELECTOR, m as useDirection, r as inertValue } from '../InternalBackdrop-es.js';
|
|
12
|
+
import * as ReactDOM from 'react-dom';
|
|
13
|
+
import { u as useCompositeListItem } from '../useCompositeListItem-es.js';
|
|
11
14
|
import { y as round } from '../index.esm-es.js';
|
|
15
|
+
import { u as useNumberFieldRootContext, D as DEFAULT_STEP, s as stateAttributesMapping$6, d as NumberFieldDecrement, a as NumberFieldGroup, c as NumberFieldIncrement, b as NumberFieldInput, N as NumberFieldRoot } from '../NumberFieldInput-es.js';
|
|
16
|
+
import '../useLabel-es.js';
|
|
12
17
|
import '../floating-ui.react-dom-es.js';
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
if (process.env.NODE_ENV !== "production") ComboboxRootContext.displayName = "ComboboxRootContext";
|
|
16
|
-
const ComboboxFloatingContext = /*#__PURE__*/React.createContext(undefined);
|
|
17
|
-
if (process.env.NODE_ENV !== "production") ComboboxFloatingContext.displayName = "ComboboxFloatingContext";
|
|
18
|
-
const ComboboxDerivedItemsContext = /*#__PURE__*/React.createContext(undefined);
|
|
19
|
-
// `inputValue` can't be placed in the store.
|
|
20
|
-
// https://github.com/mui/base-ui/issues/2703
|
|
21
|
-
if (process.env.NODE_ENV !== "production") ComboboxDerivedItemsContext.displayName = "ComboboxDerivedItemsContext";
|
|
22
|
-
const ComboboxInputValueContext = /*#__PURE__*/React.createContext('');
|
|
23
|
-
if (process.env.NODE_ENV !== "production") ComboboxInputValueContext.displayName = "ComboboxInputValueContext";
|
|
24
|
-
function useComboboxRootContext() {
|
|
25
|
-
const context = React.useContext(ComboboxRootContext);
|
|
26
|
-
if (!context) {
|
|
27
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxRootContext is missing. Combobox parts must be placed within <Combobox.Root>.' : formatErrorMessage(22));
|
|
28
|
-
}
|
|
29
|
-
return context;
|
|
30
|
-
}
|
|
31
|
-
function useComboboxFloatingContext() {
|
|
32
|
-
const context = React.useContext(ComboboxFloatingContext);
|
|
33
|
-
if (!context) {
|
|
34
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxFloatingContext is missing. Combobox parts must be placed within <Combobox.Root>.' : formatErrorMessage(23));
|
|
35
|
-
}
|
|
36
|
-
return context;
|
|
37
|
-
}
|
|
38
|
-
function useComboboxDerivedItemsContext() {
|
|
39
|
-
const context = React.useContext(ComboboxDerivedItemsContext);
|
|
40
|
-
if (!context) {
|
|
41
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxItemsContext is missing. Combobox parts must be placed within <Combobox.Root>.' : formatErrorMessage(24));
|
|
42
|
-
}
|
|
43
|
-
return context;
|
|
44
|
-
}
|
|
45
|
-
function useComboboxInputValueContext() {
|
|
46
|
-
return React.useContext(ComboboxInputValueContext);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const defaultItemEquality = (itemValue, selectedValue) => Object.is(itemValue, selectedValue);
|
|
50
|
-
function compareItemEquality(itemValue, selectedValue, comparer) {
|
|
51
|
-
if (itemValue == null || selectedValue == null) {
|
|
52
|
-
return Object.is(itemValue, selectedValue);
|
|
53
|
-
}
|
|
54
|
-
return comparer(itemValue, selectedValue);
|
|
55
|
-
}
|
|
56
|
-
function selectedValueIncludes(selectedValues, itemValue, comparer) {
|
|
57
|
-
if (!selectedValues || selectedValues.length === 0) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return selectedValues.some(selectedValue => {
|
|
61
|
-
if (selectedValue === undefined) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
return compareItemEquality(itemValue, selectedValue, comparer);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function findItemIndex(itemValues, selectedValue, comparer) {
|
|
68
|
-
if (!itemValues || itemValues.length === 0) {
|
|
69
|
-
return -1;
|
|
70
|
-
}
|
|
71
|
-
return itemValues.findIndex(itemValue => {
|
|
72
|
-
if (itemValue === undefined) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
return compareItemEquality(itemValue, selectedValue, comparer);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function removeItem(selectedValues, itemValue, comparer) {
|
|
79
|
-
return selectedValues.filter(selectedValue => !compareItemEquality(itemValue, selectedValue, comparer));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function serializeValue(value) {
|
|
83
|
-
if (value == null) {
|
|
84
|
-
return '';
|
|
85
|
-
}
|
|
86
|
-
if (typeof value === 'string') {
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
return JSON.stringify(value);
|
|
91
|
-
} catch {
|
|
92
|
-
return String(value);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isGroupedItems(items) {
|
|
97
|
-
return items != null && items.length > 0 && typeof items[0] === 'object' && items[0] != null && 'items' in items[0];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Checks if the items array contains an item with a null value that has a non-null label.
|
|
102
|
-
*/
|
|
103
|
-
function hasNullItemLabel(items) {
|
|
104
|
-
if (!Array.isArray(items)) {
|
|
105
|
-
return items != null && 'null' in items;
|
|
106
|
-
}
|
|
107
|
-
const arrayItems = items;
|
|
108
|
-
if (isGroupedItems(arrayItems)) {
|
|
109
|
-
for (const group of arrayItems) {
|
|
110
|
-
for (const item of group.items) {
|
|
111
|
-
if (item && item.value == null && item.label != null) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
for (const item of arrayItems) {
|
|
119
|
-
if (item && item.value == null && item.label != null) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
function stringifyAsLabel(item, itemToStringLabel) {
|
|
126
|
-
if (itemToStringLabel && item != null) {
|
|
127
|
-
return itemToStringLabel(item) ?? '';
|
|
128
|
-
}
|
|
129
|
-
if (item && typeof item === 'object') {
|
|
130
|
-
if ('label' in item && item.label != null) {
|
|
131
|
-
return String(item.label);
|
|
132
|
-
}
|
|
133
|
-
if ('value' in item) {
|
|
134
|
-
return String(item.value);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return serializeValue(item);
|
|
138
|
-
}
|
|
139
|
-
function stringifyAsValue(item, itemToStringValue) {
|
|
140
|
-
if (itemToStringValue && item != null) {
|
|
141
|
-
return itemToStringValue(item) ?? '';
|
|
142
|
-
}
|
|
143
|
-
if (item && typeof item === 'object' && 'value' in item && 'label' in item) {
|
|
144
|
-
return serializeValue(item.value);
|
|
145
|
-
}
|
|
146
|
-
return serializeValue(item);
|
|
147
|
-
}
|
|
148
|
-
function resolveSelectedLabel(value, items, itemToStringLabel) {
|
|
149
|
-
function fallback() {
|
|
150
|
-
return stringifyAsLabel(value, itemToStringLabel);
|
|
151
|
-
}
|
|
152
|
-
if (itemToStringLabel && value != null) {
|
|
153
|
-
return itemToStringLabel(value);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Custom object with explicit label takes precedence
|
|
157
|
-
if (value && typeof value === 'object' && 'label' in value && value.label != null) {
|
|
158
|
-
return value.label;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Items provided as plain record map
|
|
162
|
-
if (items && !Array.isArray(items)) {
|
|
163
|
-
return items[value] ?? fallback();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Items provided as array (flat or grouped)
|
|
167
|
-
if (Array.isArray(items)) {
|
|
168
|
-
const arrayItems = items;
|
|
169
|
-
const flatItems = isGroupedItems(arrayItems) ? arrayItems.flatMap(group => group.items) : arrayItems;
|
|
170
|
-
if (value == null || typeof value !== 'object') {
|
|
171
|
-
const match = flatItems.find(item => item.value === value);
|
|
172
|
-
if (match && match.label != null) {
|
|
173
|
-
return match.label;
|
|
174
|
-
}
|
|
175
|
-
return fallback();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Object without explicit label: try matching by its `value` property
|
|
179
|
-
if ('value' in value) {
|
|
180
|
-
const match = flatItems.find(item => item && item.value === value.value);
|
|
181
|
-
if (match && match.label != null) {
|
|
182
|
-
return match.label;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return fallback();
|
|
187
|
-
}
|
|
188
|
-
function resolveMultipleLabels(values, items, itemToStringLabel) {
|
|
189
|
-
return values.reduce((acc, value, index) => {
|
|
190
|
-
if (index > 0) {
|
|
191
|
-
acc.push(', ');
|
|
192
|
-
}
|
|
193
|
-
acc.push(/*#__PURE__*/jsx(React.Fragment, {
|
|
194
|
-
children: resolveSelectedLabel(value, items, itemToStringLabel)
|
|
195
|
-
}, index));
|
|
196
|
-
return acc;
|
|
197
|
-
}, []);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const selectors = {
|
|
201
|
-
id: createSelector(state => state.id),
|
|
202
|
-
labelId: createSelector(state => state.labelId),
|
|
203
|
-
query: createSelector(state => state.query),
|
|
204
|
-
items: createSelector(state => state.items),
|
|
205
|
-
selectedValue: createSelector(state => state.selectedValue),
|
|
206
|
-
hasSelectionChips: createSelector(state => {
|
|
207
|
-
const selectedValue = state.selectedValue;
|
|
208
|
-
return Array.isArray(selectedValue) && selectedValue.length > 0;
|
|
209
|
-
}),
|
|
210
|
-
hasSelectedValue: createSelector(state => {
|
|
211
|
-
const {
|
|
212
|
-
selectedValue,
|
|
213
|
-
selectionMode
|
|
214
|
-
} = state;
|
|
215
|
-
if (selectedValue == null) {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
if (selectionMode === 'multiple' && Array.isArray(selectedValue)) {
|
|
219
|
-
return selectedValue.length > 0;
|
|
220
|
-
}
|
|
221
|
-
return true;
|
|
222
|
-
}),
|
|
223
|
-
hasNullItemLabel: createSelector((state, enabled) => {
|
|
224
|
-
return enabled ? hasNullItemLabel(state.items) : false;
|
|
225
|
-
}),
|
|
226
|
-
open: createSelector(state => state.open),
|
|
227
|
-
mounted: createSelector(state => state.mounted),
|
|
228
|
-
forceMounted: createSelector(state => state.forceMounted),
|
|
229
|
-
inline: createSelector(state => state.inline),
|
|
230
|
-
activeIndex: createSelector(state => state.activeIndex),
|
|
231
|
-
selectedIndex: createSelector(state => state.selectedIndex),
|
|
232
|
-
isActive: createSelector((state, index) => state.activeIndex === index),
|
|
233
|
-
isSelected: createSelector((state, itemValue) => {
|
|
234
|
-
const comparer = state.isItemEqualToValue;
|
|
235
|
-
const selectedValue = state.selectedValue;
|
|
236
|
-
if (Array.isArray(selectedValue)) {
|
|
237
|
-
return selectedValue.some(selectedItem => compareItemEquality(itemValue, selectedItem, comparer));
|
|
238
|
-
}
|
|
239
|
-
return compareItemEquality(itemValue, selectedValue, comparer);
|
|
240
|
-
}),
|
|
241
|
-
transitionStatus: createSelector(state => state.transitionStatus),
|
|
242
|
-
popupProps: createSelector(state => state.popupProps),
|
|
243
|
-
inputProps: createSelector(state => state.inputProps),
|
|
244
|
-
triggerProps: createSelector(state => state.triggerProps),
|
|
245
|
-
getItemProps: createSelector(state => state.getItemProps),
|
|
246
|
-
positionerElement: createSelector(state => state.positionerElement),
|
|
247
|
-
listElement: createSelector(state => state.listElement),
|
|
248
|
-
triggerElement: createSelector(state => state.triggerElement),
|
|
249
|
-
inputElement: createSelector(state => state.inputElement),
|
|
250
|
-
inputGroupElement: createSelector(state => state.inputGroupElement),
|
|
251
|
-
popupSide: createSelector(state => state.popupSide),
|
|
252
|
-
openMethod: createSelector(state => state.openMethod),
|
|
253
|
-
inputInsidePopup: createSelector(state => state.inputInsidePopup),
|
|
254
|
-
selectionMode: createSelector(state => state.selectionMode),
|
|
255
|
-
listRef: createSelector(state => state.listRef),
|
|
256
|
-
labelsRef: createSelector(state => state.labelsRef),
|
|
257
|
-
popupRef: createSelector(state => state.popupRef),
|
|
258
|
-
emptyRef: createSelector(state => state.emptyRef),
|
|
259
|
-
inputRef: createSelector(state => state.inputRef),
|
|
260
|
-
keyboardActiveRef: createSelector(state => state.keyboardActiveRef),
|
|
261
|
-
chipsContainerRef: createSelector(state => state.chipsContainerRef),
|
|
262
|
-
clearRef: createSelector(state => state.clearRef),
|
|
263
|
-
valuesRef: createSelector(state => state.valuesRef),
|
|
264
|
-
allValuesRef: createSelector(state => state.allValuesRef),
|
|
265
|
-
name: createSelector(state => state.name),
|
|
266
|
-
disabled: createSelector(state => state.disabled),
|
|
267
|
-
readOnly: createSelector(state => state.readOnly),
|
|
268
|
-
required: createSelector(state => state.required),
|
|
269
|
-
grid: createSelector(state => state.grid),
|
|
270
|
-
isGrouped: createSelector(state => state.isGrouped),
|
|
271
|
-
virtualized: createSelector(state => state.virtualized),
|
|
272
|
-
onOpenChangeComplete: createSelector(state => state.onOpenChangeComplete),
|
|
273
|
-
openOnInputClick: createSelector(state => state.openOnInputClick),
|
|
274
|
-
itemToStringLabel: createSelector(state => state.itemToStringLabel),
|
|
275
|
-
isItemEqualToValue: createSelector(state => state.isItemEqualToValue),
|
|
276
|
-
modal: createSelector(state => state.modal),
|
|
277
|
-
autoHighlight: createSelector(state => state.autoHighlight),
|
|
278
|
-
submitOnItemClick: createSelector(state => state.submitOnItemClick)
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Enhanced filter using Intl.Collator for more robust string matching.
|
|
283
|
-
* Uses the provided `itemToStringLabel` function if available, otherwise falls back to:
|
|
284
|
-
* • When `item` is an object with a `value` property, that property is used.
|
|
285
|
-
* • When `item` is a primitive (e.g. `string`), it is used directly.
|
|
286
|
-
*/
|
|
287
|
-
function createCollatorItemFilter(collatorFilter, itemToStringLabel) {
|
|
288
|
-
return (item, query) => {
|
|
289
|
-
if (item == null) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
const itemString = stringifyAsLabel(item, itemToStringLabel);
|
|
293
|
-
return collatorFilter.contains(itemString, query);
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Enhanced filter for single selection mode using Intl.Collator that shows all items
|
|
299
|
-
* when query is empty or matches the current selection, making it easier to browse options.
|
|
300
|
-
*/
|
|
301
|
-
function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue) {
|
|
302
|
-
return (item, query) => {
|
|
303
|
-
if (item == null) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
if (!query) {
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
const itemString = stringifyAsLabel(item, itemToStringLabel);
|
|
310
|
-
const selectedString = selectedValue != null ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';
|
|
311
|
-
|
|
312
|
-
// Handle case-insensitive matching consistently
|
|
313
|
-
if (selectedString && collatorFilter.contains(selectedString, query) && selectedString.length === query.length) {
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
return collatorFilter.contains(itemString, query);
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const filterCache = new Map();
|
|
321
|
-
function stringifyLocale(locale) {
|
|
322
|
-
if (Array.isArray(locale)) {
|
|
323
|
-
return locale.map(value => stringifyLocale(value)).join(',');
|
|
324
|
-
}
|
|
325
|
-
if (locale == null) {
|
|
326
|
-
return '';
|
|
327
|
-
}
|
|
328
|
-
return String(locale);
|
|
329
|
-
}
|
|
330
|
-
function getFilter(options = {}) {
|
|
331
|
-
const mergedOptions = {
|
|
332
|
-
usage: 'search',
|
|
333
|
-
sensitivity: 'base',
|
|
334
|
-
ignorePunctuation: true,
|
|
335
|
-
...options
|
|
336
|
-
};
|
|
337
|
-
const cacheKey = `${stringifyLocale(options.locale)}|${JSON.stringify(mergedOptions)}`;
|
|
338
|
-
const cachedFilter = filterCache.get(cacheKey);
|
|
339
|
-
if (cachedFilter) {
|
|
340
|
-
return cachedFilter;
|
|
341
|
-
}
|
|
342
|
-
const collator = new Intl.Collator(options.locale, mergedOptions);
|
|
343
|
-
const filter = {
|
|
344
|
-
contains(item, query, itemToString) {
|
|
345
|
-
if (!query) {
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
const itemString = stringifyAsLabel(item, itemToString);
|
|
349
|
-
for (let i = 0; i <= itemString.length - query.length; i += 1) {
|
|
350
|
-
if (collator.compare(itemString.slice(i, i + query.length), query) === 0) {
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return false;
|
|
355
|
-
},
|
|
356
|
-
startsWith(item, query, itemToString) {
|
|
357
|
-
if (!query) {
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
const itemString = stringifyAsLabel(item, itemToString);
|
|
361
|
-
return collator.compare(itemString.slice(0, query.length), query) === 0;
|
|
362
|
-
},
|
|
363
|
-
endsWith(item, query, itemToString) {
|
|
364
|
-
if (!query) {
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
const itemString = stringifyAsLabel(item, itemToString);
|
|
368
|
-
const queryLength = query.length;
|
|
369
|
-
return itemString.length >= queryLength && collator.compare(itemString.slice(itemString.length - queryLength), query) === 0;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
filterCache.set(cacheKey, filter);
|
|
373
|
-
return filter;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Matches items against a query using `Intl.Collator` for robust string matching.
|
|
378
|
-
*/
|
|
379
|
-
const useCoreFilter = getFilter;
|
|
380
|
-
/**
|
|
381
|
-
* Matches items against a query using `Intl.Collator` for robust string matching.
|
|
382
|
-
*/
|
|
383
|
-
function useComboboxFilter(options = {}) {
|
|
19
|
+
function AutocompleteRoot(props) {
|
|
384
20
|
const {
|
|
385
|
-
|
|
21
|
+
openOnInputClick = false,
|
|
386
22
|
value,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const contains = React.useCallback((item, query, itemToString) => {
|
|
391
|
-
if (multiple) {
|
|
392
|
-
return createCollatorItemFilter(coreFilter, itemToString)(item, query);
|
|
393
|
-
}
|
|
394
|
-
return createSingleSelectionCollatorFilter(coreFilter, itemToString, value)(item, query);
|
|
395
|
-
}, [coreFilter, value, multiple]);
|
|
396
|
-
return React.useMemo(() => ({
|
|
397
|
-
contains,
|
|
398
|
-
startsWith: coreFilter.startsWith,
|
|
399
|
-
endsWith: coreFilter.endsWith
|
|
400
|
-
}), [contains, coreFilter]);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const NO_ACTIVE_VALUE = Symbol('none');
|
|
404
|
-
const INITIAL_LAST_HIGHLIGHT = {
|
|
405
|
-
value: NO_ACTIVE_VALUE,
|
|
406
|
-
index: -1
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
function AriaCombobox(props) {
|
|
410
|
-
const {
|
|
411
|
-
id: idProp,
|
|
412
|
-
onOpenChangeComplete: onOpenChangeCompleteProp,
|
|
413
|
-
defaultSelectedValue = null,
|
|
414
|
-
selectedValue: selectedValueProp,
|
|
415
|
-
onSelectedValueChange,
|
|
416
|
-
defaultInputValue: defaultInputValueProp,
|
|
417
|
-
inputValue: inputValueProp,
|
|
418
|
-
selectionMode = 'none',
|
|
419
|
-
onItemHighlighted: onItemHighlightedProp,
|
|
420
|
-
name: nameProp,
|
|
421
|
-
disabled: disabledProp = false,
|
|
422
|
-
readOnly = false,
|
|
423
|
-
required = false,
|
|
424
|
-
inputRef: inputRefProp,
|
|
425
|
-
grid = false,
|
|
426
|
-
items,
|
|
427
|
-
filteredItems: filteredItemsProp,
|
|
428
|
-
filter: filterProp,
|
|
429
|
-
openOnInputClick = true,
|
|
430
|
-
autoHighlight = false,
|
|
431
|
-
keepHighlight = false,
|
|
432
|
-
highlightItemOnHover = true,
|
|
433
|
-
loopFocus = true,
|
|
434
|
-
itemToStringLabel,
|
|
23
|
+
defaultValue,
|
|
24
|
+
onValueChange,
|
|
25
|
+
mode = 'list',
|
|
435
26
|
itemToStringValue,
|
|
436
|
-
|
|
437
|
-
virtualized = false,
|
|
438
|
-
inline: inlineProp = false,
|
|
439
|
-
fillInputOnItemPress = true,
|
|
440
|
-
modal = false,
|
|
441
|
-
limit = -1,
|
|
442
|
-
autoComplete = 'list',
|
|
443
|
-
formAutoComplete,
|
|
444
|
-
locale,
|
|
445
|
-
submitOnItemClick = false
|
|
27
|
+
...other
|
|
446
28
|
} = props;
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
} = useFormContext();
|
|
450
|
-
const {
|
|
451
|
-
setDirty,
|
|
452
|
-
validityData,
|
|
453
|
-
shouldValidateOnChange,
|
|
454
|
-
setFilled,
|
|
455
|
-
name: fieldName,
|
|
456
|
-
disabled: fieldDisabled,
|
|
457
|
-
setTouched,
|
|
458
|
-
setFocused,
|
|
459
|
-
validationMode,
|
|
460
|
-
validation
|
|
461
|
-
} = useFieldRootContext();
|
|
462
|
-
const id = useLabelableId({
|
|
463
|
-
id: idProp
|
|
464
|
-
});
|
|
465
|
-
const collatorFilter = useCoreFilter({
|
|
466
|
-
locale
|
|
467
|
-
});
|
|
468
|
-
const [queryChangedAfterOpen, setQueryChangedAfterOpen] = React.useState(false);
|
|
469
|
-
const [closeQuery, setCloseQuery] = React.useState(null);
|
|
470
|
-
const listRef = React.useRef([]);
|
|
471
|
-
const labelsRef = React.useRef([]);
|
|
472
|
-
const popupRef = React.useRef(null);
|
|
473
|
-
const inputRef = React.useRef(null);
|
|
474
|
-
const startDismissRef = React.useRef(null);
|
|
475
|
-
const endDismissRef = React.useRef(null);
|
|
476
|
-
const emptyRef = React.useRef(null);
|
|
477
|
-
const keyboardActiveRef = React.useRef(true);
|
|
478
|
-
const hadInputClearRef = React.useRef(false);
|
|
479
|
-
const chipsContainerRef = React.useRef(null);
|
|
480
|
-
const clearRef = React.useRef(null);
|
|
481
|
-
const selectionEventRef = React.useRef(null);
|
|
482
|
-
const lastHighlightRef = React.useRef(INITIAL_LAST_HIGHLIGHT);
|
|
483
|
-
const pendingQueryHighlightRef = React.useRef(null);
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Contains the currently visible list of item values post-filtering.
|
|
487
|
-
*/
|
|
488
|
-
const valuesRef = React.useRef([]);
|
|
489
|
-
/**
|
|
490
|
-
* Contains all item values in a stable, unfiltered order.
|
|
491
|
-
* This is only used when `items` prop is not provided.
|
|
492
|
-
* It accumulates values on first mount and does not remove them on unmount due to
|
|
493
|
-
* filtering, providing a stable index for selected value tracking.
|
|
494
|
-
*/
|
|
495
|
-
const allValuesRef = React.useRef([]);
|
|
496
|
-
const disabled = fieldDisabled || disabledProp;
|
|
497
|
-
const name = fieldName ?? nameProp;
|
|
498
|
-
const multiple = selectionMode === 'multiple';
|
|
499
|
-
const single = selectionMode === 'single';
|
|
500
|
-
const hasInputValue = inputValueProp !== undefined || defaultInputValueProp !== undefined;
|
|
501
|
-
const hasItems = items !== undefined;
|
|
502
|
-
const hasFilteredItemsProp = filteredItemsProp !== undefined;
|
|
503
|
-
let autoHighlightMode;
|
|
504
|
-
if (autoHighlight === 'always') {
|
|
505
|
-
autoHighlightMode = 'always';
|
|
506
|
-
} else {
|
|
507
|
-
autoHighlightMode = autoHighlight ? 'input-change' : false;
|
|
508
|
-
}
|
|
509
|
-
const [selectedValue, setSelectedValueUnwrapped] = useControlled({
|
|
510
|
-
controlled: selectedValueProp,
|
|
511
|
-
default: multiple ? defaultSelectedValue ?? EMPTY_ARRAY : defaultSelectedValue,
|
|
512
|
-
name: 'Combobox',
|
|
513
|
-
state: 'selectedValue'
|
|
514
|
-
});
|
|
515
|
-
const filter = React.useMemo(() => {
|
|
516
|
-
if (filterProp === null) {
|
|
517
|
-
return () => true;
|
|
518
|
-
}
|
|
519
|
-
if (filterProp !== undefined) {
|
|
520
|
-
return filterProp;
|
|
521
|
-
}
|
|
522
|
-
if (single && !queryChangedAfterOpen) {
|
|
523
|
-
return createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue);
|
|
524
|
-
}
|
|
525
|
-
return createCollatorItemFilter(collatorFilter, itemToStringLabel);
|
|
526
|
-
}, [filterProp, single, selectedValue, queryChangedAfterOpen, collatorFilter, itemToStringLabel]);
|
|
29
|
+
const enableInline = mode === 'inline' || mode === 'both';
|
|
30
|
+
const staticItems = mode === 'inline' || mode === 'none';
|
|
527
31
|
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
return '';
|
|
538
|
-
}).current;
|
|
539
|
-
const [inputValue, setInputValueUnwrapped] = useControlled({
|
|
540
|
-
controlled: inputValueProp,
|
|
541
|
-
default: initialDefaultInputValue,
|
|
542
|
-
name: 'Combobox',
|
|
543
|
-
state: 'inputValue'
|
|
544
|
-
});
|
|
545
|
-
const [open, setOpenUnwrapped] = useControlled({
|
|
546
|
-
controlled: props.open,
|
|
547
|
-
default: props.defaultOpen,
|
|
548
|
-
name: 'Combobox',
|
|
549
|
-
state: 'open'
|
|
550
|
-
});
|
|
551
|
-
const isGrouped = isGroupedItems(items);
|
|
552
|
-
const query = closeQuery ?? (inputValue === '' ? '' : String(inputValue).trim());
|
|
553
|
-
const selectedLabelString = single ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';
|
|
554
|
-
const shouldBypassFiltering = single && !queryChangedAfterOpen && query !== '' && selectedLabelString !== '' && selectedLabelString.length === query.length && collatorFilter.contains(selectedLabelString, query);
|
|
555
|
-
const filterQuery = shouldBypassFiltering ? '' : query;
|
|
556
|
-
const shouldIgnoreExternalFiltering = hasItems && hasFilteredItemsProp && shouldBypassFiltering;
|
|
557
|
-
const flatItems = React.useMemo(() => {
|
|
558
|
-
if (!items) {
|
|
559
|
-
return EMPTY_ARRAY;
|
|
560
|
-
}
|
|
561
|
-
if (isGrouped) {
|
|
562
|
-
return items.flatMap(group => group.items);
|
|
563
|
-
}
|
|
564
|
-
return items;
|
|
565
|
-
}, [items, isGrouped]);
|
|
566
|
-
const filteredItems = React.useMemo(() => {
|
|
567
|
-
if (filteredItemsProp && !shouldIgnoreExternalFiltering) {
|
|
568
|
-
return filteredItemsProp;
|
|
569
|
-
}
|
|
570
|
-
if (!items) {
|
|
571
|
-
return EMPTY_ARRAY;
|
|
572
|
-
}
|
|
573
|
-
if (isGrouped) {
|
|
574
|
-
const groupedItems = items;
|
|
575
|
-
const resultingGroups = [];
|
|
576
|
-
let currentCount = 0;
|
|
577
|
-
for (const group of groupedItems) {
|
|
578
|
-
if (limit > -1 && currentCount >= limit) {
|
|
579
|
-
break;
|
|
580
|
-
}
|
|
581
|
-
const candidateItems = filterQuery === '' ? group.items : group.items.filter(item => filter(item, filterQuery, itemToStringLabel));
|
|
582
|
-
if (candidateItems.length === 0) {
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
|
|
586
|
-
const itemsToTake = candidateItems.slice(0, remainingLimit);
|
|
587
|
-
if (itemsToTake.length > 0) {
|
|
588
|
-
const newGroup = {
|
|
589
|
-
...group,
|
|
590
|
-
items: itemsToTake
|
|
591
|
-
};
|
|
592
|
-
resultingGroups.push(newGroup);
|
|
593
|
-
currentCount += itemsToTake.length;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return resultingGroups;
|
|
597
|
-
}
|
|
598
|
-
if (filterQuery === '') {
|
|
599
|
-
return limit > -1 ? flatItems.slice(0, limit) :
|
|
600
|
-
// The cast here is done as `flatItems` is readonly.
|
|
601
|
-
// valuesRef.current, a mutable ref, can be set to `flatFilteredItems`, which may
|
|
602
|
-
// reference this exact readonly value, creating a mutation risk.
|
|
603
|
-
// However, <Combobox.Item> can never mutate this value as the mutating effect
|
|
604
|
-
// bails early when `items` is provided, and this is only ever returned
|
|
605
|
-
// when `items` is provided due to the early return at the top of this hook.
|
|
606
|
-
flatItems;
|
|
607
|
-
}
|
|
608
|
-
const limitedItems = [];
|
|
609
|
-
for (const item of flatItems) {
|
|
610
|
-
if (limit > -1 && limitedItems.length >= limit) {
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
if (filter(item, filterQuery, itemToStringLabel)) {
|
|
614
|
-
limitedItems.push(item);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return limitedItems;
|
|
618
|
-
}, [filteredItemsProp, shouldIgnoreExternalFiltering, items, isGrouped, filterQuery, limit, filter, itemToStringLabel, flatItems]);
|
|
619
|
-
const flatFilteredItems = React.useMemo(() => {
|
|
620
|
-
if (isGrouped) {
|
|
621
|
-
const groups = filteredItems;
|
|
622
|
-
return groups.flatMap(g => g.items);
|
|
623
|
-
}
|
|
624
|
-
return filteredItems;
|
|
625
|
-
}, [filteredItems, isGrouped]);
|
|
626
|
-
const store = useRefWithInit(() => new Store({
|
|
627
|
-
id,
|
|
628
|
-
labelId: undefined,
|
|
629
|
-
selectedValue,
|
|
630
|
-
open,
|
|
631
|
-
filter,
|
|
632
|
-
query,
|
|
633
|
-
items,
|
|
634
|
-
selectionMode,
|
|
635
|
-
listRef,
|
|
636
|
-
labelsRef,
|
|
637
|
-
popupRef,
|
|
638
|
-
emptyRef,
|
|
639
|
-
inputRef,
|
|
640
|
-
startDismissRef,
|
|
641
|
-
endDismissRef,
|
|
642
|
-
keyboardActiveRef,
|
|
643
|
-
chipsContainerRef,
|
|
644
|
-
clearRef,
|
|
645
|
-
valuesRef,
|
|
646
|
-
allValuesRef,
|
|
647
|
-
selectionEventRef,
|
|
648
|
-
name,
|
|
649
|
-
disabled,
|
|
650
|
-
readOnly,
|
|
651
|
-
required,
|
|
652
|
-
grid,
|
|
653
|
-
isGrouped,
|
|
654
|
-
virtualized,
|
|
655
|
-
openOnInputClick,
|
|
656
|
-
itemToStringLabel,
|
|
657
|
-
isItemEqualToValue,
|
|
658
|
-
modal,
|
|
659
|
-
autoHighlight: autoHighlightMode,
|
|
660
|
-
submitOnItemClick,
|
|
661
|
-
hasInputValue,
|
|
662
|
-
mounted: false,
|
|
663
|
-
forceMounted: false,
|
|
664
|
-
transitionStatus: 'idle',
|
|
665
|
-
inline: inlineProp,
|
|
666
|
-
activeIndex: null,
|
|
667
|
-
selectedIndex: null,
|
|
668
|
-
popupProps: {},
|
|
669
|
-
inputProps: {},
|
|
670
|
-
triggerProps: {},
|
|
671
|
-
positionerElement: null,
|
|
672
|
-
listElement: null,
|
|
673
|
-
triggerElement: null,
|
|
674
|
-
inputElement: null,
|
|
675
|
-
inputGroupElement: null,
|
|
676
|
-
popupSide: null,
|
|
677
|
-
openMethod: null,
|
|
678
|
-
inputInsidePopup: true,
|
|
679
|
-
onOpenChangeComplete: onOpenChangeCompleteProp || NOOP,
|
|
680
|
-
// Placeholder callbacks replaced on first render
|
|
681
|
-
setOpen: NOOP,
|
|
682
|
-
setInputValue: NOOP,
|
|
683
|
-
setSelectedValue: NOOP,
|
|
684
|
-
setIndices: NOOP,
|
|
685
|
-
onItemHighlighted: NOOP,
|
|
686
|
-
handleSelection: NOOP,
|
|
687
|
-
getItemProps: () => EMPTY_OBJECT,
|
|
688
|
-
forceMount: NOOP,
|
|
689
|
-
requestSubmit: NOOP
|
|
690
|
-
})).current;
|
|
691
|
-
const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;
|
|
692
|
-
const fieldStringValue = React.useMemo(() => {
|
|
693
|
-
if (selectionMode === 'none') {
|
|
694
|
-
return fieldRawValue;
|
|
695
|
-
}
|
|
696
|
-
if (Array.isArray(selectedValue)) {
|
|
697
|
-
return selectedValue.map(value => stringifyAsValue(value, itemToStringValue));
|
|
698
|
-
}
|
|
699
|
-
return stringifyAsValue(selectedValue, itemToStringValue);
|
|
700
|
-
}, [fieldRawValue, itemToStringValue, selectionMode, selectedValue]);
|
|
701
|
-
const onItemHighlighted = useStableCallback(onItemHighlightedProp);
|
|
702
|
-
const onOpenChangeComplete = useStableCallback(onOpenChangeCompleteProp);
|
|
703
|
-
const activeIndex = useStore(store, selectors.activeIndex);
|
|
704
|
-
const selectedIndex = useStore(store, selectors.selectedIndex);
|
|
705
|
-
const positionerElement = useStore(store, selectors.positionerElement);
|
|
706
|
-
const listElement = useStore(store, selectors.listElement);
|
|
707
|
-
const triggerElement = useStore(store, selectors.triggerElement);
|
|
708
|
-
const inputElement = useStore(store, selectors.inputElement);
|
|
709
|
-
const inputGroupElement = useStore(store, selectors.inputGroupElement);
|
|
710
|
-
const inline = useStore(store, selectors.inline);
|
|
711
|
-
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
712
|
-
const triggerRef = useValueAsRef(triggerElement);
|
|
713
|
-
const {
|
|
714
|
-
mounted,
|
|
715
|
-
setMounted,
|
|
716
|
-
transitionStatus
|
|
717
|
-
} = useTransitionStatus(open);
|
|
718
|
-
const {
|
|
719
|
-
openMethod,
|
|
720
|
-
triggerProps
|
|
721
|
-
} = useOpenInteractionType(open);
|
|
722
|
-
useField({
|
|
723
|
-
id,
|
|
724
|
-
name,
|
|
725
|
-
commit: validation.commit,
|
|
726
|
-
value: fieldRawValue,
|
|
727
|
-
controlRef: inputInsidePopup ? triggerRef : inputRef,
|
|
728
|
-
getValue: () => fieldStringValue
|
|
729
|
-
});
|
|
730
|
-
const forceMount = useStableCallback(() => {
|
|
731
|
-
if (items) {
|
|
732
|
-
// Ensure typeahead works on a closed list.
|
|
733
|
-
labelsRef.current = flatFilteredItems.map(item => stringifyAsLabel(item, itemToStringLabel));
|
|
734
|
-
} else {
|
|
735
|
-
store.set('forceMounted', true);
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
const initialSelectedValueRef = React.useRef(selectedValue);
|
|
739
|
-
useIsoLayoutEffect(() => {
|
|
740
|
-
// Ensure the values and labels are registered for programmatic value changes.
|
|
741
|
-
if (selectedValue !== initialSelectedValueRef.current) {
|
|
742
|
-
forceMount();
|
|
743
|
-
}
|
|
744
|
-
}, [forceMount, selectedValue]);
|
|
745
|
-
const setIndices = useStableCallback(options => {
|
|
746
|
-
store.update(options);
|
|
747
|
-
const type = options.type || 'none';
|
|
748
|
-
if (options.activeIndex === undefined) {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
if (options.activeIndex === null) {
|
|
752
|
-
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
753
|
-
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
754
|
-
onItemHighlighted(undefined, createGenericEventDetails(type, undefined, {
|
|
755
|
-
index: -1
|
|
756
|
-
}));
|
|
757
|
-
}
|
|
758
|
-
} else {
|
|
759
|
-
const activeValue = valuesRef.current[options.activeIndex];
|
|
760
|
-
lastHighlightRef.current = {
|
|
761
|
-
value: activeValue,
|
|
762
|
-
index: options.activeIndex
|
|
763
|
-
};
|
|
764
|
-
onItemHighlighted(activeValue, createGenericEventDetails(type, undefined, {
|
|
765
|
-
index: options.activeIndex
|
|
766
|
-
}));
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
const setInputValue = useStableCallback((next, eventDetails) => {
|
|
770
|
-
hadInputClearRef.current = eventDetails.reason === inputClear;
|
|
771
|
-
props.onInputValueChange?.(next, eventDetails);
|
|
772
|
-
if (eventDetails.isCanceled) {
|
|
773
|
-
return;
|
|
32
|
+
// Mirror the typed value for uncontrolled usage so we can compose the temporary
|
|
33
|
+
// inline input value.
|
|
34
|
+
const isControlled = value !== undefined;
|
|
35
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue ?? '');
|
|
36
|
+
const [inlineInputValue, setInlineInputValue] = React.useState('');
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
if (isControlled) {
|
|
39
|
+
setInlineInputValue('');
|
|
774
40
|
}
|
|
41
|
+
}, [value, isControlled]);
|
|
775
42
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
// Defer index updates until after the filtered items have been derived to ensure
|
|
790
|
-
// `onItemHighlighted` receives the latest item.
|
|
791
|
-
pendingQueryHighlightRef.current = {
|
|
792
|
-
hasQuery
|
|
793
|
-
};
|
|
794
|
-
if (hasQuery && autoHighlightMode && store.state.activeIndex == null) {
|
|
795
|
-
store.set('activeIndex', 0);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
43
|
+
// Compose the input value shown to the user: inline value takes precedence when present.
|
|
44
|
+
let resolvedInputValue;
|
|
45
|
+
if (enableInline && inlineInputValue !== '') {
|
|
46
|
+
resolvedInputValue = inlineInputValue;
|
|
47
|
+
} else if (isControlled) {
|
|
48
|
+
resolvedInputValue = value ?? '';
|
|
49
|
+
} else {
|
|
50
|
+
resolvedInputValue = internalValue;
|
|
51
|
+
}
|
|
52
|
+
const handleValueChange = useStableCallback((nextValue, eventDetails) => {
|
|
53
|
+
setInlineInputValue('');
|
|
54
|
+
if (!isControlled) {
|
|
55
|
+
setInternalValue(nextValue);
|
|
798
56
|
}
|
|
799
|
-
|
|
57
|
+
onValueChange?.(nextValue, eventDetails);
|
|
800
58
|
});
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
59
|
+
const collator = useCoreFilter();
|
|
60
|
+
const baseFilter = React.useMemo(() => {
|
|
61
|
+
if (other.filter !== undefined) {
|
|
62
|
+
return other.filter;
|
|
804
63
|
}
|
|
64
|
+
return collator.contains;
|
|
65
|
+
}, [other.filter, collator]);
|
|
66
|
+
const resolvedQuery = String(isControlled ? value : internalValue).trim();
|
|
805
67
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
eventDetails.allowPropagation();
|
|
811
|
-
}
|
|
812
|
-
props.onOpenChange?.(nextOpen, eventDetails);
|
|
813
|
-
if (eventDetails.isCanceled) {
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
if (!nextOpen && queryChangedAfterOpen) {
|
|
817
|
-
if (single) {
|
|
818
|
-
if (!inline) {
|
|
819
|
-
setCloseQuery(query);
|
|
820
|
-
}
|
|
821
|
-
// Avoid a flicker when closing the popup with an empty query.
|
|
822
|
-
if (query === '') {
|
|
823
|
-
setQueryChangedAfterOpen(false);
|
|
824
|
-
}
|
|
825
|
-
} else if (multiple) {
|
|
826
|
-
if (inline || inputInsidePopup) {
|
|
827
|
-
setIndices({
|
|
828
|
-
activeIndex: null
|
|
829
|
-
});
|
|
830
|
-
} else {
|
|
831
|
-
// Freeze the current query so filtering remains stable while exiting.
|
|
832
|
-
setCloseQuery(query);
|
|
833
|
-
}
|
|
834
|
-
// Clear the input immediately on close while retaining filtering via closeQuery for exit animations
|
|
835
|
-
// if the input is outside the popup.
|
|
836
|
-
setInputValue('', createChangeEventDetails(inputClear, eventDetails.event));
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
setOpenUnwrapped(nextOpen);
|
|
840
|
-
if (!nextOpen && inputInsidePopup && (eventDetails.reason === focusOut || eventDetails.reason === outsidePress)) {
|
|
841
|
-
setTouched(true);
|
|
842
|
-
setFocused(false);
|
|
843
|
-
if (validationMode === 'onBlur') {
|
|
844
|
-
const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;
|
|
845
|
-
validation.commit(valueToValidate);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
const setSelectedValue = useStableCallback((nextValue, eventDetails) => {
|
|
850
|
-
// Cast to `any` due to conditional value type (single vs. multiple).
|
|
851
|
-
// The runtime implementation already ensures the correct value shape.
|
|
852
|
-
onSelectedValueChange?.(nextValue, eventDetails);
|
|
853
|
-
if (eventDetails.isCanceled) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
setSelectedValueUnwrapped(nextValue);
|
|
857
|
-
const shouldFillInput = selectionMode === 'none' && popupRef.current && fillInputOnItemPress || single && !store.state.inputInsidePopup;
|
|
858
|
-
if (shouldFillInput) {
|
|
859
|
-
setInputValue(stringifyAsLabel(nextValue, itemToStringLabel), createChangeEventDetails(eventDetails.reason, eventDetails.event));
|
|
860
|
-
}
|
|
861
|
-
if (single && nextValue != null && eventDetails.reason !== inputChange && queryChangedAfterOpen && !inline) {
|
|
862
|
-
setCloseQuery(query);
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
const handleSelection = useStableCallback((event, passedValue) => {
|
|
866
|
-
let itemValue = passedValue;
|
|
867
|
-
if (itemValue === undefined) {
|
|
868
|
-
if (activeIndex === null) {
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
itemValue = valuesRef.current[activeIndex];
|
|
68
|
+
// In "both", wrap filtering to use only the typed value, ignoring the inline value.
|
|
69
|
+
const resolvedFilter = React.useMemo(() => {
|
|
70
|
+
if (mode !== 'both') {
|
|
71
|
+
return staticItems ? null : baseFilter;
|
|
872
72
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
selectionEventRef.current = null;
|
|
876
|
-
const eventDetails = createChangeEventDetails(itemPress, overrideEvent);
|
|
877
|
-
|
|
878
|
-
// Let the link handle the click.
|
|
879
|
-
const href = targetEl?.closest('a')?.getAttribute('href');
|
|
880
|
-
if (href) {
|
|
881
|
-
if (href.startsWith('#')) {
|
|
882
|
-
setOpen(false, eventDetails);
|
|
883
|
-
}
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (multiple) {
|
|
887
|
-
const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
888
|
-
const isCurrentlySelected = selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
|
|
889
|
-
const nextValue = isCurrentlySelected ? removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
|
|
890
|
-
setSelectedValue(nextValue, eventDetails);
|
|
891
|
-
const wasFiltering = inputRef.current ? inputRef.current.value.trim() !== '' : false;
|
|
892
|
-
if (!wasFiltering) {
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
if (store.state.inputInsidePopup) {
|
|
896
|
-
setInputValue('', createChangeEventDetails(inputClear, eventDetails.event));
|
|
897
|
-
} else {
|
|
898
|
-
setOpen(false, eventDetails);
|
|
899
|
-
}
|
|
900
|
-
} else {
|
|
901
|
-
setSelectedValue(itemValue, eventDetails);
|
|
902
|
-
setOpen(false, eventDetails);
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
const requestSubmit = useStableCallback(() => {
|
|
906
|
-
if (!store.state.submitOnItemClick) {
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
const form = store.state.inputElement?.form;
|
|
910
|
-
if (form && typeof form.requestSubmit === 'function') {
|
|
911
|
-
form.requestSubmit();
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
const handleUnmount = useStableCallback(() => {
|
|
915
|
-
setMounted(false);
|
|
916
|
-
onOpenChangeComplete?.(false);
|
|
917
|
-
setQueryChangedAfterOpen(false);
|
|
918
|
-
setCloseQuery(null);
|
|
919
|
-
if (selectionMode === 'none') {
|
|
920
|
-
setIndices({
|
|
921
|
-
activeIndex: null,
|
|
922
|
-
selectedIndex: null
|
|
923
|
-
});
|
|
924
|
-
} else {
|
|
925
|
-
setIndices({
|
|
926
|
-
activeIndex: null
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// Multiple selection mode:
|
|
931
|
-
// If the user typed a filter and didn't select in multiple mode, clear the input
|
|
932
|
-
// after close completes to avoid mid-exit flicker and start fresh on next open.
|
|
933
|
-
if (multiple && inputRef.current && inputRef.current.value !== '' && !hadInputClearRef.current) {
|
|
934
|
-
setInputValue('', createChangeEventDetails(inputClear));
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// Single selection mode:
|
|
938
|
-
// - If input is rendered inside the popup, clear it so the next open is blank
|
|
939
|
-
// - If input is outside the popup, sync it to the selected value
|
|
940
|
-
if (single) {
|
|
941
|
-
if (store.state.inputInsidePopup) {
|
|
942
|
-
if (inputRef.current && inputRef.current.value !== '') {
|
|
943
|
-
setInputValue('', createChangeEventDetails(inputClear));
|
|
944
|
-
}
|
|
945
|
-
} else {
|
|
946
|
-
const stringVal = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
947
|
-
if (inputRef.current && inputRef.current.value !== stringVal) {
|
|
948
|
-
// If no selection was made, treat this as clearing the typed filter.
|
|
949
|
-
const reason = stringVal === '' ? inputClear : none;
|
|
950
|
-
setInputValue(stringVal, createChangeEventDetails(reason));
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
// Support composing the Dialog component around an inline combobox.
|
|
957
|
-
// `[role="dialog"]` is more interoperable than using a context, e.g. it can work
|
|
958
|
-
// with third-party modal libraries, though the limitation is that the closest
|
|
959
|
-
// `role=dialog` part must be the animated element.
|
|
960
|
-
const resolvedPopupRef = React.useMemo(() => {
|
|
961
|
-
if (inline && positionerElement) {
|
|
962
|
-
return {
|
|
963
|
-
current: positionerElement.closest('[role="dialog"]')
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
return popupRef;
|
|
967
|
-
}, [inline, positionerElement]);
|
|
968
|
-
useOpenChangeComplete({
|
|
969
|
-
enabled: !props.actionsRef,
|
|
970
|
-
open,
|
|
971
|
-
ref: resolvedPopupRef,
|
|
972
|
-
onComplete() {
|
|
973
|
-
if (!open) {
|
|
974
|
-
handleUnmount();
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
React.useImperativeHandle(props.actionsRef, () => ({
|
|
979
|
-
unmount: handleUnmount
|
|
980
|
-
}), [handleUnmount]);
|
|
981
|
-
useIsoLayoutEffect(function syncSelectedIndex() {
|
|
982
|
-
if (open || selectionMode === 'none') {
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
const registry = items ? flatItems : allValuesRef.current;
|
|
986
|
-
if (multiple) {
|
|
987
|
-
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
988
|
-
const lastValue = currentValue[currentValue.length - 1];
|
|
989
|
-
const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);
|
|
990
|
-
setIndices({
|
|
991
|
-
selectedIndex: lastIndex === -1 ? null : lastIndex
|
|
992
|
-
});
|
|
993
|
-
} else {
|
|
994
|
-
const index = findItemIndex(registry, selectedValue, isItemEqualToValue);
|
|
995
|
-
setIndices({
|
|
996
|
-
selectedIndex: index === -1 ? null : index
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
}, [open, selectedValue, items, selectionMode, flatItems, multiple, isItemEqualToValue, setIndices]);
|
|
1000
|
-
useIsoLayoutEffect(() => {
|
|
1001
|
-
if (items) {
|
|
1002
|
-
valuesRef.current = flatFilteredItems;
|
|
1003
|
-
listRef.current.length = flatFilteredItems.length;
|
|
1004
|
-
}
|
|
1005
|
-
}, [items, flatFilteredItems]);
|
|
1006
|
-
useIsoLayoutEffect(() => {
|
|
1007
|
-
const pendingHighlight = pendingQueryHighlightRef.current;
|
|
1008
|
-
if (pendingHighlight) {
|
|
1009
|
-
if (pendingHighlight.hasQuery) {
|
|
1010
|
-
if (autoHighlightMode) {
|
|
1011
|
-
store.set('activeIndex', 0);
|
|
1012
|
-
}
|
|
1013
|
-
} else if (autoHighlightMode === 'always') {
|
|
1014
|
-
store.set('activeIndex', 0);
|
|
1015
|
-
}
|
|
1016
|
-
pendingQueryHighlightRef.current = null;
|
|
1017
|
-
}
|
|
1018
|
-
if (!open && !inline) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
const shouldUseFlatFilteredItems = hasItems || hasFilteredItemsProp;
|
|
1022
|
-
const candidateItems = shouldUseFlatFilteredItems ? flatFilteredItems : valuesRef.current;
|
|
1023
|
-
const storeActiveIndex = store.state.activeIndex;
|
|
1024
|
-
if (storeActiveIndex == null) {
|
|
1025
|
-
if (autoHighlightMode === 'always' && candidateItems.length > 0) {
|
|
1026
|
-
store.set('activeIndex', 0);
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
1030
|
-
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
1031
|
-
store.state.onItemHighlighted(undefined, createGenericEventDetails(none, undefined, {
|
|
1032
|
-
index: -1
|
|
1033
|
-
}));
|
|
1034
|
-
}
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
if (storeActiveIndex >= candidateItems.length) {
|
|
1038
|
-
if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {
|
|
1039
|
-
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
1040
|
-
store.state.onItemHighlighted(undefined, createGenericEventDetails(none, undefined, {
|
|
1041
|
-
index: -1
|
|
1042
|
-
}));
|
|
1043
|
-
}
|
|
1044
|
-
store.set('activeIndex', null);
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
const itemValue = candidateItems[storeActiveIndex];
|
|
1048
|
-
const previouslyHighlightedItemValue = lastHighlightRef.current.value;
|
|
1049
|
-
const isSameItem = previouslyHighlightedItemValue !== NO_ACTIVE_VALUE && compareItemEquality(itemValue, previouslyHighlightedItemValue, store.state.isItemEqualToValue);
|
|
1050
|
-
if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {
|
|
1051
|
-
lastHighlightRef.current = {
|
|
1052
|
-
value: itemValue,
|
|
1053
|
-
index: storeActiveIndex
|
|
1054
|
-
};
|
|
1055
|
-
store.state.onItemHighlighted(itemValue, createGenericEventDetails(none, undefined, {
|
|
1056
|
-
index: storeActiveIndex
|
|
1057
|
-
}));
|
|
1058
|
-
}
|
|
1059
|
-
}, [activeIndex, autoHighlightMode, hasFilteredItemsProp, hasItems, flatFilteredItems, inline, open, store]);
|
|
1060
|
-
useIsoLayoutEffect(() => {
|
|
1061
|
-
if (selectionMode === 'none') {
|
|
1062
|
-
setFilled(String(inputValue) !== '');
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
setFilled(multiple ? Array.isArray(selectedValue) && selectedValue.length > 0 : selectedValue != null);
|
|
1066
|
-
}, [setFilled, selectionMode, inputValue, selectedValue, multiple]);
|
|
1067
|
-
|
|
1068
|
-
// Ensures that the active index is not set to 0 when the list is empty.
|
|
1069
|
-
// This avoids needing to press ArrowDown twice under certain conditions.
|
|
1070
|
-
React.useEffect(() => {
|
|
1071
|
-
if (hasItems && autoHighlightMode && flatFilteredItems.length === 0) {
|
|
1072
|
-
setIndices({
|
|
1073
|
-
activeIndex: null
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
}, [hasItems, autoHighlightMode, flatFilteredItems.length, setIndices]);
|
|
1077
|
-
useValueChanged(query, () => {
|
|
1078
|
-
if (!open || query === '' || query === String(initialDefaultInputValue)) {
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
setQueryChangedAfterOpen(true);
|
|
1082
|
-
});
|
|
1083
|
-
useValueChanged(selectedValue, () => {
|
|
1084
|
-
if (selectionMode === 'none') {
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
clearErrors(name);
|
|
1088
|
-
setDirty(selectedValue !== validityData.initialValue);
|
|
1089
|
-
if (shouldValidateOnChange()) {
|
|
1090
|
-
validation.commit(selectedValue);
|
|
1091
|
-
} else {
|
|
1092
|
-
validation.commit(selectedValue, true);
|
|
1093
|
-
}
|
|
1094
|
-
if (single && !hasInputValue && !inputInsidePopup) {
|
|
1095
|
-
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
1096
|
-
if (inputValue !== nextInputValue) {
|
|
1097
|
-
setInputValue(nextInputValue, createChangeEventDetails(none));
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
});
|
|
1101
|
-
useValueChanged(inputValue, () => {
|
|
1102
|
-
if (selectionMode !== 'none') {
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
clearErrors(name);
|
|
1106
|
-
setDirty(inputValue !== validityData.initialValue);
|
|
1107
|
-
if (shouldValidateOnChange()) {
|
|
1108
|
-
validation.commit(inputValue);
|
|
1109
|
-
} else {
|
|
1110
|
-
validation.commit(inputValue, true);
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
useValueChanged(items, () => {
|
|
1114
|
-
if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
1118
|
-
if (inputValue !== nextInputValue) {
|
|
1119
|
-
setInputValue(nextInputValue, createChangeEventDetails(none));
|
|
1120
|
-
}
|
|
1121
|
-
});
|
|
1122
|
-
const floatingRootContext = useFloatingRootContext({
|
|
1123
|
-
open: inline ? true : open,
|
|
1124
|
-
onOpenChange: setOpen,
|
|
1125
|
-
elements: {
|
|
1126
|
-
reference: inputInsidePopup ? triggerElement : inputElement,
|
|
1127
|
-
floating: positionerElement
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
let ariaHasPopup;
|
|
1131
|
-
let ariaExpanded;
|
|
1132
|
-
if (!inline) {
|
|
1133
|
-
ariaHasPopup = grid ? 'grid' : 'listbox';
|
|
1134
|
-
ariaExpanded = open ? 'true' : 'false';
|
|
1135
|
-
}
|
|
1136
|
-
const role = React.useMemo(() => {
|
|
1137
|
-
const isPlainInput = inputElement?.tagName === 'INPUT';
|
|
1138
|
-
// During SSR and initial hydration, the input ref is not available yet.
|
|
1139
|
-
// Assume an input-like control so combobox ARIA attributes are present.
|
|
1140
|
-
const shouldTreatAsInput = inputElement == null || isPlainInput;
|
|
1141
|
-
const shouldApplyAria = shouldTreatAsInput || open;
|
|
1142
|
-
const reference = shouldTreatAsInput ? {
|
|
1143
|
-
autoComplete: 'off',
|
|
1144
|
-
spellCheck: 'false',
|
|
1145
|
-
autoCorrect: 'off',
|
|
1146
|
-
autoCapitalize: 'none'
|
|
1147
|
-
} : {};
|
|
1148
|
-
if (shouldApplyAria) {
|
|
1149
|
-
reference.role = 'combobox';
|
|
1150
|
-
reference['aria-expanded'] = ariaExpanded;
|
|
1151
|
-
reference['aria-haspopup'] = ariaHasPopup;
|
|
1152
|
-
reference['aria-controls'] = open ? listElement?.id : undefined;
|
|
1153
|
-
reference['aria-autocomplete'] = autoComplete;
|
|
1154
|
-
}
|
|
1155
|
-
return {
|
|
1156
|
-
reference,
|
|
1157
|
-
floating: {
|
|
1158
|
-
role: 'presentation'
|
|
1159
|
-
}
|
|
1160
|
-
};
|
|
1161
|
-
}, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);
|
|
1162
|
-
const click = useClick(floatingRootContext, {
|
|
1163
|
-
enabled: !readOnly && !disabled && openOnInputClick,
|
|
1164
|
-
event: 'mousedown-only',
|
|
1165
|
-
toggle: false,
|
|
1166
|
-
// Apply a small delay for touch to let iOS viewport centering settle.
|
|
1167
|
-
// This avoids top-bottom flip flickers if the preferred position is "top" when first tapping.
|
|
1168
|
-
touchOpenDelay: inputInsidePopup ? 0 : 50,
|
|
1169
|
-
reason: inputPress
|
|
1170
|
-
});
|
|
1171
|
-
const dismiss = useDismiss(floatingRootContext, {
|
|
1172
|
-
enabled: !readOnly && !disabled && !inline,
|
|
1173
|
-
outsidePressEvent: {
|
|
1174
|
-
mouse: 'sloppy',
|
|
1175
|
-
// The visual viewport (affected by the mobile software keyboard) can be
|
|
1176
|
-
// somewhat small. The user may want to scroll the screen to see more of
|
|
1177
|
-
// the popup.
|
|
1178
|
-
touch: 'intentional'
|
|
1179
|
-
},
|
|
1180
|
-
// Without a popup, let the Escape key bubble the event up to other popups' handlers.
|
|
1181
|
-
bubbles: inline ? true : undefined,
|
|
1182
|
-
outsidePress(event) {
|
|
1183
|
-
const target = getTarget(event);
|
|
1184
|
-
return !contains(triggerElement, target) && !contains(clearRef.current, target) && !contains(chipsContainerRef.current, target) && !contains(inputGroupElement, target);
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
const listNavigation = useListNavigation(floatingRootContext, {
|
|
1188
|
-
enabled: !readOnly && !disabled,
|
|
1189
|
-
id,
|
|
1190
|
-
listRef,
|
|
1191
|
-
activeIndex,
|
|
1192
|
-
selectedIndex,
|
|
1193
|
-
virtual: true,
|
|
1194
|
-
loopFocus,
|
|
1195
|
-
allowEscape: loopFocus && !autoHighlightMode,
|
|
1196
|
-
focusItemOnOpen: queryChangedAfterOpen || selectionMode === 'none' && !autoHighlightMode ? false : 'auto',
|
|
1197
|
-
focusItemOnHover: highlightItemOnHover,
|
|
1198
|
-
resetOnPointerLeave: !keepHighlight,
|
|
1199
|
-
// `cols` > 1 enables grid navigation.
|
|
1200
|
-
// Since <Combobox.Row> infers column sizes (and is required when building a grid),
|
|
1201
|
-
// it works correctly even with a value of `2`.
|
|
1202
|
-
// Floating UI tests don't require `role="row"` wrappers, so retains the number API.
|
|
1203
|
-
cols: grid ? 2 : 1,
|
|
1204
|
-
orientation: grid ? 'horizontal' : undefined,
|
|
1205
|
-
disabledIndices: EMPTY_ARRAY,
|
|
1206
|
-
onNavigate(nextActiveIndex, event) {
|
|
1207
|
-
// Retain the highlight only while actually transitioning out or closed.
|
|
1208
|
-
if (!event && !open || transitionStatus === 'ending') {
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
if (!event) {
|
|
1212
|
-
setIndices({
|
|
1213
|
-
activeIndex: nextActiveIndex
|
|
1214
|
-
});
|
|
1215
|
-
} else {
|
|
1216
|
-
setIndices({
|
|
1217
|
-
activeIndex: nextActiveIndex,
|
|
1218
|
-
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
const {
|
|
1224
|
-
getReferenceProps,
|
|
1225
|
-
getFloatingProps,
|
|
1226
|
-
getItemProps
|
|
1227
|
-
} = useInteractions([role, click, dismiss, listNavigation]);
|
|
1228
|
-
useOnFirstRender(() => {
|
|
1229
|
-
store.update({
|
|
1230
|
-
inline: inlineProp,
|
|
1231
|
-
popupProps: getFloatingProps(),
|
|
1232
|
-
inputProps: getReferenceProps(),
|
|
1233
|
-
triggerProps,
|
|
1234
|
-
getItemProps,
|
|
1235
|
-
setOpen,
|
|
1236
|
-
setInputValue,
|
|
1237
|
-
setSelectedValue,
|
|
1238
|
-
setIndices,
|
|
1239
|
-
onItemHighlighted,
|
|
1240
|
-
handleSelection,
|
|
1241
|
-
forceMount,
|
|
1242
|
-
requestSubmit
|
|
1243
|
-
});
|
|
1244
|
-
});
|
|
1245
|
-
useIsoLayoutEffect(() => {
|
|
1246
|
-
store.update({
|
|
1247
|
-
id,
|
|
1248
|
-
selectedValue,
|
|
1249
|
-
open,
|
|
1250
|
-
mounted,
|
|
1251
|
-
transitionStatus,
|
|
1252
|
-
items,
|
|
1253
|
-
inline: inlineProp,
|
|
1254
|
-
popupProps: getFloatingProps(),
|
|
1255
|
-
inputProps: getReferenceProps(),
|
|
1256
|
-
triggerProps,
|
|
1257
|
-
openMethod,
|
|
1258
|
-
getItemProps,
|
|
1259
|
-
selectionMode,
|
|
1260
|
-
name,
|
|
1261
|
-
disabled,
|
|
1262
|
-
readOnly,
|
|
1263
|
-
required,
|
|
1264
|
-
grid,
|
|
1265
|
-
isGrouped,
|
|
1266
|
-
virtualized,
|
|
1267
|
-
onOpenChangeComplete,
|
|
1268
|
-
openOnInputClick,
|
|
1269
|
-
itemToStringLabel,
|
|
1270
|
-
modal,
|
|
1271
|
-
autoHighlight: autoHighlightMode,
|
|
1272
|
-
isItemEqualToValue,
|
|
1273
|
-
submitOnItemClick,
|
|
1274
|
-
hasInputValue,
|
|
1275
|
-
requestSubmit
|
|
1276
|
-
});
|
|
1277
|
-
}, [store, id, selectedValue, open, mounted, transitionStatus, items, getFloatingProps, getReferenceProps, getItemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, validation, grid, isGrouped, virtualized, onOpenChangeComplete, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, requestSubmit, autoHighlightMode]);
|
|
1278
|
-
const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);
|
|
1279
|
-
const itemsContextValue = React.useMemo(() => ({
|
|
1280
|
-
query,
|
|
1281
|
-
hasItems,
|
|
1282
|
-
filteredItems,
|
|
1283
|
-
flatFilteredItems
|
|
1284
|
-
}), [query, hasItems, filteredItems, flatFilteredItems]);
|
|
1285
|
-
const serializedValue = React.useMemo(() => {
|
|
1286
|
-
if (Array.isArray(fieldRawValue)) {
|
|
1287
|
-
return '';
|
|
1288
|
-
}
|
|
1289
|
-
return stringifyAsValue(fieldRawValue, itemToStringValue);
|
|
1290
|
-
}, [fieldRawValue, itemToStringValue]);
|
|
1291
|
-
const hasMultipleSelection = multiple && Array.isArray(selectedValue) && selectedValue.length > 0;
|
|
1292
|
-
const hiddenInputName = multiple || selectionMode === 'none' ? undefined : name;
|
|
1293
|
-
const hiddenInputs = React.useMemo(() => {
|
|
1294
|
-
if (!multiple || !Array.isArray(selectedValue) || !name) {
|
|
1295
|
-
return null;
|
|
1296
|
-
}
|
|
1297
|
-
return selectedValue.map(value => {
|
|
1298
|
-
const currentSerializedValue = stringifyAsValue(value, itemToStringValue);
|
|
1299
|
-
return /*#__PURE__*/jsx("input", {
|
|
1300
|
-
type: "hidden",
|
|
1301
|
-
name: name,
|
|
1302
|
-
value: currentSerializedValue
|
|
1303
|
-
}, currentSerializedValue);
|
|
1304
|
-
});
|
|
1305
|
-
}, [multiple, selectedValue, name, itemToStringValue]);
|
|
1306
|
-
const children = /*#__PURE__*/jsxs(React.Fragment, {
|
|
1307
|
-
children: [props.children, /*#__PURE__*/jsx("input", {
|
|
1308
|
-
...validation.getInputValidationProps({
|
|
1309
|
-
// Move focus when the hidden input is focused.
|
|
1310
|
-
onFocus() {
|
|
1311
|
-
if (inputInsidePopup) {
|
|
1312
|
-
triggerElement?.focus();
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
(inputRef.current || triggerElement)?.focus();
|
|
1316
|
-
},
|
|
1317
|
-
// Handle browser autofill.
|
|
1318
|
-
onChange(event) {
|
|
1319
|
-
// Workaround for https://github.com/facebook/react/issues/9023
|
|
1320
|
-
if (event.nativeEvent.defaultPrevented) {
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
const nextValue = event.target.value;
|
|
1324
|
-
const details = createChangeEventDetails(none, event.nativeEvent);
|
|
1325
|
-
function handleChange() {
|
|
1326
|
-
// Browser autofill only writes a single scalar value.
|
|
1327
|
-
if (multiple) {
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
if (selectionMode === 'none') {
|
|
1331
|
-
setDirty(nextValue !== validityData.initialValue);
|
|
1332
|
-
setInputValue(nextValue, details);
|
|
1333
|
-
if (shouldValidateOnChange()) {
|
|
1334
|
-
validation.commit(nextValue);
|
|
1335
|
-
}
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
const matchingValue = valuesRef.current.find(v => {
|
|
1339
|
-
const candidate = stringifyAsValue(v, itemToStringValue);
|
|
1340
|
-
if (candidate.toLowerCase() === nextValue.toLowerCase()) {
|
|
1341
|
-
return true;
|
|
1342
|
-
}
|
|
1343
|
-
return false;
|
|
1344
|
-
});
|
|
1345
|
-
if (matchingValue != null) {
|
|
1346
|
-
setDirty(matchingValue !== validityData.initialValue);
|
|
1347
|
-
setSelectedValue?.(matchingValue, details);
|
|
1348
|
-
if (shouldValidateOnChange()) {
|
|
1349
|
-
validation.commit(matchingValue);
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
if (items) {
|
|
1354
|
-
handleChange();
|
|
1355
|
-
} else {
|
|
1356
|
-
forceMount();
|
|
1357
|
-
queueMicrotask(handleChange);
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}),
|
|
1361
|
-
id: id && hiddenInputName == null ? `${id}-hidden-input` : undefined,
|
|
1362
|
-
name: hiddenInputName,
|
|
1363
|
-
autoComplete: formAutoComplete,
|
|
1364
|
-
disabled: disabled,
|
|
1365
|
-
required: required && !hasMultipleSelection,
|
|
1366
|
-
readOnly: readOnly,
|
|
1367
|
-
value: serializedValue,
|
|
1368
|
-
ref: hiddenInputRef,
|
|
1369
|
-
style: hiddenInputName ? visuallyHiddenInput : visuallyHidden,
|
|
1370
|
-
tabIndex: -1,
|
|
1371
|
-
"aria-hidden": true
|
|
1372
|
-
}), hiddenInputs]
|
|
1373
|
-
});
|
|
1374
|
-
return /*#__PURE__*/jsx(ComboboxRootContext.Provider, {
|
|
1375
|
-
value: store,
|
|
1376
|
-
children: /*#__PURE__*/jsx(ComboboxFloatingContext.Provider, {
|
|
1377
|
-
value: floatingRootContext,
|
|
1378
|
-
children: /*#__PURE__*/jsx(ComboboxDerivedItemsContext.Provider, {
|
|
1379
|
-
value: itemsContextValue,
|
|
1380
|
-
children: /*#__PURE__*/jsx(ComboboxInputValueContext.Provider, {
|
|
1381
|
-
value: inputValue,
|
|
1382
|
-
children: children
|
|
1383
|
-
})
|
|
1384
|
-
})
|
|
1385
|
-
})
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
function AutocompleteRoot(props) {
|
|
1390
|
-
const {
|
|
1391
|
-
openOnInputClick = false,
|
|
1392
|
-
value,
|
|
1393
|
-
defaultValue,
|
|
1394
|
-
onValueChange,
|
|
1395
|
-
mode = 'list',
|
|
1396
|
-
itemToStringValue,
|
|
1397
|
-
...other
|
|
1398
|
-
} = props;
|
|
1399
|
-
const enableInline = mode === 'inline' || mode === 'both';
|
|
1400
|
-
const staticItems = mode === 'inline' || mode === 'none';
|
|
1401
|
-
|
|
1402
|
-
// Mirror the typed value for uncontrolled usage so we can compose the temporary
|
|
1403
|
-
// inline input value.
|
|
1404
|
-
const isControlled = value !== undefined;
|
|
1405
|
-
const [internalValue, setInternalValue] = React.useState(defaultValue ?? '');
|
|
1406
|
-
const [inlineInputValue, setInlineInputValue] = React.useState('');
|
|
1407
|
-
React.useEffect(() => {
|
|
1408
|
-
if (isControlled) {
|
|
1409
|
-
setInlineInputValue('');
|
|
1410
|
-
}
|
|
1411
|
-
}, [value, isControlled]);
|
|
1412
|
-
|
|
1413
|
-
// Compose the input value shown to the user: inline value takes precedence when present.
|
|
1414
|
-
let resolvedInputValue;
|
|
1415
|
-
if (enableInline && inlineInputValue !== '') {
|
|
1416
|
-
resolvedInputValue = inlineInputValue;
|
|
1417
|
-
} else if (isControlled) {
|
|
1418
|
-
resolvedInputValue = value ?? '';
|
|
1419
|
-
} else {
|
|
1420
|
-
resolvedInputValue = internalValue;
|
|
1421
|
-
}
|
|
1422
|
-
const handleValueChange = useStableCallback((nextValue, eventDetails) => {
|
|
1423
|
-
setInlineInputValue('');
|
|
1424
|
-
if (!isControlled) {
|
|
1425
|
-
setInternalValue(nextValue);
|
|
1426
|
-
}
|
|
1427
|
-
onValueChange?.(nextValue, eventDetails);
|
|
1428
|
-
});
|
|
1429
|
-
const collator = useCoreFilter();
|
|
1430
|
-
const baseFilter = React.useMemo(() => {
|
|
1431
|
-
if (other.filter !== undefined) {
|
|
1432
|
-
return other.filter;
|
|
1433
|
-
}
|
|
1434
|
-
return collator.contains;
|
|
1435
|
-
}, [other.filter, collator]);
|
|
1436
|
-
const resolvedQuery = String(isControlled ? value : internalValue).trim();
|
|
1437
|
-
|
|
1438
|
-
// In "both", wrap filtering to use only the typed value, ignoring the inline value.
|
|
1439
|
-
const resolvedFilter = React.useMemo(() => {
|
|
1440
|
-
if (mode !== 'both') {
|
|
1441
|
-
return staticItems ? null : baseFilter;
|
|
1442
|
-
}
|
|
1443
|
-
if (baseFilter === null) {
|
|
1444
|
-
return null;
|
|
73
|
+
if (baseFilter === null) {
|
|
74
|
+
return null;
|
|
1445
75
|
}
|
|
1446
76
|
return (item, _query, toString) => {
|
|
1447
77
|
return baseFilter(item, resolvedQuery, toString);
|
|
1448
78
|
};
|
|
1449
|
-
}, [baseFilter, mode, resolvedQuery, staticItems]);
|
|
1450
|
-
const handleItemHighlighted = useStableCallback((highlightedValue, eventDetails) => {
|
|
1451
|
-
props.onItemHighlighted?.(highlightedValue, eventDetails);
|
|
1452
|
-
if (eventDetails.reason === pointer) {
|
|
1453
|
-
return;
|
|
1454
|
-
}
|
|
1455
|
-
if (enableInline) {
|
|
1456
|
-
if (highlightedValue == null) {
|
|
1457
|
-
setInlineInputValue('');
|
|
1458
|
-
} else {
|
|
1459
|
-
setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue));
|
|
1460
|
-
}
|
|
1461
|
-
} else {
|
|
1462
|
-
setInlineInputValue('');
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1465
|
-
return /*#__PURE__*/jsx(AriaCombobox, {
|
|
1466
|
-
...other,
|
|
1467
|
-
itemToStringLabel: itemToStringValue,
|
|
1468
|
-
openOnInputClick: openOnInputClick,
|
|
1469
|
-
selectionMode: "none",
|
|
1470
|
-
fillInputOnItemPress: true,
|
|
1471
|
-
filter: resolvedFilter,
|
|
1472
|
-
autoComplete: mode,
|
|
1473
|
-
inputValue: resolvedInputValue,
|
|
1474
|
-
defaultInputValue: defaultValue,
|
|
1475
|
-
onInputValueChange: handleValueChange,
|
|
1476
|
-
onItemHighlighted: handleItemHighlighted
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
function AutocompleteValue(props) {
|
|
1481
|
-
const {
|
|
1482
|
-
children
|
|
1483
|
-
} = props;
|
|
1484
|
-
const inputValue = useComboboxInputValueContext();
|
|
1485
|
-
let returnValue = null;
|
|
1486
|
-
if (typeof children === 'function') {
|
|
1487
|
-
returnValue = children(String(inputValue));
|
|
1488
|
-
} else if (children != null) {
|
|
1489
|
-
returnValue = children;
|
|
1490
|
-
} else {
|
|
1491
|
-
returnValue = inputValue;
|
|
1492
|
-
}
|
|
1493
|
-
return /*#__PURE__*/jsx(React.Fragment, {
|
|
1494
|
-
children: returnValue
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
const triggerStateAttributesMapping = {
|
|
1499
|
-
...pressableTriggerOpenStateMapping,
|
|
1500
|
-
...fieldValidityMapping,
|
|
1501
|
-
popupSide: side => side ? {
|
|
1502
|
-
'data-popup-side': side
|
|
1503
|
-
} : null,
|
|
1504
|
-
listEmpty: empty => empty ? {
|
|
1505
|
-
'data-list-empty': ''
|
|
1506
|
-
} : null
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
function getDefaultLabelId(id) {
|
|
1510
|
-
return id == null ? undefined : `${id}-label`;
|
|
1511
|
-
}
|
|
1512
|
-
function resolveAriaLabelledBy(fieldLabelId, localLabelId) {
|
|
1513
|
-
return fieldLabelId ?? localLabelId;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
const BOUNDARY_OFFSET = 2;
|
|
1517
|
-
|
|
1518
|
-
/**
|
|
1519
|
-
* A button that opens the popup.
|
|
1520
|
-
* Renders a `<button>` element.
|
|
1521
|
-
*/
|
|
1522
|
-
const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(componentProps, forwardedRef) {
|
|
1523
|
-
const {
|
|
1524
|
-
render,
|
|
1525
|
-
className,
|
|
1526
|
-
nativeButton = true,
|
|
1527
|
-
disabled: disabledProp = false,
|
|
1528
|
-
id: idProp,
|
|
1529
|
-
...elementProps
|
|
1530
|
-
} = componentProps;
|
|
1531
|
-
const {
|
|
1532
|
-
state: fieldState,
|
|
1533
|
-
disabled: fieldDisabled,
|
|
1534
|
-
setTouched,
|
|
1535
|
-
setFocused,
|
|
1536
|
-
validationMode,
|
|
1537
|
-
validation
|
|
1538
|
-
} = useFieldRootContext();
|
|
1539
|
-
const {
|
|
1540
|
-
labelId: fieldLabelId
|
|
1541
|
-
} = useLabelableContext();
|
|
1542
|
-
const store = useComboboxRootContext();
|
|
1543
|
-
const {
|
|
1544
|
-
filteredItems
|
|
1545
|
-
} = useComboboxDerivedItemsContext();
|
|
1546
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
1547
|
-
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
1548
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
1549
|
-
const required = useStore(store, selectors.required);
|
|
1550
|
-
const mounted = useStore(store, selectors.mounted);
|
|
1551
|
-
const popupSideValue = useStore(store, selectors.popupSide);
|
|
1552
|
-
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1553
|
-
const listElement = useStore(store, selectors.listElement);
|
|
1554
|
-
const triggerProps = useStore(store, selectors.triggerProps);
|
|
1555
|
-
const triggerElement = useStore(store, selectors.triggerElement);
|
|
1556
|
-
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
1557
|
-
const rootId = useStore(store, selectors.id);
|
|
1558
|
-
const comboboxLabelId = useStore(store, selectors.labelId);
|
|
1559
|
-
const open = useStore(store, selectors.open);
|
|
1560
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
1561
|
-
const activeIndex = useStore(store, selectors.activeIndex);
|
|
1562
|
-
const selectedIndex = useStore(store, selectors.selectedIndex);
|
|
1563
|
-
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
1564
|
-
const floatingRootContext = useComboboxFloatingContext();
|
|
1565
|
-
const inputValue = useComboboxInputValueContext();
|
|
1566
|
-
const focusTimeout = useTimeout();
|
|
1567
|
-
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
|
|
1568
|
-
const listEmpty = filteredItems.length === 0;
|
|
1569
|
-
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
1570
|
-
useLabelableId({
|
|
1571
|
-
id: inputInsidePopup ? idProp : undefined
|
|
1572
|
-
});
|
|
1573
|
-
const id = inputInsidePopup ? idProp ?? rootId : idProp;
|
|
1574
|
-
const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, comboboxLabelId);
|
|
1575
|
-
const currentPointerTypeRef = React.useRef('');
|
|
1576
|
-
function trackPointerType(event) {
|
|
1577
|
-
currentPointerTypeRef.current = event.pointerType;
|
|
1578
|
-
}
|
|
1579
|
-
const domReference = floatingRootContext.useState('domReferenceElement');
|
|
1580
|
-
|
|
1581
|
-
// Update the floating root context to use the trigger element when it differs from the current reference.
|
|
1582
|
-
// This ensures useClick and useTypeahead attach handlers to the correct element.
|
|
1583
|
-
React.useEffect(() => {
|
|
1584
|
-
if (!inputInsidePopup) {
|
|
1585
|
-
return;
|
|
1586
|
-
}
|
|
1587
|
-
if (triggerElement && triggerElement !== domReference) {
|
|
1588
|
-
floatingRootContext.set('domReferenceElement', triggerElement);
|
|
1589
|
-
}
|
|
1590
|
-
}, [triggerElement, domReference, floatingRootContext, inputInsidePopup]);
|
|
1591
|
-
const {
|
|
1592
|
-
reference: triggerTypeaheadProps
|
|
1593
|
-
} = useTypeahead(floatingRootContext, {
|
|
1594
|
-
enabled: !open && !readOnly && !comboboxDisabled && selectionMode === 'single',
|
|
1595
|
-
listRef: store.state.labelsRef,
|
|
1596
|
-
activeIndex,
|
|
1597
|
-
selectedIndex,
|
|
1598
|
-
onMatch(index) {
|
|
1599
|
-
const nextSelectedValue = store.state.valuesRef.current[index];
|
|
1600
|
-
if (nextSelectedValue !== undefined) {
|
|
1601
|
-
store.state.setSelectedValue(nextSelectedValue, createChangeEventDetails('none'));
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
});
|
|
1605
|
-
const {
|
|
1606
|
-
reference: triggerClickProps
|
|
1607
|
-
} = useClick(floatingRootContext, {
|
|
1608
|
-
enabled: !readOnly && !comboboxDisabled,
|
|
1609
|
-
event: 'mousedown'
|
|
1610
|
-
});
|
|
1611
|
-
const {
|
|
1612
|
-
buttonRef,
|
|
1613
|
-
getButtonProps
|
|
1614
|
-
} = useButton({
|
|
1615
|
-
native: nativeButton,
|
|
1616
|
-
disabled
|
|
1617
|
-
});
|
|
1618
|
-
const state = {
|
|
1619
|
-
...fieldState,
|
|
1620
|
-
open,
|
|
1621
|
-
disabled,
|
|
1622
|
-
popupSide,
|
|
1623
|
-
listEmpty,
|
|
1624
|
-
placeholder: !hasSelectedValue
|
|
1625
|
-
};
|
|
1626
|
-
const setTriggerElement = useStableCallback(element => {
|
|
1627
|
-
store.set('triggerElement', element);
|
|
1628
|
-
});
|
|
1629
|
-
const element = useRenderElement('button', componentProps, {
|
|
1630
|
-
ref: [forwardedRef, buttonRef, setTriggerElement],
|
|
1631
|
-
state,
|
|
1632
|
-
props: [triggerProps, triggerClickProps, triggerTypeaheadProps, {
|
|
1633
|
-
id,
|
|
1634
|
-
tabIndex: inputInsidePopup ? 0 : -1,
|
|
1635
|
-
role: inputInsidePopup ? 'combobox' : undefined,
|
|
1636
|
-
'aria-expanded': open ? 'true' : 'false',
|
|
1637
|
-
'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
|
|
1638
|
-
'aria-controls': open ? listElement?.id : undefined,
|
|
1639
|
-
'aria-required': inputInsidePopup ? required || undefined : undefined,
|
|
1640
|
-
'aria-labelledby': ariaLabelledBy,
|
|
1641
|
-
onPointerDown: trackPointerType,
|
|
1642
|
-
onPointerEnter: trackPointerType,
|
|
1643
|
-
onFocus() {
|
|
1644
|
-
setFocused(true);
|
|
1645
|
-
if (disabled || readOnly) {
|
|
1646
|
-
return;
|
|
1647
|
-
}
|
|
1648
|
-
focusTimeout.start(0, store.state.forceMount);
|
|
1649
|
-
},
|
|
1650
|
-
onBlur(event) {
|
|
1651
|
-
// If focus is moving into the popup, don't count it as a blur.
|
|
1652
|
-
if (contains(positionerElement, event.relatedTarget)) {
|
|
1653
|
-
return;
|
|
1654
|
-
}
|
|
1655
|
-
setTouched(true);
|
|
1656
|
-
setFocused(false);
|
|
1657
|
-
if (validationMode === 'onBlur') {
|
|
1658
|
-
const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;
|
|
1659
|
-
validation.commit(valueToValidate);
|
|
1660
|
-
}
|
|
1661
|
-
},
|
|
1662
|
-
onMouseDown(event) {
|
|
1663
|
-
if (disabled || readOnly) {
|
|
1664
|
-
return;
|
|
1665
|
-
}
|
|
1666
|
-
if (!inputInsidePopup) {
|
|
1667
|
-
floatingRootContext.set('domReferenceElement', event.currentTarget);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
// Ensure items are registered for initial selection highlight.
|
|
1671
|
-
store.state.forceMount();
|
|
1672
|
-
if (currentPointerTypeRef.current !== 'touch') {
|
|
1673
|
-
store.state.inputRef.current?.focus();
|
|
1674
|
-
if (!inputInsidePopup) {
|
|
1675
|
-
event.preventDefault();
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
if (open) {
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
const doc = ownerDocument(event.currentTarget);
|
|
1682
|
-
function handleMouseUp(mouseEvent) {
|
|
1683
|
-
if (!triggerElement) {
|
|
1684
|
-
return;
|
|
1685
|
-
}
|
|
1686
|
-
const mouseUpTarget = getTarget(mouseEvent);
|
|
1687
|
-
const positioner = store.state.positionerElement;
|
|
1688
|
-
const list = store.state.listElement;
|
|
1689
|
-
if (contains(triggerElement, mouseUpTarget) || contains(positioner, mouseUpTarget) || contains(list, mouseUpTarget) || mouseUpTarget === triggerElement) {
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1692
|
-
const bounds = getPseudoElementBounds(triggerElement);
|
|
1693
|
-
const withinHorizontal = mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET;
|
|
1694
|
-
const withinVertical = mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET;
|
|
1695
|
-
if (withinHorizontal && withinVertical) {
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
store.state.setOpen(false, createChangeEventDetails('cancel-open', mouseEvent));
|
|
1699
|
-
}
|
|
1700
|
-
if (inputInsidePopup) {
|
|
1701
|
-
doc.addEventListener('mouseup', handleMouseUp, {
|
|
1702
|
-
once: true
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
},
|
|
1706
|
-
onKeyDown(event) {
|
|
1707
|
-
if (disabled || readOnly) {
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
1711
|
-
stopEvent(event);
|
|
1712
|
-
store.state.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent));
|
|
1713
|
-
store.state.inputRef.current?.focus();
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
}, validation ? validation.getValidationProps(elementProps) : elementProps, getButtonProps],
|
|
1717
|
-
stateAttributesMapping: triggerStateAttributesMapping
|
|
1718
|
-
});
|
|
1719
|
-
return element;
|
|
1720
|
-
});
|
|
1721
|
-
if (process.env.NODE_ENV !== "production") ComboboxTrigger.displayName = "ComboboxTrigger";
|
|
1722
|
-
|
|
1723
|
-
const ComboboxChipsContext = /*#__PURE__*/React.createContext(undefined);
|
|
1724
|
-
if (process.env.NODE_ENV !== "production") ComboboxChipsContext.displayName = "ComboboxChipsContext";
|
|
1725
|
-
function useComboboxChipsContext() {
|
|
1726
|
-
return React.useContext(ComboboxChipsContext);
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
const ComboboxPositionerContext = /*#__PURE__*/React.createContext(undefined);
|
|
1730
|
-
if (process.env.NODE_ENV !== "production") ComboboxPositionerContext.displayName = "ComboboxPositionerContext";
|
|
1731
|
-
function useComboboxPositionerContext(optional) {
|
|
1732
|
-
const context = React.useContext(ComboboxPositionerContext);
|
|
1733
|
-
if (context === undefined && !optional) {
|
|
1734
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Combobox.Popup> and <Combobox.Arrow> must be used within the <Combobox.Positioner> component' : formatErrorMessage(21));
|
|
1735
|
-
}
|
|
1736
|
-
return context;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
/**
|
|
1740
|
-
* @internal
|
|
1741
|
-
*/
|
|
1742
|
-
const ComboboxInternalDismissButton = /*#__PURE__*/React.forwardRef(function ComboboxInternalDismissButton(_, forwardedRef) {
|
|
1743
|
-
const store = useComboboxRootContext();
|
|
1744
|
-
const {
|
|
1745
|
-
buttonRef,
|
|
1746
|
-
getButtonProps
|
|
1747
|
-
} = useButton({
|
|
1748
|
-
native: false
|
|
1749
|
-
});
|
|
1750
|
-
const mergedRef = useMergedRefs(forwardedRef, buttonRef);
|
|
1751
|
-
const handleDismiss = useStableCallback(event => {
|
|
1752
|
-
store.state.setOpen(false, createChangeEventDetails(closePress, event.nativeEvent, event.currentTarget));
|
|
1753
|
-
});
|
|
1754
|
-
const dismissProps = getButtonProps({
|
|
1755
|
-
onClick: handleDismiss
|
|
1756
|
-
});
|
|
1757
|
-
return /*#__PURE__*/jsx("span", {
|
|
1758
|
-
ref: mergedRef,
|
|
1759
|
-
...dismissProps,
|
|
1760
|
-
"aria-label": "Dismiss",
|
|
1761
|
-
tabIndex: undefined,
|
|
1762
|
-
style: visuallyHiddenInput
|
|
1763
|
-
});
|
|
1764
|
-
});
|
|
1765
|
-
if (process.env.NODE_ENV !== "production") ComboboxInternalDismissButton.displayName = "ComboboxInternalDismissButton";
|
|
1766
|
-
|
|
1767
|
-
const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(componentProps, forwardedRef) {
|
|
1768
|
-
const {
|
|
1769
|
-
render,
|
|
1770
|
-
className,
|
|
1771
|
-
disabled: disabledProp = false,
|
|
1772
|
-
id: idProp,
|
|
1773
|
-
...elementProps
|
|
1774
|
-
} = componentProps;
|
|
1775
|
-
const {
|
|
1776
|
-
state: fieldState,
|
|
1777
|
-
disabled: fieldDisabled,
|
|
1778
|
-
setTouched,
|
|
1779
|
-
setFocused,
|
|
1780
|
-
validationMode,
|
|
1781
|
-
validation
|
|
1782
|
-
} = useFieldRootContext();
|
|
1783
|
-
const {
|
|
1784
|
-
labelId: fieldLabelId
|
|
1785
|
-
} = useLabelableContext();
|
|
1786
|
-
const comboboxChipsContext = useComboboxChipsContext();
|
|
1787
|
-
const positioning = useComboboxPositionerContext(true);
|
|
1788
|
-
const hasPositionerParent = Boolean(positioning);
|
|
1789
|
-
const store = useComboboxRootContext();
|
|
1790
|
-
const {
|
|
1791
|
-
filteredItems
|
|
1792
|
-
} = useComboboxDerivedItemsContext();
|
|
1793
|
-
// `inputValue` can't be placed in the store.
|
|
1794
|
-
// https://github.com/mui/base-ui/issues/2703
|
|
1795
|
-
const inputValue = useComboboxInputValueContext();
|
|
1796
|
-
const direction = useDirection();
|
|
1797
|
-
const required = useStore(store, selectors.required);
|
|
1798
|
-
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
1799
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
1800
|
-
const name = useStore(store, selectors.name);
|
|
1801
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
1802
|
-
const autoHighlightMode = useStore(store, selectors.autoHighlight);
|
|
1803
|
-
const inputProps = useStore(store, selectors.inputProps);
|
|
1804
|
-
const triggerProps = useStore(store, selectors.triggerProps);
|
|
1805
|
-
const open = useStore(store, selectors.open);
|
|
1806
|
-
const mounted = useStore(store, selectors.mounted);
|
|
1807
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
1808
|
-
const popupSideValue = useStore(store, selectors.popupSide);
|
|
1809
|
-
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1810
|
-
const rootId = useStore(store, selectors.id);
|
|
1811
|
-
const inline = useStore(store, selectors.inline);
|
|
1812
|
-
const modal = useStore(store, selectors.modal);
|
|
1813
|
-
const autoHighlightEnabled = Boolean(autoHighlightMode);
|
|
1814
|
-
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
1815
|
-
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
|
|
1816
|
-
const listEmpty = filteredItems.length === 0;
|
|
1817
|
-
const isInsidePopup = hasPositionerParent || inline;
|
|
1818
|
-
const focusManagerModal = !isInsidePopup || modal;
|
|
1819
|
-
const id = useBaseUiId(idProp ?? (!isInsidePopup ? rootId : undefined));
|
|
1820
|
-
const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, undefined);
|
|
1821
|
-
const fieldStateForInput = hasPositionerParent ? DEFAULT_FIELD_STATE_ATTRIBUTES : fieldState;
|
|
1822
|
-
const [composingValue, setComposingValue] = React.useState(null);
|
|
1823
|
-
const isComposingRef = React.useRef(false);
|
|
1824
|
-
const lastActiveIndexRef = React.useRef(null);
|
|
1825
|
-
const shouldRestoreActiveIndexRef = React.useRef(false);
|
|
1826
|
-
const setInputElement = useStableCallback(element => {
|
|
1827
|
-
const nextIsInsidePopup = hasPositionerParent || store.state.inline;
|
|
1828
|
-
if (nextIsInsidePopup && !store.state.hasInputValue) {
|
|
1829
|
-
store.state.setInputValue('', createChangeEventDetails(none));
|
|
1830
|
-
}
|
|
1831
|
-
store.update({
|
|
1832
|
-
inputElement: element,
|
|
1833
|
-
inputInsidePopup: nextIsInsidePopup
|
|
1834
|
-
});
|
|
1835
|
-
});
|
|
1836
|
-
const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(elementProps);
|
|
1837
|
-
const state = {
|
|
1838
|
-
...fieldStateForInput,
|
|
1839
|
-
open,
|
|
1840
|
-
disabled,
|
|
1841
|
-
readOnly,
|
|
1842
|
-
popupSide,
|
|
1843
|
-
listEmpty
|
|
1844
|
-
};
|
|
1845
|
-
function handleKeyDown(event) {
|
|
1846
|
-
if (!comboboxChipsContext) {
|
|
1847
|
-
return undefined;
|
|
1848
|
-
}
|
|
1849
|
-
let nextIndex;
|
|
1850
|
-
const {
|
|
1851
|
-
highlightedChipIndex
|
|
1852
|
-
} = comboboxChipsContext;
|
|
1853
|
-
if (highlightedChipIndex !== undefined) {
|
|
1854
|
-
if (event.key === 'ArrowLeft') {
|
|
1855
|
-
event.preventDefault();
|
|
1856
|
-
if (highlightedChipIndex > 0) {
|
|
1857
|
-
nextIndex = highlightedChipIndex - 1;
|
|
1858
|
-
} else {
|
|
1859
|
-
nextIndex = undefined;
|
|
1860
|
-
}
|
|
1861
|
-
} else if (event.key === 'ArrowRight') {
|
|
1862
|
-
event.preventDefault();
|
|
1863
|
-
if (highlightedChipIndex < selectedValue.length - 1) {
|
|
1864
|
-
nextIndex = highlightedChipIndex + 1;
|
|
1865
|
-
} else {
|
|
1866
|
-
nextIndex = undefined;
|
|
1867
|
-
}
|
|
1868
|
-
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
|
1869
|
-
event.preventDefault();
|
|
1870
|
-
// Move highlight appropriately after removal.
|
|
1871
|
-
const computedNextIndex = highlightedChipIndex >= selectedValue.length - 1 ? selectedValue.length - 2 : highlightedChipIndex;
|
|
1872
|
-
// If the computed index is negative, treat it as no highlight.
|
|
1873
|
-
nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
|
|
1874
|
-
store.state.setIndices({
|
|
1875
|
-
activeIndex: null,
|
|
1876
|
-
selectedIndex: null,
|
|
1877
|
-
type: 'keyboard'
|
|
1878
|
-
});
|
|
1879
|
-
}
|
|
1880
|
-
return nextIndex;
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
// Handle navigation when no chip is highlighted
|
|
1884
|
-
if (event.key === 'ArrowLeft' && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
|
|
1885
|
-
event.preventDefault();
|
|
1886
|
-
const lastChipIndex = Math.max(selectedValue.length - 1, 0);
|
|
1887
|
-
nextIndex = lastChipIndex;
|
|
1888
|
-
} else if (event.key === 'Backspace' && event.currentTarget.value === '' && selectedValue.length > 0) {
|
|
1889
|
-
store.state.setIndices({
|
|
1890
|
-
activeIndex: null,
|
|
1891
|
-
selectedIndex: null,
|
|
1892
|
-
type: 'keyboard'
|
|
1893
|
-
});
|
|
1894
|
-
event.preventDefault();
|
|
1895
|
-
}
|
|
1896
|
-
return nextIndex;
|
|
1897
|
-
}
|
|
1898
|
-
const element = useRenderElement('input', componentProps, {
|
|
1899
|
-
state,
|
|
1900
|
-
ref: [forwardedRef, store.state.inputRef, setInputElement],
|
|
1901
|
-
props: [inputProps, triggerProps, {
|
|
1902
|
-
type: 'text',
|
|
1903
|
-
value: componentProps.value ?? composingValue ?? inputValue,
|
|
1904
|
-
'aria-readonly': readOnly || undefined,
|
|
1905
|
-
'aria-required': required || undefined,
|
|
1906
|
-
'aria-labelledby': ariaLabelledBy,
|
|
1907
|
-
disabled,
|
|
1908
|
-
readOnly,
|
|
1909
|
-
required: selectionMode === 'none' ? required : undefined,
|
|
1910
|
-
...(selectionMode === 'none' && name && {
|
|
1911
|
-
name
|
|
1912
|
-
}),
|
|
1913
|
-
id,
|
|
1914
|
-
onFocus() {
|
|
1915
|
-
setFocused(true);
|
|
1916
|
-
if (!inline || !shouldRestoreActiveIndexRef.current) {
|
|
1917
|
-
return;
|
|
1918
|
-
}
|
|
1919
|
-
shouldRestoreActiveIndexRef.current = false;
|
|
1920
|
-
const nextActiveIndex = lastActiveIndexRef.current;
|
|
1921
|
-
if (nextActiveIndex == null ||
|
|
1922
|
-
// `valuesRef` can be sparse, so guard against restoring a removed slot.
|
|
1923
|
-
!Object.hasOwn(store.state.valuesRef.current, nextActiveIndex)) {
|
|
1924
|
-
return;
|
|
1925
|
-
}
|
|
1926
|
-
store.state.setIndices({
|
|
1927
|
-
activeIndex: nextActiveIndex
|
|
1928
|
-
});
|
|
1929
|
-
},
|
|
1930
|
-
onBlur() {
|
|
1931
|
-
setTouched(true);
|
|
1932
|
-
setFocused(false);
|
|
1933
|
-
const activeIndex = store.state.activeIndex;
|
|
1934
|
-
if (inline && activeIndex !== null && autoHighlightMode !== 'always') {
|
|
1935
|
-
lastActiveIndexRef.current = activeIndex;
|
|
1936
|
-
shouldRestoreActiveIndexRef.current = true;
|
|
1937
|
-
store.state.setIndices({
|
|
1938
|
-
activeIndex: null
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
if (validationMode === 'onBlur') {
|
|
1942
|
-
const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;
|
|
1943
|
-
validation.commit(valueToValidate);
|
|
1944
|
-
}
|
|
1945
|
-
},
|
|
1946
|
-
onCompositionStart(event) {
|
|
1947
|
-
if (isAndroid) {
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
isComposingRef.current = true;
|
|
1951
|
-
setComposingValue(event.currentTarget.value);
|
|
1952
|
-
},
|
|
1953
|
-
onCompositionEnd(event) {
|
|
1954
|
-
isComposingRef.current = false;
|
|
1955
|
-
const next = event.currentTarget.value;
|
|
1956
|
-
setComposingValue(null);
|
|
1957
|
-
store.state.setInputValue(next, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
1958
|
-
},
|
|
1959
|
-
onChange(event) {
|
|
1960
|
-
// Autofill may not provide `inputType` (Chrome) or may report
|
|
1961
|
-
// `insertReplacementText` (Firefox).
|
|
1962
|
-
const inputType = event.nativeEvent.inputType;
|
|
1963
|
-
const autofillLikeInput = !inputType || inputType === 'insertReplacementText';
|
|
1964
|
-
const shouldOpenOnInput = isComposingRef.current || !autofillLikeInput;
|
|
1965
|
-
|
|
1966
|
-
// During IME composition, avoid propagating controlled updates to prevent
|
|
1967
|
-
// filtering the options prematurely so `Empty` won't show incorrectly.
|
|
1968
|
-
// We can't rely on this check for Android due to how it handles composition
|
|
1969
|
-
// events with some keyboards (e.g. Samsung keyboard with predictive text on
|
|
1970
|
-
// treats all text as always-composing).
|
|
1971
|
-
// https://github.com/mui/base-ui/issues/2942
|
|
1972
|
-
if (isComposingRef.current) {
|
|
1973
|
-
const nextVal = event.currentTarget.value;
|
|
1974
|
-
setComposingValue(nextVal);
|
|
1975
|
-
if (nextVal === '' && !store.state.openOnInputClick && !store.state.inputInsidePopup) {
|
|
1976
|
-
store.state.setOpen(false, createChangeEventDetails(inputClear, event.nativeEvent));
|
|
1977
|
-
}
|
|
1978
|
-
const trimmed = nextVal.trim();
|
|
1979
|
-
const shouldMaintainHighlight = autoHighlightEnabled && trimmed !== '';
|
|
1980
|
-
if (!readOnly && !disabled && trimmed) {
|
|
1981
|
-
if (shouldOpenOnInput) {
|
|
1982
|
-
store.state.setOpen(true, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
1983
|
-
if (!autoHighlightEnabled) {
|
|
1984
|
-
store.state.setIndices({
|
|
1985
|
-
activeIndex: null,
|
|
1986
|
-
selectedIndex: null,
|
|
1987
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
if (open && store.state.activeIndex !== null && !shouldMaintainHighlight) {
|
|
1993
|
-
store.state.setIndices({
|
|
1994
|
-
activeIndex: null,
|
|
1995
|
-
selectedIndex: null,
|
|
1996
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
return;
|
|
2000
|
-
}
|
|
2001
|
-
store.state.setInputValue(event.currentTarget.value, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
2002
|
-
const empty = event.currentTarget.value === '';
|
|
2003
|
-
const clearDetails = createChangeEventDetails(inputClear, event.nativeEvent);
|
|
2004
|
-
if (empty && !store.state.inputInsidePopup) {
|
|
2005
|
-
if (selectionMode === 'single') {
|
|
2006
|
-
store.state.setSelectedValue(null, clearDetails);
|
|
2007
|
-
}
|
|
2008
|
-
if (!store.state.openOnInputClick) {
|
|
2009
|
-
store.state.setOpen(false, clearDetails);
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
const trimmed = event.currentTarget.value.trim();
|
|
2013
|
-
if (!readOnly && !disabled && trimmed) {
|
|
2014
|
-
if (shouldOpenOnInput) {
|
|
2015
|
-
store.state.setOpen(true, createChangeEventDetails(inputChange, event.nativeEvent));
|
|
2016
|
-
// When autoHighlight is enabled, keep the highlight (will be set to 0 in root).
|
|
2017
|
-
if (!autoHighlightEnabled) {
|
|
2018
|
-
store.state.setIndices({
|
|
2019
|
-
activeIndex: null,
|
|
2020
|
-
selectedIndex: null,
|
|
2021
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2022
|
-
});
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
// When the user types, ensure the list resets its highlight so that
|
|
2028
|
-
// virtual focus returns to the input (aria-activedescendant is
|
|
2029
|
-
// cleared).
|
|
2030
|
-
if (open && store.state.activeIndex !== null && !autoHighlightEnabled) {
|
|
2031
|
-
store.state.setIndices({
|
|
2032
|
-
activeIndex: null,
|
|
2033
|
-
selectedIndex: null,
|
|
2034
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
},
|
|
2038
|
-
onKeyDown(event) {
|
|
2039
|
-
if (disabled || readOnly) {
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
store.state.keyboardActiveRef.current = true;
|
|
2046
|
-
const input = event.currentTarget;
|
|
2047
|
-
const scrollAmount = input.scrollWidth - input.clientWidth;
|
|
2048
|
-
const isRTL = direction === 'rtl';
|
|
2049
|
-
if (event.key === 'Home') {
|
|
2050
|
-
stopEvent(event);
|
|
2051
|
-
const cursor = isFirefox && isRTL ? input.value.length : 0;
|
|
2052
|
-
input.setSelectionRange(cursor, cursor);
|
|
2053
|
-
input.scrollLeft = 0;
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
2056
|
-
if (event.key === 'End') {
|
|
2057
|
-
stopEvent(event);
|
|
2058
|
-
const cursor = isFirefox && isRTL ? 0 : input.value.length;
|
|
2059
|
-
input.setSelectionRange(cursor, cursor);
|
|
2060
|
-
input.scrollLeft = isRTL ? -scrollAmount : scrollAmount;
|
|
2061
|
-
return;
|
|
2062
|
-
}
|
|
2063
|
-
if (!mounted && event.key === 'Escape') {
|
|
2064
|
-
const isClear = selectionMode === 'multiple' && Array.isArray(selectedValue) ? selectedValue.length === 0 : selectedValue === null;
|
|
2065
|
-
const details = createChangeEventDetails(escapeKey, event.nativeEvent);
|
|
2066
|
-
const value = selectionMode === 'multiple' ? [] : null;
|
|
2067
|
-
store.state.setInputValue('', details);
|
|
2068
|
-
store.state.setSelectedValue(value, details);
|
|
2069
|
-
if (!isClear && !store.state.inline && !details.isPropagationAllowed) {
|
|
2070
|
-
event.stopPropagation();
|
|
2071
|
-
}
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
// Handle deletion when no chip is highlighted and the input is empty.
|
|
2076
|
-
if (comboboxChipsContext && event.key === 'Backspace' && input.value === '' && comboboxChipsContext.highlightedChipIndex === undefined && Array.isArray(selectedValue) && selectedValue.length > 0) {
|
|
2077
|
-
const newValue = selectedValue.slice(0, -1);
|
|
2078
|
-
// If the removed item was also the active (highlighted) item, clear highlight
|
|
2079
|
-
store.state.setIndices({
|
|
2080
|
-
activeIndex: null,
|
|
2081
|
-
selectedIndex: null,
|
|
2082
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2083
|
-
});
|
|
2084
|
-
store.state.setSelectedValue(newValue, createChangeEventDetails(none, event.nativeEvent));
|
|
2085
|
-
return;
|
|
2086
|
-
}
|
|
2087
|
-
const hadHighlightedChip = comboboxChipsContext?.highlightedChipIndex !== undefined;
|
|
2088
|
-
const nextIndex = handleKeyDown(event);
|
|
2089
|
-
comboboxChipsContext?.setHighlightedChipIndex(nextIndex);
|
|
2090
|
-
if (nextIndex !== undefined) {
|
|
2091
|
-
comboboxChipsContext?.chipsRef.current[nextIndex]?.focus();
|
|
2092
|
-
} else if (hadHighlightedChip) {
|
|
2093
|
-
store.state.inputRef.current?.focus();
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
// event.isComposing
|
|
2097
|
-
if (event.which === 229) {
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
if (event.key === 'Enter' && open) {
|
|
2101
|
-
const activeIndex = store.state.activeIndex;
|
|
2102
|
-
const nativeEvent = event.nativeEvent;
|
|
2103
|
-
if (activeIndex === null) {
|
|
2104
|
-
if (inline) {
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
// Allow form submission when no item is highlighted.
|
|
2109
|
-
store.state.setOpen(false, createChangeEventDetails(none, nativeEvent));
|
|
2110
|
-
return;
|
|
2111
|
-
}
|
|
2112
|
-
stopEvent(event);
|
|
2113
|
-
const listItem = store.state.listRef.current[activeIndex];
|
|
2114
|
-
if (listItem) {
|
|
2115
|
-
store.state.selectionEventRef.current = nativeEvent;
|
|
2116
|
-
listItem.click();
|
|
2117
|
-
store.state.selectionEventRef.current = null;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
},
|
|
2121
|
-
onPointerMove() {
|
|
2122
|
-
store.state.keyboardActiveRef.current = false;
|
|
2123
|
-
},
|
|
2124
|
-
onPointerDown() {
|
|
2125
|
-
store.state.keyboardActiveRef.current = false;
|
|
2126
|
-
}
|
|
2127
|
-
}, validationProps],
|
|
2128
|
-
stateAttributesMapping: triggerStateAttributesMapping
|
|
2129
|
-
});
|
|
2130
|
-
return /*#__PURE__*/jsxs(React.Fragment, {
|
|
2131
|
-
children: [open && focusManagerModal && /*#__PURE__*/jsx(ComboboxInternalDismissButton, {
|
|
2132
|
-
ref: store.state.startDismissRef
|
|
2133
|
-
}), element]
|
|
2134
|
-
});
|
|
2135
|
-
});
|
|
2136
|
-
if (process.env.NODE_ENV !== "production") ComboboxInput.displayName = "ComboboxInput";
|
|
2137
|
-
|
|
2138
|
-
/**
|
|
2139
|
-
* A wrapper for the input and its associated controls.
|
|
2140
|
-
* Renders a `<div>` element.
|
|
2141
|
-
*/
|
|
2142
|
-
const ComboboxInputGroup = /*#__PURE__*/React.forwardRef(function ComboboxInputGroup(componentProps, forwardedRef) {
|
|
2143
|
-
const {
|
|
2144
|
-
render,
|
|
2145
|
-
className,
|
|
2146
|
-
...elementProps
|
|
2147
|
-
} = componentProps;
|
|
2148
|
-
const {
|
|
2149
|
-
state: fieldState,
|
|
2150
|
-
disabled: fieldDisabled
|
|
2151
|
-
} = useFieldRootContext();
|
|
2152
|
-
const store = useComboboxRootContext();
|
|
2153
|
-
const {
|
|
2154
|
-
filteredItems
|
|
2155
|
-
} = useComboboxDerivedItemsContext();
|
|
2156
|
-
const open = useStore(store, selectors.open);
|
|
2157
|
-
const mounted = useStore(store, selectors.mounted);
|
|
2158
|
-
const popupSideValue = useStore(store, selectors.popupSide);
|
|
2159
|
-
const positionerElement = useStore(store, selectors.positionerElement);
|
|
2160
|
-
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
2161
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
2162
|
-
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
2163
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2164
|
-
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
2165
|
-
const disabled = fieldDisabled || comboboxDisabled;
|
|
2166
|
-
const listEmpty = filteredItems.length === 0;
|
|
2167
|
-
const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
|
|
2168
|
-
const state = {
|
|
2169
|
-
...fieldState,
|
|
2170
|
-
open,
|
|
2171
|
-
disabled,
|
|
2172
|
-
readOnly,
|
|
2173
|
-
popupSide,
|
|
2174
|
-
listEmpty,
|
|
2175
|
-
placeholder
|
|
2176
|
-
};
|
|
2177
|
-
const setInputGroupElement = useStableCallback(element => {
|
|
2178
|
-
store.set('inputGroupElement', element);
|
|
2179
|
-
});
|
|
2180
|
-
return useRenderElement('div', componentProps, {
|
|
2181
|
-
ref: [forwardedRef, setInputGroupElement],
|
|
2182
|
-
props: [{
|
|
2183
|
-
role: 'group'
|
|
2184
|
-
}, elementProps],
|
|
2185
|
-
state,
|
|
2186
|
-
stateAttributesMapping: triggerStateAttributesMapping
|
|
2187
|
-
});
|
|
2188
|
-
});
|
|
2189
|
-
if (process.env.NODE_ENV !== "production") ComboboxInputGroup.displayName = "ComboboxInputGroup";
|
|
2190
|
-
|
|
2191
|
-
/**
|
|
2192
|
-
* An icon that indicates that the trigger button opens the popup.
|
|
2193
|
-
* Renders a `<span>` element.
|
|
2194
|
-
*/
|
|
2195
|
-
const ComboboxIcon = /*#__PURE__*/React.forwardRef(function ComboboxIcon(componentProps, forwardedRef) {
|
|
2196
|
-
const {
|
|
2197
|
-
className,
|
|
2198
|
-
render,
|
|
2199
|
-
...elementProps
|
|
2200
|
-
} = componentProps;
|
|
2201
|
-
const element = useRenderElement('span', componentProps, {
|
|
2202
|
-
ref: forwardedRef,
|
|
2203
|
-
props: [{
|
|
2204
|
-
'aria-hidden': true,
|
|
2205
|
-
children: '▼'
|
|
2206
|
-
}, elementProps]
|
|
2207
|
-
});
|
|
2208
|
-
return element;
|
|
2209
|
-
});
|
|
2210
|
-
if (process.env.NODE_ENV !== "production") ComboboxIcon.displayName = "ComboboxIcon";
|
|
2211
|
-
|
|
2212
|
-
const stateAttributesMapping$8 = {
|
|
2213
|
-
...transitionStatusMapping,
|
|
2214
|
-
...triggerOpenStateMapping
|
|
2215
|
-
};
|
|
2216
|
-
|
|
2217
|
-
/**
|
|
2218
|
-
* Clears the value when clicked.
|
|
2219
|
-
* Renders a `<button>` element.
|
|
2220
|
-
*/
|
|
2221
|
-
const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(componentProps, forwardedRef) {
|
|
2222
|
-
const {
|
|
2223
|
-
render,
|
|
2224
|
-
className,
|
|
2225
|
-
disabled: disabledProp = false,
|
|
2226
|
-
nativeButton = true,
|
|
2227
|
-
keepMounted = false,
|
|
2228
|
-
...elementProps
|
|
2229
|
-
} = componentProps;
|
|
2230
|
-
const {
|
|
2231
|
-
disabled: fieldDisabled
|
|
2232
|
-
} = useFieldRootContext();
|
|
2233
|
-
const store = useComboboxRootContext();
|
|
2234
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2235
|
-
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
2236
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
2237
|
-
const open = useStore(store, selectors.open);
|
|
2238
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
2239
|
-
const hasSelectionChips = useStore(store, selectors.hasSelectionChips);
|
|
2240
|
-
const inputValue = useComboboxInputValueContext();
|
|
2241
|
-
let visible = false;
|
|
2242
|
-
if (selectionMode === 'none') {
|
|
2243
|
-
visible = inputValue !== '';
|
|
2244
|
-
} else if (selectionMode === 'single') {
|
|
2245
|
-
visible = selectedValue != null;
|
|
2246
|
-
} else {
|
|
2247
|
-
visible = hasSelectionChips;
|
|
2248
|
-
}
|
|
2249
|
-
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
|
|
2250
|
-
const {
|
|
2251
|
-
buttonRef,
|
|
2252
|
-
getButtonProps
|
|
2253
|
-
} = useButton({
|
|
2254
|
-
native: nativeButton,
|
|
2255
|
-
disabled
|
|
2256
|
-
});
|
|
2257
|
-
const {
|
|
2258
|
-
mounted,
|
|
2259
|
-
transitionStatus,
|
|
2260
|
-
setMounted
|
|
2261
|
-
} = useTransitionStatus(visible);
|
|
2262
|
-
const state = {
|
|
2263
|
-
disabled,
|
|
2264
|
-
open,
|
|
2265
|
-
transitionStatus
|
|
2266
|
-
};
|
|
2267
|
-
useOpenChangeComplete({
|
|
2268
|
-
open: visible,
|
|
2269
|
-
ref: store.state.clearRef,
|
|
2270
|
-
onComplete() {
|
|
2271
|
-
if (!visible) {
|
|
2272
|
-
setMounted(false);
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
});
|
|
2276
|
-
const element = useRenderElement('button', componentProps, {
|
|
2277
|
-
state,
|
|
2278
|
-
ref: [forwardedRef, buttonRef, store.state.clearRef],
|
|
2279
|
-
props: [{
|
|
2280
|
-
tabIndex: -1,
|
|
2281
|
-
children: 'x',
|
|
2282
|
-
// Avoid stealing focus from the input.
|
|
2283
|
-
onMouseDown(event) {
|
|
2284
|
-
event.preventDefault();
|
|
2285
|
-
},
|
|
2286
|
-
onClick(event) {
|
|
2287
|
-
if (disabled || readOnly) {
|
|
2288
|
-
return;
|
|
2289
|
-
}
|
|
2290
|
-
const keyboardActiveRef = store.state.keyboardActiveRef;
|
|
2291
|
-
store.state.setInputValue('', createChangeEventDetails(clearPress, event.nativeEvent));
|
|
2292
|
-
if (selectionMode !== 'none') {
|
|
2293
|
-
store.state.setSelectedValue(Array.isArray(selectedValue) ? [] : null, createChangeEventDetails(clearPress, event.nativeEvent));
|
|
2294
|
-
store.state.setIndices({
|
|
2295
|
-
activeIndex: null,
|
|
2296
|
-
selectedIndex: null,
|
|
2297
|
-
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2298
|
-
});
|
|
2299
|
-
} else {
|
|
2300
|
-
store.state.setIndices({
|
|
2301
|
-
activeIndex: null,
|
|
2302
|
-
type: keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
store.state.inputRef.current?.focus();
|
|
2306
|
-
}
|
|
2307
|
-
}, elementProps, getButtonProps],
|
|
2308
|
-
stateAttributesMapping: stateAttributesMapping$8
|
|
2309
|
-
});
|
|
2310
|
-
const shouldRender = keepMounted || mounted;
|
|
2311
|
-
if (!shouldRender) {
|
|
2312
|
-
return null;
|
|
2313
|
-
}
|
|
2314
|
-
return element;
|
|
2315
|
-
});
|
|
2316
|
-
if (process.env.NODE_ENV !== "production") ComboboxClear.displayName = "ComboboxClear";
|
|
2317
|
-
|
|
2318
|
-
const GroupCollectionContext = /*#__PURE__*/React.createContext(null);
|
|
2319
|
-
if (process.env.NODE_ENV !== "production") GroupCollectionContext.displayName = "GroupCollectionContext";
|
|
2320
|
-
function useGroupCollectionContext() {
|
|
2321
|
-
return React.useContext(GroupCollectionContext);
|
|
2322
|
-
}
|
|
2323
|
-
function GroupCollectionProvider(props) {
|
|
2324
|
-
const {
|
|
2325
|
-
children,
|
|
2326
|
-
items
|
|
2327
|
-
} = props;
|
|
2328
|
-
const contextValue = React.useMemo(() => ({
|
|
2329
|
-
items
|
|
2330
|
-
}), [items]);
|
|
2331
|
-
return /*#__PURE__*/jsx(GroupCollectionContext.Provider, {
|
|
2332
|
-
value: contextValue,
|
|
2333
|
-
children: children
|
|
2334
|
-
});
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function ComboboxCollection(props) {
|
|
2338
|
-
const {
|
|
2339
|
-
children
|
|
2340
|
-
} = props;
|
|
2341
|
-
const {
|
|
2342
|
-
filteredItems
|
|
2343
|
-
} = useComboboxDerivedItemsContext();
|
|
2344
|
-
const groupContext = useGroupCollectionContext();
|
|
2345
|
-
const itemsToRender = groupContext ? groupContext.items : filteredItems;
|
|
2346
|
-
if (!itemsToRender) {
|
|
2347
|
-
return null;
|
|
2348
|
-
}
|
|
2349
|
-
return /*#__PURE__*/jsx(React.Fragment, {
|
|
2350
|
-
children: itemsToRender.map(children)
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2354
|
-
const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(componentProps, forwardedRef) {
|
|
2355
|
-
var _ComboboxCollection;
|
|
2356
|
-
const {
|
|
2357
|
-
render,
|
|
2358
|
-
className,
|
|
2359
|
-
children,
|
|
2360
|
-
...elementProps
|
|
2361
|
-
} = componentProps;
|
|
2362
|
-
const store = useComboboxRootContext();
|
|
2363
|
-
const floatingRootContext = useComboboxFloatingContext();
|
|
2364
|
-
const hasPositionerContext = Boolean(useComboboxPositionerContext(true));
|
|
2365
|
-
const {
|
|
2366
|
-
filteredItems
|
|
2367
|
-
} = useComboboxDerivedItemsContext();
|
|
2368
|
-
const items = useStore(store, selectors.items);
|
|
2369
|
-
const labelsRef = useStore(store, selectors.labelsRef);
|
|
2370
|
-
const listRef = useStore(store, selectors.listRef);
|
|
2371
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2372
|
-
const grid = useStore(store, selectors.grid);
|
|
2373
|
-
const popupProps = useStore(store, selectors.popupProps);
|
|
2374
|
-
const disabled = useStore(store, selectors.disabled);
|
|
2375
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
2376
|
-
const virtualized = useStore(store, selectors.virtualized);
|
|
2377
|
-
const multiple = selectionMode === 'multiple';
|
|
2378
|
-
const empty = filteredItems.length === 0;
|
|
2379
|
-
const setPositionerElement = useStableCallback(element => {
|
|
2380
|
-
store.set('positionerElement', element);
|
|
2381
|
-
});
|
|
2382
|
-
const setListElement = useStableCallback(element => {
|
|
2383
|
-
store.set('listElement', element);
|
|
2384
|
-
});
|
|
2385
|
-
|
|
2386
|
-
// Support "closed template" API: if children is a function, implicitly wrap it
|
|
2387
|
-
// with a Combobox.Collection that reads items from context/root.
|
|
2388
|
-
// Ensures this component's `popupProps` subscription does not cause <Combobox.Item>
|
|
2389
|
-
// to re-render on every active index change.
|
|
2390
|
-
const resolvedChildren = React.useMemo(() => {
|
|
2391
|
-
if (typeof children === 'function') {
|
|
2392
|
-
return _ComboboxCollection || (_ComboboxCollection = /*#__PURE__*/jsx(ComboboxCollection, {
|
|
2393
|
-
children: children
|
|
2394
|
-
}));
|
|
2395
|
-
}
|
|
2396
|
-
return children;
|
|
2397
|
-
}, [children]);
|
|
2398
|
-
const state = {
|
|
2399
|
-
empty
|
|
2400
|
-
};
|
|
2401
|
-
const floatingId = floatingRootContext.useState('floatingId');
|
|
2402
|
-
const element = useRenderElement('div', componentProps, {
|
|
2403
|
-
state,
|
|
2404
|
-
ref: [forwardedRef, setListElement, hasPositionerContext ? null : setPositionerElement],
|
|
2405
|
-
props: [popupProps, {
|
|
2406
|
-
children: resolvedChildren,
|
|
2407
|
-
tabIndex: -1,
|
|
2408
|
-
id: floatingId,
|
|
2409
|
-
role: grid ? 'grid' : 'listbox',
|
|
2410
|
-
'aria-multiselectable': multiple ? 'true' : undefined,
|
|
2411
|
-
onKeyDown(event) {
|
|
2412
|
-
if (disabled || readOnly) {
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
if (event.key === 'Enter') {
|
|
2416
|
-
const activeIndex = store.state.activeIndex;
|
|
2417
|
-
if (activeIndex == null) {
|
|
2418
|
-
// Allow form submission when no item is highlighted.
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
stopEvent(event);
|
|
2422
|
-
const nativeEvent = event.nativeEvent;
|
|
2423
|
-
const listItem = store.state.listRef.current[activeIndex];
|
|
2424
|
-
if (listItem) {
|
|
2425
|
-
store.state.selectionEventRef.current = nativeEvent;
|
|
2426
|
-
listItem.click();
|
|
2427
|
-
store.state.selectionEventRef.current = null;
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
},
|
|
2431
|
-
onKeyDownCapture() {
|
|
2432
|
-
store.state.keyboardActiveRef.current = true;
|
|
2433
|
-
},
|
|
2434
|
-
onPointerMoveCapture() {
|
|
2435
|
-
store.state.keyboardActiveRef.current = false;
|
|
2436
|
-
}
|
|
2437
|
-
}, elementProps]
|
|
2438
|
-
});
|
|
2439
|
-
if (virtualized) {
|
|
2440
|
-
return element;
|
|
2441
|
-
}
|
|
2442
|
-
return /*#__PURE__*/jsx(CompositeList, {
|
|
2443
|
-
elementsRef: listRef,
|
|
2444
|
-
labelsRef: items ? undefined : labelsRef,
|
|
2445
|
-
children: element
|
|
2446
|
-
});
|
|
2447
|
-
});
|
|
2448
|
-
if (process.env.NODE_ENV !== "production") ComboboxList.displayName = "ComboboxList";
|
|
2449
|
-
|
|
2450
|
-
/**
|
|
2451
|
-
* Displays a status message whose content changes are announced politely to screen readers.
|
|
2452
|
-
* Useful for conveying the status of an asynchronously loaded list.
|
|
2453
|
-
* Renders a `<div>` element.
|
|
2454
|
-
*/
|
|
2455
|
-
const ComboboxStatus = /*#__PURE__*/React.forwardRef(function ComboboxStatus(componentProps, forwardedRef) {
|
|
2456
|
-
const {
|
|
2457
|
-
render,
|
|
2458
|
-
className,
|
|
2459
|
-
...elementProps
|
|
2460
|
-
} = componentProps;
|
|
2461
|
-
return useRenderElement('div', componentProps, {
|
|
2462
|
-
ref: forwardedRef,
|
|
2463
|
-
props: [{
|
|
2464
|
-
role: 'status',
|
|
2465
|
-
'aria-live': 'polite',
|
|
2466
|
-
'aria-atomic': true
|
|
2467
|
-
}, elementProps]
|
|
2468
|
-
});
|
|
2469
|
-
});
|
|
2470
|
-
if (process.env.NODE_ENV !== "production") ComboboxStatus.displayName = "ComboboxStatus";
|
|
2471
|
-
|
|
2472
|
-
const ComboboxPortalContext = /*#__PURE__*/React.createContext(undefined);
|
|
2473
|
-
if (process.env.NODE_ENV !== "production") ComboboxPortalContext.displayName = "ComboboxPortalContext";
|
|
2474
|
-
function useComboboxPortalContext() {
|
|
2475
|
-
const context = React.useContext(ComboboxPortalContext);
|
|
2476
|
-
if (context === undefined) {
|
|
2477
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Combobox.Portal> is missing.' : formatErrorMessage(20));
|
|
2478
|
-
}
|
|
2479
|
-
return context;
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
const ComboboxPortal = /*#__PURE__*/React.forwardRef(function ComboboxPortal(props, forwardedRef) {
|
|
2483
|
-
const {
|
|
2484
|
-
keepMounted = false,
|
|
2485
|
-
...portalProps
|
|
2486
|
-
} = props;
|
|
2487
|
-
const store = useComboboxRootContext();
|
|
2488
|
-
const mounted = useStore(store, selectors.mounted);
|
|
2489
|
-
const forceMounted = useStore(store, selectors.forceMounted);
|
|
2490
|
-
const shouldRender = mounted || keepMounted || forceMounted;
|
|
2491
|
-
if (!shouldRender) {
|
|
2492
|
-
return null;
|
|
2493
|
-
}
|
|
2494
|
-
return /*#__PURE__*/jsx(ComboboxPortalContext.Provider, {
|
|
2495
|
-
value: keepMounted,
|
|
2496
|
-
children: /*#__PURE__*/jsx(FloatingPortal, {
|
|
2497
|
-
ref: forwardedRef,
|
|
2498
|
-
...portalProps
|
|
2499
|
-
})
|
|
2500
|
-
});
|
|
2501
|
-
});
|
|
2502
|
-
if (process.env.NODE_ENV !== "production") ComboboxPortal.displayName = "ComboboxPortal";
|
|
2503
|
-
|
|
2504
|
-
const stateAttributesMapping$7 = {
|
|
2505
|
-
...popupStateMapping,
|
|
2506
|
-
...transitionStatusMapping
|
|
2507
|
-
};
|
|
2508
|
-
|
|
2509
|
-
/**
|
|
2510
|
-
* An overlay displayed beneath the popup.
|
|
2511
|
-
* Renders a `<div>` element.
|
|
2512
|
-
*/
|
|
2513
|
-
const ComboboxBackdrop = /*#__PURE__*/React.forwardRef(function ComboboxBackdrop(componentProps, forwardedRef) {
|
|
2514
|
-
const {
|
|
2515
|
-
className,
|
|
2516
|
-
render,
|
|
2517
|
-
...elementProps
|
|
2518
|
-
} = componentProps;
|
|
2519
|
-
const store = useComboboxRootContext();
|
|
2520
|
-
const open = useStore(store, selectors.open);
|
|
2521
|
-
const mounted = useStore(store, selectors.mounted);
|
|
2522
|
-
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
2523
|
-
const state = {
|
|
2524
|
-
open,
|
|
2525
|
-
transitionStatus
|
|
2526
|
-
};
|
|
2527
|
-
return useRenderElement('div', componentProps, {
|
|
2528
|
-
state,
|
|
2529
|
-
ref: forwardedRef,
|
|
2530
|
-
stateAttributesMapping: stateAttributesMapping$7,
|
|
2531
|
-
props: [{
|
|
2532
|
-
role: 'presentation',
|
|
2533
|
-
hidden: !mounted,
|
|
2534
|
-
style: {
|
|
2535
|
-
userSelect: 'none',
|
|
2536
|
-
WebkitUserSelect: 'none'
|
|
2537
|
-
}
|
|
2538
|
-
}, elementProps]
|
|
2539
|
-
});
|
|
2540
|
-
});
|
|
2541
|
-
if (process.env.NODE_ENV !== "production") ComboboxBackdrop.displayName = "ComboboxBackdrop";
|
|
2542
|
-
|
|
2543
|
-
const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositioner(componentProps, forwardedRef) {
|
|
2544
|
-
const {
|
|
2545
|
-
render,
|
|
2546
|
-
className,
|
|
2547
|
-
anchor,
|
|
2548
|
-
positionMethod = 'absolute',
|
|
2549
|
-
side = 'bottom',
|
|
2550
|
-
align = 'center',
|
|
2551
|
-
sideOffset = 0,
|
|
2552
|
-
alignOffset = 0,
|
|
2553
|
-
collisionBoundary = 'clipping-ancestors',
|
|
2554
|
-
collisionPadding = 5,
|
|
2555
|
-
arrowPadding = 5,
|
|
2556
|
-
sticky = false,
|
|
2557
|
-
disableAnchorTracking = false,
|
|
2558
|
-
collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
|
|
2559
|
-
...elementProps
|
|
2560
|
-
} = componentProps;
|
|
2561
|
-
const store = useComboboxRootContext();
|
|
2562
|
-
const {
|
|
2563
|
-
filteredItems
|
|
2564
|
-
} = useComboboxDerivedItemsContext();
|
|
2565
|
-
const floatingRootContext = useComboboxFloatingContext();
|
|
2566
|
-
const keepMounted = useComboboxPortalContext();
|
|
2567
|
-
const modal = useStore(store, selectors.modal);
|
|
2568
|
-
const open = useStore(store, selectors.open);
|
|
2569
|
-
const mounted = useStore(store, selectors.mounted);
|
|
2570
|
-
const openMethod = useStore(store, selectors.openMethod);
|
|
2571
|
-
const triggerElement = useStore(store, selectors.triggerElement);
|
|
2572
|
-
const inputElement = useStore(store, selectors.inputElement);
|
|
2573
|
-
const inputGroupElement = useStore(store, selectors.inputGroupElement);
|
|
2574
|
-
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
2575
|
-
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
2576
|
-
const empty = filteredItems.length === 0;
|
|
2577
|
-
const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputGroupElement ?? inputElement);
|
|
2578
|
-
const positioning = useAnchorPositioning({
|
|
2579
|
-
anchor: resolvedAnchor,
|
|
2580
|
-
floatingRootContext,
|
|
2581
|
-
positionMethod,
|
|
2582
|
-
mounted,
|
|
2583
|
-
side,
|
|
2584
|
-
sideOffset,
|
|
2585
|
-
align,
|
|
2586
|
-
alignOffset,
|
|
2587
|
-
arrowPadding,
|
|
2588
|
-
collisionBoundary,
|
|
2589
|
-
collisionPadding,
|
|
2590
|
-
sticky,
|
|
2591
|
-
disableAnchorTracking,
|
|
2592
|
-
keepMounted,
|
|
2593
|
-
collisionAvoidance,
|
|
2594
|
-
lazyFlip: true
|
|
2595
|
-
});
|
|
2596
|
-
useScrollLock(open && modal && openMethod !== 'touch', triggerElement);
|
|
2597
|
-
const defaultProps = React.useMemo(() => {
|
|
2598
|
-
const style = {
|
|
2599
|
-
...positioning.positionerStyles
|
|
2600
|
-
};
|
|
2601
|
-
if (!open) {
|
|
2602
|
-
style.pointerEvents = 'none';
|
|
2603
|
-
}
|
|
2604
|
-
return {
|
|
2605
|
-
role: 'presentation',
|
|
2606
|
-
hidden: !mounted,
|
|
2607
|
-
style
|
|
2608
|
-
};
|
|
2609
|
-
}, [open, mounted, positioning.positionerStyles]);
|
|
2610
|
-
const state = {
|
|
2611
|
-
open,
|
|
2612
|
-
side: positioning.side,
|
|
2613
|
-
align: positioning.align,
|
|
2614
|
-
anchorHidden: positioning.anchorHidden,
|
|
2615
|
-
empty
|
|
2616
|
-
};
|
|
2617
|
-
useIsoLayoutEffect(() => {
|
|
2618
|
-
store.set('popupSide', positioning.side);
|
|
2619
|
-
}, [store, positioning.side]);
|
|
2620
|
-
const contextValue = React.useMemo(() => ({
|
|
2621
|
-
side: positioning.side,
|
|
2622
|
-
align: positioning.align,
|
|
2623
|
-
arrowRef: positioning.arrowRef,
|
|
2624
|
-
arrowUncentered: positioning.arrowUncentered,
|
|
2625
|
-
arrowStyles: positioning.arrowStyles,
|
|
2626
|
-
anchorHidden: positioning.anchorHidden,
|
|
2627
|
-
isPositioned: positioning.isPositioned
|
|
2628
|
-
}), [positioning.side, positioning.align, positioning.arrowRef, positioning.arrowUncentered, positioning.arrowStyles, positioning.anchorHidden, positioning.isPositioned]);
|
|
2629
|
-
const setPositionerElement = useStableCallback(element => {
|
|
2630
|
-
store.set('positionerElement', element);
|
|
2631
|
-
});
|
|
2632
|
-
const element = useRenderElement('div', componentProps, {
|
|
2633
|
-
state,
|
|
2634
|
-
ref: [forwardedRef, setPositionerElement],
|
|
2635
|
-
props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],
|
|
2636
|
-
stateAttributesMapping: popupStateMapping
|
|
2637
|
-
});
|
|
2638
|
-
return /*#__PURE__*/jsxs(ComboboxPositionerContext.Provider, {
|
|
2639
|
-
value: contextValue,
|
|
2640
|
-
children: [mounted && modal && /*#__PURE__*/jsx(InternalBackdrop, {
|
|
2641
|
-
inert: inertValue(!open),
|
|
2642
|
-
cutout: inputGroupElement ?? inputElement ?? triggerElement
|
|
2643
|
-
}), element]
|
|
2644
|
-
});
|
|
2645
|
-
});
|
|
2646
|
-
if (process.env.NODE_ENV !== "production") ComboboxPositioner.displayName = "ComboboxPositioner";
|
|
2647
|
-
|
|
2648
|
-
const stateAttributesMapping$6 = {
|
|
2649
|
-
...popupStateMapping,
|
|
2650
|
-
...transitionStatusMapping
|
|
2651
|
-
};
|
|
2652
|
-
|
|
2653
|
-
/**
|
|
2654
|
-
* A container for the list.
|
|
2655
|
-
* Renders a `<div>` element.
|
|
2656
|
-
*/
|
|
2657
|
-
const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(componentProps, forwardedRef) {
|
|
2658
|
-
const {
|
|
2659
|
-
render,
|
|
2660
|
-
className,
|
|
2661
|
-
initialFocus,
|
|
2662
|
-
finalFocus,
|
|
2663
|
-
...elementProps
|
|
2664
|
-
} = componentProps;
|
|
2665
|
-
const store = useComboboxRootContext();
|
|
2666
|
-
const positioning = useComboboxPositionerContext();
|
|
2667
|
-
const floatingRootContext = useComboboxFloatingContext();
|
|
2668
|
-
const {
|
|
2669
|
-
filteredItems
|
|
2670
|
-
} = useComboboxDerivedItemsContext();
|
|
2671
|
-
const mounted = useStore(store, selectors.mounted);
|
|
2672
|
-
const open = useStore(store, selectors.open);
|
|
2673
|
-
const openMethod = useStore(store, selectors.openMethod);
|
|
2674
|
-
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
2675
|
-
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
2676
|
-
const inputElement = useStore(store, selectors.inputElement);
|
|
2677
|
-
const modal = useStore(store, selectors.modal);
|
|
2678
|
-
const empty = filteredItems.length === 0;
|
|
2679
|
-
useOpenChangeComplete({
|
|
2680
|
-
open,
|
|
2681
|
-
ref: store.state.popupRef,
|
|
2682
|
-
onComplete() {
|
|
2683
|
-
if (open) {
|
|
2684
|
-
store.state.onOpenChangeComplete(true);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
});
|
|
2688
|
-
const state = {
|
|
2689
|
-
open,
|
|
2690
|
-
side: positioning.side,
|
|
2691
|
-
align: positioning.align,
|
|
2692
|
-
anchorHidden: positioning.anchorHidden,
|
|
2693
|
-
transitionStatus,
|
|
2694
|
-
empty
|
|
2695
|
-
};
|
|
2696
|
-
const element = useRenderElement('div', componentProps, {
|
|
2697
|
-
state,
|
|
2698
|
-
ref: [forwardedRef, store.state.popupRef],
|
|
2699
|
-
props: [{
|
|
2700
|
-
role: inputInsidePopup ? 'dialog' : 'presentation',
|
|
2701
|
-
tabIndex: -1,
|
|
2702
|
-
onFocus(event) {
|
|
2703
|
-
const target = getTarget(event.nativeEvent);
|
|
2704
|
-
if (openMethod !== 'touch' && (contains(store.state.listElement, target) || target === event.currentTarget)) {
|
|
2705
|
-
store.state.inputRef.current?.focus();
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
}, getDisabledMountTransitionStyles(transitionStatus), elementProps],
|
|
2709
|
-
stateAttributesMapping: stateAttributesMapping$6
|
|
2710
|
-
});
|
|
2711
|
-
|
|
2712
|
-
// Default initial focus logic:
|
|
2713
|
-
// If opened by touch, focus the popup element to prevent the virtual keyboard from opening
|
|
2714
|
-
// (this is required for Android specifically as iOS handles this automatically).
|
|
2715
|
-
const computedDefaultInitialFocus = inputInsidePopup ? interactionType => interactionType === 'touch' ? store.state.popupRef.current : inputElement : false;
|
|
2716
|
-
const resolvedInitialFocus = initialFocus === undefined ? computedDefaultInitialFocus : initialFocus;
|
|
2717
|
-
let resolvedFinalFocus;
|
|
2718
|
-
if (finalFocus != null) {
|
|
2719
|
-
resolvedFinalFocus = finalFocus;
|
|
2720
|
-
} else {
|
|
2721
|
-
resolvedFinalFocus = inputInsidePopup ? undefined : false;
|
|
2722
|
-
}
|
|
2723
|
-
const focusManagerModal = !inputInsidePopup || modal;
|
|
2724
|
-
return /*#__PURE__*/jsx(FloatingFocusManager, {
|
|
2725
|
-
context: floatingRootContext,
|
|
2726
|
-
disabled: !mounted,
|
|
2727
|
-
modal: focusManagerModal,
|
|
2728
|
-
openInteractionType: openMethod,
|
|
2729
|
-
initialFocus: resolvedInitialFocus,
|
|
2730
|
-
returnFocus: resolvedFinalFocus,
|
|
2731
|
-
getInsideElements: () => [store.state.startDismissRef.current, store.state.endDismissRef.current],
|
|
2732
|
-
children: /*#__PURE__*/jsxs(React.Fragment, {
|
|
2733
|
-
children: [element, focusManagerModal && /*#__PURE__*/jsx(ComboboxInternalDismissButton, {
|
|
2734
|
-
ref: store.state.endDismissRef
|
|
2735
|
-
})]
|
|
2736
|
-
})
|
|
2737
|
-
});
|
|
2738
|
-
});
|
|
2739
|
-
if (process.env.NODE_ENV !== "production") ComboboxPopup.displayName = "ComboboxPopup";
|
|
2740
|
-
|
|
2741
|
-
/**
|
|
2742
|
-
* Displays an element positioned against the anchor.
|
|
2743
|
-
* Renders a `<div>` element.
|
|
2744
|
-
*/
|
|
2745
|
-
const ComboboxArrow = /*#__PURE__*/React.forwardRef(function ComboboxArrow(componentProps, forwardedRef) {
|
|
2746
|
-
const {
|
|
2747
|
-
className,
|
|
2748
|
-
render,
|
|
2749
|
-
...elementProps
|
|
2750
|
-
} = componentProps;
|
|
2751
|
-
const store = useComboboxRootContext();
|
|
2752
|
-
const {
|
|
2753
|
-
arrowRef,
|
|
2754
|
-
side,
|
|
2755
|
-
align,
|
|
2756
|
-
arrowUncentered,
|
|
2757
|
-
arrowStyles
|
|
2758
|
-
} = useComboboxPositionerContext();
|
|
2759
|
-
const open = useStore(store, selectors.open);
|
|
2760
|
-
const state = {
|
|
2761
|
-
open,
|
|
2762
|
-
side,
|
|
2763
|
-
align,
|
|
2764
|
-
uncentered: arrowUncentered
|
|
2765
|
-
};
|
|
2766
|
-
return useRenderElement('div', componentProps, {
|
|
2767
|
-
ref: [arrowRef, forwardedRef],
|
|
2768
|
-
stateAttributesMapping: popupStateMapping,
|
|
2769
|
-
state,
|
|
2770
|
-
props: {
|
|
2771
|
-
style: arrowStyles,
|
|
2772
|
-
'aria-hidden': true,
|
|
2773
|
-
...elementProps
|
|
2774
|
-
}
|
|
2775
|
-
});
|
|
2776
|
-
});
|
|
2777
|
-
if (process.env.NODE_ENV !== "production") ComboboxArrow.displayName = "ComboboxArrow";
|
|
2778
|
-
|
|
2779
|
-
const ComboboxGroupContext = /*#__PURE__*/React.createContext(undefined);
|
|
2780
|
-
if (process.env.NODE_ENV !== "production") ComboboxGroupContext.displayName = "ComboboxGroupContext";
|
|
2781
|
-
function useComboboxGroupContext() {
|
|
2782
|
-
const context = React.useContext(ComboboxGroupContext);
|
|
2783
|
-
if (context === undefined) {
|
|
2784
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxGroupContext is missing. ComboboxGroup parts must be placed within <Combobox.Group>.' : formatErrorMessage(18));
|
|
2785
|
-
}
|
|
2786
|
-
return context;
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
const ComboboxGroup = /*#__PURE__*/React.forwardRef(function ComboboxGroup(componentProps, forwardedRef) {
|
|
2790
|
-
const {
|
|
2791
|
-
render,
|
|
2792
|
-
className,
|
|
2793
|
-
items,
|
|
2794
|
-
...elementProps
|
|
2795
|
-
} = componentProps;
|
|
2796
|
-
const [labelId, setLabelId] = React.useState();
|
|
2797
|
-
const contextValue = React.useMemo(() => ({
|
|
2798
|
-
labelId,
|
|
2799
|
-
setLabelId,
|
|
2800
|
-
items
|
|
2801
|
-
}), [labelId, setLabelId, items]);
|
|
2802
|
-
const element = useRenderElement('div', componentProps, {
|
|
2803
|
-
ref: forwardedRef,
|
|
2804
|
-
props: [{
|
|
2805
|
-
role: 'group',
|
|
2806
|
-
'aria-labelledby': labelId
|
|
2807
|
-
}, elementProps]
|
|
2808
|
-
});
|
|
2809
|
-
const wrappedElement = /*#__PURE__*/jsx(ComboboxGroupContext.Provider, {
|
|
2810
|
-
value: contextValue,
|
|
2811
|
-
children: element
|
|
2812
|
-
});
|
|
2813
|
-
if (items) {
|
|
2814
|
-
return /*#__PURE__*/jsx(GroupCollectionProvider, {
|
|
2815
|
-
items: items,
|
|
2816
|
-
children: wrappedElement
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
2819
|
-
return wrappedElement;
|
|
2820
|
-
});
|
|
2821
|
-
if (process.env.NODE_ENV !== "production") ComboboxGroup.displayName = "ComboboxGroup";
|
|
2822
|
-
|
|
2823
|
-
/**
|
|
2824
|
-
* An accessible label that is automatically associated with its parent group.
|
|
2825
|
-
* Renders a `<div>` element.
|
|
2826
|
-
*/
|
|
2827
|
-
const ComboboxGroupLabel = /*#__PURE__*/React.forwardRef(function ComboboxGroupLabel(componentProps, forwardedRef) {
|
|
2828
|
-
const {
|
|
2829
|
-
render,
|
|
2830
|
-
className,
|
|
2831
|
-
id: idProp,
|
|
2832
|
-
...elementProps
|
|
2833
|
-
} = componentProps;
|
|
2834
|
-
const {
|
|
2835
|
-
setLabelId
|
|
2836
|
-
} = useComboboxGroupContext();
|
|
2837
|
-
const id = useBaseUiId(idProp);
|
|
2838
|
-
useIsoLayoutEffect(() => {
|
|
2839
|
-
setLabelId(id);
|
|
2840
|
-
return () => {
|
|
2841
|
-
setLabelId(undefined);
|
|
2842
|
-
};
|
|
2843
|
-
}, [id, setLabelId]);
|
|
2844
|
-
const element = useRenderElement('div', componentProps, {
|
|
2845
|
-
ref: forwardedRef,
|
|
2846
|
-
props: [{
|
|
2847
|
-
id
|
|
2848
|
-
}, elementProps]
|
|
2849
|
-
});
|
|
2850
|
-
return element;
|
|
2851
|
-
});
|
|
2852
|
-
if (process.env.NODE_ENV !== "production") ComboboxGroupLabel.displayName = "ComboboxGroupLabel";
|
|
2853
|
-
|
|
2854
|
-
const ComboboxItemContext = /*#__PURE__*/React.createContext(undefined);
|
|
2855
|
-
if (process.env.NODE_ENV !== "production") ComboboxItemContext.displayName = "ComboboxItemContext";
|
|
2856
|
-
function useComboboxItemContext() {
|
|
2857
|
-
const context = React.useContext(ComboboxItemContext);
|
|
2858
|
-
if (!context) {
|
|
2859
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxItemContext is missing. ComboboxItem parts must be placed within <Combobox.Item>.' : formatErrorMessage(19));
|
|
2860
|
-
}
|
|
2861
|
-
return context;
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
const ComboboxRowContext = /*#__PURE__*/React.createContext(false);
|
|
2865
|
-
if (process.env.NODE_ENV !== "production") ComboboxRowContext.displayName = "ComboboxRowContext";
|
|
2866
|
-
function useComboboxRowContext() {
|
|
2867
|
-
return React.useContext(ComboboxRowContext);
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(function ComboboxItem(componentProps, forwardedRef) {
|
|
2871
|
-
const {
|
|
2872
|
-
render,
|
|
2873
|
-
className,
|
|
2874
|
-
value: itemValue = null,
|
|
2875
|
-
index: indexProp,
|
|
2876
|
-
disabled = false,
|
|
2877
|
-
nativeButton = false,
|
|
2878
|
-
...elementProps
|
|
2879
|
-
} = componentProps;
|
|
2880
|
-
const didPointerDownRef = React.useRef(false);
|
|
2881
|
-
const textRef = React.useRef(null);
|
|
2882
|
-
const listItem = useCompositeListItem({
|
|
2883
|
-
index: indexProp,
|
|
2884
|
-
textRef,
|
|
2885
|
-
indexGuessBehavior: IndexGuessBehavior.GuessFromOrder
|
|
2886
|
-
});
|
|
2887
|
-
const store = useComboboxRootContext();
|
|
2888
|
-
const isRow = useComboboxRowContext();
|
|
2889
|
-
const {
|
|
2890
|
-
flatFilteredItems,
|
|
2891
|
-
hasItems
|
|
2892
|
-
} = useComboboxDerivedItemsContext();
|
|
2893
|
-
const open = useStore(store, selectors.open);
|
|
2894
|
-
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2895
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
2896
|
-
const virtualized = useStore(store, selectors.virtualized);
|
|
2897
|
-
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
2898
|
-
const selectable = selectionMode !== 'none';
|
|
2899
|
-
const index = indexProp ?? (virtualized ? findItemIndex(flatFilteredItems, itemValue, isItemEqualToValue) : listItem.index);
|
|
2900
|
-
const hasRegistered = listItem.index !== -1;
|
|
2901
|
-
const rootId = useStore(store, selectors.id);
|
|
2902
|
-
const highlighted = useStore(store, selectors.isActive, index);
|
|
2903
|
-
const matchesSelectedValue = useStore(store, selectors.isSelected, itemValue);
|
|
2904
|
-
const getItemProps = useStore(store, selectors.getItemProps);
|
|
2905
|
-
const itemRef = React.useRef(null);
|
|
2906
|
-
const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined;
|
|
2907
|
-
const selected = matchesSelectedValue && selectable;
|
|
2908
|
-
useIsoLayoutEffect(() => {
|
|
2909
|
-
const shouldRun = hasRegistered && (virtualized || indexProp != null);
|
|
2910
|
-
if (!shouldRun) {
|
|
2911
|
-
return undefined;
|
|
2912
|
-
}
|
|
2913
|
-
const list = store.state.listRef.current;
|
|
2914
|
-
list[index] = itemRef.current;
|
|
2915
|
-
return () => {
|
|
2916
|
-
delete list[index];
|
|
2917
|
-
};
|
|
2918
|
-
}, [hasRegistered, virtualized, index, indexProp, store]);
|
|
2919
|
-
useIsoLayoutEffect(() => {
|
|
2920
|
-
if (!hasRegistered || hasItems) {
|
|
2921
|
-
return undefined;
|
|
2922
|
-
}
|
|
2923
|
-
const visibleMap = store.state.valuesRef.current;
|
|
2924
|
-
visibleMap[index] = itemValue;
|
|
2925
|
-
|
|
2926
|
-
// Stable registry that doesn't depend on filtering. Assume that no
|
|
2927
|
-
// filtering had occurred at this point; otherwise, an `items` prop is
|
|
2928
|
-
// required.
|
|
2929
|
-
if (selectionMode !== 'none') {
|
|
2930
|
-
store.state.allValuesRef.current.push(itemValue);
|
|
2931
|
-
}
|
|
2932
|
-
return () => {
|
|
2933
|
-
delete visibleMap[index];
|
|
2934
|
-
};
|
|
2935
|
-
}, [hasRegistered, hasItems, index, itemValue, store, selectionMode]);
|
|
2936
|
-
useIsoLayoutEffect(() => {
|
|
2937
|
-
if (!open) {
|
|
2938
|
-
didPointerDownRef.current = false;
|
|
2939
|
-
return;
|
|
2940
|
-
}
|
|
2941
|
-
if (!hasRegistered || hasItems) {
|
|
2942
|
-
return;
|
|
2943
|
-
}
|
|
2944
|
-
const selectedValue = store.state.selectedValue;
|
|
2945
|
-
const lastSelectedValue = Array.isArray(selectedValue) ? selectedValue[selectedValue.length - 1] : selectedValue;
|
|
2946
|
-
if (compareItemEquality(itemValue, lastSelectedValue, isItemEqualToValue)) {
|
|
2947
|
-
store.set('selectedIndex', index);
|
|
2948
|
-
}
|
|
2949
|
-
}, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
|
|
2950
|
-
const state = {
|
|
2951
|
-
disabled,
|
|
2952
|
-
selected,
|
|
2953
|
-
highlighted
|
|
2954
|
-
};
|
|
2955
|
-
const rootProps = getItemProps({
|
|
2956
|
-
active: highlighted,
|
|
2957
|
-
selected
|
|
2958
|
-
});
|
|
2959
|
-
rootProps.id = undefined;
|
|
2960
|
-
rootProps.onFocus = undefined;
|
|
2961
|
-
const {
|
|
2962
|
-
getButtonProps,
|
|
2963
|
-
buttonRef
|
|
2964
|
-
} = useButton({
|
|
2965
|
-
disabled,
|
|
2966
|
-
focusableWhenDisabled: true,
|
|
2967
|
-
native: nativeButton,
|
|
2968
|
-
composite: true
|
|
2969
|
-
});
|
|
2970
|
-
function commitSelection(nativeEvent) {
|
|
2971
|
-
function selectItem() {
|
|
2972
|
-
store.state.handleSelection(nativeEvent, itemValue);
|
|
2973
|
-
}
|
|
2974
|
-
if (store.state.submitOnItemClick) {
|
|
2975
|
-
ReactDOM.flushSync(selectItem);
|
|
2976
|
-
store.state.requestSubmit();
|
|
2977
|
-
} else {
|
|
2978
|
-
selectItem();
|
|
79
|
+
}, [baseFilter, mode, resolvedQuery, staticItems]);
|
|
80
|
+
const handleItemHighlighted = useStableCallback((highlightedValue, eventDetails) => {
|
|
81
|
+
props.onItemHighlighted?.(highlightedValue, eventDetails);
|
|
82
|
+
if (eventDetails.reason === pointer) {
|
|
83
|
+
return;
|
|
2979
84
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
// Focusable items steal focus from the input upon mouseup.
|
|
2986
|
-
// Warn if the user renders a natively focusable element like `<button>`,
|
|
2987
|
-
// as it should be a `<div>` instead.
|
|
2988
|
-
tabIndex: undefined,
|
|
2989
|
-
onPointerDownCapture(event) {
|
|
2990
|
-
didPointerDownRef.current = true;
|
|
2991
|
-
event.preventDefault();
|
|
2992
|
-
},
|
|
2993
|
-
onClick(event) {
|
|
2994
|
-
if (disabled || readOnly) {
|
|
2995
|
-
return;
|
|
2996
|
-
}
|
|
2997
|
-
commitSelection(event.nativeEvent);
|
|
2998
|
-
},
|
|
2999
|
-
onMouseUp(event) {
|
|
3000
|
-
const pointerStartedOnItem = didPointerDownRef.current;
|
|
3001
|
-
didPointerDownRef.current = false;
|
|
3002
|
-
if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) {
|
|
3003
|
-
return;
|
|
85
|
+
if (enableInline) {
|
|
86
|
+
if (highlightedValue == null) {
|
|
87
|
+
setInlineInputValue('');
|
|
88
|
+
} else {
|
|
89
|
+
setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue));
|
|
3004
90
|
}
|
|
3005
|
-
|
|
91
|
+
} else {
|
|
92
|
+
setInlineInputValue('');
|
|
3006
93
|
}
|
|
3007
|
-
};
|
|
3008
|
-
const element = useRenderElement('div', componentProps, {
|
|
3009
|
-
ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
|
|
3010
|
-
state,
|
|
3011
|
-
props: [rootProps, defaultProps, elementProps, getButtonProps]
|
|
3012
|
-
});
|
|
3013
|
-
const contextValue = React.useMemo(() => ({
|
|
3014
|
-
selected,
|
|
3015
|
-
textRef
|
|
3016
|
-
}), [selected, textRef]);
|
|
3017
|
-
return /*#__PURE__*/jsx(ComboboxItemContext.Provider, {
|
|
3018
|
-
value: contextValue,
|
|
3019
|
-
children: element
|
|
3020
|
-
});
|
|
3021
|
-
}));
|
|
3022
|
-
if (process.env.NODE_ENV !== "production") ComboboxItem.displayName = "ComboboxItem";
|
|
3023
|
-
|
|
3024
|
-
const ComboboxRow = /*#__PURE__*/React.forwardRef(function ComboboxRow(componentProps, forwardedRef) {
|
|
3025
|
-
const {
|
|
3026
|
-
render,
|
|
3027
|
-
className,
|
|
3028
|
-
...elementProps
|
|
3029
|
-
} = componentProps;
|
|
3030
|
-
const element = useRenderElement('div', componentProps, {
|
|
3031
|
-
ref: forwardedRef,
|
|
3032
|
-
props: [{
|
|
3033
|
-
role: 'row'
|
|
3034
|
-
}, elementProps]
|
|
3035
94
|
});
|
|
3036
|
-
return /*#__PURE__*/jsx(
|
|
3037
|
-
|
|
3038
|
-
|
|
95
|
+
return /*#__PURE__*/jsx(AriaCombobox, {
|
|
96
|
+
...other,
|
|
97
|
+
itemToStringLabel: itemToStringValue,
|
|
98
|
+
openOnInputClick: openOnInputClick,
|
|
99
|
+
selectionMode: "none",
|
|
100
|
+
fillInputOnItemPress: true,
|
|
101
|
+
filter: resolvedFilter,
|
|
102
|
+
autoComplete: mode,
|
|
103
|
+
inputValue: resolvedInputValue,
|
|
104
|
+
defaultInputValue: defaultValue,
|
|
105
|
+
onInputValueChange: handleValueChange,
|
|
106
|
+
onItemHighlighted: handleItemHighlighted
|
|
3039
107
|
});
|
|
3040
|
-
}
|
|
3041
|
-
if (process.env.NODE_ENV !== "production") ComboboxRow.displayName = "ComboboxRow";
|
|
108
|
+
}
|
|
3042
109
|
|
|
3043
|
-
|
|
3044
|
-
* Renders its children only when the list is empty.
|
|
3045
|
-
* Requires the `items` prop on the root component.
|
|
3046
|
-
* Announces changes politely to screen readers.
|
|
3047
|
-
* Renders a `<div>` element.
|
|
3048
|
-
*/
|
|
3049
|
-
const ComboboxEmpty = /*#__PURE__*/React.forwardRef(function ComboboxEmpty(componentProps, forwardedRef) {
|
|
3050
|
-
const {
|
|
3051
|
-
render,
|
|
3052
|
-
className,
|
|
3053
|
-
children: childrenProp,
|
|
3054
|
-
...elementProps
|
|
3055
|
-
} = componentProps;
|
|
110
|
+
function AutocompleteValue(props) {
|
|
3056
111
|
const {
|
|
3057
|
-
|
|
3058
|
-
} =
|
|
3059
|
-
const
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
112
|
+
children
|
|
113
|
+
} = props;
|
|
114
|
+
const inputValue = useComboboxInputValueContext();
|
|
115
|
+
let returnValue = null;
|
|
116
|
+
if (typeof children === 'function') {
|
|
117
|
+
returnValue = children(String(inputValue));
|
|
118
|
+
} else if (children != null) {
|
|
119
|
+
returnValue = children;
|
|
120
|
+
} else {
|
|
121
|
+
returnValue = inputValue;
|
|
122
|
+
}
|
|
123
|
+
return /*#__PURE__*/jsx(React.Fragment, {
|
|
124
|
+
children: returnValue
|
|
3069
125
|
});
|
|
3070
|
-
});
|
|
3071
|
-
if (process.env.NODE_ENV !== "production") ComboboxEmpty.displayName = "ComboboxEmpty";
|
|
3072
|
-
|
|
3073
|
-
/**
|
|
3074
|
-
* Returns the internally filtered items.
|
|
3075
|
-
*/
|
|
3076
|
-
function useFilteredItems() {
|
|
3077
|
-
const items = useComboboxDerivedItemsContext();
|
|
3078
|
-
return items.filteredItems;
|
|
3079
126
|
}
|
|
3080
127
|
|
|
3081
128
|
var index_parts$5 = /*#__PURE__*/Object.freeze({
|
|
@@ -7018,404 +4065,6 @@ var index_parts$2 = /*#__PURE__*/Object.freeze({
|
|
|
7018
4065
|
Viewport: ScrollAreaViewport
|
|
7019
4066
|
});
|
|
7020
4067
|
|
|
7021
|
-
function ComboboxRoot(props) {
|
|
7022
|
-
const {
|
|
7023
|
-
multiple = false,
|
|
7024
|
-
defaultValue,
|
|
7025
|
-
value,
|
|
7026
|
-
onValueChange,
|
|
7027
|
-
autoComplete,
|
|
7028
|
-
...other
|
|
7029
|
-
} = props;
|
|
7030
|
-
return /*#__PURE__*/jsx(AriaCombobox, {
|
|
7031
|
-
...other,
|
|
7032
|
-
selectionMode: multiple ? 'multiple' : 'single',
|
|
7033
|
-
selectedValue: value,
|
|
7034
|
-
defaultSelectedValue: defaultValue,
|
|
7035
|
-
onSelectedValueChange: onValueChange,
|
|
7036
|
-
formAutoComplete: autoComplete
|
|
7037
|
-
});
|
|
7038
|
-
}
|
|
7039
|
-
|
|
7040
|
-
/**
|
|
7041
|
-
* An accessible label that is automatically associated with the combobox trigger.
|
|
7042
|
-
* Renders a `<div>` element.
|
|
7043
|
-
*
|
|
7044
|
-
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
7045
|
-
*/
|
|
7046
|
-
const ComboboxLabel = /*#__PURE__*/React.forwardRef(function ComboboxLabel(componentProps, forwardedRef) {
|
|
7047
|
-
const {
|
|
7048
|
-
render,
|
|
7049
|
-
className,
|
|
7050
|
-
...elementProps
|
|
7051
|
-
} = componentProps;
|
|
7052
|
-
// Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
|
|
7053
|
-
const elementPropsWithoutId = elementProps;
|
|
7054
|
-
delete elementPropsWithoutId.id;
|
|
7055
|
-
const fieldRootContext = useFieldRootContext();
|
|
7056
|
-
const store = useComboboxRootContext();
|
|
7057
|
-
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
7058
|
-
const triggerElement = useStore(store, selectors.triggerElement);
|
|
7059
|
-
const inputElement = useStore(store, selectors.inputElement);
|
|
7060
|
-
const rootId = useStore(store, selectors.id);
|
|
7061
|
-
const defaultLabelId = getDefaultLabelId(rootId);
|
|
7062
|
-
const localControlId = triggerElement?.id ?? (inputInsidePopup ? rootId : undefined);
|
|
7063
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
7064
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
7065
|
-
React.useEffect(() => {
|
|
7066
|
-
if (!inputElement || inputInsidePopup) {
|
|
7067
|
-
return;
|
|
7068
|
-
}
|
|
7069
|
-
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
|
|
7070
|
-
const message = '<Combobox.Label> labels <Combobox.Trigger> only. ' + 'When <Combobox.Input> is the form control, use a native <label> or <Field.Label> instead.';
|
|
7071
|
-
error(`${message}${ownerStackMessage}`);
|
|
7072
|
-
}, [inputElement, inputInsidePopup]);
|
|
7073
|
-
}
|
|
7074
|
-
const labelProps = useLabel({
|
|
7075
|
-
id: defaultLabelId,
|
|
7076
|
-
fallbackControlId: localControlId,
|
|
7077
|
-
setLabelId(nextLabelId) {
|
|
7078
|
-
store.set('labelId', nextLabelId);
|
|
7079
|
-
}
|
|
7080
|
-
});
|
|
7081
|
-
return useRenderElement('div', componentProps, {
|
|
7082
|
-
ref: forwardedRef,
|
|
7083
|
-
state: fieldRootContext.state,
|
|
7084
|
-
props: [labelProps, elementProps],
|
|
7085
|
-
stateAttributesMapping: fieldValidityMapping
|
|
7086
|
-
});
|
|
7087
|
-
});
|
|
7088
|
-
if (process.env.NODE_ENV !== "production") ComboboxLabel.displayName = "ComboboxLabel";
|
|
7089
|
-
|
|
7090
|
-
function ComboboxValue(props) {
|
|
7091
|
-
const {
|
|
7092
|
-
children: childrenProp,
|
|
7093
|
-
placeholder
|
|
7094
|
-
} = props;
|
|
7095
|
-
const store = useComboboxRootContext();
|
|
7096
|
-
const itemToStringLabel = useStore(store, selectors.itemToStringLabel);
|
|
7097
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
7098
|
-
const items = useStore(store, selectors.items);
|
|
7099
|
-
const multiple = useStore(store, selectors.selectionMode) === 'multiple';
|
|
7100
|
-
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
7101
|
-
const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;
|
|
7102
|
-
const hasNullLabel = useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
|
|
7103
|
-
let children = null;
|
|
7104
|
-
if (typeof childrenProp === 'function') {
|
|
7105
|
-
children = childrenProp(selectedValue);
|
|
7106
|
-
} else if (childrenProp != null) {
|
|
7107
|
-
children = childrenProp;
|
|
7108
|
-
} else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {
|
|
7109
|
-
children = placeholder;
|
|
7110
|
-
} else if (multiple && Array.isArray(selectedValue)) {
|
|
7111
|
-
children = resolveMultipleLabels(selectedValue, items, itemToStringLabel);
|
|
7112
|
-
} else {
|
|
7113
|
-
children = resolveSelectedLabel(selectedValue, items, itemToStringLabel);
|
|
7114
|
-
}
|
|
7115
|
-
return /*#__PURE__*/jsx(React.Fragment, {
|
|
7116
|
-
children: children
|
|
7117
|
-
});
|
|
7118
|
-
}
|
|
7119
|
-
|
|
7120
|
-
const ComboboxItemIndicator = /*#__PURE__*/React.forwardRef(function ComboboxItemIndicator(componentProps, forwardedRef) {
|
|
7121
|
-
const keepMounted = componentProps.keepMounted ?? false;
|
|
7122
|
-
const {
|
|
7123
|
-
selected
|
|
7124
|
-
} = useComboboxItemContext();
|
|
7125
|
-
const shouldRender = keepMounted || selected;
|
|
7126
|
-
if (!shouldRender) {
|
|
7127
|
-
return null;
|
|
7128
|
-
}
|
|
7129
|
-
|
|
7130
|
-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
7131
|
-
return /*#__PURE__*/jsx(Inner, {
|
|
7132
|
-
...componentProps,
|
|
7133
|
-
ref: forwardedRef
|
|
7134
|
-
});
|
|
7135
|
-
});
|
|
7136
|
-
|
|
7137
|
-
/** The core implementation of the indicator is split here to avoid paying the hooks
|
|
7138
|
-
* costs unless the element needs to be mounted. */
|
|
7139
|
-
if (process.env.NODE_ENV !== "production") ComboboxItemIndicator.displayName = "ComboboxItemIndicator";
|
|
7140
|
-
const Inner = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef((componentProps, forwardedRef) => {
|
|
7141
|
-
const {
|
|
7142
|
-
render,
|
|
7143
|
-
className,
|
|
7144
|
-
keepMounted,
|
|
7145
|
-
...elementProps
|
|
7146
|
-
} = componentProps;
|
|
7147
|
-
const {
|
|
7148
|
-
selected
|
|
7149
|
-
} = useComboboxItemContext();
|
|
7150
|
-
const indicatorRef = React.useRef(null);
|
|
7151
|
-
const {
|
|
7152
|
-
transitionStatus,
|
|
7153
|
-
setMounted
|
|
7154
|
-
} = useTransitionStatus(selected);
|
|
7155
|
-
const state = {
|
|
7156
|
-
selected,
|
|
7157
|
-
transitionStatus
|
|
7158
|
-
};
|
|
7159
|
-
const element = useRenderElement('span', componentProps, {
|
|
7160
|
-
ref: [forwardedRef, indicatorRef],
|
|
7161
|
-
state,
|
|
7162
|
-
props: [{
|
|
7163
|
-
'aria-hidden': true,
|
|
7164
|
-
children: '✔️'
|
|
7165
|
-
}, elementProps],
|
|
7166
|
-
stateAttributesMapping: transitionStatusMapping
|
|
7167
|
-
});
|
|
7168
|
-
useOpenChangeComplete({
|
|
7169
|
-
open: selected,
|
|
7170
|
-
ref: indicatorRef,
|
|
7171
|
-
onComplete() {
|
|
7172
|
-
if (!selected) {
|
|
7173
|
-
setMounted(false);
|
|
7174
|
-
}
|
|
7175
|
-
}
|
|
7176
|
-
});
|
|
7177
|
-
return element;
|
|
7178
|
-
}));
|
|
7179
|
-
if (process.env.NODE_ENV !== "production") Inner.displayName = "Inner";
|
|
7180
|
-
|
|
7181
|
-
const ComboboxChips = /*#__PURE__*/React.forwardRef(function ComboboxChips(componentProps, forwardedRef) {
|
|
7182
|
-
const {
|
|
7183
|
-
render,
|
|
7184
|
-
className,
|
|
7185
|
-
...elementProps
|
|
7186
|
-
} = componentProps;
|
|
7187
|
-
const store = useComboboxRootContext();
|
|
7188
|
-
const open = useStore(store, selectors.open);
|
|
7189
|
-
const hasSelectionChips = useStore(store, selectors.hasSelectionChips);
|
|
7190
|
-
const [highlightedChipIndex, setHighlightedChipIndex] = React.useState(undefined);
|
|
7191
|
-
if (open && highlightedChipIndex !== undefined) {
|
|
7192
|
-
setHighlightedChipIndex(undefined);
|
|
7193
|
-
}
|
|
7194
|
-
const chipsRef = React.useRef([]);
|
|
7195
|
-
const element = useRenderElement('div', componentProps, {
|
|
7196
|
-
ref: [forwardedRef, store.state.chipsContainerRef],
|
|
7197
|
-
// NVDA enters browse mode instead of staying in focus mode when navigating with
|
|
7198
|
-
// arrow keys inside a container unless it has a toolbar role.
|
|
7199
|
-
props: [hasSelectionChips ? {
|
|
7200
|
-
role: 'toolbar'
|
|
7201
|
-
} : EMPTY_OBJECT, elementProps]
|
|
7202
|
-
});
|
|
7203
|
-
const contextValue = React.useMemo(() => ({
|
|
7204
|
-
highlightedChipIndex,
|
|
7205
|
-
setHighlightedChipIndex,
|
|
7206
|
-
chipsRef
|
|
7207
|
-
}), [highlightedChipIndex, setHighlightedChipIndex, chipsRef]);
|
|
7208
|
-
return /*#__PURE__*/jsx(ComboboxChipsContext.Provider, {
|
|
7209
|
-
value: contextValue,
|
|
7210
|
-
children: /*#__PURE__*/jsx(CompositeList, {
|
|
7211
|
-
elementsRef: chipsRef,
|
|
7212
|
-
children: element
|
|
7213
|
-
})
|
|
7214
|
-
});
|
|
7215
|
-
});
|
|
7216
|
-
if (process.env.NODE_ENV !== "production") ComboboxChips.displayName = "ComboboxChips";
|
|
7217
|
-
|
|
7218
|
-
const ComboboxChipContext = /*#__PURE__*/React.createContext(undefined);
|
|
7219
|
-
if (process.env.NODE_ENV !== "production") ComboboxChipContext.displayName = "ComboboxChipContext";
|
|
7220
|
-
function useComboboxChipContext() {
|
|
7221
|
-
const context = React.useContext(ComboboxChipContext);
|
|
7222
|
-
if (!context) {
|
|
7223
|
-
throw new Error(process.env.NODE_ENV !== "production" ? 'useComboboxChipContext must be used within a ComboboxChip' : formatErrorMessage(17));
|
|
7224
|
-
}
|
|
7225
|
-
return context;
|
|
7226
|
-
}
|
|
7227
|
-
|
|
7228
|
-
const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(componentProps, forwardedRef) {
|
|
7229
|
-
const {
|
|
7230
|
-
render,
|
|
7231
|
-
className,
|
|
7232
|
-
...elementProps
|
|
7233
|
-
} = componentProps;
|
|
7234
|
-
const store = useComboboxRootContext();
|
|
7235
|
-
const {
|
|
7236
|
-
setHighlightedChipIndex,
|
|
7237
|
-
chipsRef
|
|
7238
|
-
} = useComboboxChipsContext();
|
|
7239
|
-
const disabled = useStore(store, selectors.disabled);
|
|
7240
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
7241
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
7242
|
-
const {
|
|
7243
|
-
ref,
|
|
7244
|
-
index
|
|
7245
|
-
} = useCompositeListItem();
|
|
7246
|
-
function handleKeyDown(event) {
|
|
7247
|
-
let nextIndex = index;
|
|
7248
|
-
if (event.key === 'ArrowLeft') {
|
|
7249
|
-
event.preventDefault();
|
|
7250
|
-
if (index > 0) {
|
|
7251
|
-
nextIndex = index - 1;
|
|
7252
|
-
} else {
|
|
7253
|
-
nextIndex = undefined;
|
|
7254
|
-
}
|
|
7255
|
-
} else if (event.key === 'ArrowRight') {
|
|
7256
|
-
event.preventDefault();
|
|
7257
|
-
if (index < selectedValue.length - 1) {
|
|
7258
|
-
nextIndex = index + 1;
|
|
7259
|
-
} else {
|
|
7260
|
-
nextIndex = undefined;
|
|
7261
|
-
}
|
|
7262
|
-
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
|
7263
|
-
const computedNextIndex = index >= selectedValue.length - 1 ? selectedValue.length - 2 : index;
|
|
7264
|
-
nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;
|
|
7265
|
-
stopEvent(event);
|
|
7266
|
-
store.state.setIndices({
|
|
7267
|
-
activeIndex: null,
|
|
7268
|
-
selectedIndex: null,
|
|
7269
|
-
type: 'keyboard'
|
|
7270
|
-
});
|
|
7271
|
-
store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), createChangeEventDetails(none, event.nativeEvent));
|
|
7272
|
-
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
7273
|
-
stopEvent(event);
|
|
7274
|
-
nextIndex = undefined;
|
|
7275
|
-
} else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
7276
|
-
stopEvent(event);
|
|
7277
|
-
store.state.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent));
|
|
7278
|
-
nextIndex = undefined;
|
|
7279
|
-
} else if (
|
|
7280
|
-
// Check for printable characters (letters, numbers, symbols)
|
|
7281
|
-
event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
7282
|
-
nextIndex = undefined;
|
|
7283
|
-
}
|
|
7284
|
-
return nextIndex;
|
|
7285
|
-
}
|
|
7286
|
-
const state = {
|
|
7287
|
-
disabled
|
|
7288
|
-
};
|
|
7289
|
-
const element = useRenderElement('div', componentProps, {
|
|
7290
|
-
ref: [forwardedRef, ref],
|
|
7291
|
-
state,
|
|
7292
|
-
props: [{
|
|
7293
|
-
tabIndex: -1,
|
|
7294
|
-
'aria-disabled': disabled || undefined,
|
|
7295
|
-
'aria-readonly': readOnly || undefined,
|
|
7296
|
-
onKeyDown(event) {
|
|
7297
|
-
if (disabled || readOnly) {
|
|
7298
|
-
return;
|
|
7299
|
-
}
|
|
7300
|
-
const nextIndex = handleKeyDown(event);
|
|
7301
|
-
ReactDOM.flushSync(() => {
|
|
7302
|
-
setHighlightedChipIndex(nextIndex);
|
|
7303
|
-
});
|
|
7304
|
-
if (nextIndex === undefined) {
|
|
7305
|
-
store.state.inputRef.current?.focus();
|
|
7306
|
-
} else {
|
|
7307
|
-
chipsRef.current[nextIndex]?.focus();
|
|
7308
|
-
}
|
|
7309
|
-
},
|
|
7310
|
-
onMouseDown(event) {
|
|
7311
|
-
if (readOnly) {
|
|
7312
|
-
return;
|
|
7313
|
-
}
|
|
7314
|
-
event.preventDefault();
|
|
7315
|
-
if (disabled) {
|
|
7316
|
-
return;
|
|
7317
|
-
}
|
|
7318
|
-
store.state.inputRef.current?.focus();
|
|
7319
|
-
}
|
|
7320
|
-
}, elementProps]
|
|
7321
|
-
});
|
|
7322
|
-
const contextValue = React.useMemo(() => ({
|
|
7323
|
-
index
|
|
7324
|
-
}), [index]);
|
|
7325
|
-
return /*#__PURE__*/jsx(ComboboxChipContext.Provider, {
|
|
7326
|
-
value: contextValue,
|
|
7327
|
-
children: element
|
|
7328
|
-
});
|
|
7329
|
-
});
|
|
7330
|
-
if (process.env.NODE_ENV !== "production") ComboboxChip.displayName = "ComboboxChip";
|
|
7331
|
-
|
|
7332
|
-
/**
|
|
7333
|
-
* A button to remove a chip.
|
|
7334
|
-
* Renders a `<button>` element.
|
|
7335
|
-
*/
|
|
7336
|
-
const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRemove(componentProps, forwardedRef) {
|
|
7337
|
-
const {
|
|
7338
|
-
render,
|
|
7339
|
-
className,
|
|
7340
|
-
disabled: disabledProp = false,
|
|
7341
|
-
nativeButton = true,
|
|
7342
|
-
...elementProps
|
|
7343
|
-
} = componentProps;
|
|
7344
|
-
const store = useComboboxRootContext();
|
|
7345
|
-
const {
|
|
7346
|
-
index
|
|
7347
|
-
} = useComboboxChipContext();
|
|
7348
|
-
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
7349
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
7350
|
-
const selectedValue = useStore(store, selectors.selectedValue);
|
|
7351
|
-
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
7352
|
-
const disabled = comboboxDisabled || disabledProp;
|
|
7353
|
-
const {
|
|
7354
|
-
buttonRef,
|
|
7355
|
-
getButtonProps
|
|
7356
|
-
} = useButton({
|
|
7357
|
-
native: nativeButton,
|
|
7358
|
-
disabled: disabled || readOnly,
|
|
7359
|
-
focusableWhenDisabled: true
|
|
7360
|
-
});
|
|
7361
|
-
const state = {
|
|
7362
|
-
disabled
|
|
7363
|
-
};
|
|
7364
|
-
function clearActiveIndexForRemovedItem(removedItem) {
|
|
7365
|
-
const activeIndex = store.state.activeIndex;
|
|
7366
|
-
if (activeIndex == null) {
|
|
7367
|
-
return;
|
|
7368
|
-
}
|
|
7369
|
-
|
|
7370
|
-
// Try current visible list first; if not found, it's filtered out.
|
|
7371
|
-
// No need to clear highlight in that case since it can't equal activeIndex.
|
|
7372
|
-
const removedIndex = findItemIndex(store.state.valuesRef.current, removedItem, isItemEqualToValue);
|
|
7373
|
-
if (removedIndex !== -1 && activeIndex === removedIndex) {
|
|
7374
|
-
store.state.setIndices({
|
|
7375
|
-
activeIndex: null,
|
|
7376
|
-
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer'
|
|
7377
|
-
});
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
function removeChip(event) {
|
|
7381
|
-
const eventDetails = createChangeEventDetails(chipRemovePress, event.nativeEvent);
|
|
7382
|
-
const removedItem = selectedValue[index];
|
|
7383
|
-
clearActiveIndexForRemovedItem(removedItem);
|
|
7384
|
-
store.state.setSelectedValue(selectedValue.filter((_, i) => i !== index), eventDetails);
|
|
7385
|
-
store.state.inputRef.current?.focus();
|
|
7386
|
-
return eventDetails;
|
|
7387
|
-
}
|
|
7388
|
-
const element = useRenderElement('button', componentProps, {
|
|
7389
|
-
ref: [forwardedRef, buttonRef],
|
|
7390
|
-
state,
|
|
7391
|
-
props: [{
|
|
7392
|
-
tabIndex: -1,
|
|
7393
|
-
onClick(event) {
|
|
7394
|
-
if (disabled || readOnly) {
|
|
7395
|
-
return;
|
|
7396
|
-
}
|
|
7397
|
-
const eventDetails = removeChip(event);
|
|
7398
|
-
if (!eventDetails.isPropagationAllowed) {
|
|
7399
|
-
event.stopPropagation();
|
|
7400
|
-
}
|
|
7401
|
-
},
|
|
7402
|
-
onKeyDown(event) {
|
|
7403
|
-
if (disabled || readOnly) {
|
|
7404
|
-
return;
|
|
7405
|
-
}
|
|
7406
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
7407
|
-
const eventDetails = removeChip(event);
|
|
7408
|
-
if (!eventDetails.isPropagationAllowed) {
|
|
7409
|
-
stopEvent(event);
|
|
7410
|
-
}
|
|
7411
|
-
}
|
|
7412
|
-
}
|
|
7413
|
-
}, elementProps, getButtonProps]
|
|
7414
|
-
});
|
|
7415
|
-
return element;
|
|
7416
|
-
});
|
|
7417
|
-
if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
|
|
7418
|
-
|
|
7419
4068
|
var index_parts$1 = /*#__PURE__*/Object.freeze({
|
|
7420
4069
|
__proto__: null,
|
|
7421
4070
|
Arrow: ComboboxArrow,
|
|
@@ -7739,7 +4388,7 @@ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldS
|
|
|
7739
4388
|
ref: [forwardedRef, scrubAreaRef],
|
|
7740
4389
|
state,
|
|
7741
4390
|
props: [defaultProps, elementProps],
|
|
7742
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
4391
|
+
stateAttributesMapping: stateAttributesMapping$6
|
|
7743
4392
|
});
|
|
7744
4393
|
const contextValue = React.useMemo(() => ({
|
|
7745
4394
|
isScrubbing,
|
|
@@ -7797,7 +4446,7 @@ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function Number
|
|
|
7797
4446
|
pointerEvents: 'none'
|
|
7798
4447
|
}
|
|
7799
4448
|
}, elementProps],
|
|
7800
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
4449
|
+
stateAttributesMapping: stateAttributesMapping$6
|
|
7801
4450
|
});
|
|
7802
4451
|
return element && /*#__PURE__*/ReactDOM.createPortal(element, ownerDocument(domElement).body);
|
|
7803
4452
|
});
|