@jobber/components 8.21.1 → 8.23.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/Dialog.d.ts +1 -0
- package/Dialog.js +17 -0
- package/dist/BottomSheet-cjs.js +34 -7
- package/dist/BottomSheet-es.js +34 -8
- package/dist/CSPContext-cjs.js +69 -0
- package/dist/CSPContext-es.js +46 -0
- package/dist/Card/index.cjs +3 -0
- package/dist/Card/index.mjs +3 -0
- package/dist/ComboboxChipRemove-cjs.js +54 -211
- package/dist/ComboboxChipRemove-es.js +8 -164
- package/dist/DataDump/index.cjs +3 -0
- package/dist/DataDump/index.mjs +3 -0
- package/dist/Dialog/Dialog.d.ts +3 -0
- package/dist/Dialog/Dialog.types.d.ts +205 -0
- package/dist/Dialog/DialogBottomSheet.d.ts +28 -0
- package/dist/Dialog/DialogModal.d.ts +20 -0
- package/dist/Dialog/constants.d.ts +1 -0
- package/dist/Dialog/dialogComposableShared.d.ts +53 -0
- package/dist/Dialog/dialogReturnFocus.d.ts +43 -0
- package/dist/Dialog/index.cjs +33 -0
- package/dist/Dialog/index.d.ts +4 -0
- package/dist/Dialog/index.mjs +25 -0
- package/dist/DrawerDescription-cjs.js +410 -0
- package/dist/DrawerDescription-es.js +383 -0
- package/dist/DrawerTitle-cjs.js +116 -0
- package/dist/DrawerTitle-es.js +92 -0
- package/dist/InputNumberExperimental-cjs.js +24 -23
- package/dist/InputNumberExperimental-es.js +3 -2
- package/dist/InternalBackdrop-es.js +3 -3
- package/dist/Menu/index.cjs +3 -0
- package/dist/Menu/index.mjs +3 -0
- package/dist/Menu-cjs.js +6 -4
- package/dist/Menu-es.js +6 -4
- package/dist/MenuSubmenuTrigger-cjs.js +53 -103
- package/dist/MenuSubmenuTrigger-es.js +55 -103
- package/dist/NumberFieldInput-cjs.js +9 -9
- package/dist/NumberFieldInput-es.js +3 -3
- package/dist/Page/index.cjs +3 -0
- package/dist/Page/index.mjs +3 -0
- package/dist/ScrollAreaViewport-cjs.js +921 -960
- package/dist/ScrollAreaViewport-es.js +917 -958
- package/dist/SelectGroupLabel-cjs.js +1924 -0
- package/dist/SelectGroupLabel-es.js +1888 -0
- package/dist/SelectPrimitive-cjs.js +112 -0
- package/dist/SelectPrimitive-es.js +110 -0
- package/dist/ToolbarRootContext-cjs.js +35 -0
- package/dist/ToolbarRootContext-es.js +14 -0
- package/dist/dialogReturnFocus-cjs.js +427 -0
- package/dist/dialogReturnFocus-es.js +423 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +326 -0
- package/dist/docs/index.md +1 -0
- package/dist/floating-ui.react-dom-cjs.js +1 -0
- package/dist/floating-ui.react-dom-es.js +2 -2
- package/dist/floating-ui.utils.dom-es.js +1 -1
- package/dist/index.cjs +15 -7
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +12 -7
- package/dist/primitives/BottomSheet/BottomSheet.d.ts +19 -1
- package/dist/primitives/BottomSheet/BottomSheet.types.d.ts +8 -0
- package/dist/primitives/BottomSheet/index.cjs +2 -0
- package/dist/primitives/BottomSheet/index.d.ts +1 -1
- package/dist/primitives/BottomSheet/index.mjs +2 -1
- package/dist/primitives/ComboboxPrimitive/index.cjs +3 -1
- package/dist/primitives/ComboboxPrimitive/index.mjs +3 -1
- package/dist/primitives/InputNumberExperimental/index.cjs +2 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +2 -1
- package/dist/primitives/SelectPrimitive/SelectPrimitive.d.ts +63 -0
- package/dist/primitives/SelectPrimitive/index.cjs +29 -0
- package/dist/primitives/SelectPrimitive/index.d.ts +2 -0
- package/dist/primitives/SelectPrimitive/index.mjs +23 -0
- package/dist/primitives/SelectPrimitive/types.d.ts +16 -0
- package/dist/primitives/index.cjs +9 -1
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.mjs +8 -2
- package/dist/primitives/mergeClassName.d.ts +12 -0
- package/dist/resolveAriaLabelledBy-cjs.js +195 -0
- package/dist/resolveAriaLabelledBy-es.js +162 -0
- package/dist/styles.css +685 -0
- package/dist/unstyledPrimitives/index.cjs +434 -259
- package/dist/unstyledPrimitives/index.d.ts +2 -0
- package/dist/unstyledPrimitives/index.mjs +425 -252
- package/dist/useButton-cjs.js +2 -0
- package/dist/useButton-es.js +3 -2
- package/dist/useCompositeListItem-es.js +4 -4
- package/dist/useLabel-cjs.js +3 -329
- package/dist/useLabel-es.js +4 -299
- package/dist/useLabelableId-cjs.js +332 -0
- package/dist/useLabelableId-es.js +300 -0
- package/dist/useRenderElement-cjs.js +3 -0
- package/dist/useRenderElement-es.js +4 -1
- package/dist/utils/meta/meta.json +11 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1924 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var useButton = require('./useButton-cjs.js');
|
|
5
|
+
var useRenderElement = require('./useRenderElement-cjs.js');
|
|
6
|
+
var InternalBackdrop = require('./InternalBackdrop-cjs.js');
|
|
7
|
+
var useLabelableId = require('./useLabelableId-cjs.js');
|
|
8
|
+
var resolveAriaLabelledBy = require('./resolveAriaLabelledBy-cjs.js');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
var useCompositeListItem = require('./useCompositeListItem-cjs.js');
|
|
11
|
+
var ReactDOM = require('react-dom');
|
|
12
|
+
var index_esm = require('./index.esm-cjs.js');
|
|
13
|
+
var CSPContext = require('./CSPContext-cjs.js');
|
|
14
|
+
var ToolbarRootContext = require('./ToolbarRootContext-cjs.js');
|
|
15
|
+
var clamp = require('./clamp-cjs.js');
|
|
16
|
+
var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
|
|
17
|
+
var floatingUi_reactDom = require('./floating-ui.react-dom-cjs.js');
|
|
18
|
+
|
|
19
|
+
function _interopNamespaceDefault(e) {
|
|
20
|
+
var n = Object.create(null);
|
|
21
|
+
if (e) {
|
|
22
|
+
Object.keys(e).forEach(function (k) {
|
|
23
|
+
if (k !== 'default') {
|
|
24
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
25
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () { return e[k]; }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
n.default = e;
|
|
33
|
+
return Object.freeze(n);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
37
|
+
var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM);
|
|
38
|
+
|
|
39
|
+
const SelectRootContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
40
|
+
if (process.env.NODE_ENV !== "production") SelectRootContext.displayName = "SelectRootContext";
|
|
41
|
+
const SelectFloatingContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
42
|
+
if (process.env.NODE_ENV !== "production") SelectFloatingContext.displayName = "SelectFloatingContext";
|
|
43
|
+
function useSelectRootContext() {
|
|
44
|
+
const context = React__namespace.useContext(SelectRootContext);
|
|
45
|
+
if (context === null) {
|
|
46
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectRootContext is missing. Select parts must be placed within <Select.Root>.' : useRenderElement.formatErrorMessage(60));
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
49
|
+
}
|
|
50
|
+
function useSelectFloatingContext() {
|
|
51
|
+
const context = React__namespace.useContext(SelectFloatingContext);
|
|
52
|
+
if (context === null) {
|
|
53
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectFloatingContext is missing. Select parts must be placed within <Select.Root>.' : useRenderElement.formatErrorMessage(61));
|
|
54
|
+
}
|
|
55
|
+
return context;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selectors = {
|
|
59
|
+
id: InternalBackdrop.createSelector(state => state.id),
|
|
60
|
+
labelId: InternalBackdrop.createSelector(state => state.labelId),
|
|
61
|
+
modal: InternalBackdrop.createSelector(state => state.modal),
|
|
62
|
+
multiple: InternalBackdrop.createSelector(state => state.multiple),
|
|
63
|
+
items: InternalBackdrop.createSelector(state => state.items),
|
|
64
|
+
itemToStringLabel: InternalBackdrop.createSelector(state => state.itemToStringLabel),
|
|
65
|
+
itemToStringValue: InternalBackdrop.createSelector(state => state.itemToStringValue),
|
|
66
|
+
isItemEqualToValue: InternalBackdrop.createSelector(state => state.isItemEqualToValue),
|
|
67
|
+
value: InternalBackdrop.createSelector(state => state.value),
|
|
68
|
+
hasSelectedValue: InternalBackdrop.createSelector(state => {
|
|
69
|
+
const {
|
|
70
|
+
value,
|
|
71
|
+
multiple,
|
|
72
|
+
itemToStringValue
|
|
73
|
+
} = state;
|
|
74
|
+
if (value == null) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (multiple && Array.isArray(value)) {
|
|
78
|
+
return value.length > 0;
|
|
79
|
+
}
|
|
80
|
+
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue) !== '';
|
|
81
|
+
}),
|
|
82
|
+
hasNullItemLabel: InternalBackdrop.createSelector((state, enabled) => {
|
|
83
|
+
return enabled ? resolveAriaLabelledBy.hasNullItemLabel(state.items) : false;
|
|
84
|
+
}),
|
|
85
|
+
open: InternalBackdrop.createSelector(state => state.open),
|
|
86
|
+
mounted: InternalBackdrop.createSelector(state => state.mounted),
|
|
87
|
+
forceMount: InternalBackdrop.createSelector(state => state.forceMount),
|
|
88
|
+
transitionStatus: InternalBackdrop.createSelector(state => state.transitionStatus),
|
|
89
|
+
openMethod: InternalBackdrop.createSelector(state => state.openMethod),
|
|
90
|
+
activeIndex: InternalBackdrop.createSelector(state => state.activeIndex),
|
|
91
|
+
selectedIndex: InternalBackdrop.createSelector(state => state.selectedIndex),
|
|
92
|
+
isActive: InternalBackdrop.createSelector((state, index) => state.activeIndex === index),
|
|
93
|
+
isSelected: InternalBackdrop.createSelector((state, index, itemValue) => {
|
|
94
|
+
const comparer = state.isItemEqualToValue;
|
|
95
|
+
const storeValue = state.value;
|
|
96
|
+
if (state.multiple) {
|
|
97
|
+
return Array.isArray(storeValue) && storeValue.some(selectedItem => resolveAriaLabelledBy.compareItemEquality(itemValue, selectedItem, comparer));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// `selectedIndex` is only updated after the items mount for the first time,
|
|
101
|
+
// the value check avoids a re-render for the initially selected item.
|
|
102
|
+
if (state.selectedIndex === index && state.selectedIndex !== null) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return resolveAriaLabelledBy.compareItemEquality(itemValue, storeValue, comparer);
|
|
106
|
+
}),
|
|
107
|
+
isSelectedByFocus: InternalBackdrop.createSelector((state, index) => {
|
|
108
|
+
return state.selectedIndex === index;
|
|
109
|
+
}),
|
|
110
|
+
popupProps: InternalBackdrop.createSelector(state => state.popupProps),
|
|
111
|
+
triggerProps: InternalBackdrop.createSelector(state => state.triggerProps),
|
|
112
|
+
triggerElement: InternalBackdrop.createSelector(state => state.triggerElement),
|
|
113
|
+
positionerElement: InternalBackdrop.createSelector(state => state.positionerElement),
|
|
114
|
+
listElement: InternalBackdrop.createSelector(state => state.listElement),
|
|
115
|
+
scrollUpArrowVisible: InternalBackdrop.createSelector(state => state.scrollUpArrowVisible),
|
|
116
|
+
scrollDownArrowVisible: InternalBackdrop.createSelector(state => state.scrollDownArrowVisible),
|
|
117
|
+
hasScrollArrows: InternalBackdrop.createSelector(state => state.hasScrollArrows)
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function SelectRoot(props) {
|
|
121
|
+
const {
|
|
122
|
+
id,
|
|
123
|
+
value: valueProp,
|
|
124
|
+
defaultValue = null,
|
|
125
|
+
onValueChange,
|
|
126
|
+
open: openProp,
|
|
127
|
+
defaultOpen = false,
|
|
128
|
+
onOpenChange,
|
|
129
|
+
name: nameProp,
|
|
130
|
+
autoComplete,
|
|
131
|
+
disabled: disabledProp = false,
|
|
132
|
+
readOnly = false,
|
|
133
|
+
required = false,
|
|
134
|
+
modal = true,
|
|
135
|
+
actionsRef,
|
|
136
|
+
inputRef,
|
|
137
|
+
onOpenChangeComplete,
|
|
138
|
+
items,
|
|
139
|
+
multiple = false,
|
|
140
|
+
itemToStringLabel,
|
|
141
|
+
itemToStringValue,
|
|
142
|
+
isItemEqualToValue = resolveAriaLabelledBy.defaultItemEquality,
|
|
143
|
+
highlightItemOnHover = true,
|
|
144
|
+
children
|
|
145
|
+
} = props;
|
|
146
|
+
const {
|
|
147
|
+
clearErrors
|
|
148
|
+
} = useLabelableId.useFormContext();
|
|
149
|
+
const {
|
|
150
|
+
setDirty,
|
|
151
|
+
setTouched,
|
|
152
|
+
setFocused,
|
|
153
|
+
shouldValidateOnChange,
|
|
154
|
+
validityData,
|
|
155
|
+
setFilled,
|
|
156
|
+
name: fieldName,
|
|
157
|
+
disabled: fieldDisabled,
|
|
158
|
+
validation,
|
|
159
|
+
validationMode
|
|
160
|
+
} = useLabelableId.useFieldRootContext();
|
|
161
|
+
const generatedId = useLabelableId.useLabelableId({
|
|
162
|
+
id
|
|
163
|
+
});
|
|
164
|
+
const disabled = fieldDisabled || disabledProp;
|
|
165
|
+
const name = fieldName ?? nameProp;
|
|
166
|
+
const [value, setValueUnwrapped] = useButton.useControlled({
|
|
167
|
+
controlled: valueProp,
|
|
168
|
+
default: multiple ? defaultValue ?? useRenderElement.EMPTY_ARRAY : defaultValue,
|
|
169
|
+
name: 'Select',
|
|
170
|
+
state: 'value'
|
|
171
|
+
});
|
|
172
|
+
const [open, setOpenUnwrapped] = useButton.useControlled({
|
|
173
|
+
controlled: openProp,
|
|
174
|
+
default: defaultOpen,
|
|
175
|
+
name: 'Select',
|
|
176
|
+
state: 'open'
|
|
177
|
+
});
|
|
178
|
+
const listRef = React__namespace.useRef([]);
|
|
179
|
+
const labelsRef = React__namespace.useRef([]);
|
|
180
|
+
const popupRef = React__namespace.useRef(null);
|
|
181
|
+
const scrollHandlerRef = React__namespace.useRef(null);
|
|
182
|
+
const scrollArrowsMountedCountRef = React__namespace.useRef(0);
|
|
183
|
+
const valueRef = React__namespace.useRef(null);
|
|
184
|
+
const valuesRef = React__namespace.useRef([]);
|
|
185
|
+
const typingRef = React__namespace.useRef(false);
|
|
186
|
+
const keyboardActiveRef = React__namespace.useRef(false);
|
|
187
|
+
const selectedItemTextRef = React__namespace.useRef(null);
|
|
188
|
+
const selectionRef = React__namespace.useRef({
|
|
189
|
+
allowSelectedMouseUp: false,
|
|
190
|
+
allowUnselectedMouseUp: false
|
|
191
|
+
});
|
|
192
|
+
const alignItemWithTriggerActiveRef = React__namespace.useRef(false);
|
|
193
|
+
const {
|
|
194
|
+
mounted,
|
|
195
|
+
setMounted,
|
|
196
|
+
transitionStatus
|
|
197
|
+
} = useButton.useTransitionStatus(open);
|
|
198
|
+
const {
|
|
199
|
+
openMethod,
|
|
200
|
+
triggerProps: interactionTypeProps
|
|
201
|
+
} = InternalBackdrop.useOpenInteractionType(open);
|
|
202
|
+
const store = useRenderElement.useRefWithInit(() => new InternalBackdrop.Store({
|
|
203
|
+
id: generatedId,
|
|
204
|
+
labelId: undefined,
|
|
205
|
+
modal,
|
|
206
|
+
multiple,
|
|
207
|
+
itemToStringLabel,
|
|
208
|
+
itemToStringValue,
|
|
209
|
+
isItemEqualToValue,
|
|
210
|
+
value,
|
|
211
|
+
open,
|
|
212
|
+
mounted,
|
|
213
|
+
transitionStatus,
|
|
214
|
+
items,
|
|
215
|
+
forceMount: false,
|
|
216
|
+
openMethod: null,
|
|
217
|
+
activeIndex: null,
|
|
218
|
+
selectedIndex: null,
|
|
219
|
+
popupProps: {},
|
|
220
|
+
triggerProps: {},
|
|
221
|
+
triggerElement: null,
|
|
222
|
+
positionerElement: null,
|
|
223
|
+
listElement: null,
|
|
224
|
+
scrollUpArrowVisible: false,
|
|
225
|
+
scrollDownArrowVisible: false,
|
|
226
|
+
hasScrollArrows: false
|
|
227
|
+
})).current;
|
|
228
|
+
const activeIndex = InternalBackdrop.useStore(store, selectors.activeIndex);
|
|
229
|
+
const selectedIndex = InternalBackdrop.useStore(store, selectors.selectedIndex);
|
|
230
|
+
const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
|
|
231
|
+
const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
|
|
232
|
+
const serializedValue = React__namespace.useMemo(() => {
|
|
233
|
+
if (multiple && Array.isArray(value) && value.length === 0) {
|
|
234
|
+
return '';
|
|
235
|
+
}
|
|
236
|
+
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue);
|
|
237
|
+
}, [multiple, value, itemToStringValue]);
|
|
238
|
+
const fieldStringValue = React__namespace.useMemo(() => {
|
|
239
|
+
if (multiple && Array.isArray(value)) {
|
|
240
|
+
return value.map(currentValue => resolveAriaLabelledBy.stringifyAsValue(currentValue, itemToStringValue));
|
|
241
|
+
}
|
|
242
|
+
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue);
|
|
243
|
+
}, [multiple, value, itemToStringValue]);
|
|
244
|
+
const controlRef = useButton.useValueAsRef(store.state.triggerElement);
|
|
245
|
+
useLabelableId.useField({
|
|
246
|
+
id: generatedId,
|
|
247
|
+
commit: validation.commit,
|
|
248
|
+
value,
|
|
249
|
+
controlRef,
|
|
250
|
+
name,
|
|
251
|
+
getValue: () => fieldStringValue
|
|
252
|
+
});
|
|
253
|
+
const initialValueRef = React__namespace.useRef(value);
|
|
254
|
+
useButton.useIsoLayoutEffect(() => {
|
|
255
|
+
// Ensure the values and labels are registered for programmatic value changes.
|
|
256
|
+
if (value !== initialValueRef.current) {
|
|
257
|
+
store.set('forceMount', true);
|
|
258
|
+
}
|
|
259
|
+
}, [store, value]);
|
|
260
|
+
useButton.useIsoLayoutEffect(() => {
|
|
261
|
+
setFilled(multiple ? Array.isArray(value) && value.length > 0 : value != null);
|
|
262
|
+
}, [multiple, value, setFilled]);
|
|
263
|
+
useButton.useIsoLayoutEffect(function syncSelectedIndex() {
|
|
264
|
+
if (open) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const registry = valuesRef.current;
|
|
268
|
+
if (multiple) {
|
|
269
|
+
const currentValue = Array.isArray(value) ? value : [];
|
|
270
|
+
if (currentValue.length === 0) {
|
|
271
|
+
store.set('selectedIndex', null);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const lastValue = currentValue[currentValue.length - 1];
|
|
275
|
+
const lastIndex = resolveAriaLabelledBy.findItemIndex(registry, lastValue, isItemEqualToValue);
|
|
276
|
+
store.set('selectedIndex', lastIndex === -1 ? null : lastIndex);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const index = resolveAriaLabelledBy.findItemIndex(registry, value, isItemEqualToValue);
|
|
280
|
+
store.set('selectedIndex', index === -1 ? null : index);
|
|
281
|
+
}, [multiple, open, value, valuesRef, isItemEqualToValue, store]);
|
|
282
|
+
useButton.useValueChanged(value, () => {
|
|
283
|
+
clearErrors(name);
|
|
284
|
+
setDirty(value !== validityData.initialValue);
|
|
285
|
+
if (shouldValidateOnChange()) {
|
|
286
|
+
validation.commit(value);
|
|
287
|
+
} else {
|
|
288
|
+
validation.commit(value, true);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
const setOpen = useButton.useStableCallback((nextOpen, eventDetails) => {
|
|
292
|
+
onOpenChange?.(nextOpen, eventDetails);
|
|
293
|
+
if (eventDetails.isCanceled) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
setOpenUnwrapped(nextOpen);
|
|
297
|
+
if (!nextOpen && (eventDetails.reason === useButton.focusOut || eventDetails.reason === useButton.outsidePress)) {
|
|
298
|
+
setTouched(true);
|
|
299
|
+
setFocused(false);
|
|
300
|
+
if (validationMode === 'onBlur') {
|
|
301
|
+
validation.commit(value);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// The active index will sync to the last selected index on the next open.
|
|
306
|
+
// Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted
|
|
307
|
+
// option upon close when tabbing out due to `keepMounted=true`:
|
|
308
|
+
// https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199
|
|
309
|
+
// This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed
|
|
310
|
+
// when tabbing outside.
|
|
311
|
+
if (!nextOpen && store.state.activeIndex !== null) {
|
|
312
|
+
const activeOption = listRef.current[store.state.activeIndex];
|
|
313
|
+
// Wait for Floating UI's focus effect to have fired
|
|
314
|
+
queueMicrotask(() => {
|
|
315
|
+
activeOption?.setAttribute('tabindex', '-1');
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
const handleUnmount = useButton.useStableCallback(() => {
|
|
320
|
+
setMounted(false);
|
|
321
|
+
store.set('activeIndex', null);
|
|
322
|
+
onOpenChangeComplete?.(false);
|
|
323
|
+
});
|
|
324
|
+
useButton.useOpenChangeComplete({
|
|
325
|
+
enabled: !actionsRef,
|
|
326
|
+
open,
|
|
327
|
+
ref: popupRef,
|
|
328
|
+
onComplete() {
|
|
329
|
+
if (!open) {
|
|
330
|
+
handleUnmount();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
React__namespace.useImperativeHandle(actionsRef, () => ({
|
|
335
|
+
unmount: handleUnmount
|
|
336
|
+
}), [handleUnmount]);
|
|
337
|
+
const setValue = useButton.useStableCallback((nextValue, eventDetails) => {
|
|
338
|
+
onValueChange?.(nextValue, eventDetails);
|
|
339
|
+
if (eventDetails.isCanceled) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
setValueUnwrapped(nextValue);
|
|
343
|
+
});
|
|
344
|
+
const handleScrollArrowVisibility = useButton.useStableCallback(() => {
|
|
345
|
+
const scroller = store.state.listElement || popupRef.current;
|
|
346
|
+
if (!scroller) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const viewportTop = scroller.scrollTop;
|
|
350
|
+
const viewportBottom = scroller.scrollTop + scroller.clientHeight;
|
|
351
|
+
const shouldShowUp = viewportTop > 1;
|
|
352
|
+
const shouldShowDown = viewportBottom < scroller.scrollHeight - 1;
|
|
353
|
+
if (store.state.scrollUpArrowVisible !== shouldShowUp) {
|
|
354
|
+
store.set('scrollUpArrowVisible', shouldShowUp);
|
|
355
|
+
}
|
|
356
|
+
if (store.state.scrollDownArrowVisible !== shouldShowDown) {
|
|
357
|
+
store.set('scrollDownArrowVisible', shouldShowDown);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
const floatingContext = useCompositeListItem.useFloatingRootContext({
|
|
361
|
+
open,
|
|
362
|
+
onOpenChange: setOpen,
|
|
363
|
+
elements: {
|
|
364
|
+
reference: triggerElement,
|
|
365
|
+
floating: positionerElement
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
const click = InternalBackdrop.useClick(floatingContext, {
|
|
369
|
+
enabled: !readOnly && !disabled,
|
|
370
|
+
event: 'mousedown'
|
|
371
|
+
});
|
|
372
|
+
const dismiss = InternalBackdrop.useDismiss(floatingContext, {
|
|
373
|
+
bubbles: false
|
|
374
|
+
});
|
|
375
|
+
const listNavigation = useCompositeListItem.useListNavigation(floatingContext, {
|
|
376
|
+
enabled: !readOnly && !disabled,
|
|
377
|
+
listRef,
|
|
378
|
+
activeIndex,
|
|
379
|
+
selectedIndex,
|
|
380
|
+
disabledIndices: useRenderElement.EMPTY_ARRAY,
|
|
381
|
+
onNavigate(nextActiveIndex) {
|
|
382
|
+
// Retain the highlight while transitioning out.
|
|
383
|
+
if (nextActiveIndex === null && !open) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
store.set('activeIndex', nextActiveIndex);
|
|
387
|
+
},
|
|
388
|
+
// Implement our own listeners since `onPointerLeave` on each option fires while scrolling with
|
|
389
|
+
// the `alignItemWithTrigger=true`, causing a performance issue on Chrome.
|
|
390
|
+
focusItemOnHover: false
|
|
391
|
+
});
|
|
392
|
+
const typeahead = useCompositeListItem.useTypeahead(floatingContext, {
|
|
393
|
+
enabled: !readOnly && !disabled && (open || !multiple),
|
|
394
|
+
listRef: labelsRef,
|
|
395
|
+
activeIndex,
|
|
396
|
+
selectedIndex,
|
|
397
|
+
onMatch(index) {
|
|
398
|
+
if (open) {
|
|
399
|
+
store.set('activeIndex', index);
|
|
400
|
+
} else {
|
|
401
|
+
setValue(valuesRef.current[index], useButton.createChangeEventDetails('none'));
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
onTypingChange(typing) {
|
|
405
|
+
typingRef.current = typing;
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
const {
|
|
409
|
+
getReferenceProps,
|
|
410
|
+
getFloatingProps,
|
|
411
|
+
getItemProps
|
|
412
|
+
} = InternalBackdrop.useInteractions([click, dismiss, listNavigation, typeahead]);
|
|
413
|
+
const mergedTriggerProps = React__namespace.useMemo(() => {
|
|
414
|
+
return useRenderElement.mergeProps(getReferenceProps(), interactionTypeProps, generatedId ? {
|
|
415
|
+
id: generatedId
|
|
416
|
+
} : useRenderElement.EMPTY_OBJECT);
|
|
417
|
+
}, [getReferenceProps, interactionTypeProps, generatedId]);
|
|
418
|
+
InternalBackdrop.useOnFirstRender(() => {
|
|
419
|
+
store.update({
|
|
420
|
+
popupProps: getFloatingProps(),
|
|
421
|
+
triggerProps: mergedTriggerProps
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
useButton.useIsoLayoutEffect(() => {
|
|
425
|
+
store.update({
|
|
426
|
+
id: generatedId,
|
|
427
|
+
modal,
|
|
428
|
+
multiple,
|
|
429
|
+
value,
|
|
430
|
+
open,
|
|
431
|
+
mounted,
|
|
432
|
+
transitionStatus,
|
|
433
|
+
popupProps: getFloatingProps(),
|
|
434
|
+
triggerProps: mergedTriggerProps,
|
|
435
|
+
items,
|
|
436
|
+
itemToStringLabel,
|
|
437
|
+
itemToStringValue,
|
|
438
|
+
isItemEqualToValue,
|
|
439
|
+
openMethod
|
|
440
|
+
});
|
|
441
|
+
}, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus, getFloatingProps, mergedTriggerProps, items, itemToStringLabel, itemToStringValue, isItemEqualToValue, openMethod]);
|
|
442
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
443
|
+
store,
|
|
444
|
+
name,
|
|
445
|
+
required,
|
|
446
|
+
disabled,
|
|
447
|
+
readOnly,
|
|
448
|
+
multiple,
|
|
449
|
+
itemToStringLabel,
|
|
450
|
+
itemToStringValue,
|
|
451
|
+
highlightItemOnHover,
|
|
452
|
+
setValue,
|
|
453
|
+
setOpen,
|
|
454
|
+
listRef,
|
|
455
|
+
popupRef,
|
|
456
|
+
scrollHandlerRef,
|
|
457
|
+
handleScrollArrowVisibility,
|
|
458
|
+
scrollArrowsMountedCountRef,
|
|
459
|
+
getItemProps,
|
|
460
|
+
events: floatingContext.context.events,
|
|
461
|
+
valueRef,
|
|
462
|
+
valuesRef,
|
|
463
|
+
labelsRef,
|
|
464
|
+
typingRef,
|
|
465
|
+
selectionRef,
|
|
466
|
+
selectedItemTextRef,
|
|
467
|
+
validation,
|
|
468
|
+
onOpenChangeComplete,
|
|
469
|
+
keyboardActiveRef,
|
|
470
|
+
alignItemWithTriggerActiveRef,
|
|
471
|
+
initialValueRef
|
|
472
|
+
}), [store, name, required, disabled, readOnly, multiple, itemToStringLabel, itemToStringValue, highlightItemOnHover, setValue, setOpen, getItemProps, floatingContext.context.events, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
|
|
473
|
+
const ref = useRenderElement.useMergedRefs(inputRef, validation.inputRef);
|
|
474
|
+
const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;
|
|
475
|
+
const hiddenInputName = multiple ? undefined : name;
|
|
476
|
+
const hiddenInputs = React__namespace.useMemo(() => {
|
|
477
|
+
if (!multiple || !Array.isArray(value) || !name) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
return value.map(v => {
|
|
481
|
+
const currentSerializedValue = resolveAriaLabelledBy.stringifyAsValue(v, itemToStringValue);
|
|
482
|
+
return /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
483
|
+
type: "hidden",
|
|
484
|
+
name: name,
|
|
485
|
+
value: currentSerializedValue
|
|
486
|
+
}, currentSerializedValue);
|
|
487
|
+
});
|
|
488
|
+
}, [multiple, value, name, itemToStringValue]);
|
|
489
|
+
return /*#__PURE__*/jsxRuntime.jsx(SelectRootContext.Provider, {
|
|
490
|
+
value: contextValue,
|
|
491
|
+
children: /*#__PURE__*/jsxRuntime.jsxs(SelectFloatingContext.Provider, {
|
|
492
|
+
value: floatingContext,
|
|
493
|
+
children: [children, /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
494
|
+
...validation.getInputValidationProps({
|
|
495
|
+
onFocus() {
|
|
496
|
+
// Move focus to the trigger element when the hidden input is focused.
|
|
497
|
+
store.state.triggerElement?.focus({
|
|
498
|
+
// Supported in Chrome from 144 (January 2026)
|
|
499
|
+
// @ts-expect-error - focusVisible is not yet in the lib.dom.d.ts
|
|
500
|
+
focusVisible: true
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
// Handle browser autofill.
|
|
504
|
+
onChange(event) {
|
|
505
|
+
// Workaround for https://github.com/facebook/react/issues/9023
|
|
506
|
+
if (event.nativeEvent.defaultPrevented) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const nextValue = event.target.value;
|
|
510
|
+
const details = useButton.createChangeEventDetails(useButton.none, event.nativeEvent);
|
|
511
|
+
function handleChange() {
|
|
512
|
+
if (multiple) {
|
|
513
|
+
// Browser autofill only writes a single scalar value.
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Handle single selection: match against registered values using serialization
|
|
518
|
+
const matchingValue = valuesRef.current.find(v => {
|
|
519
|
+
const candidate = resolveAriaLabelledBy.stringifyAsValue(v, itemToStringValue);
|
|
520
|
+
if (candidate.toLowerCase() === nextValue.toLowerCase()) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
});
|
|
525
|
+
if (matchingValue != null) {
|
|
526
|
+
setDirty(matchingValue !== validityData.initialValue);
|
|
527
|
+
setValue(matchingValue, details);
|
|
528
|
+
if (shouldValidateOnChange()) {
|
|
529
|
+
validation.commit(matchingValue);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
store.set('forceMount', true);
|
|
534
|
+
queueMicrotask(handleChange);
|
|
535
|
+
}
|
|
536
|
+
}),
|
|
537
|
+
id: generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined,
|
|
538
|
+
name: hiddenInputName,
|
|
539
|
+
autoComplete: autoComplete,
|
|
540
|
+
value: serializedValue,
|
|
541
|
+
disabled: disabled,
|
|
542
|
+
required: required && !hasMultipleSelection,
|
|
543
|
+
readOnly: readOnly,
|
|
544
|
+
ref: ref,
|
|
545
|
+
style: name ? useButton.visuallyHiddenInput : useButton.visuallyHidden,
|
|
546
|
+
tabIndex: -1,
|
|
547
|
+
"aria-hidden": true
|
|
548
|
+
}), hiddenInputs]
|
|
549
|
+
})
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const BOUNDARY_OFFSET = 2;
|
|
554
|
+
const SELECTED_DELAY = 400;
|
|
555
|
+
const UNSELECTED_DELAY = 200;
|
|
556
|
+
const stateAttributesMapping$2 = {
|
|
557
|
+
...InternalBackdrop.pressableTriggerOpenStateMapping,
|
|
558
|
+
...useLabelableId.fieldValidityMapping,
|
|
559
|
+
value: () => null
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* A button that opens the select popup.
|
|
564
|
+
* Renders a `<button>` element.
|
|
565
|
+
*
|
|
566
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
567
|
+
*/
|
|
568
|
+
const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTrigger(componentProps, forwardedRef) {
|
|
569
|
+
const {
|
|
570
|
+
render,
|
|
571
|
+
className,
|
|
572
|
+
id: idProp,
|
|
573
|
+
disabled: disabledProp = false,
|
|
574
|
+
nativeButton = true,
|
|
575
|
+
...elementProps
|
|
576
|
+
} = componentProps;
|
|
577
|
+
const {
|
|
578
|
+
setTouched,
|
|
579
|
+
setFocused,
|
|
580
|
+
validationMode,
|
|
581
|
+
state: fieldState,
|
|
582
|
+
disabled: fieldDisabled
|
|
583
|
+
} = useLabelableId.useFieldRootContext();
|
|
584
|
+
const {
|
|
585
|
+
labelId: fieldLabelId
|
|
586
|
+
} = useLabelableId.useLabelableContext();
|
|
587
|
+
const {
|
|
588
|
+
store,
|
|
589
|
+
setOpen,
|
|
590
|
+
selectionRef,
|
|
591
|
+
validation,
|
|
592
|
+
readOnly,
|
|
593
|
+
required,
|
|
594
|
+
alignItemWithTriggerActiveRef,
|
|
595
|
+
disabled: selectDisabled,
|
|
596
|
+
keyboardActiveRef
|
|
597
|
+
} = useSelectRootContext();
|
|
598
|
+
const disabled = fieldDisabled || selectDisabled || disabledProp;
|
|
599
|
+
const open = InternalBackdrop.useStore(store, selectors.open);
|
|
600
|
+
const value = InternalBackdrop.useStore(store, selectors.value);
|
|
601
|
+
const triggerProps = InternalBackdrop.useStore(store, selectors.triggerProps);
|
|
602
|
+
const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
|
|
603
|
+
const listElement = InternalBackdrop.useStore(store, selectors.listElement);
|
|
604
|
+
const rootId = InternalBackdrop.useStore(store, selectors.id);
|
|
605
|
+
const selectLabelId = InternalBackdrop.useStore(store, selectors.labelId);
|
|
606
|
+
const hasSelectedValue = InternalBackdrop.useStore(store, selectors.hasSelectedValue);
|
|
607
|
+
const shouldCheckNullItemLabel = !hasSelectedValue && open;
|
|
608
|
+
const hasNullItemLabel = InternalBackdrop.useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
|
|
609
|
+
const id = idProp ?? rootId;
|
|
610
|
+
const ariaLabelledBy = resolveAriaLabelledBy.resolveAriaLabelledBy(fieldLabelId, selectLabelId);
|
|
611
|
+
useLabelableId.useLabelableId({
|
|
612
|
+
id
|
|
613
|
+
});
|
|
614
|
+
const positionerRef = useButton.useValueAsRef(positionerElement);
|
|
615
|
+
const triggerRef = React__namespace.useRef(null);
|
|
616
|
+
const {
|
|
617
|
+
getButtonProps,
|
|
618
|
+
buttonRef
|
|
619
|
+
} = useButton.useButton({
|
|
620
|
+
disabled,
|
|
621
|
+
native: nativeButton
|
|
622
|
+
});
|
|
623
|
+
const setTriggerElement = useButton.useStableCallback(element => {
|
|
624
|
+
store.set('triggerElement', element);
|
|
625
|
+
});
|
|
626
|
+
const mergedRef = useRenderElement.useMergedRefs(forwardedRef, triggerRef, buttonRef, setTriggerElement);
|
|
627
|
+
const timeoutFocus = useButton.useTimeout();
|
|
628
|
+
const timeoutMouseDown = useButton.useTimeout();
|
|
629
|
+
const selectedDelayTimeout = useButton.useTimeout();
|
|
630
|
+
const unselectedDelayTimeout = useButton.useTimeout();
|
|
631
|
+
React__namespace.useEffect(() => {
|
|
632
|
+
if (open) {
|
|
633
|
+
const hasSelectedItemInList = hasSelectedValue || hasNullItemLabel;
|
|
634
|
+
const shouldDelayUnselectedMouseUpLonger = !hasSelectedItemInList;
|
|
635
|
+
|
|
636
|
+
// When there is no selected item in the list (placeholder-only selects), a mousedown
|
|
637
|
+
// on the trigger followed by a quick mouseup over the first option can accidentally select
|
|
638
|
+
// within 200ms. Delay unselected mouseup to match the safer 400ms window.
|
|
639
|
+
if (shouldDelayUnselectedMouseUpLonger) {
|
|
640
|
+
selectedDelayTimeout.start(SELECTED_DELAY, () => {
|
|
641
|
+
selectionRef.current.allowUnselectedMouseUp = true;
|
|
642
|
+
selectionRef.current.allowSelectedMouseUp = true;
|
|
643
|
+
});
|
|
644
|
+
} else {
|
|
645
|
+
// mousedown -> move to unselected item -> mouseup should not select within 200ms.
|
|
646
|
+
unselectedDelayTimeout.start(UNSELECTED_DELAY, () => {
|
|
647
|
+
selectionRef.current.allowUnselectedMouseUp = true;
|
|
648
|
+
|
|
649
|
+
// mousedown -> mouseup on selected item should not select within 400ms.
|
|
650
|
+
selectedDelayTimeout.start(UNSELECTED_DELAY, () => {
|
|
651
|
+
selectionRef.current.allowSelectedMouseUp = true;
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
return () => {
|
|
656
|
+
selectedDelayTimeout.clear();
|
|
657
|
+
unselectedDelayTimeout.clear();
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
selectionRef.current = {
|
|
661
|
+
allowSelectedMouseUp: false,
|
|
662
|
+
allowUnselectedMouseUp: false
|
|
663
|
+
};
|
|
664
|
+
timeoutMouseDown.clear();
|
|
665
|
+
return undefined;
|
|
666
|
+
}, [open, hasSelectedValue, hasNullItemLabel, selectionRef, timeoutMouseDown, selectedDelayTimeout, unselectedDelayTimeout]);
|
|
667
|
+
const ariaControlsId = React__namespace.useMemo(() => {
|
|
668
|
+
return listElement?.id ?? useButton.getFloatingFocusElement(positionerElement)?.id;
|
|
669
|
+
}, [listElement, positionerElement]);
|
|
670
|
+
const props = useRenderElement.mergeProps(triggerProps, {
|
|
671
|
+
id,
|
|
672
|
+
role: 'combobox',
|
|
673
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
674
|
+
'aria-haspopup': 'listbox',
|
|
675
|
+
'aria-controls': open ? ariaControlsId : undefined,
|
|
676
|
+
'aria-labelledby': ariaLabelledBy,
|
|
677
|
+
'aria-readonly': readOnly || undefined,
|
|
678
|
+
'aria-required': required || undefined,
|
|
679
|
+
tabIndex: disabled ? -1 : 0,
|
|
680
|
+
ref: mergedRef,
|
|
681
|
+
onFocus(event) {
|
|
682
|
+
setFocused(true);
|
|
683
|
+
|
|
684
|
+
// The popup element shouldn't obscure the focused trigger.
|
|
685
|
+
if (open && alignItemWithTriggerActiveRef.current) {
|
|
686
|
+
setOpen(false, useButton.createChangeEventDetails(useButton.none, event.nativeEvent));
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Saves a re-render on initial click: `forceMount === true` mounts
|
|
690
|
+
// the items before `open === true`. We could sync those cycles better
|
|
691
|
+
// without a timeout, but this is enough for now.
|
|
692
|
+
//
|
|
693
|
+
// XXX: might be causing `act()` warnings.
|
|
694
|
+
timeoutFocus.start(0, () => {
|
|
695
|
+
store.set('forceMount', true);
|
|
696
|
+
});
|
|
697
|
+
},
|
|
698
|
+
onBlur(event) {
|
|
699
|
+
// If focus is moving into the popup, don't count it as a blur.
|
|
700
|
+
if (useButton.contains(positionerElement, event.relatedTarget)) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
setTouched(true);
|
|
704
|
+
setFocused(false);
|
|
705
|
+
if (validationMode === 'onBlur') {
|
|
706
|
+
validation.commit(value);
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
onPointerMove() {
|
|
710
|
+
keyboardActiveRef.current = false;
|
|
711
|
+
},
|
|
712
|
+
onKeyDown() {
|
|
713
|
+
keyboardActiveRef.current = true;
|
|
714
|
+
},
|
|
715
|
+
onMouseDown(event) {
|
|
716
|
+
if (open) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const doc = useButton.ownerDocument(event.currentTarget);
|
|
720
|
+
function handleMouseUp(mouseEvent) {
|
|
721
|
+
if (!triggerRef.current) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const mouseUpTarget = mouseEvent.target;
|
|
725
|
+
|
|
726
|
+
// Early return if clicked on trigger element or its children
|
|
727
|
+
if (useButton.contains(triggerRef.current, mouseUpTarget) || useButton.contains(positionerRef.current, mouseUpTarget) || mouseUpTarget === triggerRef.current) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const bounds = useCompositeListItem.getPseudoElementBounds(triggerRef.current);
|
|
731
|
+
if (mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET && mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
setOpen(false, useButton.createChangeEventDetails(useButton.cancelOpen, mouseEvent));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Firefox can fire this upon mousedown
|
|
738
|
+
timeoutMouseDown.start(0, () => {
|
|
739
|
+
doc.addEventListener('mouseup', handleMouseUp, {
|
|
740
|
+
once: true
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}, validation.getValidationProps, elementProps, getButtonProps);
|
|
745
|
+
|
|
746
|
+
// ensure nested useButton does not overwrite the combobox role:
|
|
747
|
+
// <Toolbar.Button render={<Select.Trigger />} />
|
|
748
|
+
props.role = 'combobox';
|
|
749
|
+
const state = {
|
|
750
|
+
...fieldState,
|
|
751
|
+
open,
|
|
752
|
+
disabled,
|
|
753
|
+
value,
|
|
754
|
+
readOnly,
|
|
755
|
+
placeholder: !hasSelectedValue
|
|
756
|
+
};
|
|
757
|
+
return useRenderElement.useRenderElement('button', componentProps, {
|
|
758
|
+
ref: [forwardedRef, triggerRef],
|
|
759
|
+
state,
|
|
760
|
+
stateAttributesMapping: stateAttributesMapping$2,
|
|
761
|
+
props
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
if (process.env.NODE_ENV !== "production") SelectTrigger.displayName = "SelectTrigger";
|
|
765
|
+
|
|
766
|
+
const stateAttributesMapping$1 = {
|
|
767
|
+
value: () => null
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* A text label of the currently selected item.
|
|
772
|
+
* Renders a `<span>` element.
|
|
773
|
+
*
|
|
774
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
775
|
+
*/
|
|
776
|
+
const SelectValue = /*#__PURE__*/React__namespace.forwardRef(function SelectValue(componentProps, forwardedRef) {
|
|
777
|
+
const {
|
|
778
|
+
className,
|
|
779
|
+
render,
|
|
780
|
+
children: childrenProp,
|
|
781
|
+
placeholder,
|
|
782
|
+
...elementProps
|
|
783
|
+
} = componentProps;
|
|
784
|
+
const {
|
|
785
|
+
store,
|
|
786
|
+
valueRef
|
|
787
|
+
} = useSelectRootContext();
|
|
788
|
+
const value = InternalBackdrop.useStore(store, selectors.value);
|
|
789
|
+
const items = InternalBackdrop.useStore(store, selectors.items);
|
|
790
|
+
const itemToStringLabel = InternalBackdrop.useStore(store, selectors.itemToStringLabel);
|
|
791
|
+
const hasSelectedValue = InternalBackdrop.useStore(store, selectors.hasSelectedValue);
|
|
792
|
+
const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;
|
|
793
|
+
const hasNullLabel = InternalBackdrop.useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
|
|
794
|
+
const state = {
|
|
795
|
+
value,
|
|
796
|
+
placeholder: !hasSelectedValue
|
|
797
|
+
};
|
|
798
|
+
let children = null;
|
|
799
|
+
if (typeof childrenProp === 'function') {
|
|
800
|
+
children = childrenProp(value);
|
|
801
|
+
} else if (childrenProp != null) {
|
|
802
|
+
children = childrenProp;
|
|
803
|
+
} else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {
|
|
804
|
+
children = placeholder;
|
|
805
|
+
} else if (Array.isArray(value)) {
|
|
806
|
+
children = resolveAriaLabelledBy.resolveMultipleLabels(value, items, itemToStringLabel);
|
|
807
|
+
} else {
|
|
808
|
+
children = resolveAriaLabelledBy.resolveSelectedLabel(value, items, itemToStringLabel);
|
|
809
|
+
}
|
|
810
|
+
const element = useRenderElement.useRenderElement('span', componentProps, {
|
|
811
|
+
state,
|
|
812
|
+
ref: [forwardedRef, valueRef],
|
|
813
|
+
props: [{
|
|
814
|
+
children
|
|
815
|
+
}, elementProps],
|
|
816
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
817
|
+
});
|
|
818
|
+
return element;
|
|
819
|
+
});
|
|
820
|
+
if (process.env.NODE_ENV !== "production") SelectValue.displayName = "SelectValue";
|
|
821
|
+
|
|
822
|
+
const SelectPortalContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
823
|
+
if (process.env.NODE_ENV !== "production") SelectPortalContext.displayName = "SelectPortalContext";
|
|
824
|
+
|
|
825
|
+
const SelectPortal = /*#__PURE__*/React__namespace.forwardRef(function SelectPortal(portalProps, forwardedRef) {
|
|
826
|
+
const {
|
|
827
|
+
store
|
|
828
|
+
} = useSelectRootContext();
|
|
829
|
+
const mounted = InternalBackdrop.useStore(store, selectors.mounted);
|
|
830
|
+
const forceMount = InternalBackdrop.useStore(store, selectors.forceMount);
|
|
831
|
+
const shouldRender = mounted || forceMount;
|
|
832
|
+
if (!shouldRender) {
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
return /*#__PURE__*/jsxRuntime.jsx(SelectPortalContext.Provider, {
|
|
836
|
+
value: true,
|
|
837
|
+
children: /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingPortal, {
|
|
838
|
+
ref: forwardedRef,
|
|
839
|
+
...portalProps
|
|
840
|
+
})
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
if (process.env.NODE_ENV !== "production") SelectPortal.displayName = "SelectPortal";
|
|
844
|
+
|
|
845
|
+
const SelectPositionerContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
846
|
+
if (process.env.NODE_ENV !== "production") SelectPositionerContext.displayName = "SelectPositionerContext";
|
|
847
|
+
function useSelectPositionerContext() {
|
|
848
|
+
const context = React__namespace.useContext(SelectPositionerContext);
|
|
849
|
+
if (!context) {
|
|
850
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectPositionerContext is missing. SelectPositioner parts must be placed within <Select.Positioner>.' : useRenderElement.formatErrorMessage(59));
|
|
851
|
+
}
|
|
852
|
+
return context;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function clearStyles(element, originalStyles) {
|
|
856
|
+
if (element) {
|
|
857
|
+
Object.assign(element.style, originalStyles);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const LIST_FUNCTIONAL_STYLES = {
|
|
861
|
+
position: 'relative',
|
|
862
|
+
maxHeight: '100%',
|
|
863
|
+
overflowX: 'hidden',
|
|
864
|
+
overflowY: 'auto'
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
const FIXED = {
|
|
868
|
+
position: 'fixed'
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Positions the select popup.
|
|
873
|
+
* Renders a `<div>` element.
|
|
874
|
+
*
|
|
875
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
876
|
+
*/
|
|
877
|
+
const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function SelectPositioner(componentProps, forwardedRef) {
|
|
878
|
+
const {
|
|
879
|
+
anchor,
|
|
880
|
+
positionMethod = 'absolute',
|
|
881
|
+
className,
|
|
882
|
+
render,
|
|
883
|
+
side = 'bottom',
|
|
884
|
+
align = 'center',
|
|
885
|
+
sideOffset = 0,
|
|
886
|
+
alignOffset = 0,
|
|
887
|
+
collisionBoundary = 'clipping-ancestors',
|
|
888
|
+
collisionPadding,
|
|
889
|
+
arrowPadding = 5,
|
|
890
|
+
sticky = false,
|
|
891
|
+
disableAnchorTracking,
|
|
892
|
+
alignItemWithTrigger = true,
|
|
893
|
+
collisionAvoidance = InternalBackdrop.DROPDOWN_COLLISION_AVOIDANCE,
|
|
894
|
+
...elementProps
|
|
895
|
+
} = componentProps;
|
|
896
|
+
const {
|
|
897
|
+
store,
|
|
898
|
+
listRef,
|
|
899
|
+
labelsRef,
|
|
900
|
+
alignItemWithTriggerActiveRef,
|
|
901
|
+
selectedItemTextRef,
|
|
902
|
+
valuesRef,
|
|
903
|
+
initialValueRef,
|
|
904
|
+
popupRef,
|
|
905
|
+
setValue
|
|
906
|
+
} = useSelectRootContext();
|
|
907
|
+
const floatingRootContext = useSelectFloatingContext();
|
|
908
|
+
const open = InternalBackdrop.useStore(store, selectors.open);
|
|
909
|
+
const mounted = InternalBackdrop.useStore(store, selectors.mounted);
|
|
910
|
+
const modal = InternalBackdrop.useStore(store, selectors.modal);
|
|
911
|
+
const value = InternalBackdrop.useStore(store, selectors.value);
|
|
912
|
+
const openMethod = InternalBackdrop.useStore(store, selectors.openMethod);
|
|
913
|
+
const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
|
|
914
|
+
const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
|
|
915
|
+
const isItemEqualToValue = InternalBackdrop.useStore(store, selectors.isItemEqualToValue);
|
|
916
|
+
const transitionStatus = InternalBackdrop.useStore(store, selectors.transitionStatus);
|
|
917
|
+
const scrollUpArrowRef = React__namespace.useRef(null);
|
|
918
|
+
const scrollDownArrowRef = React__namespace.useRef(null);
|
|
919
|
+
const [controlledAlignItemWithTrigger, setControlledAlignItemWithTrigger] = React__namespace.useState(alignItemWithTrigger);
|
|
920
|
+
const alignItemWithTriggerActive = mounted && controlledAlignItemWithTrigger && openMethod !== 'touch';
|
|
921
|
+
if (!mounted && controlledAlignItemWithTrigger !== alignItemWithTrigger) {
|
|
922
|
+
setControlledAlignItemWithTrigger(alignItemWithTrigger);
|
|
923
|
+
}
|
|
924
|
+
useButton.useIsoLayoutEffect(() => {
|
|
925
|
+
if (!mounted) {
|
|
926
|
+
if (selectors.scrollUpArrowVisible(store.state)) {
|
|
927
|
+
store.set('scrollUpArrowVisible', false);
|
|
928
|
+
}
|
|
929
|
+
if (selectors.scrollDownArrowVisible(store.state)) {
|
|
930
|
+
store.set('scrollDownArrowVisible', false);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}, [store, mounted]);
|
|
934
|
+
React__namespace.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);
|
|
935
|
+
InternalBackdrop.useScrollLock((alignItemWithTriggerActive || modal) && open && openMethod !== 'touch', triggerElement);
|
|
936
|
+
const positioning = useCompositeListItem.useAnchorPositioning({
|
|
937
|
+
anchor,
|
|
938
|
+
floatingRootContext,
|
|
939
|
+
positionMethod,
|
|
940
|
+
mounted,
|
|
941
|
+
side,
|
|
942
|
+
sideOffset,
|
|
943
|
+
align,
|
|
944
|
+
alignOffset,
|
|
945
|
+
arrowPadding,
|
|
946
|
+
collisionBoundary,
|
|
947
|
+
collisionPadding,
|
|
948
|
+
sticky,
|
|
949
|
+
disableAnchorTracking: disableAnchorTracking ?? alignItemWithTriggerActive,
|
|
950
|
+
collisionAvoidance,
|
|
951
|
+
keepMounted: true
|
|
952
|
+
});
|
|
953
|
+
const renderedSide = alignItemWithTriggerActive ? 'none' : positioning.side;
|
|
954
|
+
const positionerStyles = alignItemWithTriggerActive ? FIXED : positioning.positionerStyles;
|
|
955
|
+
const defaultProps = React__namespace.useMemo(() => {
|
|
956
|
+
const hiddenStyles = {};
|
|
957
|
+
if (!open) {
|
|
958
|
+
hiddenStyles.pointerEvents = 'none';
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
role: 'presentation',
|
|
962
|
+
hidden: !mounted,
|
|
963
|
+
style: {
|
|
964
|
+
...positionerStyles,
|
|
965
|
+
...hiddenStyles
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}, [open, mounted, positionerStyles]);
|
|
969
|
+
const state = {
|
|
970
|
+
open,
|
|
971
|
+
side: renderedSide,
|
|
972
|
+
align: positioning.align,
|
|
973
|
+
anchorHidden: positioning.anchorHidden
|
|
974
|
+
};
|
|
975
|
+
const setPositionerElement = useButton.useStableCallback(element => {
|
|
976
|
+
store.set('positionerElement', element);
|
|
977
|
+
});
|
|
978
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
979
|
+
ref: [forwardedRef, setPositionerElement],
|
|
980
|
+
state,
|
|
981
|
+
stateAttributesMapping: InternalBackdrop.popupStateMapping,
|
|
982
|
+
props: [defaultProps, useCompositeListItem.getDisabledMountTransitionStyles(transitionStatus), elementProps]
|
|
983
|
+
});
|
|
984
|
+
const prevMapSizeRef = React__namespace.useRef(0);
|
|
985
|
+
const onMapChange = useButton.useStableCallback(map => {
|
|
986
|
+
if (map.size === 0 && prevMapSizeRef.current === 0) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (valuesRef.current.length === 0) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const prevSize = prevMapSizeRef.current;
|
|
993
|
+
prevMapSizeRef.current = map.size;
|
|
994
|
+
if (map.size === prevSize) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const eventDetails = useButton.createChangeEventDetails(useButton.none);
|
|
998
|
+
if (prevSize !== 0 && !store.state.multiple && value !== null) {
|
|
999
|
+
const selectedValueIndex = resolveAriaLabelledBy.findItemIndex(valuesRef.current, value, isItemEqualToValue);
|
|
1000
|
+
if (selectedValueIndex === -1) {
|
|
1001
|
+
const initialSelectedValue = initialValueRef.current;
|
|
1002
|
+
const hasInitial = initialSelectedValue != null && resolveAriaLabelledBy.findItemIndex(valuesRef.current, initialSelectedValue, isItemEqualToValue) !== -1;
|
|
1003
|
+
const nextValue = hasInitial ? initialSelectedValue : null;
|
|
1004
|
+
setValue(nextValue, eventDetails);
|
|
1005
|
+
if (nextValue === null) {
|
|
1006
|
+
store.set('selectedIndex', null);
|
|
1007
|
+
selectedItemTextRef.current = null;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (prevSize !== 0 && store.state.multiple && Array.isArray(value)) {
|
|
1012
|
+
const hasVisibleItem = selectedItemValue => resolveAriaLabelledBy.findItemIndex(valuesRef.current, selectedItemValue, isItemEqualToValue) !== -1;
|
|
1013
|
+
const nextValue = value.filter(selectedItemValue => hasVisibleItem(selectedItemValue));
|
|
1014
|
+
if (nextValue.length !== value.length || nextValue.some(selectedItemValue => !resolveAriaLabelledBy.selectedValueIncludes(value, selectedItemValue, isItemEqualToValue))) {
|
|
1015
|
+
setValue(nextValue, eventDetails);
|
|
1016
|
+
if (nextValue.length === 0) {
|
|
1017
|
+
store.set('selectedIndex', null);
|
|
1018
|
+
selectedItemTextRef.current = null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (open && alignItemWithTriggerActive) {
|
|
1023
|
+
store.update({
|
|
1024
|
+
scrollUpArrowVisible: false,
|
|
1025
|
+
scrollDownArrowVisible: false
|
|
1026
|
+
});
|
|
1027
|
+
const stylesToClear = {
|
|
1028
|
+
height: ''
|
|
1029
|
+
};
|
|
1030
|
+
clearStyles(positionerElement, stylesToClear);
|
|
1031
|
+
clearStyles(popupRef.current, stylesToClear);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
1035
|
+
...positioning,
|
|
1036
|
+
side: renderedSide,
|
|
1037
|
+
alignItemWithTriggerActive,
|
|
1038
|
+
setControlledAlignItemWithTrigger,
|
|
1039
|
+
scrollUpArrowRef,
|
|
1040
|
+
scrollDownArrowRef
|
|
1041
|
+
}), [positioning, renderedSide, alignItemWithTriggerActive, setControlledAlignItemWithTrigger]);
|
|
1042
|
+
return /*#__PURE__*/jsxRuntime.jsx(useCompositeListItem.CompositeList, {
|
|
1043
|
+
elementsRef: listRef,
|
|
1044
|
+
labelsRef: labelsRef,
|
|
1045
|
+
onMapChange: onMapChange,
|
|
1046
|
+
children: /*#__PURE__*/jsxRuntime.jsxs(SelectPositionerContext.Provider, {
|
|
1047
|
+
value: contextValue,
|
|
1048
|
+
children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.InternalBackdrop, {
|
|
1049
|
+
inert: InternalBackdrop.inertValue(!open),
|
|
1050
|
+
cutout: triggerElement
|
|
1051
|
+
}), element]
|
|
1052
|
+
})
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
if (process.env.NODE_ENV !== "production") SelectPositioner.displayName = "SelectPositioner";
|
|
1056
|
+
|
|
1057
|
+
function isMouseWithinBounds(event) {
|
|
1058
|
+
const targetRect = event.currentTarget.getBoundingClientRect();
|
|
1059
|
+
|
|
1060
|
+
// Safari randomly fires `mouseleave` incorrectly when the item is
|
|
1061
|
+
// aligned to the trigger. This is a workaround to prevent the highlight
|
|
1062
|
+
// from being removed while the cursor is still within the bounds of the item.
|
|
1063
|
+
// https://github.com/mui/base-ui/issues/869
|
|
1064
|
+
const isWithinBounds = targetRect.top + 1 <= event.clientY && event.clientY <= targetRect.bottom - 1 && targetRect.left + 1 <= event.clientX && event.clientX <= targetRect.right - 1;
|
|
1065
|
+
return isWithinBounds;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const SCROLL_EPS_PX = 1;
|
|
1069
|
+
const stateAttributesMapping = {
|
|
1070
|
+
...InternalBackdrop.popupStateMapping,
|
|
1071
|
+
...useButton.transitionStatusMapping
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* A container for the select list.
|
|
1076
|
+
* Renders a `<div>` element.
|
|
1077
|
+
*
|
|
1078
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
1079
|
+
*/
|
|
1080
|
+
const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopup(componentProps, forwardedRef) {
|
|
1081
|
+
const {
|
|
1082
|
+
render,
|
|
1083
|
+
className,
|
|
1084
|
+
finalFocus,
|
|
1085
|
+
...elementProps
|
|
1086
|
+
} = componentProps;
|
|
1087
|
+
const {
|
|
1088
|
+
store,
|
|
1089
|
+
popupRef,
|
|
1090
|
+
onOpenChangeComplete,
|
|
1091
|
+
setOpen,
|
|
1092
|
+
valueRef,
|
|
1093
|
+
selectedItemTextRef,
|
|
1094
|
+
keyboardActiveRef,
|
|
1095
|
+
multiple,
|
|
1096
|
+
handleScrollArrowVisibility,
|
|
1097
|
+
scrollHandlerRef,
|
|
1098
|
+
highlightItemOnHover
|
|
1099
|
+
} = useSelectRootContext();
|
|
1100
|
+
const {
|
|
1101
|
+
side,
|
|
1102
|
+
align,
|
|
1103
|
+
alignItemWithTriggerActive,
|
|
1104
|
+
setControlledAlignItemWithTrigger,
|
|
1105
|
+
scrollDownArrowRef,
|
|
1106
|
+
scrollUpArrowRef
|
|
1107
|
+
} = useSelectPositionerContext();
|
|
1108
|
+
const insideToolbar = ToolbarRootContext.useToolbarRootContext(true) != null;
|
|
1109
|
+
const floatingRootContext = useSelectFloatingContext();
|
|
1110
|
+
const {
|
|
1111
|
+
nonce,
|
|
1112
|
+
disableStyleElements
|
|
1113
|
+
} = CSPContext.useCSPContext();
|
|
1114
|
+
const highlightTimeout = useButton.useTimeout();
|
|
1115
|
+
const id = InternalBackdrop.useStore(store, selectors.id);
|
|
1116
|
+
const open = InternalBackdrop.useStore(store, selectors.open);
|
|
1117
|
+
const mounted = InternalBackdrop.useStore(store, selectors.mounted);
|
|
1118
|
+
const popupProps = InternalBackdrop.useStore(store, selectors.popupProps);
|
|
1119
|
+
const transitionStatus = InternalBackdrop.useStore(store, selectors.transitionStatus);
|
|
1120
|
+
const triggerElement = InternalBackdrop.useStore(store, selectors.triggerElement);
|
|
1121
|
+
const positionerElement = InternalBackdrop.useStore(store, selectors.positionerElement);
|
|
1122
|
+
const listElement = InternalBackdrop.useStore(store, selectors.listElement);
|
|
1123
|
+
const initialHeightRef = React__namespace.useRef(0);
|
|
1124
|
+
const reachedMaxHeightRef = React__namespace.useRef(false);
|
|
1125
|
+
const maxHeightRef = React__namespace.useRef(0);
|
|
1126
|
+
const initialPlacedRef = React__namespace.useRef(false);
|
|
1127
|
+
const originalPositionerStylesRef = React__namespace.useRef({});
|
|
1128
|
+
const scrollArrowFrame = useButton.useAnimationFrame();
|
|
1129
|
+
const handleScroll = useButton.useStableCallback(scroller => {
|
|
1130
|
+
if (!positionerElement || !popupRef.current || !initialPlacedRef.current) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (reachedMaxHeightRef.current || !alignItemWithTriggerActive) {
|
|
1134
|
+
handleScrollArrowVisibility();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const isTopPositioned = positionerElement.style.top === '0px';
|
|
1138
|
+
const isBottomPositioned = positionerElement.style.bottom === '0px';
|
|
1139
|
+
const currentHeight = positionerElement.getBoundingClientRect().height;
|
|
1140
|
+
const doc = useButton.ownerDocument(positionerElement);
|
|
1141
|
+
const positionerStyles = getComputedStyle(positionerElement);
|
|
1142
|
+
const marginTop = parseFloat(positionerStyles.marginTop);
|
|
1143
|
+
const marginBottom = parseFloat(positionerStyles.marginBottom);
|
|
1144
|
+
const maxPopupHeight = getMaxPopupHeight(getComputedStyle(popupRef.current));
|
|
1145
|
+
const maxAvailableHeight = Math.min(doc.documentElement.clientHeight - marginTop - marginBottom, maxPopupHeight);
|
|
1146
|
+
const scrollTop = scroller.scrollTop;
|
|
1147
|
+
const maxScrollTop = getMaxScrollTop(scroller);
|
|
1148
|
+
let nextPositionerHeight = 0;
|
|
1149
|
+
let nextScrollTop = null;
|
|
1150
|
+
let setReachedMax = false;
|
|
1151
|
+
let scrollToMax = false;
|
|
1152
|
+
const setHeight = height => {
|
|
1153
|
+
positionerElement.style.height = `${height}px`;
|
|
1154
|
+
};
|
|
1155
|
+
const handleSmallDiff = (diff, targetScrollTop) => {
|
|
1156
|
+
const heightDelta = clamp.clamp(diff, 0, maxAvailableHeight - currentHeight);
|
|
1157
|
+
if (heightDelta > 0) {
|
|
1158
|
+
// Consume the remaining scroll in height.
|
|
1159
|
+
setHeight(currentHeight + heightDelta);
|
|
1160
|
+
}
|
|
1161
|
+
scroller.scrollTop = targetScrollTop;
|
|
1162
|
+
if (maxAvailableHeight - (currentHeight + heightDelta) <= SCROLL_EPS_PX) {
|
|
1163
|
+
reachedMaxHeightRef.current = true;
|
|
1164
|
+
}
|
|
1165
|
+
handleScrollArrowVisibility();
|
|
1166
|
+
};
|
|
1167
|
+
if (isTopPositioned) {
|
|
1168
|
+
const diff = maxScrollTop - scrollTop;
|
|
1169
|
+
const idealHeight = currentHeight + diff;
|
|
1170
|
+
const nextHeight = Math.min(idealHeight, maxAvailableHeight);
|
|
1171
|
+
nextPositionerHeight = nextHeight;
|
|
1172
|
+
if (diff <= SCROLL_EPS_PX) {
|
|
1173
|
+
handleSmallDiff(diff, maxScrollTop);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {
|
|
1177
|
+
scrollToMax = true;
|
|
1178
|
+
} else {
|
|
1179
|
+
setReachedMax = true;
|
|
1180
|
+
}
|
|
1181
|
+
} else if (isBottomPositioned) {
|
|
1182
|
+
const diff = scrollTop;
|
|
1183
|
+
const idealHeight = currentHeight + diff;
|
|
1184
|
+
const nextHeight = Math.min(idealHeight, maxAvailableHeight);
|
|
1185
|
+
const overshoot = idealHeight - maxAvailableHeight;
|
|
1186
|
+
nextPositionerHeight = nextHeight;
|
|
1187
|
+
if (diff <= SCROLL_EPS_PX) {
|
|
1188
|
+
handleSmallDiff(diff, 0);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {
|
|
1192
|
+
nextScrollTop = 0;
|
|
1193
|
+
} else {
|
|
1194
|
+
setReachedMax = true;
|
|
1195
|
+
if (scrollTop < maxScrollTop) {
|
|
1196
|
+
nextScrollTop = scrollTop - (diff - overshoot);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
nextPositionerHeight = Math.ceil(nextPositionerHeight);
|
|
1201
|
+
if (nextPositionerHeight !== 0) {
|
|
1202
|
+
setHeight(nextPositionerHeight);
|
|
1203
|
+
}
|
|
1204
|
+
if (scrollToMax || nextScrollTop != null) {
|
|
1205
|
+
// Recompute bounds after resizing (clientHeight likely changed).
|
|
1206
|
+
const nextMaxScrollTop = getMaxScrollTop(scroller);
|
|
1207
|
+
const target = scrollToMax ? nextMaxScrollTop : clamp.clamp(nextScrollTop, 0, nextMaxScrollTop);
|
|
1208
|
+
|
|
1209
|
+
// Avoid adjustments that re-trigger scroll events forever.
|
|
1210
|
+
if (Math.abs(scroller.scrollTop - target) > SCROLL_EPS_PX) {
|
|
1211
|
+
scroller.scrollTop = target;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - SCROLL_EPS_PX) {
|
|
1215
|
+
reachedMaxHeightRef.current = true;
|
|
1216
|
+
}
|
|
1217
|
+
handleScrollArrowVisibility();
|
|
1218
|
+
});
|
|
1219
|
+
React__namespace.useImperativeHandle(scrollHandlerRef, () => handleScroll, [handleScroll]);
|
|
1220
|
+
useButton.useOpenChangeComplete({
|
|
1221
|
+
open,
|
|
1222
|
+
ref: popupRef,
|
|
1223
|
+
onComplete() {
|
|
1224
|
+
if (open) {
|
|
1225
|
+
onOpenChangeComplete?.(true);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
const state = {
|
|
1230
|
+
open,
|
|
1231
|
+
transitionStatus,
|
|
1232
|
+
side,
|
|
1233
|
+
align
|
|
1234
|
+
};
|
|
1235
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1236
|
+
if (!positionerElement || !popupRef.current || Object.keys(originalPositionerStylesRef.current).length) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
originalPositionerStylesRef.current = {
|
|
1240
|
+
top: positionerElement.style.top || '0',
|
|
1241
|
+
left: positionerElement.style.left || '0',
|
|
1242
|
+
right: positionerElement.style.right,
|
|
1243
|
+
height: positionerElement.style.height,
|
|
1244
|
+
bottom: positionerElement.style.bottom,
|
|
1245
|
+
minHeight: positionerElement.style.minHeight,
|
|
1246
|
+
maxHeight: positionerElement.style.maxHeight,
|
|
1247
|
+
marginTop: positionerElement.style.marginTop,
|
|
1248
|
+
marginBottom: positionerElement.style.marginBottom
|
|
1249
|
+
};
|
|
1250
|
+
}, [popupRef, positionerElement]);
|
|
1251
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1252
|
+
if (open || alignItemWithTriggerActive) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
initialPlacedRef.current = false;
|
|
1256
|
+
reachedMaxHeightRef.current = false;
|
|
1257
|
+
initialHeightRef.current = 0;
|
|
1258
|
+
maxHeightRef.current = 0;
|
|
1259
|
+
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1260
|
+
}, [open, alignItemWithTriggerActive, positionerElement, popupRef]);
|
|
1261
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1262
|
+
const popupElement = popupRef.current;
|
|
1263
|
+
if (!open || !triggerElement || !positionerElement || !popupElement || store.state.transitionStatus === 'ending') {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (!alignItemWithTriggerActive) {
|
|
1267
|
+
initialPlacedRef.current = true;
|
|
1268
|
+
scrollArrowFrame.request(handleScrollArrowVisibility);
|
|
1269
|
+
popupElement.style.removeProperty('--transform-origin');
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// Wait for `selectedItemTextRef.current` to be set.
|
|
1274
|
+
queueMicrotask(() => {
|
|
1275
|
+
// Ensure we remove any transforms that can affect the location of the popup
|
|
1276
|
+
// and therefore the calculations.
|
|
1277
|
+
const restoreTransformStyles = unsetTransformStyles(popupElement);
|
|
1278
|
+
popupElement.style.removeProperty('--transform-origin');
|
|
1279
|
+
try {
|
|
1280
|
+
const positionerStyles = getComputedStyle(positionerElement);
|
|
1281
|
+
const popupStyles = getComputedStyle(popupElement);
|
|
1282
|
+
const doc = useButton.ownerDocument(triggerElement);
|
|
1283
|
+
const win = floatingUi_utils_dom.getWindow(positionerElement);
|
|
1284
|
+
const scale = getScale(triggerElement);
|
|
1285
|
+
const triggerRect = normalizeRect(triggerElement.getBoundingClientRect(), scale);
|
|
1286
|
+
const positionerRect = normalizeRect(positionerElement.getBoundingClientRect(), scale);
|
|
1287
|
+
const triggerX = triggerRect.left;
|
|
1288
|
+
const triggerHeight = triggerRect.height;
|
|
1289
|
+
const scroller = listElement || popupElement;
|
|
1290
|
+
const scrollHeight = scroller.scrollHeight;
|
|
1291
|
+
const borderBottom = parseFloat(popupStyles.borderBottomWidth);
|
|
1292
|
+
const marginTop = parseFloat(positionerStyles.marginTop) || 10;
|
|
1293
|
+
const marginBottom = parseFloat(positionerStyles.marginBottom) || 10;
|
|
1294
|
+
const minHeight = parseFloat(positionerStyles.minHeight) || 100;
|
|
1295
|
+
const maxPopupHeight = getMaxPopupHeight(popupStyles);
|
|
1296
|
+
const paddingLeft = 5;
|
|
1297
|
+
const paddingRight = 5;
|
|
1298
|
+
const triggerCollisionThreshold = 20;
|
|
1299
|
+
const viewportHeight = doc.documentElement.clientHeight - marginTop - marginBottom;
|
|
1300
|
+
const viewportWidth = doc.documentElement.clientWidth;
|
|
1301
|
+
const availableSpaceBeneathTrigger = viewportHeight - triggerRect.bottom + triggerHeight;
|
|
1302
|
+
const textElement = selectedItemTextRef.current;
|
|
1303
|
+
const valueElement = valueRef.current;
|
|
1304
|
+
let textRect;
|
|
1305
|
+
let offsetX = 0;
|
|
1306
|
+
let offsetY = 0;
|
|
1307
|
+
if (textElement && valueElement) {
|
|
1308
|
+
const valueRect = normalizeRect(valueElement.getBoundingClientRect(), scale);
|
|
1309
|
+
textRect = normalizeRect(textElement.getBoundingClientRect(), scale);
|
|
1310
|
+
const valueLeftFromTriggerLeft = valueRect.left - triggerX;
|
|
1311
|
+
const textLeftFromPositionerLeft = textRect.left - positionerRect.left;
|
|
1312
|
+
const valueCenterFromPositionerTop = valueRect.top - triggerRect.top + valueRect.height / 2;
|
|
1313
|
+
const textCenterFromTriggerTop = textRect.top - positionerRect.top + textRect.height / 2;
|
|
1314
|
+
offsetX = valueLeftFromTriggerLeft - textLeftFromPositionerLeft;
|
|
1315
|
+
offsetY = textCenterFromTriggerTop - valueCenterFromPositionerTop;
|
|
1316
|
+
}
|
|
1317
|
+
const idealHeight = availableSpaceBeneathTrigger + offsetY + marginBottom + borderBottom;
|
|
1318
|
+
let height = Math.min(viewportHeight, idealHeight);
|
|
1319
|
+
const maxHeight = viewportHeight - marginTop - marginBottom;
|
|
1320
|
+
const scrollTop = idealHeight - height;
|
|
1321
|
+
const left = Math.max(paddingLeft, triggerX + offsetX);
|
|
1322
|
+
const maxRight = viewportWidth - paddingRight;
|
|
1323
|
+
const rightOverflow = Math.max(0, left + positionerRect.width - maxRight);
|
|
1324
|
+
positionerElement.style.left = `${left - rightOverflow}px`;
|
|
1325
|
+
positionerElement.style.height = `${height}px`;
|
|
1326
|
+
positionerElement.style.maxHeight = 'auto';
|
|
1327
|
+
positionerElement.style.marginTop = `${marginTop}px`;
|
|
1328
|
+
positionerElement.style.marginBottom = `${marginBottom}px`;
|
|
1329
|
+
popupElement.style.height = '100%';
|
|
1330
|
+
const maxScrollTop = getMaxScrollTop(scroller);
|
|
1331
|
+
const isTopPositioned = scrollTop >= maxScrollTop - SCROLL_EPS_PX;
|
|
1332
|
+
if (isTopPositioned) {
|
|
1333
|
+
height = Math.min(viewportHeight, positionerRect.height) - (scrollTop - maxScrollTop);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// When the trigger is too close to the top or bottom of the viewport, or the minHeight is
|
|
1337
|
+
// reached, we fallback to aligning the popup to the trigger as the UX is poor otherwise.
|
|
1338
|
+
const fallbackToAlignPopupToTrigger = triggerRect.top < triggerCollisionThreshold || triggerRect.bottom > viewportHeight - triggerCollisionThreshold || Math.ceil(height) + SCROLL_EPS_PX < Math.min(scrollHeight, minHeight);
|
|
1339
|
+
|
|
1340
|
+
// Safari doesn't position the popup correctly when pinch-zoomed.
|
|
1341
|
+
const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && useButton.isWebKit;
|
|
1342
|
+
if (fallbackToAlignPopupToTrigger || isPinchZoomed) {
|
|
1343
|
+
initialPlacedRef.current = true;
|
|
1344
|
+
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1345
|
+
ReactDOM__namespace.flushSync(() => setControlledAlignItemWithTrigger(false));
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (isTopPositioned) {
|
|
1349
|
+
const topOffset = Math.max(0, viewportHeight - idealHeight);
|
|
1350
|
+
positionerElement.style.top = positionerRect.height >= maxHeight ? '0' : `${topOffset}px`;
|
|
1351
|
+
positionerElement.style.height = `${height}px`;
|
|
1352
|
+
scroller.scrollTop = getMaxScrollTop(scroller);
|
|
1353
|
+
initialHeightRef.current = Math.max(minHeight, height);
|
|
1354
|
+
} else {
|
|
1355
|
+
positionerElement.style.bottom = '0';
|
|
1356
|
+
initialHeightRef.current = Math.max(minHeight, height);
|
|
1357
|
+
scroller.scrollTop = scrollTop;
|
|
1358
|
+
}
|
|
1359
|
+
if (textRect) {
|
|
1360
|
+
const popupTop = positionerRect.top;
|
|
1361
|
+
const popupHeight = positionerRect.height;
|
|
1362
|
+
const textCenterY = textRect.top + textRect.height / 2;
|
|
1363
|
+
const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
|
|
1364
|
+
const clampedY = clamp.clamp(transformOriginY, 0, 100);
|
|
1365
|
+
popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
|
|
1366
|
+
}
|
|
1367
|
+
if (initialHeightRef.current === viewportHeight || height >= maxPopupHeight) {
|
|
1368
|
+
reachedMaxHeightRef.current = true;
|
|
1369
|
+
}
|
|
1370
|
+
handleScrollArrowVisibility();
|
|
1371
|
+
|
|
1372
|
+
// Avoid the `onScroll` event logic from triggering before the popup is placed.
|
|
1373
|
+
setTimeout(() => {
|
|
1374
|
+
initialPlacedRef.current = true;
|
|
1375
|
+
});
|
|
1376
|
+
} finally {
|
|
1377
|
+
restoreTransformStyles();
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
}, [store, open, positionerElement, triggerElement, valueRef, selectedItemTextRef, popupRef, handleScrollArrowVisibility, alignItemWithTriggerActive, setControlledAlignItemWithTrigger, scrollArrowFrame, scrollDownArrowRef, scrollUpArrowRef, listElement]);
|
|
1381
|
+
React__namespace.useEffect(() => {
|
|
1382
|
+
if (!alignItemWithTriggerActive || !positionerElement || !open) {
|
|
1383
|
+
return undefined;
|
|
1384
|
+
}
|
|
1385
|
+
const win = floatingUi_utils_dom.getWindow(positionerElement);
|
|
1386
|
+
function handleResize(event) {
|
|
1387
|
+
setOpen(false, useButton.createChangeEventDetails(useButton.windowResize, event));
|
|
1388
|
+
}
|
|
1389
|
+
win.addEventListener('resize', handleResize);
|
|
1390
|
+
return () => {
|
|
1391
|
+
win.removeEventListener('resize', handleResize);
|
|
1392
|
+
};
|
|
1393
|
+
}, [setOpen, alignItemWithTriggerActive, positionerElement, open]);
|
|
1394
|
+
const defaultProps = {
|
|
1395
|
+
...(listElement ? {
|
|
1396
|
+
role: 'presentation',
|
|
1397
|
+
'aria-orientation': undefined
|
|
1398
|
+
} : {
|
|
1399
|
+
role: 'listbox',
|
|
1400
|
+
'aria-multiselectable': multiple || undefined,
|
|
1401
|
+
id: `${id}-list`
|
|
1402
|
+
}),
|
|
1403
|
+
onKeyDown(event) {
|
|
1404
|
+
keyboardActiveRef.current = true;
|
|
1405
|
+
if (insideToolbar && CSPContext.COMPOSITE_KEYS.has(event.key)) {
|
|
1406
|
+
event.stopPropagation();
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
onMouseMove() {
|
|
1410
|
+
keyboardActiveRef.current = false;
|
|
1411
|
+
},
|
|
1412
|
+
onPointerLeave(event) {
|
|
1413
|
+
if (!highlightItemOnHover || isMouseWithinBounds(event) || event.pointerType === 'touch') {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const popup = event.currentTarget;
|
|
1417
|
+
highlightTimeout.start(0, () => {
|
|
1418
|
+
store.set('activeIndex', null);
|
|
1419
|
+
popup.focus({
|
|
1420
|
+
preventScroll: true
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
},
|
|
1424
|
+
onScroll(event) {
|
|
1425
|
+
if (listElement) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
handleScroll(event.currentTarget);
|
|
1429
|
+
},
|
|
1430
|
+
...(alignItemWithTriggerActive && {
|
|
1431
|
+
style: listElement ? {
|
|
1432
|
+
height: '100%'
|
|
1433
|
+
} : LIST_FUNCTIONAL_STYLES
|
|
1434
|
+
})
|
|
1435
|
+
};
|
|
1436
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1437
|
+
ref: [forwardedRef, popupRef],
|
|
1438
|
+
state,
|
|
1439
|
+
stateAttributesMapping,
|
|
1440
|
+
props: [popupProps, defaultProps, useCompositeListItem.getDisabledMountTransitionStyles(transitionStatus), {
|
|
1441
|
+
className: !listElement && alignItemWithTriggerActive ? CSPContext.styleDisableScrollbar.className : undefined
|
|
1442
|
+
}, elementProps]
|
|
1443
|
+
});
|
|
1444
|
+
return /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
|
|
1445
|
+
children: [!disableStyleElements && CSPContext.styleDisableScrollbar.getElement(nonce), /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingFocusManager, {
|
|
1446
|
+
context: floatingRootContext,
|
|
1447
|
+
modal: false,
|
|
1448
|
+
disabled: !mounted,
|
|
1449
|
+
returnFocus: finalFocus,
|
|
1450
|
+
restoreFocus: true,
|
|
1451
|
+
children: element
|
|
1452
|
+
})]
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
if (process.env.NODE_ENV !== "production") SelectPopup.displayName = "SelectPopup";
|
|
1456
|
+
function getMaxPopupHeight(popupStyles) {
|
|
1457
|
+
const maxHeightStyle = popupStyles.maxHeight || '';
|
|
1458
|
+
return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;
|
|
1459
|
+
}
|
|
1460
|
+
function getMaxScrollTop(scroller) {
|
|
1461
|
+
return Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
|
1462
|
+
}
|
|
1463
|
+
function getScale(element) {
|
|
1464
|
+
// The platform API is async-capable, but the DOM platform returns a plain scale object.
|
|
1465
|
+
return floatingUi_reactDom.platform.getScale(element);
|
|
1466
|
+
}
|
|
1467
|
+
function normalizeRect(rect, scale) {
|
|
1468
|
+
return index_esm.rectToClientRect({
|
|
1469
|
+
x: rect.x / scale.x,
|
|
1470
|
+
y: rect.y / scale.y,
|
|
1471
|
+
width: rect.width / scale.x,
|
|
1472
|
+
height: rect.height / scale.y
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
const TRANSFORM_STYLE_RESETS = [['transform', 'none'], ['scale', '1'], ['translate', '0 0']];
|
|
1476
|
+
function unsetTransformStyles(popupElement) {
|
|
1477
|
+
const {
|
|
1478
|
+
style
|
|
1479
|
+
} = popupElement;
|
|
1480
|
+
const originalStyles = {};
|
|
1481
|
+
for (const [property, value] of TRANSFORM_STYLE_RESETS) {
|
|
1482
|
+
originalStyles[property] = style.getPropertyValue(property);
|
|
1483
|
+
style.setProperty(property, value, 'important');
|
|
1484
|
+
}
|
|
1485
|
+
return () => {
|
|
1486
|
+
for (const [property] of TRANSFORM_STYLE_RESETS) {
|
|
1487
|
+
const originalValue = originalStyles[property];
|
|
1488
|
+
if (originalValue) {
|
|
1489
|
+
style.setProperty(property, originalValue);
|
|
1490
|
+
} else {
|
|
1491
|
+
style.removeProperty(property);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* A container for the select items.
|
|
1499
|
+
* Renders a `<div>` element.
|
|
1500
|
+
*
|
|
1501
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
1502
|
+
*/
|
|
1503
|
+
const SelectList = /*#__PURE__*/React__namespace.forwardRef(function SelectList(componentProps, forwardedRef) {
|
|
1504
|
+
const {
|
|
1505
|
+
className,
|
|
1506
|
+
render,
|
|
1507
|
+
...elementProps
|
|
1508
|
+
} = componentProps;
|
|
1509
|
+
const {
|
|
1510
|
+
store,
|
|
1511
|
+
scrollHandlerRef
|
|
1512
|
+
} = useSelectRootContext();
|
|
1513
|
+
const {
|
|
1514
|
+
alignItemWithTriggerActive
|
|
1515
|
+
} = useSelectPositionerContext();
|
|
1516
|
+
const hasScrollArrows = InternalBackdrop.useStore(store, selectors.hasScrollArrows);
|
|
1517
|
+
const openMethod = InternalBackdrop.useStore(store, selectors.openMethod);
|
|
1518
|
+
const multiple = InternalBackdrop.useStore(store, selectors.multiple);
|
|
1519
|
+
const id = InternalBackdrop.useStore(store, selectors.id);
|
|
1520
|
+
const defaultProps = {
|
|
1521
|
+
id: `${id}-list`,
|
|
1522
|
+
role: 'listbox',
|
|
1523
|
+
'aria-multiselectable': multiple || undefined,
|
|
1524
|
+
onScroll(event) {
|
|
1525
|
+
scrollHandlerRef.current?.(event.currentTarget);
|
|
1526
|
+
},
|
|
1527
|
+
...(alignItemWithTriggerActive && {
|
|
1528
|
+
style: LIST_FUNCTIONAL_STYLES
|
|
1529
|
+
}),
|
|
1530
|
+
className: hasScrollArrows && openMethod !== 'touch' ? CSPContext.styleDisableScrollbar.className : undefined
|
|
1531
|
+
};
|
|
1532
|
+
const setListElement = useButton.useStableCallback(element => {
|
|
1533
|
+
store.set('listElement', element);
|
|
1534
|
+
});
|
|
1535
|
+
return useRenderElement.useRenderElement('div', componentProps, {
|
|
1536
|
+
ref: [forwardedRef, setListElement],
|
|
1537
|
+
props: [defaultProps, elementProps]
|
|
1538
|
+
});
|
|
1539
|
+
});
|
|
1540
|
+
if (process.env.NODE_ENV !== "production") SelectList.displayName = "SelectList";
|
|
1541
|
+
|
|
1542
|
+
const SelectItemContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
1543
|
+
if (process.env.NODE_ENV !== "production") SelectItemContext.displayName = "SelectItemContext";
|
|
1544
|
+
function useSelectItemContext() {
|
|
1545
|
+
const context = React__namespace.useContext(SelectItemContext);
|
|
1546
|
+
if (!context) {
|
|
1547
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectItemContext is missing. SelectItem parts must be placed within <Select.Item>.' : useRenderElement.formatErrorMessage(57));
|
|
1548
|
+
}
|
|
1549
|
+
return context;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef(function SelectItem(componentProps, forwardedRef) {
|
|
1553
|
+
const {
|
|
1554
|
+
render,
|
|
1555
|
+
className,
|
|
1556
|
+
value: itemValue = null,
|
|
1557
|
+
label,
|
|
1558
|
+
disabled = false,
|
|
1559
|
+
nativeButton = false,
|
|
1560
|
+
...elementProps
|
|
1561
|
+
} = componentProps;
|
|
1562
|
+
const textRef = React__namespace.useRef(null);
|
|
1563
|
+
const listItem = useCompositeListItem.useCompositeListItem({
|
|
1564
|
+
label,
|
|
1565
|
+
textRef,
|
|
1566
|
+
indexGuessBehavior: useCompositeListItem.IndexGuessBehavior.GuessFromOrder
|
|
1567
|
+
});
|
|
1568
|
+
const {
|
|
1569
|
+
store,
|
|
1570
|
+
getItemProps,
|
|
1571
|
+
setOpen,
|
|
1572
|
+
setValue,
|
|
1573
|
+
selectionRef,
|
|
1574
|
+
typingRef,
|
|
1575
|
+
valuesRef,
|
|
1576
|
+
keyboardActiveRef,
|
|
1577
|
+
multiple,
|
|
1578
|
+
highlightItemOnHover
|
|
1579
|
+
} = useSelectRootContext();
|
|
1580
|
+
const highlightTimeout = useButton.useTimeout();
|
|
1581
|
+
const highlighted = InternalBackdrop.useStore(store, selectors.isActive, listItem.index);
|
|
1582
|
+
const selected = InternalBackdrop.useStore(store, selectors.isSelected, listItem.index, itemValue);
|
|
1583
|
+
const selectedByFocus = InternalBackdrop.useStore(store, selectors.isSelectedByFocus, listItem.index);
|
|
1584
|
+
const isItemEqualToValue = InternalBackdrop.useStore(store, selectors.isItemEqualToValue);
|
|
1585
|
+
const index = listItem.index;
|
|
1586
|
+
const hasRegistered = index !== -1;
|
|
1587
|
+
const itemRef = React__namespace.useRef(null);
|
|
1588
|
+
const indexRef = useButton.useValueAsRef(index);
|
|
1589
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1590
|
+
if (!hasRegistered) {
|
|
1591
|
+
return undefined;
|
|
1592
|
+
}
|
|
1593
|
+
const values = valuesRef.current;
|
|
1594
|
+
values[index] = itemValue;
|
|
1595
|
+
return () => {
|
|
1596
|
+
delete values[index];
|
|
1597
|
+
};
|
|
1598
|
+
}, [hasRegistered, index, itemValue, valuesRef]);
|
|
1599
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1600
|
+
if (!hasRegistered) {
|
|
1601
|
+
return undefined;
|
|
1602
|
+
}
|
|
1603
|
+
const selectedValue = store.state.value;
|
|
1604
|
+
let selectedCandidate = selectedValue;
|
|
1605
|
+
if (multiple && Array.isArray(selectedValue) && selectedValue.length > 0) {
|
|
1606
|
+
selectedCandidate = selectedValue[selectedValue.length - 1];
|
|
1607
|
+
}
|
|
1608
|
+
if (selectedCandidate !== undefined && resolveAriaLabelledBy.compareItemEquality(itemValue, selectedCandidate, isItemEqualToValue)) {
|
|
1609
|
+
store.set('selectedIndex', index);
|
|
1610
|
+
}
|
|
1611
|
+
return undefined;
|
|
1612
|
+
}, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue]);
|
|
1613
|
+
const state = {
|
|
1614
|
+
disabled,
|
|
1615
|
+
selected,
|
|
1616
|
+
highlighted
|
|
1617
|
+
};
|
|
1618
|
+
const rootProps = getItemProps({
|
|
1619
|
+
active: highlighted,
|
|
1620
|
+
selected
|
|
1621
|
+
});
|
|
1622
|
+
// With our custom `focusItemOnHover` implementation, this interferes with the logic and can
|
|
1623
|
+
// cause the index state to be stuck when leaving the select popup.
|
|
1624
|
+
rootProps.onFocus = undefined;
|
|
1625
|
+
rootProps.id = undefined;
|
|
1626
|
+
const lastKeyRef = React__namespace.useRef(null);
|
|
1627
|
+
const pointerTypeRef = React__namespace.useRef('mouse');
|
|
1628
|
+
const didPointerDownRef = React__namespace.useRef(false);
|
|
1629
|
+
const {
|
|
1630
|
+
getButtonProps,
|
|
1631
|
+
buttonRef
|
|
1632
|
+
} = useButton.useButton({
|
|
1633
|
+
disabled,
|
|
1634
|
+
focusableWhenDisabled: true,
|
|
1635
|
+
native: nativeButton,
|
|
1636
|
+
composite: true
|
|
1637
|
+
});
|
|
1638
|
+
function commitSelection(event) {
|
|
1639
|
+
const selectedValue = store.state.value;
|
|
1640
|
+
if (multiple) {
|
|
1641
|
+
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
1642
|
+
const nextValue = selected ? resolveAriaLabelledBy.removeItem(currentValue, itemValue, isItemEqualToValue) : [...currentValue, itemValue];
|
|
1643
|
+
setValue(nextValue, useButton.createChangeEventDetails(useButton.itemPress, event));
|
|
1644
|
+
} else {
|
|
1645
|
+
setValue(itemValue, useButton.createChangeEventDetails(useButton.itemPress, event));
|
|
1646
|
+
setOpen(false, useButton.createChangeEventDetails(useButton.itemPress, event));
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
const defaultProps = {
|
|
1650
|
+
role: 'option',
|
|
1651
|
+
'aria-selected': selected,
|
|
1652
|
+
tabIndex: highlighted ? 0 : -1,
|
|
1653
|
+
onFocus() {
|
|
1654
|
+
store.set('activeIndex', index);
|
|
1655
|
+
},
|
|
1656
|
+
onMouseEnter() {
|
|
1657
|
+
if (!keyboardActiveRef.current && store.state.selectedIndex === null && highlightItemOnHover) {
|
|
1658
|
+
store.set('activeIndex', index);
|
|
1659
|
+
}
|
|
1660
|
+
},
|
|
1661
|
+
onMouseMove() {
|
|
1662
|
+
if (highlightItemOnHover) {
|
|
1663
|
+
store.set('activeIndex', index);
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
onMouseLeave(event) {
|
|
1667
|
+
if (!highlightItemOnHover || keyboardActiveRef.current || isMouseWithinBounds(event)) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
highlightTimeout.start(0, () => {
|
|
1671
|
+
if (store.state.activeIndex === index) {
|
|
1672
|
+
store.set('activeIndex', null);
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
},
|
|
1676
|
+
onTouchStart() {
|
|
1677
|
+
selectionRef.current = {
|
|
1678
|
+
allowSelectedMouseUp: false,
|
|
1679
|
+
allowUnselectedMouseUp: false
|
|
1680
|
+
};
|
|
1681
|
+
},
|
|
1682
|
+
onKeyDown(event) {
|
|
1683
|
+
lastKeyRef.current = event.key;
|
|
1684
|
+
store.set('activeIndex', index);
|
|
1685
|
+
if (event.key === ' ' && typingRef.current) {
|
|
1686
|
+
event.preventDefault();
|
|
1687
|
+
}
|
|
1688
|
+
},
|
|
1689
|
+
onClick(event) {
|
|
1690
|
+
didPointerDownRef.current = false;
|
|
1691
|
+
|
|
1692
|
+
// Prevent double commit on {Enter}
|
|
1693
|
+
if (event.type === 'keydown' && lastKeyRef.current === null) {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current || pointerTypeRef.current !== 'touch' && !highlighted) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
lastKeyRef.current = null;
|
|
1700
|
+
commitSelection(event.nativeEvent);
|
|
1701
|
+
},
|
|
1702
|
+
onPointerEnter(event) {
|
|
1703
|
+
pointerTypeRef.current = event.pointerType;
|
|
1704
|
+
},
|
|
1705
|
+
onPointerDown(event) {
|
|
1706
|
+
pointerTypeRef.current = event.pointerType;
|
|
1707
|
+
didPointerDownRef.current = true;
|
|
1708
|
+
},
|
|
1709
|
+
onMouseUp() {
|
|
1710
|
+
if (disabled) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// Regular click (pointerdown on this element) if didPointerDownRef is set, otherwise drag-to-select
|
|
1715
|
+
if (didPointerDownRef.current) {
|
|
1716
|
+
didPointerDownRef.current = false;
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;
|
|
1720
|
+
const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;
|
|
1721
|
+
if (disallowSelectedMouseUp || disallowUnselectedMouseUp || pointerTypeRef.current !== 'touch' && !highlighted) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
itemRef.current?.click();
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1728
|
+
ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
|
|
1729
|
+
state,
|
|
1730
|
+
props: [rootProps, defaultProps, elementProps, getButtonProps]
|
|
1731
|
+
});
|
|
1732
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
1733
|
+
selected,
|
|
1734
|
+
indexRef,
|
|
1735
|
+
textRef,
|
|
1736
|
+
selectedByFocus,
|
|
1737
|
+
hasRegistered
|
|
1738
|
+
}), [selected, indexRef, textRef, selectedByFocus, hasRegistered]);
|
|
1739
|
+
return /*#__PURE__*/jsxRuntime.jsx(SelectItemContext.Provider, {
|
|
1740
|
+
value: contextValue,
|
|
1741
|
+
children: element
|
|
1742
|
+
});
|
|
1743
|
+
}));
|
|
1744
|
+
if (process.env.NODE_ENV !== "production") SelectItem.displayName = "SelectItem";
|
|
1745
|
+
|
|
1746
|
+
const SelectItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function SelectItemIndicator(componentProps, forwardedRef) {
|
|
1747
|
+
const keepMounted = componentProps.keepMounted ?? false;
|
|
1748
|
+
const {
|
|
1749
|
+
selected
|
|
1750
|
+
} = useSelectItemContext();
|
|
1751
|
+
const shouldRender = keepMounted || selected;
|
|
1752
|
+
if (!shouldRender) {
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1757
|
+
return /*#__PURE__*/jsxRuntime.jsx(Inner, {
|
|
1758
|
+
...componentProps,
|
|
1759
|
+
ref: forwardedRef
|
|
1760
|
+
});
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
/** The core implementation of the indicator is split here to avoid paying the hooks
|
|
1764
|
+
* costs unless the element needs to be mounted. */
|
|
1765
|
+
if (process.env.NODE_ENV !== "production") SelectItemIndicator.displayName = "SelectItemIndicator";
|
|
1766
|
+
const Inner = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef((componentProps, forwardedRef) => {
|
|
1767
|
+
const {
|
|
1768
|
+
render,
|
|
1769
|
+
className,
|
|
1770
|
+
keepMounted,
|
|
1771
|
+
...elementProps
|
|
1772
|
+
} = componentProps;
|
|
1773
|
+
const {
|
|
1774
|
+
selected
|
|
1775
|
+
} = useSelectItemContext();
|
|
1776
|
+
const indicatorRef = React__namespace.useRef(null);
|
|
1777
|
+
const {
|
|
1778
|
+
transitionStatus,
|
|
1779
|
+
setMounted
|
|
1780
|
+
} = useButton.useTransitionStatus(selected);
|
|
1781
|
+
const state = {
|
|
1782
|
+
selected,
|
|
1783
|
+
transitionStatus
|
|
1784
|
+
};
|
|
1785
|
+
const element = useRenderElement.useRenderElement('span', componentProps, {
|
|
1786
|
+
ref: [forwardedRef, indicatorRef],
|
|
1787
|
+
state,
|
|
1788
|
+
props: [{
|
|
1789
|
+
'aria-hidden': true,
|
|
1790
|
+
children: '✔️'
|
|
1791
|
+
}, elementProps],
|
|
1792
|
+
stateAttributesMapping: useButton.transitionStatusMapping
|
|
1793
|
+
});
|
|
1794
|
+
useButton.useOpenChangeComplete({
|
|
1795
|
+
open: selected,
|
|
1796
|
+
ref: indicatorRef,
|
|
1797
|
+
onComplete() {
|
|
1798
|
+
if (!selected) {
|
|
1799
|
+
setMounted(false);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
return element;
|
|
1804
|
+
}));
|
|
1805
|
+
if (process.env.NODE_ENV !== "production") Inner.displayName = "Inner";
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* A text label of the select item.
|
|
1809
|
+
* Renders a `<div>` element.
|
|
1810
|
+
*
|
|
1811
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
1812
|
+
*/
|
|
1813
|
+
const SelectItemText = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef(function SelectItemText(componentProps, forwardedRef) {
|
|
1814
|
+
const {
|
|
1815
|
+
indexRef,
|
|
1816
|
+
textRef,
|
|
1817
|
+
selectedByFocus,
|
|
1818
|
+
hasRegistered
|
|
1819
|
+
} = useSelectItemContext();
|
|
1820
|
+
const {
|
|
1821
|
+
selectedItemTextRef
|
|
1822
|
+
} = useSelectRootContext();
|
|
1823
|
+
const {
|
|
1824
|
+
className,
|
|
1825
|
+
render,
|
|
1826
|
+
...elementProps
|
|
1827
|
+
} = componentProps;
|
|
1828
|
+
const localRef = React__namespace.useCallback(node => {
|
|
1829
|
+
if (!node || !hasRegistered) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
const hasNoSelectedItemText = selectedItemTextRef.current === null || !selectedItemTextRef.current.isConnected;
|
|
1833
|
+
if (selectedByFocus || hasNoSelectedItemText && indexRef.current === 0) {
|
|
1834
|
+
selectedItemTextRef.current = node;
|
|
1835
|
+
}
|
|
1836
|
+
}, [selectedItemTextRef, indexRef, selectedByFocus, hasRegistered]);
|
|
1837
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1838
|
+
ref: [localRef, forwardedRef, textRef],
|
|
1839
|
+
props: elementProps
|
|
1840
|
+
});
|
|
1841
|
+
return element;
|
|
1842
|
+
}));
|
|
1843
|
+
if (process.env.NODE_ENV !== "production") SelectItemText.displayName = "SelectItemText";
|
|
1844
|
+
|
|
1845
|
+
const SelectGroupContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
1846
|
+
if (process.env.NODE_ENV !== "production") SelectGroupContext.displayName = "SelectGroupContext";
|
|
1847
|
+
function useSelectGroupContext() {
|
|
1848
|
+
const context = React__namespace.useContext(SelectGroupContext);
|
|
1849
|
+
if (context === undefined) {
|
|
1850
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: SelectGroupContext is missing. SelectGroup parts must be placed within <Select.Group>.' : useRenderElement.formatErrorMessage(56));
|
|
1851
|
+
}
|
|
1852
|
+
return context;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
const SelectGroup = /*#__PURE__*/React__namespace.forwardRef(function SelectGroup(componentProps, forwardedRef) {
|
|
1856
|
+
const {
|
|
1857
|
+
className,
|
|
1858
|
+
render,
|
|
1859
|
+
...elementProps
|
|
1860
|
+
} = componentProps;
|
|
1861
|
+
const [labelId, setLabelId] = React__namespace.useState();
|
|
1862
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
1863
|
+
labelId,
|
|
1864
|
+
setLabelId
|
|
1865
|
+
}), [labelId, setLabelId]);
|
|
1866
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1867
|
+
ref: forwardedRef,
|
|
1868
|
+
props: [{
|
|
1869
|
+
role: 'group',
|
|
1870
|
+
'aria-labelledby': labelId
|
|
1871
|
+
}, elementProps]
|
|
1872
|
+
});
|
|
1873
|
+
return /*#__PURE__*/jsxRuntime.jsx(SelectGroupContext.Provider, {
|
|
1874
|
+
value: contextValue,
|
|
1875
|
+
children: element
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
if (process.env.NODE_ENV !== "production") SelectGroup.displayName = "SelectGroup";
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* An accessible label that is automatically associated with its parent group.
|
|
1882
|
+
* Renders a `<div>` element.
|
|
1883
|
+
*
|
|
1884
|
+
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
|
|
1885
|
+
*/
|
|
1886
|
+
const SelectGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectGroupLabel(componentProps, forwardedRef) {
|
|
1887
|
+
const {
|
|
1888
|
+
className,
|
|
1889
|
+
render,
|
|
1890
|
+
id: idProp,
|
|
1891
|
+
...elementProps
|
|
1892
|
+
} = componentProps;
|
|
1893
|
+
const {
|
|
1894
|
+
setLabelId
|
|
1895
|
+
} = useSelectGroupContext();
|
|
1896
|
+
const id = useButton.useBaseUiId(idProp);
|
|
1897
|
+
useButton.useIsoLayoutEffect(() => {
|
|
1898
|
+
setLabelId(id);
|
|
1899
|
+
}, [id, setLabelId]);
|
|
1900
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1901
|
+
ref: forwardedRef,
|
|
1902
|
+
props: [{
|
|
1903
|
+
id
|
|
1904
|
+
}, elementProps]
|
|
1905
|
+
});
|
|
1906
|
+
return element;
|
|
1907
|
+
});
|
|
1908
|
+
if (process.env.NODE_ENV !== "production") SelectGroupLabel.displayName = "SelectGroupLabel";
|
|
1909
|
+
|
|
1910
|
+
exports.SelectGroup = SelectGroup;
|
|
1911
|
+
exports.SelectGroupLabel = SelectGroupLabel;
|
|
1912
|
+
exports.SelectItem = SelectItem;
|
|
1913
|
+
exports.SelectItemIndicator = SelectItemIndicator;
|
|
1914
|
+
exports.SelectItemText = SelectItemText;
|
|
1915
|
+
exports.SelectList = SelectList;
|
|
1916
|
+
exports.SelectPopup = SelectPopup;
|
|
1917
|
+
exports.SelectPortal = SelectPortal;
|
|
1918
|
+
exports.SelectPositioner = SelectPositioner;
|
|
1919
|
+
exports.SelectRoot = SelectRoot;
|
|
1920
|
+
exports.SelectTrigger = SelectTrigger;
|
|
1921
|
+
exports.SelectValue = SelectValue;
|
|
1922
|
+
exports.selectors = selectors;
|
|
1923
|
+
exports.useSelectPositionerContext = useSelectPositionerContext;
|
|
1924
|
+
exports.useSelectRootContext = useSelectRootContext;
|