@jobber/components 8.23.0 → 8.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Autocomplete/index.cjs +1 -1
- package/dist/Autocomplete/index.mjs +1 -1
- package/dist/Card/index.cjs +4 -4
- package/dist/Card/index.mjs +4 -4
- package/dist/Chip/index.cjs +1 -1
- package/dist/Chip/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
- package/dist/Chips/index.cjs +1 -1
- package/dist/Chips/index.mjs +1 -1
- package/dist/ComboboxChipRemove-cjs.js +668 -379
- package/dist/ComboboxChipRemove-es.js +508 -219
- package/dist/ConfirmationModal/index.cjs +1 -1
- package/dist/ConfirmationModal/index.mjs +1 -1
- package/dist/DataDump/index.cjs +4 -4
- package/dist/DataDump/index.mjs +4 -4
- package/dist/DataList/components/DataListActions/index.cjs +1 -1
- package/dist/DataList/components/DataListActions/index.mjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/index.mjs +1 -1
- package/dist/DataList/components/DataListHeader/index.cjs +1 -1
- package/dist/DataList/components/DataListHeader/index.mjs +1 -1
- package/dist/DataList/components/DataListItem/index.cjs +1 -1
- package/dist/DataList/components/DataListItem/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
- package/dist/DataList/components/DataListItems/index.cjs +1 -1
- package/dist/DataList/components/DataListItems/index.mjs +1 -1
- package/dist/DataList/components/DataListLayout/index.cjs +1 -1
- package/dist/DataList/components/DataListLayout/index.mjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
- package/dist/DataList/index.cjs +1 -1
- package/dist/DataList/index.mjs +1 -1
- package/dist/DatePicker/index.cjs +1 -1
- package/dist/DatePicker/index.mjs +1 -1
- package/dist/Dialog/index.cjs +3 -3
- package/dist/Dialog/index.mjs +3 -3
- package/dist/DrawerDescription-cjs.js +32 -33
- package/dist/DrawerDescription-es.js +19 -20
- package/dist/DrawerTitle-cjs.js +7 -5
- package/dist/DrawerTitle-es.js +7 -5
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
- package/dist/FilterPicker/index.cjs +1 -1
- package/dist/FilterPicker/index.mjs +1 -1
- package/dist/FormFieldWrapper-cjs.js +2 -2
- package/dist/FormFieldWrapper-es.js +2 -2
- package/dist/FormatFile/index.cjs +1 -1
- package/dist/FormatFile/index.mjs +1 -1
- package/dist/Gallery/index.cjs +1 -1
- package/dist/Gallery/index.mjs +1 -1
- package/dist/InputDate/index.cjs +1 -1
- package/dist/InputDate/index.mjs +1 -1
- package/dist/InputNumberExperimental-cjs.js +319 -102
- package/dist/InputNumberExperimental-es.js +321 -104
- package/dist/LightBox/index.cjs +1 -1
- package/dist/LightBox/index.mjs +1 -1
- package/dist/Menu/index.cjs +4 -4
- package/dist/Menu/index.mjs +4 -4
- package/dist/MenuSubmenuTrigger-cjs.js +545 -498
- package/dist/MenuSubmenuTrigger-es.js +490 -443
- package/dist/Modal/index.cjs +1 -1
- package/dist/Modal/index.mjs +1 -1
- package/dist/NumberFieldInput-cjs.js +609 -496
- package/dist/NumberFieldInput-es.js +612 -498
- package/dist/Page/index.cjs +4 -4
- package/dist/Page/index.mjs +4 -4
- package/dist/Popover/index.cjs +1 -1
- package/dist/Popover/index.mjs +1 -1
- package/dist/ScrollAreaViewport-cjs.js +326 -601
- package/dist/ScrollAreaViewport-es.js +278 -548
- package/dist/SelectGroupLabel-cjs.js +494 -528
- package/dist/SelectGroupLabel-es.js +411 -445
- package/dist/Separator-cjs.js +1 -0
- package/dist/Separator-es.js +1 -0
- package/dist/Tooltip/index.cjs +1 -1
- package/dist/Tooltip/index.mjs +1 -1
- package/dist/docs/InputTime/InputTime.md +8 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
- package/dist/floating-ui.react-cjs.js +586 -10
- package/dist/floating-ui.react-dom-cjs.js +92 -92
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +580 -4
- package/dist/floating-ui.utils-cjs.js +159 -0
- package/dist/floating-ui.utils-es.js +136 -0
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
- package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
- package/dist/primitives/BottomSheet/index.cjs +3 -3
- package/dist/primitives/BottomSheet/index.mjs +3 -3
- package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
- package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
- package/dist/primitives/SelectPrimitive/index.cjs +5 -5
- package/dist/primitives/SelectPrimitive/index.mjs +5 -5
- package/dist/primitives/index.cjs +4 -4
- package/dist/primitives/index.mjs +4 -4
- package/dist/resolveAriaLabelledBy-cjs.js +5 -0
- package/dist/resolveAriaLabelledBy-es.js +5 -1
- package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
- package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
- package/dist/styles.css +9 -3
- package/dist/unstyledPrimitives/index.cjs +1217 -639
- package/dist/unstyledPrimitives/index.mjs +1163 -585
- package/dist/useButton-cjs.js +283 -317
- package/dist/useButton-es.js +277 -298
- package/dist/useCompositeListItem-cjs.js +328 -279
- package/dist/useCompositeListItem-es.js +293 -246
- package/dist/useLabel-cjs.js +11 -1
- package/dist/useLabel-es.js +11 -2
- package/dist/useLabelableId-cjs.js +37 -91
- package/dist/useLabelableId-es.js +37 -90
- package/dist/useRenderElement-cjs.js +81 -26
- package/dist/useRenderElement-es.js +81 -27
- package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
- package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
- package/package.json +3 -3
- package/dist/index.esm-cjs.js +0 -738
- package/dist/index.esm-es.js +0 -712
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { f as formatErrorMessage,
|
|
4
|
-
import { a as useFormContext, b as useFieldRootContext, c as useLabelableId, d as
|
|
5
|
-
import { u as useFloatingRootContext, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds, C as CompositeList,
|
|
6
|
-
import { h as hasNullItemLabel, c as compareItemEquality,
|
|
7
|
-
import {
|
|
2
|
+
import { z as useControlled, b as useStableCallback, m as useValueAsRef, k as useTransitionStatus, a as useIsoLayoutEffect, J as createGenericEventDetails, G as inputClear, F as inputChange, d as createChangeEventDetails, v as focusOut, Q as outsidePress, g as getTarget, V as itemPress, f as useOpenChangeComplete, O as useValueChanged, C as none, a5 as inputPress, q as contains, D as visuallyHiddenInput, E as visuallyHidden, h as useTimeout, c as useButton, o as ownerDocument, s as stopEvent, r as listNavigation, e as closePress, u as useBaseUiId, W as android, a0 as escapeKey, a6 as clearPress, t as transitionStatusMapping, A as ios, S as SafeReact, j as error, a7 as chipRemovePress } from './useButton-es.js';
|
|
3
|
+
import { f as formatErrorMessage, b as EMPTY_ARRAY, a as useRefWithInit, E as EMPTY_OBJECT, N as NOOP, m as mergeProps, c as useMergedRefs, u as useRenderElement } from './useRenderElement-es.js';
|
|
4
|
+
import { a as useFormContext, b as useFieldRootContext, c as useLabelableId, d as useRegisterFieldControl, f as fieldValidityMapping, u as useLabelableContext, F as FieldRootContext, e as DEFAULT_FIELD_ROOT_CONTEXT, g as DEFAULT_FIELD_STATE_ATTRIBUTES } from './useLabelableId-es.js';
|
|
5
|
+
import { u as useFloatingRootContext, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds, C as CompositeList, d as useAnchorPositioning, c as useAnchoredPopupScrollLock, e as usePositioner, f as getDisabledMountTransitionStyles, h as useCompositeListItem, I as IndexGuessBehavior } from './useCompositeListItem-es.js';
|
|
6
|
+
import { h as hasNullItemLabel, c as compareItemEquality, a as stringifyAsLabel, k as isGroupedItems, s as stringifyAsValue, i as selectedValueIncludes, j as removeItem, f as findItemIndex, d as defaultItemEquality, b as areArraysEqual, r as resolveAriaLabelledBy, l as getDefaultLabelId, e as resolveMultipleLabels, g as resolveSelectedLabel } from './resolveAriaLabelledBy-es.js';
|
|
7
|
+
import { ak as getGridNavigatedIndex, e as isIndexOutOfListBounds, t as createSelector, r as useDirection, S as Store, w as useStore, v as useOpenInteractionType, x as useClick, y as useDismiss, F as FOCUSABLE_POPUP_PROPS, z as useOnFirstRender, B as pressableTriggerOpenStateMapping, al as gecko, a0 as isInteractiveElement, W as triggerOpenStateMapping, E as FloatingPortal, p as popupStateMapping, I as InternalBackdrop, G as inertValue, H as DROPDOWN_COLLISION_AVOIDANCE, a as FloatingFocusManager } from './useScrollLock-es.js';
|
|
8
8
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
9
|
import * as ReactDOM from 'react-dom';
|
|
10
|
-
import { u as useLabel } from './useLabel-es.js';
|
|
10
|
+
import { s as stringifyLocale, u as useLabel } from './useLabel-es.js';
|
|
11
|
+
import { b as isElement } from './floating-ui.utils.dom-es.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Positional arguments are deliberate: property names of an options object
|
|
15
|
+
* don't minify, and the signature is locked to the caller via `typeof` on the
|
|
16
|
+
* `grid` option of `useListNavigation`.
|
|
17
|
+
*
|
|
18
|
+
* The injected grid navigator only ever operates on a uniform 1x1 grid (sizes are
|
|
19
|
+
* always `1x1` and packing is never dense), so the cell-map machinery that supports
|
|
20
|
+
* multi-cell items collapses to an identity transform over the item list. Calling
|
|
21
|
+
* `getGridNavigatedIndex` directly keeps the cell-map helpers
|
|
22
|
+
* (`createGridCellMap`/`getGridCellIndexOfCorner`/`getGridCellIndices`) out of
|
|
23
|
+
* grid-combobox bundles.
|
|
24
|
+
*/
|
|
25
|
+
function gridNavigation(event, prevIndex, listRef, orientation, loopFocus, rtl, disabledIndices, minIndex, maxIndex, cols = 2) {
|
|
26
|
+
const nextIndex = getGridNavigatedIndex(listRef.current, {
|
|
27
|
+
event,
|
|
28
|
+
orientation,
|
|
29
|
+
loopFocus,
|
|
30
|
+
rtl,
|
|
31
|
+
cols,
|
|
32
|
+
disabledIndices,
|
|
33
|
+
minIndex,
|
|
34
|
+
maxIndex,
|
|
35
|
+
// An out-of-range previous index falls back to the first enabled item.
|
|
36
|
+
prevIndex: prevIndex > maxIndex ? minIndex : prevIndex,
|
|
37
|
+
stopEvent: true
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// `getGridNavigatedIndex` can return an out-of-bounds sentinel (e.g. `-1` when there is no
|
|
41
|
+
// previous item to move from); surface that as `undefined` so the caller treats it as
|
|
42
|
+
// "no navigation" rather than highlighting index `-1`.
|
|
43
|
+
return isIndexOutOfListBounds(listRef.current, nextIndex) ? undefined : nextIndex;
|
|
44
|
+
}
|
|
11
45
|
|
|
12
46
|
const ComboboxRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
13
47
|
if (process.env.NODE_ENV !== "production") ComboboxRootContext.displayName = "ComboboxRootContext";
|
|
14
48
|
const ComboboxFloatingContext = /*#__PURE__*/React.createContext(undefined);
|
|
15
49
|
if (process.env.NODE_ENV !== "production") ComboboxFloatingContext.displayName = "ComboboxFloatingContext";
|
|
16
50
|
const ComboboxDerivedItemsContext = /*#__PURE__*/React.createContext(undefined);
|
|
51
|
+
if (process.env.NODE_ENV !== "production") ComboboxDerivedItemsContext.displayName = "ComboboxDerivedItemsContext";
|
|
52
|
+
const ComboboxHasItemsContext = /*#__PURE__*/React.createContext(false);
|
|
17
53
|
// `inputValue` can't be placed in the store.
|
|
18
54
|
// https://github.com/mui/base-ui/issues/2703
|
|
19
|
-
if (process.env.NODE_ENV !== "production")
|
|
55
|
+
if (process.env.NODE_ENV !== "production") ComboboxHasItemsContext.displayName = "ComboboxHasItemsContext";
|
|
20
56
|
const ComboboxInputValueContext = /*#__PURE__*/React.createContext('');
|
|
21
57
|
if (process.env.NODE_ENV !== "production") ComboboxInputValueContext.displayName = "ComboboxInputValueContext";
|
|
22
58
|
function useComboboxRootContext() {
|
|
@@ -43,11 +79,13 @@ function useComboboxDerivedItemsContext() {
|
|
|
43
79
|
function useComboboxInputValueContext() {
|
|
44
80
|
return React.useContext(ComboboxInputValueContext);
|
|
45
81
|
}
|
|
82
|
+
function useComboboxHasItemsContext() {
|
|
83
|
+
return React.useContext(ComboboxHasItemsContext);
|
|
84
|
+
}
|
|
46
85
|
|
|
47
86
|
const selectors = {
|
|
48
87
|
id: createSelector(state => state.id),
|
|
49
88
|
labelId: createSelector(state => state.labelId),
|
|
50
|
-
query: createSelector(state => state.query),
|
|
51
89
|
items: createSelector(state => state.items),
|
|
52
90
|
selectedValue: createSelector(state => state.selectedValue),
|
|
53
91
|
hasSelectionChips: createSelector(state => {
|
|
@@ -89,35 +127,25 @@ const selectors = {
|
|
|
89
127
|
popupProps: createSelector(state => state.popupProps),
|
|
90
128
|
inputProps: createSelector(state => state.inputProps),
|
|
91
129
|
triggerProps: createSelector(state => state.triggerProps),
|
|
92
|
-
|
|
130
|
+
itemProps: createSelector(state => state.itemProps),
|
|
93
131
|
positionerElement: createSelector(state => state.positionerElement),
|
|
94
132
|
listElement: createSelector(state => state.listElement),
|
|
133
|
+
popupId: createSelector(state => state.popupId),
|
|
95
134
|
triggerElement: createSelector(state => state.triggerElement),
|
|
96
135
|
inputElement: createSelector(state => state.inputElement),
|
|
97
136
|
inputGroupElement: createSelector(state => state.inputGroupElement),
|
|
98
137
|
popupSide: createSelector(state => state.popupSide),
|
|
99
138
|
openMethod: createSelector(state => state.openMethod),
|
|
100
139
|
inputInsidePopup: createSelector(state => state.inputInsidePopup),
|
|
140
|
+
inputOwnsFormValue: createSelector(state => state.inputOwnsFormValue),
|
|
101
141
|
selectionMode: createSelector(state => state.selectionMode),
|
|
102
|
-
listRef: createSelector(state => state.listRef),
|
|
103
|
-
labelsRef: createSelector(state => state.labelsRef),
|
|
104
|
-
popupRef: createSelector(state => state.popupRef),
|
|
105
|
-
emptyRef: createSelector(state => state.emptyRef),
|
|
106
|
-
inputRef: createSelector(state => state.inputRef),
|
|
107
|
-
keyboardActiveRef: createSelector(state => state.keyboardActiveRef),
|
|
108
|
-
chipsContainerRef: createSelector(state => state.chipsContainerRef),
|
|
109
|
-
clearRef: createSelector(state => state.clearRef),
|
|
110
|
-
valuesRef: createSelector(state => state.valuesRef),
|
|
111
|
-
allValuesRef: createSelector(state => state.allValuesRef),
|
|
112
142
|
name: createSelector(state => state.name),
|
|
143
|
+
form: createSelector(state => state.form),
|
|
113
144
|
disabled: createSelector(state => state.disabled),
|
|
114
145
|
readOnly: createSelector(state => state.readOnly),
|
|
115
146
|
required: createSelector(state => state.required),
|
|
116
147
|
grid: createSelector(state => state.grid),
|
|
117
|
-
isGrouped: createSelector(state => state.isGrouped),
|
|
118
148
|
virtualized: createSelector(state => state.virtualized),
|
|
119
|
-
onOpenChangeComplete: createSelector(state => state.onOpenChangeComplete),
|
|
120
|
-
openOnInputClick: createSelector(state => state.openOnInputClick),
|
|
121
149
|
itemToStringLabel: createSelector(state => state.itemToStringLabel),
|
|
122
150
|
isItemEqualToValue: createSelector(state => state.isItemEqualToValue),
|
|
123
151
|
modal: createSelector(state => state.modal),
|
|
@@ -125,6 +153,15 @@ const selectors = {
|
|
|
125
153
|
submitOnItemClick: createSelector(state => state.submitOnItemClick)
|
|
126
154
|
};
|
|
127
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Derives the default id assigned to `Combobox.Popup` when the input is rendered inside it.
|
|
158
|
+
* Shared by the popup (which applies it) and the trigger (which references it via `aria-controls`)
|
|
159
|
+
* so the convention only lives in one place.
|
|
160
|
+
*/
|
|
161
|
+
function getComboboxPopupId(rootId) {
|
|
162
|
+
return rootId == null ? undefined : `${rootId}-popup`;
|
|
163
|
+
}
|
|
164
|
+
|
|
128
165
|
/**
|
|
129
166
|
* Enhanced filter using Intl.Collator for more robust string matching.
|
|
130
167
|
* Uses the provided `itemToStringLabel` function if available, otherwise falls back to:
|
|
@@ -165,15 +202,6 @@ function createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel,
|
|
|
165
202
|
}
|
|
166
203
|
|
|
167
204
|
const filterCache = new Map();
|
|
168
|
-
function stringifyLocale(locale) {
|
|
169
|
-
if (Array.isArray(locale)) {
|
|
170
|
-
return locale.map(value => stringifyLocale(value)).join(',');
|
|
171
|
-
}
|
|
172
|
-
if (locale == null) {
|
|
173
|
-
return '';
|
|
174
|
-
}
|
|
175
|
-
return String(locale);
|
|
176
|
-
}
|
|
177
205
|
function getFilter(options = {}) {
|
|
178
206
|
const mergedOptions = {
|
|
179
207
|
usage: 'search',
|
|
@@ -262,9 +290,12 @@ function AriaCombobox(props) {
|
|
|
262
290
|
onSelectedValueChange,
|
|
263
291
|
defaultInputValue: defaultInputValueProp,
|
|
264
292
|
inputValue: inputValueProp,
|
|
293
|
+
open: openProp,
|
|
294
|
+
defaultOpen = false,
|
|
265
295
|
selectionMode = 'none',
|
|
266
296
|
onItemHighlighted: onItemHighlightedProp,
|
|
267
297
|
name: nameProp,
|
|
298
|
+
form,
|
|
268
299
|
disabled: disabledProp = false,
|
|
269
300
|
readOnly = false,
|
|
270
301
|
required = false,
|
|
@@ -297,7 +328,6 @@ function AriaCombobox(props) {
|
|
|
297
328
|
const {
|
|
298
329
|
setDirty,
|
|
299
330
|
validityData,
|
|
300
|
-
shouldValidateOnChange,
|
|
301
331
|
setFilled,
|
|
302
332
|
name: fieldName,
|
|
303
333
|
disabled: fieldDisabled,
|
|
@@ -306,6 +336,7 @@ function AriaCombobox(props) {
|
|
|
306
336
|
validationMode,
|
|
307
337
|
validation
|
|
308
338
|
} = useFieldRootContext();
|
|
339
|
+
const direction = useDirection();
|
|
309
340
|
const id = useLabelableId({
|
|
310
341
|
id: idProp
|
|
311
342
|
});
|
|
@@ -390,8 +421,8 @@ function AriaCombobox(props) {
|
|
|
390
421
|
state: 'inputValue'
|
|
391
422
|
});
|
|
392
423
|
const [open, setOpenUnwrapped] = useControlled({
|
|
393
|
-
controlled:
|
|
394
|
-
default:
|
|
424
|
+
controlled: openProp,
|
|
425
|
+
default: defaultOpen,
|
|
395
426
|
name: 'Combobox',
|
|
396
427
|
state: 'open'
|
|
397
428
|
});
|
|
@@ -493,6 +524,7 @@ function AriaCombobox(props) {
|
|
|
493
524
|
allValuesRef,
|
|
494
525
|
selectionEventRef,
|
|
495
526
|
name,
|
|
527
|
+
form,
|
|
496
528
|
disabled,
|
|
497
529
|
readOnly,
|
|
498
530
|
required,
|
|
@@ -515,14 +547,19 @@ function AriaCombobox(props) {
|
|
|
515
547
|
popupProps: {},
|
|
516
548
|
inputProps: {},
|
|
517
549
|
triggerProps: {},
|
|
550
|
+
itemProps: EMPTY_OBJECT,
|
|
518
551
|
positionerElement: null,
|
|
519
552
|
listElement: null,
|
|
553
|
+
popupId: undefined,
|
|
520
554
|
triggerElement: null,
|
|
521
555
|
inputElement: null,
|
|
522
556
|
inputGroupElement: null,
|
|
523
557
|
popupSide: null,
|
|
524
558
|
openMethod: null,
|
|
525
559
|
inputInsidePopup: true,
|
|
560
|
+
// Avoid duplicate names in the server HTML. Popup inputs aren't rendered
|
|
561
|
+
// until after hydration, so the hidden input takes over then if needed.
|
|
562
|
+
inputOwnsFormValue: selectionMode === 'none',
|
|
526
563
|
onOpenChangeComplete: onOpenChangeCompleteProp || NOOP,
|
|
527
564
|
// Placeholder callbacks replaced on first render
|
|
528
565
|
setOpen: NOOP,
|
|
@@ -531,7 +568,6 @@ function AriaCombobox(props) {
|
|
|
531
568
|
setIndices: NOOP,
|
|
532
569
|
onItemHighlighted: NOOP,
|
|
533
570
|
handleSelection: NOOP,
|
|
534
|
-
getItemProps: () => EMPTY_OBJECT,
|
|
535
571
|
forceMount: NOOP,
|
|
536
572
|
requestSubmit: NOOP
|
|
537
573
|
})).current;
|
|
@@ -556,6 +592,7 @@ function AriaCombobox(props) {
|
|
|
556
592
|
const inputGroupElement = useStore(store, selectors.inputGroupElement);
|
|
557
593
|
const inline = useStore(store, selectors.inline);
|
|
558
594
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
595
|
+
const inputOwnsFormValue = useStore(store, selectors.inputOwnsFormValue);
|
|
559
596
|
const triggerRef = useValueAsRef(triggerElement);
|
|
560
597
|
const {
|
|
561
598
|
mounted,
|
|
@@ -566,14 +603,8 @@ function AriaCombobox(props) {
|
|
|
566
603
|
openMethod,
|
|
567
604
|
triggerProps
|
|
568
605
|
} = useOpenInteractionType(open);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
name,
|
|
572
|
-
commit: validation.commit,
|
|
573
|
-
value: fieldRawValue,
|
|
574
|
-
controlRef: inputInsidePopup ? triggerRef : inputRef,
|
|
575
|
-
getValue: () => fieldStringValue
|
|
576
|
-
});
|
|
606
|
+
const getStringifiedValueForForm = useStableCallback(() => fieldStringValue);
|
|
607
|
+
useRegisterFieldControl(inputInsidePopup ? triggerRef : inputRef, id, fieldRawValue, getStringifiedValueForForm, !disabled, nameProp);
|
|
577
608
|
const forceMount = useStableCallback(() => {
|
|
578
609
|
if (items) {
|
|
579
610
|
// Ensure typeahead works on a closed list.
|
|
@@ -660,6 +691,16 @@ function AriaCombobox(props) {
|
|
|
660
691
|
if (eventDetails.isCanceled) {
|
|
661
692
|
return;
|
|
662
693
|
}
|
|
694
|
+
|
|
695
|
+
// If reopening interrupts the close animation, handleUnmount won't run to clear the
|
|
696
|
+
// frozen closeQuery and pending popup input.
|
|
697
|
+
if (nextOpen && multiple && inputInsidePopup && !inline && closeQuery !== null) {
|
|
698
|
+
setQueryChangedAfterOpen(false);
|
|
699
|
+
setCloseQuery(null);
|
|
700
|
+
if (inputValue !== '') {
|
|
701
|
+
setInputValue('', createChangeEventDetails(inputClear, eventDetails.event));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
663
704
|
if (!nextOpen && queryChangedAfterOpen) {
|
|
664
705
|
if (single) {
|
|
665
706
|
if (!inline) {
|
|
@@ -670,17 +711,22 @@ function AriaCombobox(props) {
|
|
|
670
711
|
setQueryChangedAfterOpen(false);
|
|
671
712
|
}
|
|
672
713
|
} else if (multiple) {
|
|
673
|
-
if (inline
|
|
714
|
+
if (!inline) {
|
|
715
|
+
// Freeze the current query so filtering remains stable while exiting.
|
|
716
|
+
setCloseQuery(query);
|
|
717
|
+
}
|
|
718
|
+
if (inputInsidePopup) {
|
|
674
719
|
setIndices({
|
|
675
720
|
activeIndex: null
|
|
676
721
|
});
|
|
677
|
-
} else {
|
|
678
|
-
// Freeze the current query so filtering remains stable while exiting.
|
|
679
|
-
setCloseQuery(query);
|
|
680
722
|
}
|
|
723
|
+
|
|
681
724
|
// Clear the input immediately on close while retaining filtering via closeQuery for exit animations
|
|
682
|
-
// if the input is outside the popup.
|
|
683
|
-
|
|
725
|
+
// if the input is outside the popup. When the input is inside the popup, defer the clear until
|
|
726
|
+
// unmount so the filtered list doesn't flash to unfiltered during the exit animation.
|
|
727
|
+
if (!inputInsidePopup || inline) {
|
|
728
|
+
setInputValue('', createChangeEventDetails(inputClear, eventDetails.event));
|
|
729
|
+
}
|
|
684
730
|
}
|
|
685
731
|
}
|
|
686
732
|
setOpenUnwrapped(nextOpen);
|
|
@@ -735,6 +781,9 @@ function AriaCombobox(props) {
|
|
|
735
781
|
const isCurrentlySelected = selectedValueIncludes(currentSelectedValue, itemValue, store.state.isItemEqualToValue);
|
|
736
782
|
const nextValue = isCurrentlySelected ? removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue) : [...currentSelectedValue, itemValue];
|
|
737
783
|
setSelectedValue(nextValue, eventDetails);
|
|
784
|
+
if (eventDetails.isCanceled) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
738
787
|
const wasFiltering = inputRef.current ? inputRef.current.value.trim() !== '' : false;
|
|
739
788
|
if (!wasFiltering) {
|
|
740
789
|
return;
|
|
@@ -746,6 +795,9 @@ function AriaCombobox(props) {
|
|
|
746
795
|
}
|
|
747
796
|
} else {
|
|
748
797
|
setSelectedValue(itemValue, eventDetails);
|
|
798
|
+
if (eventDetails.isCanceled) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
749
801
|
setOpen(false, eventDetails);
|
|
750
802
|
}
|
|
751
803
|
});
|
|
@@ -753,9 +805,9 @@ function AriaCombobox(props) {
|
|
|
753
805
|
if (!store.state.submitOnItemClick) {
|
|
754
806
|
return;
|
|
755
807
|
}
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
|
|
808
|
+
const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
|
|
809
|
+
if (formElement && typeof formElement.requestSubmit === 'function') {
|
|
810
|
+
formElement.requestSubmit();
|
|
759
811
|
}
|
|
760
812
|
});
|
|
761
813
|
const handleUnmount = useStableCallback(() => {
|
|
@@ -921,6 +973,13 @@ function AriaCombobox(props) {
|
|
|
921
973
|
});
|
|
922
974
|
}
|
|
923
975
|
}, [hasItems, autoHighlightMode, flatFilteredItems.length, setIndices]);
|
|
976
|
+
function isSelectedValueDirty(value) {
|
|
977
|
+
const initialValue = validityData.initialValue;
|
|
978
|
+
if (Array.isArray(value) && Array.isArray(initialValue)) {
|
|
979
|
+
return !areArraysEqual(value, initialValue, (itemValue, initialItemValue) => compareItemEquality(itemValue, initialItemValue, isItemEqualToValue));
|
|
980
|
+
}
|
|
981
|
+
return value !== initialValue;
|
|
982
|
+
}
|
|
924
983
|
useValueChanged(query, () => {
|
|
925
984
|
if (!open || query === '' || query === String(initialDefaultInputValue)) {
|
|
926
985
|
return;
|
|
@@ -932,12 +991,8 @@ function AriaCombobox(props) {
|
|
|
932
991
|
return;
|
|
933
992
|
}
|
|
934
993
|
clearErrors(name);
|
|
935
|
-
setDirty(selectedValue
|
|
936
|
-
|
|
937
|
-
validation.commit(selectedValue);
|
|
938
|
-
} else {
|
|
939
|
-
validation.commit(selectedValue, true);
|
|
940
|
-
}
|
|
994
|
+
setDirty(isSelectedValueDirty(selectedValue));
|
|
995
|
+
validation.change(selectedValue);
|
|
941
996
|
if (single && !hasInputValue && !inputInsidePopup) {
|
|
942
997
|
const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);
|
|
943
998
|
if (inputValue !== nextInputValue) {
|
|
@@ -951,11 +1006,7 @@ function AriaCombobox(props) {
|
|
|
951
1006
|
}
|
|
952
1007
|
clearErrors(name);
|
|
953
1008
|
setDirty(inputValue !== validityData.initialValue);
|
|
954
|
-
|
|
955
|
-
validation.commit(inputValue);
|
|
956
|
-
} else {
|
|
957
|
-
validation.commit(inputValue, true);
|
|
958
|
-
}
|
|
1009
|
+
validation.change(inputValue);
|
|
959
1010
|
});
|
|
960
1011
|
useValueChanged(items, () => {
|
|
961
1012
|
if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {
|
|
@@ -1010,9 +1061,9 @@ function AriaCombobox(props) {
|
|
|
1010
1061
|
enabled: !readOnly && !disabled && openOnInputClick,
|
|
1011
1062
|
event: 'mousedown-only',
|
|
1012
1063
|
toggle: false,
|
|
1013
|
-
// Apply a small delay for touch to let
|
|
1064
|
+
// Apply a small delay for touch to let mobile viewport/keyboard positioning settle.
|
|
1014
1065
|
// This avoids top-bottom flip flickers if the preferred position is "top" when first tapping.
|
|
1015
|
-
touchOpenDelay: inputInsidePopup ? 0 :
|
|
1066
|
+
touchOpenDelay: inputInsidePopup ? 0 : 100,
|
|
1016
1067
|
reason: inputPress
|
|
1017
1068
|
});
|
|
1018
1069
|
const dismiss = useDismiss(floatingRootContext, {
|
|
@@ -1043,13 +1094,10 @@ function AriaCombobox(props) {
|
|
|
1043
1094
|
focusItemOnOpen: queryChangedAfterOpen || selectionMode === 'none' && !autoHighlightMode ? false : 'auto',
|
|
1044
1095
|
focusItemOnHover: highlightItemOnHover,
|
|
1045
1096
|
resetOnPointerLeave: !keepHighlight,
|
|
1046
|
-
// `cols` > 1 enables grid navigation.
|
|
1047
|
-
// Since <Combobox.Row> infers column sizes (and is required when building a grid),
|
|
1048
|
-
// it works correctly even with a value of `2`.
|
|
1049
|
-
// Floating UI tests don't require `role="row"` wrappers, so retains the number API.
|
|
1050
|
-
cols: grid ? 2 : 1,
|
|
1051
1097
|
orientation: grid ? 'horizontal' : undefined,
|
|
1098
|
+
rtl: direction === 'rtl',
|
|
1052
1099
|
disabledIndices: EMPTY_ARRAY,
|
|
1100
|
+
grid: grid ? gridNavigation : undefined,
|
|
1053
1101
|
onNavigate(nextActiveIndex, event) {
|
|
1054
1102
|
// Retain the highlight only while actually transitioning out or closed.
|
|
1055
1103
|
if (!event && !open || transitionStatus === 'ending') {
|
|
@@ -1067,18 +1115,37 @@ function AriaCombobox(props) {
|
|
|
1067
1115
|
}
|
|
1068
1116
|
}
|
|
1069
1117
|
});
|
|
1070
|
-
const {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1118
|
+
const inputProps = React.useMemo(() => mergeProps(listNavigation.reference, {
|
|
1119
|
+
onKeyDown(event) {
|
|
1120
|
+
// In grid mode the navigation hook treats ArrowLeft/ArrowRight as horizontal
|
|
1121
|
+
// grid movement. When the input has focus and no item is highlighted the user
|
|
1122
|
+
// is still editing the query, so let the input keep its native caret behavior.
|
|
1123
|
+
if (grid && store.state.activeIndex == null && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
|
|
1124
|
+
event.preventBaseUIHandler();
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}, dismiss.reference, click.reference, role.reference), [listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store]);
|
|
1128
|
+
const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, listNavigation.floating, dismiss.floating, role.floating), [listNavigation.floating, dismiss.floating, role.floating]);
|
|
1129
|
+
const itemProps = React.useMemo(() => {
|
|
1130
|
+
const listNavigationItemProps = listNavigation.item;
|
|
1131
|
+
if (!listNavigationItemProps) {
|
|
1132
|
+
return EMPTY_OBJECT;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Combobox keeps focus on the input; item focus would incorrectly sync
|
|
1136
|
+
// list navigation state from DOM focus.
|
|
1137
|
+
return {
|
|
1138
|
+
...listNavigationItemProps,
|
|
1139
|
+
onFocus: undefined
|
|
1140
|
+
};
|
|
1141
|
+
}, [listNavigation.item]);
|
|
1075
1142
|
useOnFirstRender(() => {
|
|
1076
1143
|
store.update({
|
|
1077
1144
|
inline: inlineProp,
|
|
1078
|
-
popupProps
|
|
1079
|
-
inputProps
|
|
1145
|
+
popupProps,
|
|
1146
|
+
inputProps,
|
|
1080
1147
|
triggerProps,
|
|
1081
|
-
|
|
1148
|
+
itemProps,
|
|
1082
1149
|
setOpen,
|
|
1083
1150
|
setInputValue,
|
|
1084
1151
|
setSelectedValue,
|
|
@@ -1098,13 +1165,14 @@ function AriaCombobox(props) {
|
|
|
1098
1165
|
transitionStatus,
|
|
1099
1166
|
items,
|
|
1100
1167
|
inline: inlineProp,
|
|
1101
|
-
popupProps
|
|
1102
|
-
inputProps
|
|
1168
|
+
popupProps,
|
|
1169
|
+
inputProps,
|
|
1103
1170
|
triggerProps,
|
|
1104
1171
|
openMethod,
|
|
1105
|
-
|
|
1172
|
+
itemProps,
|
|
1106
1173
|
selectionMode,
|
|
1107
1174
|
name,
|
|
1175
|
+
form,
|
|
1108
1176
|
disabled,
|
|
1109
1177
|
readOnly,
|
|
1110
1178
|
required,
|
|
@@ -1119,9 +1187,10 @@ function AriaCombobox(props) {
|
|
|
1119
1187
|
isItemEqualToValue,
|
|
1120
1188
|
submitOnItemClick,
|
|
1121
1189
|
hasInputValue,
|
|
1122
|
-
requestSubmit
|
|
1190
|
+
requestSubmit,
|
|
1191
|
+
inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup)
|
|
1123
1192
|
});
|
|
1124
|
-
}, [store, id, selectedValue, open, mounted, transitionStatus, items,
|
|
1193
|
+
}, [store, id, selectedValue, open, mounted, transitionStatus, items, popupProps, inputProps, itemProps, openMethod, triggerProps, selectionMode, name, disabled, readOnly, required, validation, grid, isGrouped, virtualized, onOpenChangeComplete, openOnInputClick, itemToStringLabel, modal, isItemEqualToValue, submitOnItemClick, hasInputValue, inlineProp, requestSubmit, autoHighlightMode, form]);
|
|
1125
1194
|
const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);
|
|
1126
1195
|
const itemsContextValue = React.useMemo(() => ({
|
|
1127
1196
|
query,
|
|
@@ -1136,7 +1205,7 @@ function AriaCombobox(props) {
|
|
|
1136
1205
|
return stringifyAsValue(fieldRawValue, itemToStringValue);
|
|
1137
1206
|
}, [fieldRawValue, itemToStringValue]);
|
|
1138
1207
|
const hasMultipleSelection = multiple && Array.isArray(selectedValue) && selectedValue.length > 0;
|
|
1139
|
-
const hiddenInputName = multiple || selectionMode === 'none' ? undefined : name;
|
|
1208
|
+
const hiddenInputName = multiple || selectionMode === 'none' && inputOwnsFormValue ? undefined : name;
|
|
1140
1209
|
const hiddenInputs = React.useMemo(() => {
|
|
1141
1210
|
if (!multiple || !Array.isArray(selectedValue) || !name) {
|
|
1142
1211
|
return null;
|
|
@@ -1145,14 +1214,16 @@ function AriaCombobox(props) {
|
|
|
1145
1214
|
const currentSerializedValue = stringifyAsValue(value, itemToStringValue);
|
|
1146
1215
|
return /*#__PURE__*/jsx("input", {
|
|
1147
1216
|
type: "hidden",
|
|
1217
|
+
form: form,
|
|
1148
1218
|
name: name,
|
|
1149
|
-
value: currentSerializedValue
|
|
1219
|
+
value: currentSerializedValue,
|
|
1220
|
+
disabled: disabled
|
|
1150
1221
|
}, currentSerializedValue);
|
|
1151
1222
|
});
|
|
1152
|
-
}, [multiple, selectedValue, name, itemToStringValue]);
|
|
1223
|
+
}, [multiple, selectedValue, form, name, itemToStringValue, disabled]);
|
|
1153
1224
|
const children = /*#__PURE__*/jsxs(React.Fragment, {
|
|
1154
1225
|
children: [props.children, /*#__PURE__*/jsx("input", {
|
|
1155
|
-
...validation.
|
|
1226
|
+
...validation.getValidationProps(disabled, {
|
|
1156
1227
|
// Move focus when the hidden input is focused.
|
|
1157
1228
|
onFocus() {
|
|
1158
1229
|
if (inputInsidePopup) {
|
|
@@ -1164,48 +1235,58 @@ function AriaCombobox(props) {
|
|
|
1164
1235
|
// Handle browser autofill.
|
|
1165
1236
|
onChange(event) {
|
|
1166
1237
|
// Workaround for https://github.com/facebook/react/issues/9023
|
|
1167
|
-
if (event.nativeEvent.defaultPrevented) {
|
|
1238
|
+
if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
|
|
1168
1239
|
return;
|
|
1169
1240
|
}
|
|
1170
|
-
const nextValue = event.
|
|
1241
|
+
const nextValue = event.currentTarget.value;
|
|
1242
|
+
const nextValueLower = nextValue.toLowerCase();
|
|
1171
1243
|
const details = createChangeEventDetails(none, event.nativeEvent);
|
|
1244
|
+
const findSerializedMatchIndex = () => valuesRef.current.findIndex(candidate => stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
|
|
1172
1245
|
function handleChange() {
|
|
1173
1246
|
// Browser autofill only writes a single scalar value.
|
|
1174
1247
|
if (multiple) {
|
|
1175
1248
|
return;
|
|
1176
1249
|
}
|
|
1177
1250
|
if (selectionMode === 'none') {
|
|
1178
|
-
setDirty(nextValue !== validityData.initialValue);
|
|
1179
1251
|
setInputValue(nextValue, details);
|
|
1180
|
-
if (shouldValidateOnChange()) {
|
|
1181
|
-
validation.commit(nextValue);
|
|
1182
|
-
}
|
|
1183
1252
|
return;
|
|
1184
1253
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1254
|
+
|
|
1255
|
+
// Preserve the original serialized matching, then fall back to rendered text,
|
|
1256
|
+
// which browsers can autofill for primitive values like `value="US">United States`.
|
|
1257
|
+
let matchingIndex = findSerializedMatchIndex();
|
|
1258
|
+
if (matchingIndex === -1) {
|
|
1259
|
+
matchingIndex = valuesRef.current.findIndex((_, index) => {
|
|
1260
|
+
const renderedLabel = labelsRef.current[index];
|
|
1261
|
+
return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
const matchingValue = matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
|
|
1192
1265
|
if (matchingValue != null) {
|
|
1193
|
-
|
|
1266
|
+
// `setSelectedValue` may be canceled by `onValueChange`; rely on
|
|
1267
|
+
// `useValueChanged` to mark the field dirty and run validation only
|
|
1268
|
+
// when the value actually changes.
|
|
1194
1269
|
setSelectedValue?.(matchingValue, details);
|
|
1195
|
-
if (shouldValidateOnChange()) {
|
|
1196
|
-
validation.commit(matchingValue);
|
|
1197
|
-
}
|
|
1198
1270
|
}
|
|
1199
1271
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1272
|
+
|
|
1273
|
+
// Only single-selection autofill matches against the registered values/labels.
|
|
1274
|
+
// `multiple` ignores autofill and `none` just writes the input value, so avoid the
|
|
1275
|
+
// sticky `forceMounted` mount (which never resets) for those modes.
|
|
1276
|
+
if (single) {
|
|
1203
1277
|
forceMount();
|
|
1204
|
-
|
|
1278
|
+
if (items && findSerializedMatchIndex() === -1) {
|
|
1279
|
+
// `forceMount` only refreshes the derived labels for the `items` prop. When
|
|
1280
|
+
// serialized matching misses, also mount the list so rendered labels (which can
|
|
1281
|
+
// differ from the serialized values) are registered for autofill matching.
|
|
1282
|
+
store.set('forceMounted', true);
|
|
1283
|
+
}
|
|
1205
1284
|
}
|
|
1285
|
+
queueMicrotask(handleChange);
|
|
1206
1286
|
}
|
|
1207
1287
|
}),
|
|
1208
1288
|
id: id && hiddenInputName == null ? `${id}-hidden-input` : undefined,
|
|
1289
|
+
form: form,
|
|
1209
1290
|
name: hiddenInputName,
|
|
1210
1291
|
autoComplete: formAutoComplete,
|
|
1211
1292
|
disabled: disabled,
|
|
@@ -1215,18 +1296,22 @@ function AriaCombobox(props) {
|
|
|
1215
1296
|
ref: hiddenInputRef,
|
|
1216
1297
|
style: hiddenInputName ? visuallyHiddenInput : visuallyHidden,
|
|
1217
1298
|
tabIndex: -1,
|
|
1218
|
-
"aria-hidden": true
|
|
1299
|
+
"aria-hidden": true,
|
|
1300
|
+
suppressHydrationWarning: true
|
|
1219
1301
|
}), hiddenInputs]
|
|
1220
1302
|
});
|
|
1221
1303
|
return /*#__PURE__*/jsx(ComboboxRootContext.Provider, {
|
|
1222
1304
|
value: store,
|
|
1223
1305
|
children: /*#__PURE__*/jsx(ComboboxFloatingContext.Provider, {
|
|
1224
1306
|
value: floatingRootContext,
|
|
1225
|
-
children: /*#__PURE__*/jsx(
|
|
1226
|
-
value:
|
|
1227
|
-
children: /*#__PURE__*/jsx(
|
|
1228
|
-
value:
|
|
1229
|
-
children:
|
|
1307
|
+
children: /*#__PURE__*/jsx(ComboboxHasItemsContext.Provider, {
|
|
1308
|
+
value: hasItems,
|
|
1309
|
+
children: /*#__PURE__*/jsx(ComboboxDerivedItemsContext.Provider, {
|
|
1310
|
+
value: itemsContextValue,
|
|
1311
|
+
children: /*#__PURE__*/jsx(ComboboxInputValueContext.Provider, {
|
|
1312
|
+
value: inputValue,
|
|
1313
|
+
children: children
|
|
1314
|
+
})
|
|
1230
1315
|
})
|
|
1231
1316
|
})
|
|
1232
1317
|
})
|
|
@@ -1249,6 +1334,8 @@ const BOUNDARY_OFFSET = 2;
|
|
|
1249
1334
|
/**
|
|
1250
1335
|
* A button that opens the popup.
|
|
1251
1336
|
* Renders a `<button>` element.
|
|
1337
|
+
*
|
|
1338
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
1252
1339
|
*/
|
|
1253
1340
|
const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(componentProps, forwardedRef) {
|
|
1254
1341
|
const {
|
|
@@ -1257,6 +1344,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1257
1344
|
nativeButton = true,
|
|
1258
1345
|
disabled: disabledProp = false,
|
|
1259
1346
|
id: idProp,
|
|
1347
|
+
style,
|
|
1260
1348
|
...elementProps
|
|
1261
1349
|
} = componentProps;
|
|
1262
1350
|
const {
|
|
@@ -1282,6 +1370,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1282
1370
|
const popupSideValue = useStore(store, selectors.popupSide);
|
|
1283
1371
|
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1284
1372
|
const listElement = useStore(store, selectors.listElement);
|
|
1373
|
+
const storedPopupId = useStore(store, selectors.popupId);
|
|
1285
1374
|
const triggerProps = useStore(store, selectors.triggerProps);
|
|
1286
1375
|
const triggerElement = useStore(store, selectors.triggerElement);
|
|
1287
1376
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
@@ -1303,6 +1392,14 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1303
1392
|
});
|
|
1304
1393
|
const id = inputInsidePopup ? idProp ?? rootId : idProp;
|
|
1305
1394
|
const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, comboboxLabelId);
|
|
1395
|
+
let ariaControls;
|
|
1396
|
+
if (open && inputInsidePopup) {
|
|
1397
|
+
// Fall back to the default id while the popup registers its own (custom ids are stored once the
|
|
1398
|
+
// popup mounts), so `aria-controls` is set on the same commit `open` becomes `true`.
|
|
1399
|
+
ariaControls = storedPopupId ?? getComboboxPopupId(rootId);
|
|
1400
|
+
} else if (open) {
|
|
1401
|
+
ariaControls = listElement?.id;
|
|
1402
|
+
}
|
|
1306
1403
|
const currentPointerTypeRef = React.useRef('');
|
|
1307
1404
|
function trackPointerType(event) {
|
|
1308
1405
|
currentPointerTypeRef.current = event.pointerType;
|
|
@@ -1352,7 +1449,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1352
1449
|
disabled,
|
|
1353
1450
|
popupSide,
|
|
1354
1451
|
listEmpty,
|
|
1355
|
-
placeholder: !hasSelectedValue
|
|
1452
|
+
placeholder: selectionMode === 'none' ? false : !hasSelectedValue
|
|
1356
1453
|
};
|
|
1357
1454
|
const setTriggerElement = useStableCallback(element => {
|
|
1358
1455
|
store.set('triggerElement', element);
|
|
@@ -1366,7 +1463,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1366
1463
|
role: inputInsidePopup ? 'combobox' : undefined,
|
|
1367
1464
|
'aria-expanded': open ? 'true' : 'false',
|
|
1368
1465
|
'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',
|
|
1369
|
-
'aria-controls':
|
|
1466
|
+
'aria-controls': ariaControls,
|
|
1370
1467
|
'aria-required': inputInsidePopup ? required || undefined : undefined,
|
|
1371
1468
|
'aria-labelledby': ariaLabelledBy,
|
|
1372
1469
|
onPointerDown: trackPointerType,
|
|
@@ -1444,7 +1541,7 @@ const ComboboxTrigger = /*#__PURE__*/React.forwardRef(function ComboboxTrigger(c
|
|
|
1444
1541
|
store.state.inputRef.current?.focus();
|
|
1445
1542
|
}
|
|
1446
1543
|
}
|
|
1447
|
-
}, validation ? validation.getValidationProps(elementProps) : elementProps, getButtonProps],
|
|
1544
|
+
}, validation ? validation.getValidationProps(disabled, elementProps) : elementProps, getButtonProps],
|
|
1448
1545
|
stateAttributesMapping: triggerStateAttributesMapping
|
|
1449
1546
|
});
|
|
1450
1547
|
return element;
|
|
@@ -1479,9 +1576,9 @@ const ComboboxInternalDismissButton = /*#__PURE__*/React.forwardRef(function Com
|
|
|
1479
1576
|
native: false
|
|
1480
1577
|
});
|
|
1481
1578
|
const mergedRef = useMergedRefs(forwardedRef, buttonRef);
|
|
1482
|
-
|
|
1579
|
+
function handleDismiss(event) {
|
|
1483
1580
|
store.state.setOpen(false, createChangeEventDetails(closePress, event.nativeEvent, event.currentTarget));
|
|
1484
|
-
}
|
|
1581
|
+
}
|
|
1485
1582
|
const dismissProps = getButtonProps({
|
|
1486
1583
|
onClick: handleDismiss
|
|
1487
1584
|
});
|
|
@@ -1501,6 +1598,7 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1501
1598
|
className,
|
|
1502
1599
|
disabled: disabledProp = false,
|
|
1503
1600
|
id: idProp,
|
|
1601
|
+
style,
|
|
1504
1602
|
...elementProps
|
|
1505
1603
|
} = componentProps;
|
|
1506
1604
|
const {
|
|
@@ -1529,6 +1627,7 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1529
1627
|
const comboboxDisabled = useStore(store, selectors.disabled);
|
|
1530
1628
|
const readOnly = useStore(store, selectors.readOnly);
|
|
1531
1629
|
const name = useStore(store, selectors.name);
|
|
1630
|
+
const form = useStore(store, selectors.form);
|
|
1532
1631
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
1533
1632
|
const autoHighlightMode = useStore(store, selectors.autoHighlight);
|
|
1534
1633
|
const inputProps = useStore(store, selectors.inputProps);
|
|
@@ -1554,6 +1653,7 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1554
1653
|
const isComposingRef = React.useRef(false);
|
|
1555
1654
|
const lastActiveIndexRef = React.useRef(null);
|
|
1556
1655
|
const shouldRestoreActiveIndexRef = React.useRef(false);
|
|
1656
|
+
const inputOwnsFormValue = selectionMode === 'none' && !hasPositionerParent;
|
|
1557
1657
|
const setInputElement = useStableCallback(element => {
|
|
1558
1658
|
const nextIsInsidePopup = hasPositionerParent || store.state.inline;
|
|
1559
1659
|
if (nextIsInsidePopup && !store.state.hasInputValue) {
|
|
@@ -1561,10 +1661,11 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1561
1661
|
}
|
|
1562
1662
|
store.update({
|
|
1563
1663
|
inputElement: element,
|
|
1564
|
-
inputInsidePopup: nextIsInsidePopup
|
|
1664
|
+
inputInsidePopup: nextIsInsidePopup,
|
|
1665
|
+
inputOwnsFormValue
|
|
1565
1666
|
});
|
|
1566
1667
|
});
|
|
1567
|
-
const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(elementProps);
|
|
1668
|
+
const validationProps = hasPositionerParent || !validation ? elementProps : validation.getValidationProps(disabled, elementProps);
|
|
1568
1669
|
const state = {
|
|
1569
1670
|
...fieldStateForInput,
|
|
1570
1671
|
open,
|
|
@@ -1581,17 +1682,21 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1581
1682
|
const {
|
|
1582
1683
|
highlightedChipIndex
|
|
1583
1684
|
} = comboboxChipsContext;
|
|
1685
|
+
const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
|
|
1686
|
+
const isRtl = direction === 'rtl';
|
|
1687
|
+
const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
1688
|
+
const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
1584
1689
|
if (highlightedChipIndex !== undefined) {
|
|
1585
|
-
if (event.key ===
|
|
1690
|
+
if (event.key === previousChipKey) {
|
|
1586
1691
|
event.preventDefault();
|
|
1587
1692
|
if (highlightedChipIndex > 0) {
|
|
1588
1693
|
nextIndex = highlightedChipIndex - 1;
|
|
1589
1694
|
} else {
|
|
1590
1695
|
nextIndex = undefined;
|
|
1591
1696
|
}
|
|
1592
|
-
} else if (event.key ===
|
|
1697
|
+
} else if (event.key === nextChipKey) {
|
|
1593
1698
|
event.preventDefault();
|
|
1594
|
-
if (highlightedChipIndex <
|
|
1699
|
+
if (highlightedChipIndex < renderedChipsCount - 1) {
|
|
1595
1700
|
nextIndex = highlightedChipIndex + 1;
|
|
1596
1701
|
} else {
|
|
1597
1702
|
nextIndex = undefined;
|
|
@@ -1612,10 +1717,9 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1612
1717
|
}
|
|
1613
1718
|
|
|
1614
1719
|
// Handle navigation when no chip is highlighted
|
|
1615
|
-
if (event.key ===
|
|
1720
|
+
if (event.key === previousChipKey && (event.currentTarget.selectionStart ?? 0) === 0 && selectedValue.length > 0) {
|
|
1616
1721
|
event.preventDefault();
|
|
1617
|
-
|
|
1618
|
-
nextIndex = lastChipIndex;
|
|
1722
|
+
nextIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : undefined;
|
|
1619
1723
|
} else if (event.key === 'Backspace' && event.currentTarget.value === '' && selectedValue.length > 0) {
|
|
1620
1724
|
store.state.setIndices({
|
|
1621
1725
|
activeIndex: null,
|
|
@@ -1638,7 +1742,8 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1638
1742
|
disabled,
|
|
1639
1743
|
readOnly,
|
|
1640
1744
|
required: selectionMode === 'none' ? required : undefined,
|
|
1641
|
-
|
|
1745
|
+
form,
|
|
1746
|
+
...(inputOwnsFormValue && name && {
|
|
1642
1747
|
name
|
|
1643
1748
|
}),
|
|
1644
1749
|
id,
|
|
@@ -1675,7 +1780,7 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1675
1780
|
}
|
|
1676
1781
|
},
|
|
1677
1782
|
onCompositionStart(event) {
|
|
1678
|
-
if (
|
|
1783
|
+
if (android) {
|
|
1679
1784
|
return;
|
|
1680
1785
|
}
|
|
1681
1786
|
isComposingRef.current = true;
|
|
@@ -1729,7 +1834,11 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1729
1834
|
}
|
|
1730
1835
|
return;
|
|
1731
1836
|
}
|
|
1732
|
-
|
|
1837
|
+
const inputChangeDetails = createChangeEventDetails(inputChange, event.nativeEvent);
|
|
1838
|
+
store.state.setInputValue(event.currentTarget.value, inputChangeDetails);
|
|
1839
|
+
if (inputChangeDetails.isCanceled) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1733
1842
|
const empty = event.currentTarget.value === '';
|
|
1734
1843
|
const clearDetails = createChangeEventDetails(inputClear, event.nativeEvent);
|
|
1735
1844
|
if (empty && !store.state.inputInsidePopup) {
|
|
@@ -1779,14 +1888,14 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1779
1888
|
const isRTL = direction === 'rtl';
|
|
1780
1889
|
if (event.key === 'Home') {
|
|
1781
1890
|
stopEvent(event);
|
|
1782
|
-
const cursor =
|
|
1891
|
+
const cursor = gecko && isRTL ? input.value.length : 0;
|
|
1783
1892
|
input.setSelectionRange(cursor, cursor);
|
|
1784
1893
|
input.scrollLeft = 0;
|
|
1785
1894
|
return;
|
|
1786
1895
|
}
|
|
1787
1896
|
if (event.key === 'End') {
|
|
1788
1897
|
stopEvent(event);
|
|
1789
|
-
const cursor =
|
|
1898
|
+
const cursor = gecko && isRTL ? 0 : input.value.length;
|
|
1790
1899
|
input.setSelectionRange(cursor, cursor);
|
|
1791
1900
|
input.scrollLeft = isRTL ? -scrollAmount : scrollAmount;
|
|
1792
1901
|
return;
|
|
@@ -1805,7 +1914,9 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1805
1914
|
|
|
1806
1915
|
// Handle deletion when no chip is highlighted and the input is empty.
|
|
1807
1916
|
if (comboboxChipsContext && event.key === 'Backspace' && input.value === '' && comboboxChipsContext.highlightedChipIndex === undefined && Array.isArray(selectedValue) && selectedValue.length > 0) {
|
|
1808
|
-
const
|
|
1917
|
+
const renderedChipsCount = comboboxChipsContext.chipsRef.current.length;
|
|
1918
|
+
const removalIndex = renderedChipsCount > 0 ? renderedChipsCount - 1 : selectedValue.length - 1;
|
|
1919
|
+
const newValue = selectedValue.filter((_, index) => index !== removalIndex);
|
|
1809
1920
|
// If the removed item was also the active (highlighted) item, clear highlight
|
|
1810
1921
|
store.state.setIndices({
|
|
1811
1922
|
activeIndex: null,
|
|
@@ -1858,27 +1969,52 @@ const ComboboxInput = /*#__PURE__*/React.forwardRef(function ComboboxInput(compo
|
|
|
1858
1969
|
}, validationProps],
|
|
1859
1970
|
stateAttributesMapping: triggerStateAttributesMapping
|
|
1860
1971
|
});
|
|
1972
|
+
const renderedInput = hasPositionerParent ? /*#__PURE__*/jsx(FieldRootContext.Provider, {
|
|
1973
|
+
value: DEFAULT_FIELD_ROOT_CONTEXT,
|
|
1974
|
+
children: element
|
|
1975
|
+
}) : element;
|
|
1861
1976
|
return /*#__PURE__*/jsxs(React.Fragment, {
|
|
1862
1977
|
children: [open && focusManagerModal && /*#__PURE__*/jsx(ComboboxInternalDismissButton, {
|
|
1863
1978
|
ref: store.state.startDismissRef
|
|
1864
|
-
}),
|
|
1979
|
+
}), renderedInput]
|
|
1865
1980
|
});
|
|
1866
1981
|
});
|
|
1867
1982
|
if (process.env.NODE_ENV !== "production") ComboboxInput.displayName = "ComboboxInput";
|
|
1868
1983
|
|
|
1984
|
+
function handleInputPress(event, store, disabled, readOnly, shouldIgnoreTarget) {
|
|
1985
|
+
if (event.baseUIHandlerPrevented || readOnly) {
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
const target = getTarget(event.nativeEvent);
|
|
1989
|
+
const targetElement = isElement(target) ? target : null;
|
|
1990
|
+
if (targetElement !== event.currentTarget && (shouldIgnoreTarget?.(targetElement) || isInteractiveElement(targetElement))) {
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
event.preventDefault();
|
|
1994
|
+
if (disabled) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
store.state.inputRef.current?.focus();
|
|
1998
|
+
if (store.state.openOnInputClick) {
|
|
1999
|
+
store.state.setOpen(true, createChangeEventDetails(inputPress, event.nativeEvent));
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1869
2003
|
/**
|
|
1870
2004
|
* A wrapper for the input and its associated controls.
|
|
1871
2005
|
* Renders a `<div>` element.
|
|
2006
|
+
*
|
|
2007
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
1872
2008
|
*/
|
|
1873
2009
|
const ComboboxInputGroup = /*#__PURE__*/React.forwardRef(function ComboboxInputGroup(componentProps, forwardedRef) {
|
|
1874
2010
|
const {
|
|
1875
2011
|
render,
|
|
1876
2012
|
className,
|
|
2013
|
+
style,
|
|
1877
2014
|
...elementProps
|
|
1878
2015
|
} = componentProps;
|
|
1879
2016
|
const {
|
|
1880
|
-
state: fieldState
|
|
1881
|
-
disabled: fieldDisabled
|
|
2017
|
+
state: fieldState
|
|
1882
2018
|
} = useFieldRootContext();
|
|
1883
2019
|
const store = useComboboxRootContext();
|
|
1884
2020
|
const {
|
|
@@ -1893,7 +2029,7 @@ const ComboboxInputGroup = /*#__PURE__*/React.forwardRef(function ComboboxInputG
|
|
|
1893
2029
|
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
1894
2030
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
1895
2031
|
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
1896
|
-
const disabled =
|
|
2032
|
+
const disabled = comboboxDisabled;
|
|
1897
2033
|
const listEmpty = filteredItems.length === 0;
|
|
1898
2034
|
const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;
|
|
1899
2035
|
const state = {
|
|
@@ -1911,7 +2047,12 @@ const ComboboxInputGroup = /*#__PURE__*/React.forwardRef(function ComboboxInputG
|
|
|
1911
2047
|
return useRenderElement('div', componentProps, {
|
|
1912
2048
|
ref: [forwardedRef, setInputGroupElement],
|
|
1913
2049
|
props: [{
|
|
1914
|
-
role: 'group'
|
|
2050
|
+
role: 'group',
|
|
2051
|
+
onMouseDown(event) {
|
|
2052
|
+
handleInputPress(event, store, disabled, readOnly, target => {
|
|
2053
|
+
return contains(store.state.chipsContainerRef.current, target);
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
1915
2056
|
}, elementProps],
|
|
1916
2057
|
state,
|
|
1917
2058
|
stateAttributesMapping: triggerStateAttributesMapping
|
|
@@ -1922,11 +2063,14 @@ if (process.env.NODE_ENV !== "production") ComboboxInputGroup.displayName = "Com
|
|
|
1922
2063
|
/**
|
|
1923
2064
|
* An icon that indicates that the trigger button opens the popup.
|
|
1924
2065
|
* Renders a `<span>` element.
|
|
2066
|
+
*
|
|
2067
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
1925
2068
|
*/
|
|
1926
2069
|
const ComboboxIcon = /*#__PURE__*/React.forwardRef(function ComboboxIcon(componentProps, forwardedRef) {
|
|
1927
2070
|
const {
|
|
1928
|
-
className,
|
|
1929
2071
|
render,
|
|
2072
|
+
className,
|
|
2073
|
+
style,
|
|
1930
2074
|
...elementProps
|
|
1931
2075
|
} = componentProps;
|
|
1932
2076
|
const element = useRenderElement('span', componentProps, {
|
|
@@ -1948,6 +2092,8 @@ const stateAttributesMapping$2 = {
|
|
|
1948
2092
|
/**
|
|
1949
2093
|
* Clears the value when clicked.
|
|
1950
2094
|
* Renders a `<button>` element.
|
|
2095
|
+
*
|
|
2096
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
1951
2097
|
*/
|
|
1952
2098
|
const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(componentProps, forwardedRef) {
|
|
1953
2099
|
const {
|
|
@@ -1956,6 +2102,7 @@ const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(compo
|
|
|
1956
2102
|
disabled: disabledProp = false,
|
|
1957
2103
|
nativeButton = true,
|
|
1958
2104
|
keepMounted = false,
|
|
2105
|
+
style,
|
|
1959
2106
|
...elementProps
|
|
1960
2107
|
} = componentProps;
|
|
1961
2108
|
const {
|
|
@@ -1992,6 +2139,7 @@ const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(compo
|
|
|
1992
2139
|
} = useTransitionStatus(visible);
|
|
1993
2140
|
const state = {
|
|
1994
2141
|
disabled,
|
|
2142
|
+
visible,
|
|
1995
2143
|
open,
|
|
1996
2144
|
transitionStatus
|
|
1997
2145
|
};
|
|
@@ -2087,6 +2235,7 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2087
2235
|
const {
|
|
2088
2236
|
render,
|
|
2089
2237
|
className,
|
|
2238
|
+
style,
|
|
2090
2239
|
children,
|
|
2091
2240
|
...elementProps
|
|
2092
2241
|
} = componentProps;
|
|
@@ -2094,17 +2243,14 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2094
2243
|
const floatingRootContext = useComboboxFloatingContext();
|
|
2095
2244
|
const hasPositionerContext = Boolean(useComboboxPositionerContext(true));
|
|
2096
2245
|
const {
|
|
2097
|
-
filteredItems
|
|
2246
|
+
filteredItems,
|
|
2247
|
+
hasItems
|
|
2098
2248
|
} = useComboboxDerivedItemsContext();
|
|
2099
|
-
const items = useStore(store, selectors.items);
|
|
2100
|
-
const labelsRef = useStore(store, selectors.labelsRef);
|
|
2101
|
-
const listRef = useStore(store, selectors.listRef);
|
|
2102
2249
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2103
2250
|
const grid = useStore(store, selectors.grid);
|
|
2104
2251
|
const popupProps = useStore(store, selectors.popupProps);
|
|
2105
|
-
const disabled = useStore(store, selectors.disabled);
|
|
2106
|
-
const readOnly = useStore(store, selectors.readOnly);
|
|
2107
2252
|
const virtualized = useStore(store, selectors.virtualized);
|
|
2253
|
+
const forceMounted = useStore(store, selectors.forceMounted);
|
|
2108
2254
|
const multiple = selectionMode === 'multiple';
|
|
2109
2255
|
const empty = filteredItems.length === 0;
|
|
2110
2256
|
const setPositionerElement = useStableCallback(element => {
|
|
@@ -2140,7 +2286,7 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2140
2286
|
role: grid ? 'grid' : 'listbox',
|
|
2141
2287
|
'aria-multiselectable': multiple ? 'true' : undefined,
|
|
2142
2288
|
onKeyDown(event) {
|
|
2143
|
-
if (disabled || readOnly) {
|
|
2289
|
+
if (store.state.disabled || store.state.readOnly) {
|
|
2144
2290
|
return;
|
|
2145
2291
|
}
|
|
2146
2292
|
if (event.key === 'Enter') {
|
|
@@ -2170,28 +2316,93 @@ const ComboboxList = /*#__PURE__*/React.forwardRef(function ComboboxList(compone
|
|
|
2170
2316
|
if (virtualized) {
|
|
2171
2317
|
return element;
|
|
2172
2318
|
}
|
|
2319
|
+
|
|
2320
|
+
// With the `items` prop, typeahead labels are derived from the items so they survive the list
|
|
2321
|
+
// unmounting (unmounting clears the registered labels). Rendered labels only need to be
|
|
2322
|
+
// registered when the list is force-mounted to match browser autofill against rendered text.
|
|
2323
|
+
const labelsRef = hasItems && !forceMounted ? undefined : store.state.labelsRef;
|
|
2173
2324
|
return /*#__PURE__*/jsx(CompositeList, {
|
|
2174
|
-
elementsRef: listRef,
|
|
2175
|
-
labelsRef:
|
|
2325
|
+
elementsRef: store.state.listRef,
|
|
2326
|
+
labelsRef: labelsRef,
|
|
2176
2327
|
children: element
|
|
2177
2328
|
});
|
|
2178
2329
|
});
|
|
2179
2330
|
if (process.env.NODE_ENV !== "production") ComboboxList.displayName = "ComboboxList";
|
|
2180
2331
|
|
|
2332
|
+
// Word Joiner is invisible and zero-width, so it forces a text mutation without shifting layout.
|
|
2333
|
+
const LIVE_REGION_MARKER = '\u2060';
|
|
2334
|
+
// Safari VoiceOver needed roughly 200ms to reliably notice the initial polite live-region change.
|
|
2335
|
+
const INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY = 200;
|
|
2336
|
+
function findLastTextNode(root) {
|
|
2337
|
+
const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
2338
|
+
let lastTextNode = null;
|
|
2339
|
+
while (walker.nextNode()) {
|
|
2340
|
+
const textNode = walker.currentNode;
|
|
2341
|
+
if (textNode.nodeValue !== '') {
|
|
2342
|
+
lastTextNode = textNode;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
return lastTextNode;
|
|
2346
|
+
}
|
|
2347
|
+
function useInitialLiveRegionTextMutation() {
|
|
2348
|
+
const timeout = useTimeout();
|
|
2349
|
+
const rootRef = React.useRef(null);
|
|
2350
|
+
|
|
2351
|
+
// Only the initial mounted announcement needs the marker; later text updates announce naturally.
|
|
2352
|
+
React.useEffect(() => {
|
|
2353
|
+
if (ios) {
|
|
2354
|
+
return undefined;
|
|
2355
|
+
}
|
|
2356
|
+
const root = rootRef.current;
|
|
2357
|
+
if (root == null) {
|
|
2358
|
+
return undefined;
|
|
2359
|
+
}
|
|
2360
|
+
const textNode = findLastTextNode(root);
|
|
2361
|
+
if (textNode == null) {
|
|
2362
|
+
return undefined;
|
|
2363
|
+
}
|
|
2364
|
+
const originalValue = textNode.nodeValue ?? '';
|
|
2365
|
+
const markedValue = `${originalValue}${LIVE_REGION_MARKER}`;
|
|
2366
|
+
textNode.nodeValue = markedValue;
|
|
2367
|
+
timeout.start(INITIAL_LIVE_REGION_TEXT_MUTATION_RESET_DELAY, () => {
|
|
2368
|
+
if (textNode.nodeValue === markedValue) {
|
|
2369
|
+
textNode.nodeValue = originalValue;
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
return () => {
|
|
2373
|
+
timeout.clear();
|
|
2374
|
+
if (textNode.nodeValue === markedValue) {
|
|
2375
|
+
textNode.nodeValue = originalValue;
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
}, [rootRef, timeout]);
|
|
2379
|
+
return rootRef;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2181
2382
|
/**
|
|
2182
2383
|
* Displays a status message whose content changes are announced politely to screen readers.
|
|
2183
2384
|
* Useful for conveying the status of an asynchronously loaded list.
|
|
2385
|
+
* This component's root element must remain mounted in the DOM to announce
|
|
2386
|
+
* changes consistently across screen readers. Avoid hiding or removing the
|
|
2387
|
+
* component itself with `display: none`, `hidden`, `aria-hidden`, or conditional
|
|
2388
|
+
* rendering. Prefer updating or conditionally rendering its children instead.
|
|
2184
2389
|
* Renders a `<div>` element.
|
|
2390
|
+
*
|
|
2391
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2185
2392
|
*/
|
|
2186
2393
|
const ComboboxStatus = /*#__PURE__*/React.forwardRef(function ComboboxStatus(componentProps, forwardedRef) {
|
|
2187
2394
|
const {
|
|
2188
2395
|
render,
|
|
2189
2396
|
className,
|
|
2397
|
+
style,
|
|
2398
|
+
children: childrenProp,
|
|
2190
2399
|
...elementProps
|
|
2191
2400
|
} = componentProps;
|
|
2401
|
+
const statusRef = useInitialLiveRegionTextMutation();
|
|
2192
2402
|
return useRenderElement('div', componentProps, {
|
|
2193
|
-
ref: forwardedRef,
|
|
2403
|
+
ref: [forwardedRef, statusRef],
|
|
2194
2404
|
props: [{
|
|
2405
|
+
children: childrenProp,
|
|
2195
2406
|
role: 'status',
|
|
2196
2407
|
'aria-live': 'polite',
|
|
2197
2408
|
'aria-atomic': true
|
|
@@ -2240,11 +2451,14 @@ const stateAttributesMapping$1 = {
|
|
|
2240
2451
|
/**
|
|
2241
2452
|
* An overlay displayed beneath the popup.
|
|
2242
2453
|
* Renders a `<div>` element.
|
|
2454
|
+
*
|
|
2455
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2243
2456
|
*/
|
|
2244
2457
|
const ComboboxBackdrop = /*#__PURE__*/React.forwardRef(function ComboboxBackdrop(componentProps, forwardedRef) {
|
|
2245
2458
|
const {
|
|
2246
|
-
className,
|
|
2247
2459
|
render,
|
|
2460
|
+
className,
|
|
2461
|
+
style,
|
|
2248
2462
|
...elementProps
|
|
2249
2463
|
} = componentProps;
|
|
2250
2464
|
const store = useComboboxRootContext();
|
|
@@ -2287,6 +2501,7 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2287
2501
|
sticky = false,
|
|
2288
2502
|
disableAnchorTracking = false,
|
|
2289
2503
|
collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
|
|
2504
|
+
style: styleProp,
|
|
2290
2505
|
...elementProps
|
|
2291
2506
|
} = componentProps;
|
|
2292
2507
|
const store = useComboboxRootContext();
|
|
@@ -2299,6 +2514,7 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2299
2514
|
const open = useStore(store, selectors.open);
|
|
2300
2515
|
const mounted = useStore(store, selectors.mounted);
|
|
2301
2516
|
const openMethod = useStore(store, selectors.openMethod);
|
|
2517
|
+
const positionerElement = useStore(store, selectors.positionerElement);
|
|
2302
2518
|
const triggerElement = useStore(store, selectors.triggerElement);
|
|
2303
2519
|
const inputElement = useStore(store, selectors.inputElement);
|
|
2304
2520
|
const inputGroupElement = useStore(store, selectors.inputGroupElement);
|
|
@@ -2324,20 +2540,7 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2324
2540
|
collisionAvoidance,
|
|
2325
2541
|
lazyFlip: true
|
|
2326
2542
|
});
|
|
2327
|
-
|
|
2328
|
-
const defaultProps = React.useMemo(() => {
|
|
2329
|
-
const style = {
|
|
2330
|
-
...positioning.positionerStyles
|
|
2331
|
-
};
|
|
2332
|
-
if (!open) {
|
|
2333
|
-
style.pointerEvents = 'none';
|
|
2334
|
-
}
|
|
2335
|
-
return {
|
|
2336
|
-
role: 'presentation',
|
|
2337
|
-
hidden: !mounted,
|
|
2338
|
-
style
|
|
2339
|
-
};
|
|
2340
|
-
}, [open, mounted, positioning.positionerStyles]);
|
|
2543
|
+
useAnchoredPopupScrollLock(open && modal, openMethod === 'touch', positionerElement, triggerElement);
|
|
2341
2544
|
const state = {
|
|
2342
2545
|
open,
|
|
2343
2546
|
side: positioning.side,
|
|
@@ -2348,26 +2551,19 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
|
|
|
2348
2551
|
useIsoLayoutEffect(() => {
|
|
2349
2552
|
store.set('popupSide', positioning.side);
|
|
2350
2553
|
}, [store, positioning.side]);
|
|
2351
|
-
const contextValue = React.useMemo(() => ({
|
|
2352
|
-
side: positioning.side,
|
|
2353
|
-
align: positioning.align,
|
|
2354
|
-
arrowRef: positioning.arrowRef,
|
|
2355
|
-
arrowUncentered: positioning.arrowUncentered,
|
|
2356
|
-
arrowStyles: positioning.arrowStyles,
|
|
2357
|
-
anchorHidden: positioning.anchorHidden,
|
|
2358
|
-
isPositioned: positioning.isPositioned
|
|
2359
|
-
}), [positioning.side, positioning.align, positioning.arrowRef, positioning.arrowUncentered, positioning.arrowStyles, positioning.anchorHidden, positioning.isPositioned]);
|
|
2360
2554
|
const setPositionerElement = useStableCallback(element => {
|
|
2361
2555
|
store.set('positionerElement', element);
|
|
2362
2556
|
});
|
|
2363
|
-
const element =
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
props:
|
|
2367
|
-
|
|
2557
|
+
const element = usePositioner(componentProps, state, {
|
|
2558
|
+
styles: positioning.positionerStyles,
|
|
2559
|
+
transitionStatus,
|
|
2560
|
+
props: elementProps,
|
|
2561
|
+
refs: [forwardedRef, setPositionerElement],
|
|
2562
|
+
hidden: !mounted,
|
|
2563
|
+
inert: !open
|
|
2368
2564
|
});
|
|
2369
2565
|
return /*#__PURE__*/jsxs(ComboboxPositionerContext.Provider, {
|
|
2370
|
-
value:
|
|
2566
|
+
value: positioning,
|
|
2371
2567
|
children: [mounted && modal && /*#__PURE__*/jsx(InternalBackdrop, {
|
|
2372
2568
|
inert: inertValue(!open),
|
|
2373
2569
|
cutout: inputGroupElement ?? inputElement ?? triggerElement
|
|
@@ -2384,11 +2580,14 @@ const stateAttributesMapping = {
|
|
|
2384
2580
|
/**
|
|
2385
2581
|
* A container for the list.
|
|
2386
2582
|
* Renders a `<div>` element.
|
|
2583
|
+
*
|
|
2584
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2387
2585
|
*/
|
|
2388
2586
|
const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(componentProps, forwardedRef) {
|
|
2389
2587
|
const {
|
|
2390
2588
|
render,
|
|
2391
2589
|
className,
|
|
2590
|
+
style,
|
|
2392
2591
|
initialFocus,
|
|
2393
2592
|
finalFocus,
|
|
2394
2593
|
...elementProps
|
|
@@ -2406,7 +2605,16 @@ const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(compo
|
|
|
2406
2605
|
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
|
|
2407
2606
|
const inputElement = useStore(store, selectors.inputElement);
|
|
2408
2607
|
const modal = useStore(store, selectors.modal);
|
|
2608
|
+
const rootId = useStore(store, selectors.id);
|
|
2409
2609
|
const empty = filteredItems.length === 0;
|
|
2610
|
+
const popupId = elementProps.id ?? (inputInsidePopup ? getComboboxPopupId(rootId) : undefined);
|
|
2611
|
+
useIsoLayoutEffect(() => {
|
|
2612
|
+
// Prefer the rendered DOM id, which a `render` prop element or function may override.
|
|
2613
|
+
store.set('popupId', store.state.popupRef.current?.id || popupId);
|
|
2614
|
+
return () => {
|
|
2615
|
+
store.set('popupId', undefined);
|
|
2616
|
+
};
|
|
2617
|
+
}, [store, popupId]);
|
|
2410
2618
|
useOpenChangeComplete({
|
|
2411
2619
|
open,
|
|
2412
2620
|
ref: store.state.popupRef,
|
|
@@ -2428,6 +2636,7 @@ const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(compo
|
|
|
2428
2636
|
state,
|
|
2429
2637
|
ref: [forwardedRef, store.state.popupRef],
|
|
2430
2638
|
props: [{
|
|
2639
|
+
id: popupId,
|
|
2431
2640
|
role: inputInsidePopup ? 'dialog' : 'presentation',
|
|
2432
2641
|
tabIndex: -1,
|
|
2433
2642
|
onFocus(event) {
|
|
@@ -2472,11 +2681,14 @@ if (process.env.NODE_ENV !== "production") ComboboxPopup.displayName = "Combobox
|
|
|
2472
2681
|
/**
|
|
2473
2682
|
* Displays an element positioned against the anchor.
|
|
2474
2683
|
* Renders a `<div>` element.
|
|
2684
|
+
*
|
|
2685
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2475
2686
|
*/
|
|
2476
2687
|
const ComboboxArrow = /*#__PURE__*/React.forwardRef(function ComboboxArrow(componentProps, forwardedRef) {
|
|
2477
2688
|
const {
|
|
2478
|
-
className,
|
|
2479
2689
|
render,
|
|
2690
|
+
className,
|
|
2691
|
+
style,
|
|
2480
2692
|
...elementProps
|
|
2481
2693
|
} = componentProps;
|
|
2482
2694
|
const store = useComboboxRootContext();
|
|
@@ -2521,6 +2733,7 @@ const ComboboxGroup = /*#__PURE__*/React.forwardRef(function ComboboxGroup(compo
|
|
|
2521
2733
|
const {
|
|
2522
2734
|
render,
|
|
2523
2735
|
className,
|
|
2736
|
+
style,
|
|
2524
2737
|
items,
|
|
2525
2738
|
...elementProps
|
|
2526
2739
|
} = componentProps;
|
|
@@ -2554,11 +2767,14 @@ if (process.env.NODE_ENV !== "production") ComboboxGroup.displayName = "Combobox
|
|
|
2554
2767
|
/**
|
|
2555
2768
|
* An accessible label that is automatically associated with its parent group.
|
|
2556
2769
|
* Renders a `<div>` element.
|
|
2770
|
+
*
|
|
2771
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2557
2772
|
*/
|
|
2558
2773
|
const ComboboxGroupLabel = /*#__PURE__*/React.forwardRef(function ComboboxGroupLabel(componentProps, forwardedRef) {
|
|
2559
2774
|
const {
|
|
2560
2775
|
render,
|
|
2561
2776
|
className,
|
|
2777
|
+
style,
|
|
2562
2778
|
id: idProp,
|
|
2563
2779
|
...elementProps
|
|
2564
2780
|
} = componentProps;
|
|
@@ -2598,10 +2814,17 @@ function useComboboxRowContext() {
|
|
|
2598
2814
|
return React.useContext(ComboboxRowContext);
|
|
2599
2815
|
}
|
|
2600
2816
|
|
|
2601
|
-
|
|
2817
|
+
function ComboboxItemInner(props) {
|
|
2818
|
+
const {
|
|
2819
|
+
componentProps,
|
|
2820
|
+
forwardedRef,
|
|
2821
|
+
virtualized,
|
|
2822
|
+
indexFromFilter
|
|
2823
|
+
} = props;
|
|
2602
2824
|
const {
|
|
2603
2825
|
render,
|
|
2604
2826
|
className,
|
|
2827
|
+
style,
|
|
2605
2828
|
value: itemValue = null,
|
|
2606
2829
|
index: indexProp,
|
|
2607
2830
|
disabled = false,
|
|
@@ -2617,22 +2840,18 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2617
2840
|
});
|
|
2618
2841
|
const store = useComboboxRootContext();
|
|
2619
2842
|
const isRow = useComboboxRowContext();
|
|
2620
|
-
const
|
|
2621
|
-
flatFilteredItems,
|
|
2622
|
-
hasItems
|
|
2623
|
-
} = useComboboxDerivedItemsContext();
|
|
2843
|
+
const hasItems = useComboboxHasItemsContext();
|
|
2624
2844
|
const open = useStore(store, selectors.open);
|
|
2625
2845
|
const selectionMode = useStore(store, selectors.selectionMode);
|
|
2626
2846
|
const readOnly = useStore(store, selectors.readOnly);
|
|
2627
|
-
const virtualized = useStore(store, selectors.virtualized);
|
|
2628
2847
|
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
2629
2848
|
const selectable = selectionMode !== 'none';
|
|
2630
|
-
const index = indexProp ?? (virtualized ?
|
|
2849
|
+
const index = indexProp ?? (virtualized ? indexFromFilter ?? -1 : listItem.index);
|
|
2631
2850
|
const hasRegistered = listItem.index !== -1;
|
|
2632
2851
|
const rootId = useStore(store, selectors.id);
|
|
2633
2852
|
const highlighted = useStore(store, selectors.isActive, index);
|
|
2634
2853
|
const matchesSelectedValue = useStore(store, selectors.isSelected, itemValue);
|
|
2635
|
-
const
|
|
2854
|
+
const itemProps = useStore(store, selectors.itemProps);
|
|
2636
2855
|
const itemRef = React.useRef(null);
|
|
2637
2856
|
const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined;
|
|
2638
2857
|
const selected = matchesSelectedValue && selectable;
|
|
@@ -2678,17 +2897,6 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2678
2897
|
store.set('selectedIndex', index);
|
|
2679
2898
|
}
|
|
2680
2899
|
}, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);
|
|
2681
|
-
const state = {
|
|
2682
|
-
disabled,
|
|
2683
|
-
selected,
|
|
2684
|
-
highlighted
|
|
2685
|
-
};
|
|
2686
|
-
const rootProps = getItemProps({
|
|
2687
|
-
active: highlighted,
|
|
2688
|
-
selected
|
|
2689
|
-
});
|
|
2690
|
-
rootProps.id = undefined;
|
|
2691
|
-
rootProps.onFocus = undefined;
|
|
2692
2900
|
const {
|
|
2693
2901
|
getButtonProps,
|
|
2694
2902
|
buttonRef
|
|
@@ -2698,6 +2906,11 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2698
2906
|
native: nativeButton,
|
|
2699
2907
|
composite: true
|
|
2700
2908
|
});
|
|
2909
|
+
const state = {
|
|
2910
|
+
disabled,
|
|
2911
|
+
selected,
|
|
2912
|
+
highlighted
|
|
2913
|
+
};
|
|
2701
2914
|
function commitSelection(nativeEvent) {
|
|
2702
2915
|
function selectItem() {
|
|
2703
2916
|
store.state.handleSelection(nativeEvent, itemValue);
|
|
@@ -2721,6 +2934,11 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2721
2934
|
didPointerDownRef.current = true;
|
|
2722
2935
|
event.preventDefault();
|
|
2723
2936
|
},
|
|
2937
|
+
onMouseDown(event) {
|
|
2938
|
+
// iOS Safari can emit a synthetic mousedown for touch taps without a preceding
|
|
2939
|
+
// pointerdown. Prevent default here too so tapping an item does not blur the input.
|
|
2940
|
+
event.preventDefault();
|
|
2941
|
+
},
|
|
2724
2942
|
onClick(event) {
|
|
2725
2943
|
if (disabled || readOnly) {
|
|
2726
2944
|
return;
|
|
@@ -2739,7 +2957,7 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2739
2957
|
const element = useRenderElement('div', componentProps, {
|
|
2740
2958
|
ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
|
|
2741
2959
|
state,
|
|
2742
|
-
props: [
|
|
2960
|
+
props: [itemProps, defaultProps, elementProps, getButtonProps]
|
|
2743
2961
|
});
|
|
2744
2962
|
const contextValue = React.useMemo(() => ({
|
|
2745
2963
|
selected,
|
|
@@ -2749,6 +2967,61 @@ const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(funct
|
|
|
2749
2967
|
value: contextValue,
|
|
2750
2968
|
children: element
|
|
2751
2969
|
});
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
/**
|
|
2973
|
+
* Resolves the index from the filtered items for the virtualized fallback (no `index` prop).
|
|
2974
|
+
* Isolated here so that this per-keystroke subscription to the derived-items context is only
|
|
2975
|
+
* paid by virtualized items. Those re-render on every input change anyway — the parent
|
|
2976
|
+
* virtualizer re-windows the list as the filtered set changes — so the extra subscription costs
|
|
2977
|
+
* them nothing, while it keeps every non-virtualized item off that context.
|
|
2978
|
+
*/
|
|
2979
|
+
function ComboboxItemVirtualizedIndex(props) {
|
|
2980
|
+
const {
|
|
2981
|
+
componentProps,
|
|
2982
|
+
forwardedRef
|
|
2983
|
+
} = props;
|
|
2984
|
+
const store = useComboboxRootContext();
|
|
2985
|
+
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
2986
|
+
const {
|
|
2987
|
+
flatFilteredItems
|
|
2988
|
+
} = useComboboxDerivedItemsContext();
|
|
2989
|
+
const indexFromFilter = findItemIndex(flatFilteredItems, componentProps.value ?? null, isItemEqualToValue);
|
|
2990
|
+
|
|
2991
|
+
// Only reached when `virtualized` is true (see the wrapper below).
|
|
2992
|
+
return /*#__PURE__*/jsx(ComboboxItemInner, {
|
|
2993
|
+
componentProps: componentProps,
|
|
2994
|
+
forwardedRef: forwardedRef,
|
|
2995
|
+
virtualized: true,
|
|
2996
|
+
indexFromFilter: indexFromFilter
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
/**
|
|
3001
|
+
* An individual item in the list.
|
|
3002
|
+
* Renders a `<div>` element.
|
|
3003
|
+
*
|
|
3004
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
3005
|
+
*/
|
|
3006
|
+
const ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(function ComboboxItem(componentProps, forwardedRef) {
|
|
3007
|
+
const store = useComboboxRootContext();
|
|
3008
|
+
const virtualized = useStore(store, selectors.virtualized);
|
|
3009
|
+
|
|
3010
|
+
// `virtualized` (and whether an item provides an explicit `index`) must be stable for an
|
|
3011
|
+
// item's lifetime: the two branches return different component types, so flipping it at
|
|
3012
|
+
// runtime remounts the item and resets its refs and effects.
|
|
3013
|
+
if (virtualized && componentProps.index == null) {
|
|
3014
|
+
return /*#__PURE__*/jsx(ComboboxItemVirtualizedIndex, {
|
|
3015
|
+
componentProps: componentProps,
|
|
3016
|
+
forwardedRef: forwardedRef
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
return /*#__PURE__*/jsx(ComboboxItemInner, {
|
|
3020
|
+
componentProps: componentProps,
|
|
3021
|
+
forwardedRef: forwardedRef,
|
|
3022
|
+
virtualized: virtualized,
|
|
3023
|
+
indexFromFilter: undefined
|
|
3024
|
+
});
|
|
2752
3025
|
}));
|
|
2753
3026
|
if (process.env.NODE_ENV !== "production") ComboboxItem.displayName = "ComboboxItem";
|
|
2754
3027
|
|
|
@@ -2756,6 +3029,7 @@ const ComboboxRow = /*#__PURE__*/React.forwardRef(function ComboboxRow(component
|
|
|
2756
3029
|
const {
|
|
2757
3030
|
render,
|
|
2758
3031
|
className,
|
|
3032
|
+
style,
|
|
2759
3033
|
...elementProps
|
|
2760
3034
|
} = componentProps;
|
|
2761
3035
|
const element = useRenderElement('div', componentProps, {
|
|
@@ -2775,12 +3049,19 @@ if (process.env.NODE_ENV !== "production") ComboboxRow.displayName = "ComboboxRo
|
|
|
2775
3049
|
* Renders its children only when the list is empty.
|
|
2776
3050
|
* Requires the `items` prop on the root component.
|
|
2777
3051
|
* Announces changes politely to screen readers.
|
|
3052
|
+
* This component's root element must remain mounted in the DOM to announce
|
|
3053
|
+
* changes consistently across screen readers. Avoid hiding or removing the
|
|
3054
|
+
* component itself with `display: none`, `hidden`, `aria-hidden`, or conditional
|
|
3055
|
+
* rendering. Prefer updating or conditionally rendering its children instead.
|
|
2778
3056
|
* Renders a `<div>` element.
|
|
3057
|
+
*
|
|
3058
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
2779
3059
|
*/
|
|
2780
3060
|
const ComboboxEmpty = /*#__PURE__*/React.forwardRef(function ComboboxEmpty(componentProps, forwardedRef) {
|
|
2781
3061
|
const {
|
|
2782
3062
|
render,
|
|
2783
3063
|
className,
|
|
3064
|
+
style,
|
|
2784
3065
|
children: childrenProp,
|
|
2785
3066
|
...elementProps
|
|
2786
3067
|
} = componentProps;
|
|
@@ -2788,9 +3069,10 @@ const ComboboxEmpty = /*#__PURE__*/React.forwardRef(function ComboboxEmpty(compo
|
|
|
2788
3069
|
filteredItems
|
|
2789
3070
|
} = useComboboxDerivedItemsContext();
|
|
2790
3071
|
const store = useComboboxRootContext();
|
|
3072
|
+
const emptyRef = useInitialLiveRegionTextMutation();
|
|
2791
3073
|
const children = filteredItems.length === 0 ? childrenProp : null;
|
|
2792
3074
|
return useRenderElement('div', componentProps, {
|
|
2793
|
-
ref: [forwardedRef, store.state.emptyRef],
|
|
3075
|
+
ref: [forwardedRef, store.state.emptyRef, emptyRef],
|
|
2794
3076
|
props: [{
|
|
2795
3077
|
children,
|
|
2796
3078
|
role: 'status',
|
|
@@ -2838,6 +3120,7 @@ const ComboboxLabel = /*#__PURE__*/React.forwardRef(function ComboboxLabel(compo
|
|
|
2838
3120
|
const {
|
|
2839
3121
|
render,
|
|
2840
3122
|
className,
|
|
3123
|
+
style,
|
|
2841
3124
|
...elementProps
|
|
2842
3125
|
} = componentProps;
|
|
2843
3126
|
// Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.
|
|
@@ -2925,13 +3208,13 @@ const ComboboxItemIndicator = /*#__PURE__*/React.forwardRef(function ComboboxIte
|
|
|
2925
3208
|
});
|
|
2926
3209
|
});
|
|
2927
3210
|
|
|
2928
|
-
|
|
2929
|
-
* costs unless the element needs to be mounted. */
|
|
3211
|
+
// Split the core implementation to avoid paying the hook costs unless the element needs to mount.
|
|
2930
3212
|
if (process.env.NODE_ENV !== "production") ComboboxItemIndicator.displayName = "ComboboxItemIndicator";
|
|
2931
3213
|
const Inner = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef((componentProps, forwardedRef) => {
|
|
2932
3214
|
const {
|
|
2933
3215
|
render,
|
|
2934
3216
|
className,
|
|
3217
|
+
style,
|
|
2935
3218
|
keepMounted,
|
|
2936
3219
|
...elementProps
|
|
2937
3220
|
} = componentProps;
|
|
@@ -2973,6 +3256,7 @@ const ComboboxChips = /*#__PURE__*/React.forwardRef(function ComboboxChips(compo
|
|
|
2973
3256
|
const {
|
|
2974
3257
|
render,
|
|
2975
3258
|
className,
|
|
3259
|
+
style,
|
|
2976
3260
|
...elementProps
|
|
2977
3261
|
} = componentProps;
|
|
2978
3262
|
const store = useComboboxRootContext();
|
|
@@ -2989,7 +3273,11 @@ const ComboboxChips = /*#__PURE__*/React.forwardRef(function ComboboxChips(compo
|
|
|
2989
3273
|
// arrow keys inside a container unless it has a toolbar role.
|
|
2990
3274
|
props: [hasSelectionChips ? {
|
|
2991
3275
|
role: 'toolbar'
|
|
2992
|
-
} : EMPTY_OBJECT,
|
|
3276
|
+
} : EMPTY_OBJECT, {
|
|
3277
|
+
onMouseDown(event) {
|
|
3278
|
+
handleInputPress(event, store, store.state.disabled, store.state.readOnly);
|
|
3279
|
+
}
|
|
3280
|
+
}, elementProps]
|
|
2993
3281
|
});
|
|
2994
3282
|
const contextValue = React.useMemo(() => ({
|
|
2995
3283
|
highlightedChipIndex,
|
|
@@ -3011,7 +3299,7 @@ if (process.env.NODE_ENV !== "production") ComboboxChipContext.displayName = "Co
|
|
|
3011
3299
|
function useComboboxChipContext() {
|
|
3012
3300
|
const context = React.useContext(ComboboxChipContext);
|
|
3013
3301
|
if (!context) {
|
|
3014
|
-
throw new Error(process.env.NODE_ENV !== "production" ? '
|
|
3302
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ComboboxChipContext is missing. ComboboxChip parts must be placed within <Combobox.Chip>.' : formatErrorMessage(17));
|
|
3015
3303
|
}
|
|
3016
3304
|
return context;
|
|
3017
3305
|
}
|
|
@@ -3020,6 +3308,7 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3020
3308
|
const {
|
|
3021
3309
|
render,
|
|
3022
3310
|
className,
|
|
3311
|
+
style,
|
|
3023
3312
|
...elementProps
|
|
3024
3313
|
} = componentProps;
|
|
3025
3314
|
const store = useComboboxRootContext();
|
|
@@ -3027,6 +3316,7 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3027
3316
|
setHighlightedChipIndex,
|
|
3028
3317
|
chipsRef
|
|
3029
3318
|
} = useComboboxChipsContext();
|
|
3319
|
+
const direction = useDirection();
|
|
3030
3320
|
const disabled = useStore(store, selectors.disabled);
|
|
3031
3321
|
const readOnly = useStore(store, selectors.readOnly);
|
|
3032
3322
|
const selectedValue = useStore(store, selectors.selectedValue);
|
|
@@ -3036,16 +3326,19 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3036
3326
|
} = useCompositeListItem();
|
|
3037
3327
|
function handleKeyDown(event) {
|
|
3038
3328
|
let nextIndex = index;
|
|
3039
|
-
|
|
3329
|
+
const isRtl = direction === 'rtl';
|
|
3330
|
+
const previousChipKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
3331
|
+
const nextChipKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
3332
|
+
if (event.key === previousChipKey) {
|
|
3040
3333
|
event.preventDefault();
|
|
3041
3334
|
if (index > 0) {
|
|
3042
3335
|
nextIndex = index - 1;
|
|
3043
3336
|
} else {
|
|
3044
3337
|
nextIndex = undefined;
|
|
3045
3338
|
}
|
|
3046
|
-
} else if (event.key ===
|
|
3339
|
+
} else if (event.key === nextChipKey) {
|
|
3047
3340
|
event.preventDefault();
|
|
3048
|
-
if (index <
|
|
3341
|
+
if (index < chipsRef.current.length - 1) {
|
|
3049
3342
|
nextIndex = index + 1;
|
|
3050
3343
|
} else {
|
|
3051
3344
|
nextIndex = undefined;
|
|
@@ -3097,16 +3390,6 @@ const ComboboxChip = /*#__PURE__*/React.forwardRef(function ComboboxChip(compone
|
|
|
3097
3390
|
} else {
|
|
3098
3391
|
chipsRef.current[nextIndex]?.focus();
|
|
3099
3392
|
}
|
|
3100
|
-
},
|
|
3101
|
-
onMouseDown(event) {
|
|
3102
|
-
if (readOnly) {
|
|
3103
|
-
return;
|
|
3104
|
-
}
|
|
3105
|
-
event.preventDefault();
|
|
3106
|
-
if (disabled) {
|
|
3107
|
-
return;
|
|
3108
|
-
}
|
|
3109
|
-
store.state.inputRef.current?.focus();
|
|
3110
3393
|
}
|
|
3111
3394
|
}, elementProps]
|
|
3112
3395
|
});
|
|
@@ -3123,6 +3406,8 @@ if (process.env.NODE_ENV !== "production") ComboboxChip.displayName = "ComboboxC
|
|
|
3123
3406
|
/**
|
|
3124
3407
|
* A button to remove a chip.
|
|
3125
3408
|
* Renders a `<button>` element.
|
|
3409
|
+
*
|
|
3410
|
+
* Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)
|
|
3126
3411
|
*/
|
|
3127
3412
|
const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRemove(componentProps, forwardedRef) {
|
|
3128
3413
|
const {
|
|
@@ -3130,6 +3415,7 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
|
|
|
3130
3415
|
className,
|
|
3131
3416
|
disabled: disabledProp = false,
|
|
3132
3417
|
nativeButton = true,
|
|
3418
|
+
style,
|
|
3133
3419
|
...elementProps
|
|
3134
3420
|
} = componentProps;
|
|
3135
3421
|
const store = useComboboxRootContext();
|
|
@@ -3181,6 +3467,9 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
|
|
|
3181
3467
|
state,
|
|
3182
3468
|
props: [{
|
|
3183
3469
|
tabIndex: -1,
|
|
3470
|
+
onMouseDown(event) {
|
|
3471
|
+
event.preventDefault();
|
|
3472
|
+
},
|
|
3184
3473
|
onClick(event) {
|
|
3185
3474
|
if (disabled || readOnly) {
|
|
3186
3475
|
return;
|