@jobber/components 8.23.0 → 8.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Autocomplete/index.cjs +1 -1
- package/dist/Autocomplete/index.mjs +1 -1
- package/dist/Card/index.cjs +4 -4
- package/dist/Card/index.mjs +4 -4
- package/dist/Chip/index.cjs +1 -1
- package/dist/Chip/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
- package/dist/Chips/index.cjs +1 -1
- package/dist/Chips/index.mjs +1 -1
- package/dist/ComboboxChipRemove-cjs.js +668 -379
- package/dist/ComboboxChipRemove-es.js +508 -219
- package/dist/ConfirmationModal/index.cjs +1 -1
- package/dist/ConfirmationModal/index.mjs +1 -1
- package/dist/DataDump/index.cjs +4 -4
- package/dist/DataDump/index.mjs +4 -4
- package/dist/DataList/components/DataListActions/index.cjs +1 -1
- package/dist/DataList/components/DataListActions/index.mjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
- package/dist/DataList/components/DataListFilters/index.cjs +1 -1
- package/dist/DataList/components/DataListFilters/index.mjs +1 -1
- package/dist/DataList/components/DataListHeader/index.cjs +1 -1
- package/dist/DataList/components/DataListHeader/index.mjs +1 -1
- package/dist/DataList/components/DataListItem/index.cjs +1 -1
- package/dist/DataList/components/DataListItem/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
- package/dist/DataList/components/DataListItems/index.cjs +1 -1
- package/dist/DataList/components/DataListItems/index.mjs +1 -1
- package/dist/DataList/components/DataListLayout/index.cjs +1 -1
- package/dist/DataList/components/DataListLayout/index.mjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
- package/dist/DataList/index.cjs +1 -1
- package/dist/DataList/index.mjs +1 -1
- package/dist/DatePicker/index.cjs +1 -1
- package/dist/DatePicker/index.mjs +1 -1
- package/dist/Dialog/index.cjs +3 -3
- package/dist/Dialog/index.mjs +3 -3
- package/dist/DrawerDescription-cjs.js +32 -33
- package/dist/DrawerDescription-es.js +19 -20
- package/dist/DrawerTitle-cjs.js +7 -5
- package/dist/DrawerTitle-es.js +7 -5
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
- package/dist/FilterPicker/index.cjs +1 -1
- package/dist/FilterPicker/index.mjs +1 -1
- package/dist/FormFieldWrapper-cjs.js +2 -2
- package/dist/FormFieldWrapper-es.js +2 -2
- package/dist/FormatFile/index.cjs +1 -1
- package/dist/FormatFile/index.mjs +1 -1
- package/dist/Gallery/index.cjs +1 -1
- package/dist/Gallery/index.mjs +1 -1
- package/dist/InputDate/index.cjs +1 -1
- package/dist/InputDate/index.mjs +1 -1
- package/dist/InputNumberExperimental-cjs.js +319 -102
- package/dist/InputNumberExperimental-es.js +321 -104
- package/dist/LightBox/index.cjs +1 -1
- package/dist/LightBox/index.mjs +1 -1
- package/dist/Menu/index.cjs +4 -4
- package/dist/Menu/index.mjs +4 -4
- package/dist/MenuSubmenuTrigger-cjs.js +545 -498
- package/dist/MenuSubmenuTrigger-es.js +490 -443
- package/dist/Modal/index.cjs +1 -1
- package/dist/Modal/index.mjs +1 -1
- package/dist/NumberFieldInput-cjs.js +609 -496
- package/dist/NumberFieldInput-es.js +612 -498
- package/dist/Page/index.cjs +4 -4
- package/dist/Page/index.mjs +4 -4
- package/dist/Popover/index.cjs +1 -1
- package/dist/Popover/index.mjs +1 -1
- package/dist/ScrollAreaViewport-cjs.js +326 -601
- package/dist/ScrollAreaViewport-es.js +278 -548
- package/dist/SelectGroupLabel-cjs.js +494 -528
- package/dist/SelectGroupLabel-es.js +411 -445
- package/dist/Separator-cjs.js +1 -0
- package/dist/Separator-es.js +1 -0
- package/dist/Tooltip/index.cjs +1 -1
- package/dist/Tooltip/index.mjs +1 -1
- package/dist/docs/InputTime/InputTime.md +8 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
- package/dist/floating-ui.react-cjs.js +586 -10
- package/dist/floating-ui.react-dom-cjs.js +92 -92
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +580 -4
- package/dist/floating-ui.utils-cjs.js +159 -0
- package/dist/floating-ui.utils-es.js +136 -0
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
- package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
- package/dist/primitives/BottomSheet/index.cjs +3 -3
- package/dist/primitives/BottomSheet/index.mjs +3 -3
- package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
- package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
- package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
- package/dist/primitives/SelectPrimitive/index.cjs +5 -5
- package/dist/primitives/SelectPrimitive/index.mjs +5 -5
- package/dist/primitives/index.cjs +4 -4
- package/dist/primitives/index.mjs +4 -4
- package/dist/resolveAriaLabelledBy-cjs.js +5 -0
- package/dist/resolveAriaLabelledBy-es.js +5 -1
- package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
- package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
- package/dist/styles.css +9 -3
- package/dist/unstyledPrimitives/index.cjs +1217 -639
- package/dist/unstyledPrimitives/index.mjs +1163 -585
- package/dist/useButton-cjs.js +283 -317
- package/dist/useButton-es.js +277 -298
- package/dist/useCompositeListItem-cjs.js +328 -279
- package/dist/useCompositeListItem-es.js +293 -246
- package/dist/useLabel-cjs.js +11 -1
- package/dist/useLabel-es.js +11 -2
- package/dist/useLabelableId-cjs.js +37 -91
- package/dist/useLabelableId-es.js +37 -90
- package/dist/useRenderElement-cjs.js +81 -26
- package/dist/useRenderElement-es.js +81 -27
- package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
- package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
- package/package.json +3 -3
- package/dist/index.esm-cjs.js +0 -738
- package/dist/index.esm-es.js +0 -712
|
@@ -1,19 +1,37 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { f as formatErrorMessage, a as useRefWithInit, m as mergeProps,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { z as useControlled, k as useTransitionStatus, m as useValueAsRef, b as useStableCallback, a as useIsoLayoutEffect, O as useValueChanged, f as useOpenChangeComplete, d as createChangeEventDetails, C as none, D as visuallyHiddenInput, E as visuallyHidden, v as focusOut, Q as outsidePress, c as useButton, h as useTimeout, q as contains, o as ownerDocument, R as cancelOpen, t as transitionStatusMapping, n as useAnimationFrame, B as addEventListener, U as windowResize, w as isVirtualClick, V as itemPress, u as useBaseUiId } from './useButton-es.js';
|
|
3
|
+
import { f as formatErrorMessage, a as useRefWithInit, m as mergeProps, E as EMPTY_OBJECT, c as useMergedRefs, b as EMPTY_ARRAY, u as useRenderElement } from './useRenderElement-es.js';
|
|
4
|
+
import { t as createSelector, v as useOpenInteractionType, w as useStore, x as useClick, y as useDismiss, F as FOCUSABLE_POPUP_PROPS, z as useOnFirstRender, S as Store, B as pressableTriggerOpenStateMapping, g as getFloatingFocusElement, E as FloatingPortal, I as InternalBackdrop, G as inertValue, H as DROPDOWN_COLLISION_AVOIDANCE, p as popupStateMapping, r as useDirection, J as webkit, a as FloatingFocusManager } from './useScrollLock-es.js';
|
|
5
|
+
import { i as isElementDisabled, u as useToolbarRootContext } from './isElementDisabled-es.js';
|
|
6
|
+
import { a as useFormContext, b as useFieldRootContext, c as useLabelableId, d as useRegisterFieldControl, f as fieldValidityMapping, u as useLabelableContext } from './useLabelableId-es.js';
|
|
7
|
+
import { s as stringifyAsValue, h as hasNullItemLabel, c as compareItemEquality, f as findItemIndex, d as defaultItemEquality, a as stringifyAsLabel, b as areArraysEqual, r as resolveAriaLabelledBy, e as resolveMultipleLabels, g as resolveSelectedLabel, i as selectedValueIncludes, j as removeItem } from './resolveAriaLabelledBy-es.js';
|
|
8
|
+
import { g as getMaxScrollOffset, n as normalizeScrollOffset, u as useCSPContext, S as SCROLL_EDGE_TOLERANCE_PX, C as COMPOSITE_KEYS, s as styleDisableScrollbar } from './scrollEdges-es.js';
|
|
7
9
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
|
-
import { u as useFloatingRootContext, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds, c as useAnchorPositioning, C as CompositeList,
|
|
9
|
-
import
|
|
10
|
-
import { r as rectToClientRect } from './index.esm-es.js';
|
|
11
|
-
import { u as useCSPContext, C as COMPOSITE_KEYS, s as styleDisableScrollbar } from './CSPContext-es.js';
|
|
12
|
-
import { u as useToolbarRootContext } from './ToolbarRootContext-es.js';
|
|
10
|
+
import { u as useFloatingRootContext, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds, c as useAnchoredPopupScrollLock, d as useAnchorPositioning, e as usePositioner, C as CompositeList, f as getDisabledMountTransitionStyles, h as useCompositeListItem, I as IndexGuessBehavior } from './useCompositeListItem-es.js';
|
|
11
|
+
import { r as rectToClientRect } from './floating-ui.utils-es.js';
|
|
13
12
|
import { c as clamp } from './clamp-es.js';
|
|
14
13
|
import { o as getWindow } from './floating-ui.utils.dom-es.js';
|
|
15
14
|
import { p as platform } from './floating-ui.react-dom-es.js';
|
|
16
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Returns a previous value of its argument.
|
|
18
|
+
* @param value Current value.
|
|
19
|
+
* @returns Previous value, or null if there is no previous value.
|
|
20
|
+
*/
|
|
21
|
+
function usePreviousValue(value) {
|
|
22
|
+
const [state, setState] = React.useState({
|
|
23
|
+
current: value,
|
|
24
|
+
previous: null
|
|
25
|
+
});
|
|
26
|
+
if (value !== state.current) {
|
|
27
|
+
setState({
|
|
28
|
+
current: value,
|
|
29
|
+
previous: state.current
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return state.previous;
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
const SelectRootContext = /*#__PURE__*/React.createContext(null);
|
|
18
36
|
if (process.env.NODE_ENV !== "production") SelectRootContext.displayName = "SelectRootContext";
|
|
19
37
|
const SelectFloatingContext = /*#__PURE__*/React.createContext(null);
|
|
@@ -68,18 +86,16 @@ const selectors = {
|
|
|
68
86
|
activeIndex: createSelector(state => state.activeIndex),
|
|
69
87
|
selectedIndex: createSelector(state => state.selectedIndex),
|
|
70
88
|
isActive: createSelector((state, index) => state.activeIndex === index),
|
|
71
|
-
isSelected: createSelector((state,
|
|
89
|
+
isSelected: createSelector((state, itemValue) => {
|
|
72
90
|
const comparer = state.isItemEqualToValue;
|
|
73
91
|
const storeValue = state.value;
|
|
74
92
|
if (state.multiple) {
|
|
75
93
|
return Array.isArray(storeValue) && storeValue.some(selectedItem => compareItemEquality(itemValue, selectedItem, comparer));
|
|
76
94
|
}
|
|
77
95
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
96
|
+
// The value is the source of truth: a stale `selectedIndex` (e.g. the controlled
|
|
97
|
+
// value changes while the popup is open, where the index sync is deferred) must not
|
|
98
|
+
// keep a previously selected item marked as selected.
|
|
83
99
|
return compareItemEquality(itemValue, storeValue, comparer);
|
|
84
100
|
}),
|
|
85
101
|
isSelectedByFocus: createSelector((state, index) => {
|
|
@@ -90,6 +106,7 @@ const selectors = {
|
|
|
90
106
|
triggerElement: createSelector(state => state.triggerElement),
|
|
91
107
|
positionerElement: createSelector(state => state.positionerElement),
|
|
92
108
|
listElement: createSelector(state => state.listElement),
|
|
109
|
+
popupSide: createSelector(state => state.popupSide),
|
|
93
110
|
scrollUpArrowVisible: createSelector(state => state.scrollUpArrowVisible),
|
|
94
111
|
scrollDownArrowVisible: createSelector(state => state.scrollDownArrowVisible),
|
|
95
112
|
hasScrollArrows: createSelector(state => state.hasScrollArrows)
|
|
@@ -105,6 +122,7 @@ function SelectRoot(props) {
|
|
|
105
122
|
defaultOpen = false,
|
|
106
123
|
onOpenChange,
|
|
107
124
|
name: nameProp,
|
|
125
|
+
form,
|
|
108
126
|
autoComplete,
|
|
109
127
|
disabled: disabledProp = false,
|
|
110
128
|
readOnly = false,
|
|
@@ -128,7 +146,6 @@ function SelectRoot(props) {
|
|
|
128
146
|
setDirty,
|
|
129
147
|
setTouched,
|
|
130
148
|
setFocused,
|
|
131
|
-
shouldValidateOnChange,
|
|
132
149
|
validityData,
|
|
133
150
|
setFilled,
|
|
134
151
|
name: fieldName,
|
|
@@ -161,11 +178,12 @@ function SelectRoot(props) {
|
|
|
161
178
|
const valueRef = React.useRef(null);
|
|
162
179
|
const valuesRef = React.useRef([]);
|
|
163
180
|
const typingRef = React.useRef(false);
|
|
164
|
-
const
|
|
181
|
+
const firstItemTextRef = React.useRef(null);
|
|
165
182
|
const selectedItemTextRef = React.useRef(null);
|
|
166
183
|
const selectionRef = React.useRef({
|
|
167
184
|
allowSelectedMouseUp: false,
|
|
168
|
-
allowUnselectedMouseUp: false
|
|
185
|
+
allowUnselectedMouseUp: false,
|
|
186
|
+
dragY: 0
|
|
169
187
|
});
|
|
170
188
|
const alignItemWithTriggerActiveRef = React.useRef(false);
|
|
171
189
|
const {
|
|
@@ -199,6 +217,7 @@ function SelectRoot(props) {
|
|
|
199
217
|
triggerElement: null,
|
|
200
218
|
positionerElement: null,
|
|
201
219
|
listElement: null,
|
|
220
|
+
popupSide: null,
|
|
202
221
|
scrollUpArrowVisible: false,
|
|
203
222
|
scrollDownArrowVisible: false,
|
|
204
223
|
hasScrollArrows: false
|
|
@@ -207,8 +226,13 @@ function SelectRoot(props) {
|
|
|
207
226
|
const selectedIndex = useStore(store, selectors.selectedIndex);
|
|
208
227
|
const triggerElement = useStore(store, selectors.triggerElement);
|
|
209
228
|
const positionerElement = useStore(store, selectors.positionerElement);
|
|
229
|
+
const previousOpenMethod = usePreviousValue(openMethod);
|
|
230
|
+
const renderedOpenMethod = openMethod ?? previousOpenMethod ?? null;
|
|
210
231
|
const serializedValue = React.useMemo(() => {
|
|
211
|
-
|
|
232
|
+
// In multiple mode the shared input is nameless; per-value entries are submitted via
|
|
233
|
+
// `hiddenInputs`. Its value is therefore irrelevant, and passing the whole array to
|
|
234
|
+
// `stringifyAsValue` would invoke a user `itemToStringValue` with an array it doesn't expect.
|
|
235
|
+
if (multiple) {
|
|
212
236
|
return '';
|
|
213
237
|
}
|
|
214
238
|
return stringifyAsValue(value, itemToStringValue);
|
|
@@ -220,15 +244,12 @@ function SelectRoot(props) {
|
|
|
220
244
|
return stringifyAsValue(value, itemToStringValue);
|
|
221
245
|
}, [multiple, value, itemToStringValue]);
|
|
222
246
|
const controlRef = useValueAsRef(store.state.triggerElement);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
commit: validation.commit,
|
|
226
|
-
value,
|
|
227
|
-
controlRef,
|
|
228
|
-
name,
|
|
229
|
-
getValue: () => fieldStringValue
|
|
230
|
-
});
|
|
247
|
+
const getStringifiedValueForForm = useStableCallback(() => fieldStringValue);
|
|
248
|
+
useRegisterFieldControl(controlRef, generatedId, value, getStringifiedValueForForm, !disabled, nameProp);
|
|
231
249
|
const initialValueRef = React.useRef(value);
|
|
250
|
+
// Mirror the `hasSelectedValue` store selector so the Field's filled state agrees with the
|
|
251
|
+
// trigger/value placeholder semantics (a value serializing to `''` counts as empty).
|
|
252
|
+
const hasSelectedValue = multiple ? Array.isArray(value) && value.length > 0 : value != null && stringifyAsValue(value, itemToStringValue) !== '';
|
|
232
253
|
useIsoLayoutEffect(() => {
|
|
233
254
|
// Ensure the values and labels are registered for programmatic value changes.
|
|
234
255
|
if (value !== initialValueRef.current) {
|
|
@@ -236,35 +257,43 @@ function SelectRoot(props) {
|
|
|
236
257
|
}
|
|
237
258
|
}, [store, value]);
|
|
238
259
|
useIsoLayoutEffect(() => {
|
|
239
|
-
setFilled(
|
|
240
|
-
}, [
|
|
260
|
+
setFilled(hasSelectedValue);
|
|
261
|
+
}, [hasSelectedValue, setFilled]);
|
|
241
262
|
useIsoLayoutEffect(function syncSelectedIndex() {
|
|
242
|
-
if (open) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
263
|
const registry = valuesRef.current;
|
|
264
|
+
let nextIndex;
|
|
246
265
|
if (multiple) {
|
|
247
266
|
const currentValue = Array.isArray(value) ? value : [];
|
|
248
267
|
if (currentValue.length === 0) {
|
|
249
|
-
|
|
250
|
-
|
|
268
|
+
nextIndex = null;
|
|
269
|
+
} else {
|
|
270
|
+
const lastValue = currentValue[currentValue.length - 1];
|
|
271
|
+
const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);
|
|
272
|
+
nextIndex = lastIndex === -1 ? null : lastIndex;
|
|
251
273
|
}
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
274
|
+
} else {
|
|
275
|
+
const index = findItemIndex(registry, value, isItemEqualToValue);
|
|
276
|
+
nextIndex = index === -1 ? null : index;
|
|
277
|
+
}
|
|
278
|
+
if (nextIndex === null) {
|
|
279
|
+
selectedItemTextRef.current = null;
|
|
280
|
+
}
|
|
281
|
+
if (open) {
|
|
255
282
|
return;
|
|
256
283
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
284
|
+
store.set('selectedIndex', nextIndex);
|
|
285
|
+
}, [hasSelectedValue, multiple, open, value, valuesRef, isItemEqualToValue, store, selectedItemTextRef]);
|
|
286
|
+
function isSelectedValueDirty(currentValue) {
|
|
287
|
+
const initialValue = validityData.initialValue;
|
|
288
|
+
if (Array.isArray(currentValue) && Array.isArray(initialValue)) {
|
|
289
|
+
return !areArraysEqual(currentValue, initialValue, (itemValue, initialItemValue) => compareItemEquality(itemValue, initialItemValue, isItemEqualToValue));
|
|
290
|
+
}
|
|
291
|
+
return currentValue !== initialValue;
|
|
292
|
+
}
|
|
260
293
|
useValueChanged(value, () => {
|
|
261
294
|
clearErrors(name);
|
|
262
|
-
setDirty(value
|
|
263
|
-
|
|
264
|
-
validation.commit(value);
|
|
265
|
-
} else {
|
|
266
|
-
validation.commit(value, true);
|
|
267
|
-
}
|
|
295
|
+
setDirty(isSelectedValueDirty(value));
|
|
296
|
+
validation.change(value);
|
|
268
297
|
});
|
|
269
298
|
const setOpen = useStableCallback((nextOpen, eventDetails) => {
|
|
270
299
|
onOpenChange?.(nextOpen, eventDetails);
|
|
@@ -279,24 +308,13 @@ function SelectRoot(props) {
|
|
|
279
308
|
validation.commit(value);
|
|
280
309
|
}
|
|
281
310
|
}
|
|
282
|
-
|
|
283
|
-
// The active index will sync to the last selected index on the next open.
|
|
284
|
-
// Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted
|
|
285
|
-
// option upon close when tabbing out due to `keepMounted=true`:
|
|
286
|
-
// https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199
|
|
287
|
-
// This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed
|
|
288
|
-
// when tabbing outside.
|
|
289
|
-
if (!nextOpen && store.state.activeIndex !== null) {
|
|
290
|
-
const activeOption = listRef.current[store.state.activeIndex];
|
|
291
|
-
// Wait for Floating UI's focus effect to have fired
|
|
292
|
-
queueMicrotask(() => {
|
|
293
|
-
activeOption?.setAttribute('tabindex', '-1');
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
311
|
});
|
|
297
312
|
const handleUnmount = useStableCallback(() => {
|
|
298
313
|
setMounted(false);
|
|
299
|
-
store.
|
|
314
|
+
store.update({
|
|
315
|
+
activeIndex: null,
|
|
316
|
+
openMethod: null
|
|
317
|
+
});
|
|
300
318
|
onOpenChangeComplete?.(false);
|
|
301
319
|
});
|
|
302
320
|
useOpenChangeComplete({
|
|
@@ -324,10 +342,10 @@ function SelectRoot(props) {
|
|
|
324
342
|
if (!scroller) {
|
|
325
343
|
return;
|
|
326
344
|
}
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
const shouldShowUp =
|
|
330
|
-
const shouldShowDown =
|
|
345
|
+
const maxScrollTop = getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
346
|
+
const scrollTop = normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
|
|
347
|
+
const shouldShowUp = scrollTop > 0;
|
|
348
|
+
const shouldShowDown = scrollTop < maxScrollTop;
|
|
331
349
|
if (store.state.scrollUpArrowVisible !== shouldShowUp) {
|
|
332
350
|
store.set('scrollUpArrowVisible', shouldShowUp);
|
|
333
351
|
}
|
|
@@ -347,9 +365,7 @@ function SelectRoot(props) {
|
|
|
347
365
|
enabled: !readOnly && !disabled,
|
|
348
366
|
event: 'mousedown'
|
|
349
367
|
});
|
|
350
|
-
const dismiss = useDismiss(floatingContext
|
|
351
|
-
bubbles: false
|
|
352
|
-
});
|
|
368
|
+
const dismiss = useDismiss(floatingContext);
|
|
353
369
|
const listNavigation = useListNavigation(floatingContext, {
|
|
354
370
|
enabled: !readOnly && !disabled,
|
|
355
371
|
listRef,
|
|
@@ -363,15 +379,19 @@ function SelectRoot(props) {
|
|
|
363
379
|
}
|
|
364
380
|
store.set('activeIndex', nextActiveIndex);
|
|
365
381
|
},
|
|
366
|
-
|
|
367
|
-
// the `alignItemWithTrigger=true`, causing a performance issue on Chrome.
|
|
368
|
-
focusItemOnHover: false
|
|
382
|
+
focusItemOnHover: highlightItemOnHover
|
|
369
383
|
});
|
|
370
384
|
const typeahead = useTypeahead(floatingContext, {
|
|
371
385
|
enabled: !readOnly && !disabled && (open || !multiple),
|
|
372
386
|
listRef: labelsRef,
|
|
373
387
|
activeIndex,
|
|
374
388
|
selectedIndex,
|
|
389
|
+
// Skip disabled items while matching so typeahead advances to the next selectable item
|
|
390
|
+
// (a click can never select a disabled item and native `<select>` skips them too). Resolve
|
|
391
|
+
// the disabled state from the element via the attribute-only `isElementDisabled` so the
|
|
392
|
+
// hidden, force-mounted items used for closed-trigger typeahead aren't dropped by the
|
|
393
|
+
// `elementsRef`/visibility filter that `disabledIndices` deliberately sidesteps.
|
|
394
|
+
disabledIndices: index => isElementDisabled(listRef.current[index]),
|
|
375
395
|
onMatch(index) {
|
|
376
396
|
if (open) {
|
|
377
397
|
store.set('activeIndex', index);
|
|
@@ -379,23 +399,22 @@ function SelectRoot(props) {
|
|
|
379
399
|
setValue(valuesRef.current[index], createChangeEventDetails('none'));
|
|
380
400
|
}
|
|
381
401
|
},
|
|
382
|
-
|
|
402
|
+
onTyping(typing) {
|
|
383
403
|
typingRef.current = typing;
|
|
384
404
|
}
|
|
385
405
|
});
|
|
386
|
-
const {
|
|
387
|
-
getReferenceProps,
|
|
388
|
-
getFloatingProps,
|
|
389
|
-
getItemProps
|
|
390
|
-
} = useInteractions([click, dismiss, listNavigation, typeahead]);
|
|
391
406
|
const mergedTriggerProps = React.useMemo(() => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
407
|
+
const triggerInteractionProps = mergeProps(typeahead.reference, listNavigation.reference, dismiss.reference, click.reference, interactionTypeProps);
|
|
408
|
+
if (generatedId) {
|
|
409
|
+
triggerInteractionProps.id = generatedId;
|
|
410
|
+
}
|
|
411
|
+
return triggerInteractionProps;
|
|
412
|
+
}, [click.reference, typeahead.reference, listNavigation.reference, dismiss.reference, interactionTypeProps, generatedId]);
|
|
413
|
+
const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, typeahead.floating, listNavigation.floating, dismiss.floating), [typeahead.floating, listNavigation.floating, dismiss.floating]);
|
|
414
|
+
const itemProps = listNavigation.item ?? EMPTY_OBJECT;
|
|
396
415
|
useOnFirstRender(() => {
|
|
397
416
|
store.update({
|
|
398
|
-
popupProps
|
|
417
|
+
popupProps,
|
|
399
418
|
triggerProps: mergedTriggerProps
|
|
400
419
|
});
|
|
401
420
|
});
|
|
@@ -408,15 +427,15 @@ function SelectRoot(props) {
|
|
|
408
427
|
open,
|
|
409
428
|
mounted,
|
|
410
429
|
transitionStatus,
|
|
411
|
-
popupProps
|
|
430
|
+
popupProps,
|
|
412
431
|
triggerProps: mergedTriggerProps,
|
|
413
432
|
items,
|
|
414
433
|
itemToStringLabel,
|
|
415
434
|
itemToStringValue,
|
|
416
435
|
isItemEqualToValue,
|
|
417
|
-
openMethod
|
|
436
|
+
openMethod: renderedOpenMethod
|
|
418
437
|
});
|
|
419
|
-
}, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus,
|
|
438
|
+
}, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus, popupProps, mergedTriggerProps, items, itemToStringLabel, itemToStringValue, isItemEqualToValue, renderedOpenMethod]);
|
|
420
439
|
const contextValue = React.useMemo(() => ({
|
|
421
440
|
store,
|
|
422
441
|
name,
|
|
@@ -424,8 +443,6 @@ function SelectRoot(props) {
|
|
|
424
443
|
disabled,
|
|
425
444
|
readOnly,
|
|
426
445
|
multiple,
|
|
427
|
-
itemToStringLabel,
|
|
428
|
-
itemToStringValue,
|
|
429
446
|
highlightItemOnHover,
|
|
430
447
|
setValue,
|
|
431
448
|
setOpen,
|
|
@@ -434,20 +451,19 @@ function SelectRoot(props) {
|
|
|
434
451
|
scrollHandlerRef,
|
|
435
452
|
handleScrollArrowVisibility,
|
|
436
453
|
scrollArrowsMountedCountRef,
|
|
437
|
-
|
|
438
|
-
events: floatingContext.context.events,
|
|
454
|
+
itemProps,
|
|
439
455
|
valueRef,
|
|
440
456
|
valuesRef,
|
|
441
457
|
labelsRef,
|
|
442
458
|
typingRef,
|
|
443
459
|
selectionRef,
|
|
460
|
+
firstItemTextRef,
|
|
444
461
|
selectedItemTextRef,
|
|
445
462
|
validation,
|
|
446
463
|
onOpenChangeComplete,
|
|
447
|
-
keyboardActiveRef,
|
|
448
464
|
alignItemWithTriggerActiveRef,
|
|
449
465
|
initialValueRef
|
|
450
|
-
}), [store, name, required, disabled, readOnly, multiple,
|
|
466
|
+
}), [store, name, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
|
|
451
467
|
const ref = useMergedRefs(inputRef, validation.inputRef);
|
|
452
468
|
const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;
|
|
453
469
|
const hiddenInputName = multiple ? undefined : name;
|
|
@@ -459,32 +475,33 @@ function SelectRoot(props) {
|
|
|
459
475
|
const currentSerializedValue = stringifyAsValue(v, itemToStringValue);
|
|
460
476
|
return /*#__PURE__*/jsx("input", {
|
|
461
477
|
type: "hidden",
|
|
478
|
+
form: form,
|
|
462
479
|
name: name,
|
|
463
|
-
value: currentSerializedValue
|
|
480
|
+
value: currentSerializedValue,
|
|
481
|
+
disabled: disabled
|
|
464
482
|
}, currentSerializedValue);
|
|
465
483
|
});
|
|
466
|
-
}, [multiple, value, name, itemToStringValue]);
|
|
484
|
+
}, [multiple, value, form, name, itemToStringValue, disabled]);
|
|
467
485
|
return /*#__PURE__*/jsx(SelectRootContext.Provider, {
|
|
468
486
|
value: contextValue,
|
|
469
487
|
children: /*#__PURE__*/jsxs(SelectFloatingContext.Provider, {
|
|
470
488
|
value: floatingContext,
|
|
471
489
|
children: [children, /*#__PURE__*/jsx("input", {
|
|
472
|
-
...validation.
|
|
490
|
+
...validation.getValidationProps(disabled, {
|
|
473
491
|
onFocus() {
|
|
474
492
|
// Move focus to the trigger element when the hidden input is focused.
|
|
475
493
|
store.state.triggerElement?.focus({
|
|
476
494
|
// Supported in Chrome from 144 (January 2026)
|
|
477
|
-
// @ts-expect-error - focusVisible is not yet in the lib.dom.d.ts
|
|
478
495
|
focusVisible: true
|
|
479
496
|
});
|
|
480
497
|
},
|
|
481
498
|
// Handle browser autofill.
|
|
482
499
|
onChange(event) {
|
|
483
500
|
// Workaround for https://github.com/facebook/react/issues/9023
|
|
484
|
-
if (event.nativeEvent.defaultPrevented) {
|
|
501
|
+
if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
|
|
485
502
|
return;
|
|
486
503
|
}
|
|
487
|
-
const nextValue = event.
|
|
504
|
+
const nextValue = event.currentTarget.value;
|
|
488
505
|
const details = createChangeEventDetails(none, event.nativeEvent);
|
|
489
506
|
function handleChange() {
|
|
490
507
|
if (multiple) {
|
|
@@ -492,20 +509,21 @@ function SelectRoot(props) {
|
|
|
492
509
|
return;
|
|
493
510
|
}
|
|
494
511
|
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
512
|
+
// Preserve the original serialized matching, then fall back to rendered text,
|
|
513
|
+
// which browsers can autofill for primitive values like `value="US">United States`.
|
|
514
|
+
const nextValueLower = nextValue.toLowerCase();
|
|
515
|
+
let matchingIndex = valuesRef.current.findIndex(candidate => stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
|
|
516
|
+
if (matchingIndex === -1) {
|
|
517
|
+
matchingIndex = valuesRef.current.findIndex((_, index) => {
|
|
518
|
+
const renderedLabel = labelsRef.current[index];
|
|
519
|
+
return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const matchingValue = matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
|
|
503
523
|
if (matchingValue != null) {
|
|
504
|
-
|
|
524
|
+
// `setValue` may be canceled by `onValueChange`; rely on `useValueChanged` to
|
|
525
|
+
// mark the field dirty and run validation only when the value actually changes.
|
|
505
526
|
setValue(matchingValue, details);
|
|
506
|
-
if (shouldValidateOnChange()) {
|
|
507
|
-
validation.commit(matchingValue);
|
|
508
|
-
}
|
|
509
527
|
}
|
|
510
528
|
}
|
|
511
529
|
store.set('forceMount', true);
|
|
@@ -513,6 +531,7 @@ function SelectRoot(props) {
|
|
|
513
531
|
}
|
|
514
532
|
}),
|
|
515
533
|
id: generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined,
|
|
534
|
+
form: form,
|
|
516
535
|
name: hiddenInputName,
|
|
517
536
|
autoComplete: autoComplete,
|
|
518
537
|
value: serializedValue,
|
|
@@ -522,7 +541,8 @@ function SelectRoot(props) {
|
|
|
522
541
|
ref: ref,
|
|
523
542
|
style: name ? visuallyHiddenInput : visuallyHidden,
|
|
524
543
|
tabIndex: -1,
|
|
525
|
-
"aria-hidden": true
|
|
544
|
+
"aria-hidden": true,
|
|
545
|
+
suppressHydrationWarning: true
|
|
526
546
|
}), hiddenInputs]
|
|
527
547
|
})
|
|
528
548
|
});
|
|
@@ -530,10 +550,12 @@ function SelectRoot(props) {
|
|
|
530
550
|
|
|
531
551
|
const BOUNDARY_OFFSET = 2;
|
|
532
552
|
const SELECTED_DELAY = 400;
|
|
533
|
-
const UNSELECTED_DELAY = 200;
|
|
534
553
|
const stateAttributesMapping$2 = {
|
|
535
554
|
...pressableTriggerOpenStateMapping,
|
|
536
555
|
...fieldValidityMapping,
|
|
556
|
+
popupSide: side => side ? {
|
|
557
|
+
'data-popup-side': side
|
|
558
|
+
} : null,
|
|
537
559
|
value: () => null
|
|
538
560
|
};
|
|
539
561
|
|
|
@@ -550,6 +572,7 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
550
572
|
id: idProp,
|
|
551
573
|
disabled: disabledProp = false,
|
|
552
574
|
nativeButton = true,
|
|
575
|
+
style,
|
|
553
576
|
...elementProps
|
|
554
577
|
} = componentProps;
|
|
555
578
|
const {
|
|
@@ -570,20 +593,20 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
570
593
|
readOnly,
|
|
571
594
|
required,
|
|
572
595
|
alignItemWithTriggerActiveRef,
|
|
573
|
-
disabled: selectDisabled
|
|
574
|
-
keyboardActiveRef
|
|
596
|
+
disabled: selectDisabled
|
|
575
597
|
} = useSelectRootContext();
|
|
576
598
|
const disabled = fieldDisabled || selectDisabled || disabledProp;
|
|
577
599
|
const open = useStore(store, selectors.open);
|
|
600
|
+
const mounted = useStore(store, selectors.mounted);
|
|
578
601
|
const value = useStore(store, selectors.value);
|
|
579
602
|
const triggerProps = useStore(store, selectors.triggerProps);
|
|
580
603
|
const positionerElement = useStore(store, selectors.positionerElement);
|
|
581
604
|
const listElement = useStore(store, selectors.listElement);
|
|
605
|
+
const popupSideValue = useStore(store, selectors.popupSide);
|
|
582
606
|
const rootId = useStore(store, selectors.id);
|
|
583
607
|
const selectLabelId = useStore(store, selectors.labelId);
|
|
584
608
|
const hasSelectedValue = useStore(store, selectors.hasSelectedValue);
|
|
585
|
-
const
|
|
586
|
-
const hasNullItemLabel = useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
|
|
609
|
+
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
587
610
|
const id = idProp ?? rootId;
|
|
588
611
|
const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, selectLabelId);
|
|
589
612
|
useLabelableId({
|
|
@@ -601,61 +624,41 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
601
624
|
const setTriggerElement = useStableCallback(element => {
|
|
602
625
|
store.set('triggerElement', element);
|
|
603
626
|
});
|
|
604
|
-
const mergedRef = useMergedRefs(forwardedRef, triggerRef, buttonRef, setTriggerElement);
|
|
605
627
|
const timeoutFocus = useTimeout();
|
|
606
628
|
const timeoutMouseDown = useTimeout();
|
|
607
629
|
const selectedDelayTimeout = useTimeout();
|
|
608
|
-
const unselectedDelayTimeout = useTimeout();
|
|
609
630
|
React.useEffect(() => {
|
|
610
631
|
if (open) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
selectionRef.current.allowUnselectedMouseUp = true;
|
|
620
|
-
selectionRef.current.allowSelectedMouseUp = true;
|
|
621
|
-
});
|
|
622
|
-
} else {
|
|
623
|
-
// mousedown -> move to unselected item -> mouseup should not select within 200ms.
|
|
624
|
-
unselectedDelayTimeout.start(UNSELECTED_DELAY, () => {
|
|
625
|
-
selectionRef.current.allowUnselectedMouseUp = true;
|
|
626
|
-
|
|
627
|
-
// mousedown -> mouseup on selected item should not select within 400ms.
|
|
628
|
-
selectedDelayTimeout.start(UNSELECTED_DELAY, () => {
|
|
629
|
-
selectionRef.current.allowSelectedMouseUp = true;
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
}
|
|
632
|
+
// A mousedown on the trigger can open the popup under the cursor. Keep mouseup selection
|
|
633
|
+
// disabled briefly so releasing over either the selected item or a neighboring item doesn't
|
|
634
|
+
// commit an accidental selection. SelectItem can still opt into unselected mouseup sooner
|
|
635
|
+
// after a real drag over the item.
|
|
636
|
+
selectedDelayTimeout.start(SELECTED_DELAY, () => {
|
|
637
|
+
selectionRef.current.allowUnselectedMouseUp = true;
|
|
638
|
+
selectionRef.current.allowSelectedMouseUp = true;
|
|
639
|
+
});
|
|
633
640
|
return () => {
|
|
634
641
|
selectedDelayTimeout.clear();
|
|
635
|
-
unselectedDelayTimeout.clear();
|
|
636
642
|
};
|
|
637
643
|
}
|
|
638
644
|
selectionRef.current = {
|
|
639
645
|
allowSelectedMouseUp: false,
|
|
640
|
-
allowUnselectedMouseUp: false
|
|
646
|
+
allowUnselectedMouseUp: false,
|
|
647
|
+
dragY: 0
|
|
641
648
|
};
|
|
642
649
|
timeoutMouseDown.clear();
|
|
643
650
|
return undefined;
|
|
644
|
-
}, [open,
|
|
645
|
-
const
|
|
646
|
-
return listElement?.id ?? getFloatingFocusElement(positionerElement)?.id;
|
|
647
|
-
}, [listElement, positionerElement]);
|
|
648
|
-
const props = mergeProps(triggerProps, {
|
|
651
|
+
}, [open, selectionRef, timeoutMouseDown, selectedDelayTimeout]);
|
|
652
|
+
const mergedProps = mergeProps(triggerProps, {
|
|
649
653
|
id,
|
|
650
654
|
role: 'combobox',
|
|
651
655
|
'aria-expanded': open ? 'true' : 'false',
|
|
652
656
|
'aria-haspopup': 'listbox',
|
|
653
|
-
'aria-controls': open ?
|
|
657
|
+
'aria-controls': open ? listElement?.id ?? getFloatingFocusElement(positionerElement)?.id : undefined,
|
|
654
658
|
'aria-labelledby': ariaLabelledBy,
|
|
655
659
|
'aria-readonly': readOnly || undefined,
|
|
656
660
|
'aria-required': required || undefined,
|
|
657
661
|
tabIndex: disabled ? -1 : 0,
|
|
658
|
-
ref: mergedRef,
|
|
659
662
|
onFocus(event) {
|
|
660
663
|
setFocused(true);
|
|
661
664
|
|
|
@@ -667,8 +670,6 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
667
670
|
// Saves a re-render on initial click: `forceMount === true` mounts
|
|
668
671
|
// the items before `open === true`. We could sync those cycles better
|
|
669
672
|
// without a timeout, but this is enough for now.
|
|
670
|
-
//
|
|
671
|
-
// XXX: might be causing `act()` warnings.
|
|
672
673
|
timeoutFocus.start(0, () => {
|
|
673
674
|
store.set('forceMount', true);
|
|
674
675
|
});
|
|
@@ -684,12 +685,6 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
684
685
|
validation.commit(value);
|
|
685
686
|
}
|
|
686
687
|
},
|
|
687
|
-
onPointerMove() {
|
|
688
|
-
keyboardActiveRef.current = false;
|
|
689
|
-
},
|
|
690
|
-
onKeyDown() {
|
|
691
|
-
keyboardActiveRef.current = true;
|
|
692
|
-
},
|
|
693
688
|
onMouseDown(event) {
|
|
694
689
|
if (open) {
|
|
695
690
|
return;
|
|
@@ -701,8 +696,9 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
701
696
|
}
|
|
702
697
|
const mouseUpTarget = mouseEvent.target;
|
|
703
698
|
|
|
704
|
-
//
|
|
705
|
-
|
|
699
|
+
// Don't treat the release as an outside press when it lands on the trigger or inside
|
|
700
|
+
// the popup positioner (or their children).
|
|
701
|
+
if (contains(triggerRef.current, mouseUpTarget) || contains(positionerRef.current, mouseUpTarget)) {
|
|
706
702
|
return;
|
|
707
703
|
}
|
|
708
704
|
const bounds = getPseudoElementBounds(triggerRef.current);
|
|
@@ -719,7 +715,8 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
719
715
|
});
|
|
720
716
|
});
|
|
721
717
|
}
|
|
722
|
-
},
|
|
718
|
+
}, elementProps, getButtonProps);
|
|
719
|
+
const props = validation.getValidationProps(disabled, mergedProps);
|
|
723
720
|
|
|
724
721
|
// ensure nested useButton does not overwrite the combobox role:
|
|
725
722
|
// <Toolbar.Button render={<Select.Trigger />} />
|
|
@@ -730,10 +727,11 @@ const SelectTrigger = /*#__PURE__*/React.forwardRef(function SelectTrigger(compo
|
|
|
730
727
|
disabled,
|
|
731
728
|
value,
|
|
732
729
|
readOnly,
|
|
730
|
+
popupSide,
|
|
733
731
|
placeholder: !hasSelectedValue
|
|
734
732
|
};
|
|
735
733
|
return useRenderElement('button', componentProps, {
|
|
736
|
-
ref: [forwardedRef, triggerRef],
|
|
734
|
+
ref: [forwardedRef, triggerRef, buttonRef, setTriggerElement],
|
|
737
735
|
state,
|
|
738
736
|
stateAttributesMapping: stateAttributesMapping$2,
|
|
739
737
|
props
|
|
@@ -757,6 +755,7 @@ const SelectValue = /*#__PURE__*/React.forwardRef(function SelectValue(component
|
|
|
757
755
|
render,
|
|
758
756
|
children: childrenProp,
|
|
759
757
|
placeholder,
|
|
758
|
+
style,
|
|
760
759
|
...elementProps
|
|
761
760
|
} = componentProps;
|
|
762
761
|
const {
|
|
@@ -869,6 +868,7 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
|
|
|
869
868
|
disableAnchorTracking,
|
|
870
869
|
alignItemWithTrigger = true,
|
|
871
870
|
collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,
|
|
871
|
+
style,
|
|
872
872
|
...elementProps
|
|
873
873
|
} = componentProps;
|
|
874
874
|
const {
|
|
@@ -910,7 +910,7 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
|
|
|
910
910
|
}
|
|
911
911
|
}, [store, mounted]);
|
|
912
912
|
React.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);
|
|
913
|
-
|
|
913
|
+
useAnchoredPopupScrollLock((alignItemWithTriggerActive || modal) && open, openMethod === 'touch', positionerElement, triggerElement);
|
|
914
914
|
const positioning = useAnchorPositioning({
|
|
915
915
|
anchor,
|
|
916
916
|
floatingRootContext,
|
|
@@ -930,34 +930,25 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
|
|
|
930
930
|
});
|
|
931
931
|
const renderedSide = alignItemWithTriggerActive ? 'none' : positioning.side;
|
|
932
932
|
const positionerStyles = alignItemWithTriggerActive ? FIXED : positioning.positionerStyles;
|
|
933
|
-
const defaultProps = React.useMemo(() => {
|
|
934
|
-
const hiddenStyles = {};
|
|
935
|
-
if (!open) {
|
|
936
|
-
hiddenStyles.pointerEvents = 'none';
|
|
937
|
-
}
|
|
938
|
-
return {
|
|
939
|
-
role: 'presentation',
|
|
940
|
-
hidden: !mounted,
|
|
941
|
-
style: {
|
|
942
|
-
...positionerStyles,
|
|
943
|
-
...hiddenStyles
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
}, [open, mounted, positionerStyles]);
|
|
947
933
|
const state = {
|
|
948
934
|
open,
|
|
949
935
|
side: renderedSide,
|
|
950
936
|
align: positioning.align,
|
|
951
937
|
anchorHidden: positioning.anchorHidden
|
|
952
938
|
};
|
|
939
|
+
useIsoLayoutEffect(() => {
|
|
940
|
+
store.set('popupSide', positioning.side);
|
|
941
|
+
}, [store, positioning.side]);
|
|
953
942
|
const setPositionerElement = useStableCallback(element => {
|
|
954
943
|
store.set('positionerElement', element);
|
|
955
944
|
});
|
|
956
|
-
const element =
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
945
|
+
const element = usePositioner(componentProps, state, {
|
|
946
|
+
styles: positionerStyles,
|
|
947
|
+
transitionStatus,
|
|
948
|
+
props: elementProps,
|
|
949
|
+
refs: [forwardedRef, setPositionerElement],
|
|
950
|
+
hidden: !mounted,
|
|
951
|
+
inert: !open
|
|
961
952
|
});
|
|
962
953
|
const prevMapSizeRef = React.useRef(0);
|
|
963
954
|
const onMapChange = useStableCallback(map => {
|
|
@@ -1032,18 +1023,6 @@ const SelectPositioner = /*#__PURE__*/React.forwardRef(function SelectPositioner
|
|
|
1032
1023
|
});
|
|
1033
1024
|
if (process.env.NODE_ENV !== "production") SelectPositioner.displayName = "SelectPositioner";
|
|
1034
1025
|
|
|
1035
|
-
function isMouseWithinBounds(event) {
|
|
1036
|
-
const targetRect = event.currentTarget.getBoundingClientRect();
|
|
1037
|
-
|
|
1038
|
-
// Safari randomly fires `mouseleave` incorrectly when the item is
|
|
1039
|
-
// aligned to the trigger. This is a workaround to prevent the highlight
|
|
1040
|
-
// from being removed while the cursor is still within the bounds of the item.
|
|
1041
|
-
// https://github.com/mui/base-ui/issues/869
|
|
1042
|
-
const isWithinBounds = targetRect.top + 1 <= event.clientY && event.clientY <= targetRect.bottom - 1 && targetRect.left + 1 <= event.clientX && event.clientX <= targetRect.right - 1;
|
|
1043
|
-
return isWithinBounds;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
const SCROLL_EPS_PX = 1;
|
|
1047
1026
|
const stateAttributesMapping = {
|
|
1048
1027
|
...popupStateMapping,
|
|
1049
1028
|
...transitionStatusMapping
|
|
@@ -1059,6 +1038,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1059
1038
|
const {
|
|
1060
1039
|
render,
|
|
1061
1040
|
className,
|
|
1041
|
+
style,
|
|
1062
1042
|
finalFocus,
|
|
1063
1043
|
...elementProps
|
|
1064
1044
|
} = componentProps;
|
|
@@ -1068,39 +1048,38 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1068
1048
|
onOpenChangeComplete,
|
|
1069
1049
|
setOpen,
|
|
1070
1050
|
valueRef,
|
|
1051
|
+
firstItemTextRef,
|
|
1071
1052
|
selectedItemTextRef,
|
|
1072
|
-
keyboardActiveRef,
|
|
1073
1053
|
multiple,
|
|
1074
1054
|
handleScrollArrowVisibility,
|
|
1075
1055
|
scrollHandlerRef,
|
|
1056
|
+
listRef,
|
|
1076
1057
|
highlightItemOnHover
|
|
1077
1058
|
} = useSelectRootContext();
|
|
1078
1059
|
const {
|
|
1079
1060
|
side,
|
|
1080
1061
|
align,
|
|
1081
1062
|
alignItemWithTriggerActive,
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
scrollUpArrowRef
|
|
1063
|
+
isPositioned,
|
|
1064
|
+
setControlledAlignItemWithTrigger
|
|
1085
1065
|
} = useSelectPositionerContext();
|
|
1086
1066
|
const insideToolbar = useToolbarRootContext(true) != null;
|
|
1087
1067
|
const floatingRootContext = useSelectFloatingContext();
|
|
1068
|
+
const direction = useDirection();
|
|
1088
1069
|
const {
|
|
1089
1070
|
nonce,
|
|
1090
1071
|
disableStyleElements
|
|
1091
1072
|
} = useCSPContext();
|
|
1092
|
-
const highlightTimeout = useTimeout();
|
|
1093
1073
|
const id = useStore(store, selectors.id);
|
|
1094
1074
|
const open = useStore(store, selectors.open);
|
|
1075
|
+
const openMethod = useStore(store, selectors.openMethod);
|
|
1095
1076
|
const mounted = useStore(store, selectors.mounted);
|
|
1096
1077
|
const popupProps = useStore(store, selectors.popupProps);
|
|
1097
1078
|
const transitionStatus = useStore(store, selectors.transitionStatus);
|
|
1098
1079
|
const triggerElement = useStore(store, selectors.triggerElement);
|
|
1099
1080
|
const positionerElement = useStore(store, selectors.positionerElement);
|
|
1100
1081
|
const listElement = useStore(store, selectors.listElement);
|
|
1101
|
-
const initialHeightRef = React.useRef(0);
|
|
1102
1082
|
const reachedMaxHeightRef = React.useRef(false);
|
|
1103
|
-
const maxHeightRef = React.useRef(0);
|
|
1104
1083
|
const initialPlacedRef = React.useRef(false);
|
|
1105
1084
|
const originalPositionerStylesRef = React.useRef({});
|
|
1106
1085
|
const scrollArrowFrame = useAnimationFrame();
|
|
@@ -1114,12 +1093,18 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1114
1093
|
}
|
|
1115
1094
|
const isTopPositioned = positionerElement.style.top === '0px';
|
|
1116
1095
|
const isBottomPositioned = positionerElement.style.bottom === '0px';
|
|
1117
|
-
|
|
1096
|
+
if (!isTopPositioned && !isBottomPositioned) {
|
|
1097
|
+
handleScrollArrowVisibility();
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const scale = getScale(positionerElement);
|
|
1101
|
+
const currentHeight = normalizeSize(positionerElement.getBoundingClientRect().height, 'y', scale);
|
|
1118
1102
|
const doc = ownerDocument(positionerElement);
|
|
1119
|
-
const
|
|
1103
|
+
const win = getWindow(positionerElement);
|
|
1104
|
+
const positionerStyles = win.getComputedStyle(positionerElement);
|
|
1120
1105
|
const marginTop = parseFloat(positionerStyles.marginTop);
|
|
1121
1106
|
const marginBottom = parseFloat(positionerStyles.marginBottom);
|
|
1122
|
-
const maxPopupHeight = getMaxPopupHeight(getComputedStyle(popupRef.current));
|
|
1107
|
+
const maxPopupHeight = getMaxPopupHeight(win.getComputedStyle(popupRef.current));
|
|
1123
1108
|
const maxAvailableHeight = Math.min(doc.documentElement.clientHeight - marginTop - marginBottom, maxPopupHeight);
|
|
1124
1109
|
const scrollTop = scroller.scrollTop;
|
|
1125
1110
|
const maxScrollTop = getMaxScrollTop(scroller);
|
|
@@ -1137,42 +1122,29 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1137
1122
|
setHeight(currentHeight + heightDelta);
|
|
1138
1123
|
}
|
|
1139
1124
|
scroller.scrollTop = targetScrollTop;
|
|
1140
|
-
if (maxAvailableHeight - (currentHeight + heightDelta) <=
|
|
1125
|
+
if (maxAvailableHeight - (currentHeight + heightDelta) <= SCROLL_EDGE_TOLERANCE_PX) {
|
|
1141
1126
|
reachedMaxHeightRef.current = true;
|
|
1142
1127
|
}
|
|
1143
1128
|
handleScrollArrowVisibility();
|
|
1144
1129
|
};
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {
|
|
1130
|
+
const diff = isTopPositioned ? maxScrollTop - scrollTop : scrollTop;
|
|
1131
|
+
const nextHeight = Math.min(currentHeight + diff, maxAvailableHeight);
|
|
1132
|
+
nextPositionerHeight = nextHeight;
|
|
1133
|
+
if (diff <= SCROLL_EDGE_TOLERANCE_PX) {
|
|
1134
|
+
handleSmallDiff(diff, isTopPositioned ? maxScrollTop : 0);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (maxAvailableHeight - nextHeight > SCROLL_EDGE_TOLERANCE_PX) {
|
|
1138
|
+
if (isTopPositioned) {
|
|
1155
1139
|
scrollToMax = true;
|
|
1156
1140
|
} else {
|
|
1157
|
-
setReachedMax = true;
|
|
1158
|
-
}
|
|
1159
|
-
} else if (isBottomPositioned) {
|
|
1160
|
-
const diff = scrollTop;
|
|
1161
|
-
const idealHeight = currentHeight + diff;
|
|
1162
|
-
const nextHeight = Math.min(idealHeight, maxAvailableHeight);
|
|
1163
|
-
const overshoot = idealHeight - maxAvailableHeight;
|
|
1164
|
-
nextPositionerHeight = nextHeight;
|
|
1165
|
-
if (diff <= SCROLL_EPS_PX) {
|
|
1166
|
-
handleSmallDiff(diff, 0);
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {
|
|
1170
1141
|
nextScrollTop = 0;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1142
|
+
}
|
|
1143
|
+
} else {
|
|
1144
|
+
setReachedMax = true;
|
|
1145
|
+
if (isBottomPositioned && scrollTop < maxScrollTop) {
|
|
1146
|
+
const overshoot = currentHeight + diff - maxAvailableHeight;
|
|
1147
|
+
nextScrollTop = scrollTop - (diff - overshoot);
|
|
1176
1148
|
}
|
|
1177
1149
|
}
|
|
1178
1150
|
nextPositionerHeight = Math.ceil(nextPositionerHeight);
|
|
@@ -1185,11 +1157,11 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1185
1157
|
const target = scrollToMax ? nextMaxScrollTop : clamp(nextScrollTop, 0, nextMaxScrollTop);
|
|
1186
1158
|
|
|
1187
1159
|
// Avoid adjustments that re-trigger scroll events forever.
|
|
1188
|
-
if (Math.abs(scroller.scrollTop - target) >
|
|
1160
|
+
if (Math.abs(scroller.scrollTop - target) > SCROLL_EDGE_TOLERANCE_PX) {
|
|
1189
1161
|
scroller.scrollTop = target;
|
|
1190
1162
|
}
|
|
1191
1163
|
}
|
|
1192
|
-
if (setReachedMax || nextPositionerHeight >= maxAvailableHeight -
|
|
1164
|
+
if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - SCROLL_EDGE_TOLERANCE_PX) {
|
|
1193
1165
|
reachedMaxHeightRef.current = true;
|
|
1194
1166
|
}
|
|
1195
1167
|
handleScrollArrowVisibility();
|
|
@@ -1232,13 +1204,15 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1232
1204
|
}
|
|
1233
1205
|
initialPlacedRef.current = false;
|
|
1234
1206
|
reachedMaxHeightRef.current = false;
|
|
1235
|
-
initialHeightRef.current = 0;
|
|
1236
|
-
maxHeightRef.current = 0;
|
|
1237
1207
|
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1238
1208
|
}, [open, alignItemWithTriggerActive, positionerElement, popupRef]);
|
|
1239
1209
|
useIsoLayoutEffect(() => {
|
|
1240
1210
|
const popupElement = popupRef.current;
|
|
1241
|
-
|
|
1211
|
+
|
|
1212
|
+
// Wait for Floating UI's first positioning pass before reading DOM geometry.
|
|
1213
|
+
// We replace the final coordinates for aligned selects, but still need middleware
|
|
1214
|
+
// like `size()` to set CSS variables such as `--anchor-width`.
|
|
1215
|
+
if (!open || !triggerElement || !positionerElement || !popupElement || alignItemWithTriggerActive && !isPositioned || store.state.transitionStatus === 'ending') {
|
|
1242
1216
|
return;
|
|
1243
1217
|
}
|
|
1244
1218
|
if (!alignItemWithTriggerActive) {
|
|
@@ -1248,114 +1222,113 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1248
1222
|
return;
|
|
1249
1223
|
}
|
|
1250
1224
|
|
|
1251
|
-
//
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
// reached, we fallback to aligning the popup to the trigger as the UX is poor otherwise.
|
|
1316
|
-
const fallbackToAlignPopupToTrigger = triggerRect.top < triggerCollisionThreshold || triggerRect.bottom > viewportHeight - triggerCollisionThreshold || Math.ceil(height) + SCROLL_EPS_PX < Math.min(scrollHeight, minHeight);
|
|
1225
|
+
// Ensure we remove any transforms that can affect the location of the popup
|
|
1226
|
+
// and therefore the calculations.
|
|
1227
|
+
const restoreTransformStyles = unsetTransformStyles(popupElement);
|
|
1228
|
+
popupElement.style.removeProperty('--transform-origin');
|
|
1229
|
+
try {
|
|
1230
|
+
let textElement = selectedItemTextRef.current;
|
|
1231
|
+
if (!textElement?.isConnected) {
|
|
1232
|
+
const hasSelectedValue = selectors.hasSelectedValue(store.state);
|
|
1233
|
+
textElement = !hasSelectedValue && firstItemTextRef.current?.isConnected ? firstItemTextRef.current : null;
|
|
1234
|
+
}
|
|
1235
|
+
const valueElement = valueRef.current;
|
|
1236
|
+
const win = getWindow(positionerElement);
|
|
1237
|
+
const positionerStyles = win.getComputedStyle(positionerElement);
|
|
1238
|
+
const popupStyles = win.getComputedStyle(popupElement);
|
|
1239
|
+
const doc = ownerDocument(triggerElement);
|
|
1240
|
+
const scale = getScale(triggerElement);
|
|
1241
|
+
const triggerRect = normalizeRect(triggerElement.getBoundingClientRect(), scale);
|
|
1242
|
+
const positionerRect = normalizeRect(positionerElement.getBoundingClientRect(), scale);
|
|
1243
|
+
const triggerHeight = triggerRect.height;
|
|
1244
|
+
const scroller = listElement || popupElement;
|
|
1245
|
+
const scrollHeight = scroller.scrollHeight;
|
|
1246
|
+
const borderBottom = parseFloat(popupStyles.borderBottomWidth);
|
|
1247
|
+
// The `|| N` fallbacks cover an unset/`auto` value (parses to `NaN`). Note a literal `0`
|
|
1248
|
+
// also resolves to the fallback, so an explicit `margin: 0` or `min-height: 0` still takes
|
|
1249
|
+
// the default below.
|
|
1250
|
+
const marginTop = parseFloat(positionerStyles.marginTop) || 10;
|
|
1251
|
+
const marginBottom = parseFloat(positionerStyles.marginBottom) || 10;
|
|
1252
|
+
const minHeight = parseFloat(positionerStyles.minHeight) || 100;
|
|
1253
|
+
const maxPopupHeight = getMaxPopupHeight(popupStyles);
|
|
1254
|
+
const paddingLeft = 5;
|
|
1255
|
+
const paddingRight = 5;
|
|
1256
|
+
const triggerCollisionThreshold = 20;
|
|
1257
|
+
const viewportHeight = doc.documentElement.clientHeight - marginTop - marginBottom;
|
|
1258
|
+
const viewportWidth = doc.documentElement.clientWidth;
|
|
1259
|
+
const availableSpaceBeneathTrigger = viewportHeight - triggerRect.bottom + triggerHeight;
|
|
1260
|
+
let textRect;
|
|
1261
|
+
let alignedLeft = direction === 'rtl' ? triggerRect.right - positionerRect.width : triggerRect.left;
|
|
1262
|
+
let offsetY = 0;
|
|
1263
|
+
if (textElement && valueElement) {
|
|
1264
|
+
const valueRect = normalizeRect(valueElement.getBoundingClientRect(), scale);
|
|
1265
|
+
textRect = normalizeRect(textElement.getBoundingClientRect(), scale);
|
|
1266
|
+
alignedLeft = positionerRect.left + (direction === 'rtl' ? valueRect.right - textRect.right : valueRect.left - textRect.left);
|
|
1267
|
+
const valueCenterFromTriggerTop = valueRect.top - triggerRect.top + valueRect.height / 2;
|
|
1268
|
+
const textCenterFromPositionerTop = textRect.top - positionerRect.top + textRect.height / 2;
|
|
1269
|
+
offsetY = textCenterFromPositionerTop - valueCenterFromTriggerTop;
|
|
1270
|
+
}
|
|
1271
|
+
const idealHeight = availableSpaceBeneathTrigger + offsetY + marginBottom + borderBottom;
|
|
1272
|
+
let height = Math.min(viewportHeight, idealHeight);
|
|
1273
|
+
const maxHeight = viewportHeight - marginTop - marginBottom;
|
|
1274
|
+
const scrollTop = idealHeight - height;
|
|
1275
|
+
const maxRight = viewportWidth - paddingRight;
|
|
1276
|
+
positionerElement.style.left = `${clamp(alignedLeft, paddingLeft, maxRight - positionerRect.width)}px`;
|
|
1277
|
+
positionerElement.style.height = `${height}px`;
|
|
1278
|
+
// `none` (not the invalid `auto`) so the explicit height governs in align mode and isn't
|
|
1279
|
+
// clamped by a `max-height` from user CSS.
|
|
1280
|
+
positionerElement.style.maxHeight = 'none';
|
|
1281
|
+
positionerElement.style.marginTop = `${marginTop}px`;
|
|
1282
|
+
positionerElement.style.marginBottom = `${marginBottom}px`;
|
|
1283
|
+
popupElement.style.height = '100%';
|
|
1284
|
+
const maxScrollTop = getMaxScrollTop(scroller);
|
|
1285
|
+
const isTopPositioned = scrollTop >= maxScrollTop - SCROLL_EDGE_TOLERANCE_PX;
|
|
1286
|
+
if (isTopPositioned) {
|
|
1287
|
+
height = Math.min(viewportHeight, positionerRect.height) - (scrollTop - maxScrollTop);
|
|
1288
|
+
}
|
|
1317
1289
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
initialPlacedRef.current = true;
|
|
1322
|
-
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1323
|
-
ReactDOM.flushSync(() => setControlledAlignItemWithTrigger(false));
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
if (isTopPositioned) {
|
|
1327
|
-
const topOffset = Math.max(0, viewportHeight - idealHeight);
|
|
1328
|
-
positionerElement.style.top = positionerRect.height >= maxHeight ? '0' : `${topOffset}px`;
|
|
1329
|
-
positionerElement.style.height = `${height}px`;
|
|
1330
|
-
scroller.scrollTop = getMaxScrollTop(scroller);
|
|
1331
|
-
initialHeightRef.current = Math.max(minHeight, height);
|
|
1332
|
-
} else {
|
|
1333
|
-
positionerElement.style.bottom = '0';
|
|
1334
|
-
initialHeightRef.current = Math.max(minHeight, height);
|
|
1335
|
-
scroller.scrollTop = scrollTop;
|
|
1336
|
-
}
|
|
1337
|
-
if (textRect) {
|
|
1338
|
-
const popupTop = positionerRect.top;
|
|
1339
|
-
const popupHeight = positionerRect.height;
|
|
1340
|
-
const textCenterY = textRect.top + textRect.height / 2;
|
|
1341
|
-
const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
|
|
1342
|
-
const clampedY = clamp(transformOriginY, 0, 100);
|
|
1343
|
-
popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
|
|
1344
|
-
}
|
|
1345
|
-
if (initialHeightRef.current === viewportHeight || height >= maxPopupHeight) {
|
|
1346
|
-
reachedMaxHeightRef.current = true;
|
|
1347
|
-
}
|
|
1348
|
-
handleScrollArrowVisibility();
|
|
1290
|
+
// When the trigger is too close to the top or bottom of the viewport, or the minHeight is
|
|
1291
|
+
// reached, we fallback to aligning the popup to the trigger as the UX is poor otherwise.
|
|
1292
|
+
const fallbackToAlignPopupToTrigger = triggerRect.top < triggerCollisionThreshold || triggerRect.bottom > viewportHeight - triggerCollisionThreshold || Math.ceil(height) + SCROLL_EDGE_TOLERANCE_PX < Math.min(scrollHeight, minHeight);
|
|
1349
1293
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1294
|
+
// Safari doesn't position the popup correctly when pinch-zoomed.
|
|
1295
|
+
const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && webkit;
|
|
1296
|
+
if (fallbackToAlignPopupToTrigger || isPinchZoomed) {
|
|
1297
|
+
initialPlacedRef.current = true;
|
|
1298
|
+
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1299
|
+
setControlledAlignItemWithTrigger(false);
|
|
1300
|
+
return;
|
|
1356
1301
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1302
|
+
const initialHeight = Math.max(minHeight, height);
|
|
1303
|
+
if (isTopPositioned) {
|
|
1304
|
+
const topOffset = Math.max(0, viewportHeight - idealHeight);
|
|
1305
|
+
positionerElement.style.top = positionerRect.height >= maxHeight ? '0' : `${topOffset}px`;
|
|
1306
|
+
positionerElement.style.height = `${height}px`;
|
|
1307
|
+
scroller.scrollTop = getMaxScrollTop(scroller);
|
|
1308
|
+
} else {
|
|
1309
|
+
positionerElement.style.bottom = '0';
|
|
1310
|
+
scroller.scrollTop = scrollTop;
|
|
1311
|
+
}
|
|
1312
|
+
if (textRect) {
|
|
1313
|
+
const popupTop = positionerRect.top;
|
|
1314
|
+
const popupHeight = positionerRect.height;
|
|
1315
|
+
const textCenterY = textRect.top + textRect.height / 2;
|
|
1316
|
+
const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
|
|
1317
|
+
const clampedY = clamp(transformOriginY, 0, 100);
|
|
1318
|
+
popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
|
|
1319
|
+
}
|
|
1320
|
+
if (initialHeight === viewportHeight || height >= maxPopupHeight) {
|
|
1321
|
+
reachedMaxHeightRef.current = true;
|
|
1322
|
+
}
|
|
1323
|
+
handleScrollArrowVisibility();
|
|
1324
|
+
if (highlightItemOnHover && store.state.selectedIndex === null && store.state.activeIndex === null && listRef.current[0] != null) {
|
|
1325
|
+
store.set('activeIndex', 0);
|
|
1326
|
+
}
|
|
1327
|
+
initialPlacedRef.current = true;
|
|
1328
|
+
} finally {
|
|
1329
|
+
restoreTransformStyles();
|
|
1330
|
+
}
|
|
1331
|
+
}, [store, open, positionerElement, triggerElement, valueRef, firstItemTextRef, selectedItemTextRef, popupRef, handleScrollArrowVisibility, alignItemWithTriggerActive, setControlledAlignItemWithTrigger, scrollArrowFrame, listElement, listRef, highlightItemOnHover, direction, isPositioned]);
|
|
1359
1332
|
React.useEffect(() => {
|
|
1360
1333
|
if (!alignItemWithTriggerActive || !positionerElement || !open) {
|
|
1361
1334
|
return undefined;
|
|
@@ -1364,10 +1337,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1364
1337
|
function handleResize(event) {
|
|
1365
1338
|
setOpen(false, createChangeEventDetails(windowResize, event));
|
|
1366
1339
|
}
|
|
1367
|
-
|
|
1368
|
-
return () => {
|
|
1369
|
-
win.removeEventListener('resize', handleResize);
|
|
1370
|
-
};
|
|
1340
|
+
return addEventListener(win, 'resize', handleResize);
|
|
1371
1341
|
}, [setOpen, alignItemWithTriggerActive, positionerElement, open]);
|
|
1372
1342
|
const defaultProps = {
|
|
1373
1343
|
...(listElement ? {
|
|
@@ -1379,26 +1349,10 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1379
1349
|
id: `${id}-list`
|
|
1380
1350
|
}),
|
|
1381
1351
|
onKeyDown(event) {
|
|
1382
|
-
keyboardActiveRef.current = true;
|
|
1383
1352
|
if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {
|
|
1384
1353
|
event.stopPropagation();
|
|
1385
1354
|
}
|
|
1386
1355
|
},
|
|
1387
|
-
onMouseMove() {
|
|
1388
|
-
keyboardActiveRef.current = false;
|
|
1389
|
-
},
|
|
1390
|
-
onPointerLeave(event) {
|
|
1391
|
-
if (!highlightItemOnHover || isMouseWithinBounds(event) || event.pointerType === 'touch') {
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
const popup = event.currentTarget;
|
|
1395
|
-
highlightTimeout.start(0, () => {
|
|
1396
|
-
store.set('activeIndex', null);
|
|
1397
|
-
popup.focus({
|
|
1398
|
-
preventScroll: true
|
|
1399
|
-
});
|
|
1400
|
-
});
|
|
1401
|
-
},
|
|
1402
1356
|
onScroll(event) {
|
|
1403
1357
|
if (listElement) {
|
|
1404
1358
|
return;
|
|
@@ -1424,6 +1378,7 @@ const SelectPopup = /*#__PURE__*/React.forwardRef(function SelectPopup(component
|
|
|
1424
1378
|
context: floatingRootContext,
|
|
1425
1379
|
modal: false,
|
|
1426
1380
|
disabled: !mounted,
|
|
1381
|
+
openInteractionType: openMethod,
|
|
1427
1382
|
returnFocus: finalFocus,
|
|
1428
1383
|
restoreFocus: true,
|
|
1429
1384
|
children: element
|
|
@@ -1436,18 +1391,21 @@ function getMaxPopupHeight(popupStyles) {
|
|
|
1436
1391
|
return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;
|
|
1437
1392
|
}
|
|
1438
1393
|
function getMaxScrollTop(scroller) {
|
|
1439
|
-
return
|
|
1394
|
+
return getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
1440
1395
|
}
|
|
1441
1396
|
function getScale(element) {
|
|
1442
1397
|
// The platform API is async-capable, but the DOM platform returns a plain scale object.
|
|
1443
1398
|
return platform.getScale(element);
|
|
1444
1399
|
}
|
|
1400
|
+
function normalizeSize(size, axis, scale) {
|
|
1401
|
+
return size / scale[axis];
|
|
1402
|
+
}
|
|
1445
1403
|
function normalizeRect(rect, scale) {
|
|
1446
1404
|
return rectToClientRect({
|
|
1447
|
-
x: rect.x
|
|
1448
|
-
y: rect.y
|
|
1449
|
-
width: rect.width
|
|
1450
|
-
height: rect.height
|
|
1405
|
+
x: normalizeSize(rect.x, 'x', scale),
|
|
1406
|
+
y: normalizeSize(rect.y, 'y', scale),
|
|
1407
|
+
width: normalizeSize(rect.width, 'x', scale),
|
|
1408
|
+
height: normalizeSize(rect.height, 'y', scale)
|
|
1451
1409
|
});
|
|
1452
1410
|
}
|
|
1453
1411
|
const TRANSFORM_STYLE_RESETS = [['transform', 'none'], ['scale', '1'], ['translate', '0 0']];
|
|
@@ -1480,8 +1438,9 @@ function unsetTransformStyles(popupElement) {
|
|
|
1480
1438
|
*/
|
|
1481
1439
|
const SelectList = /*#__PURE__*/React.forwardRef(function SelectList(componentProps, forwardedRef) {
|
|
1482
1440
|
const {
|
|
1483
|
-
className,
|
|
1484
1441
|
render,
|
|
1442
|
+
className,
|
|
1443
|
+
style,
|
|
1485
1444
|
...elementProps
|
|
1486
1445
|
} = componentProps;
|
|
1487
1446
|
const {
|
|
@@ -1531,6 +1490,7 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1531
1490
|
const {
|
|
1532
1491
|
render,
|
|
1533
1492
|
className,
|
|
1493
|
+
style,
|
|
1534
1494
|
value: itemValue = null,
|
|
1535
1495
|
label,
|
|
1536
1496
|
disabled = false,
|
|
@@ -1545,25 +1505,25 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1545
1505
|
});
|
|
1546
1506
|
const {
|
|
1547
1507
|
store,
|
|
1548
|
-
|
|
1508
|
+
itemProps,
|
|
1549
1509
|
setOpen,
|
|
1550
1510
|
setValue,
|
|
1551
1511
|
selectionRef,
|
|
1552
1512
|
typingRef,
|
|
1553
1513
|
valuesRef,
|
|
1554
|
-
keyboardActiveRef,
|
|
1555
1514
|
multiple,
|
|
1556
|
-
|
|
1515
|
+
selectedItemTextRef,
|
|
1516
|
+
disabled: selectDisabled,
|
|
1517
|
+
readOnly
|
|
1557
1518
|
} = useSelectRootContext();
|
|
1558
|
-
const highlightTimeout = useTimeout();
|
|
1559
1519
|
const highlighted = useStore(store, selectors.isActive, listItem.index);
|
|
1560
|
-
const
|
|
1520
|
+
const open = useStore(store, selectors.open);
|
|
1521
|
+
const selected = useStore(store, selectors.isSelected, itemValue);
|
|
1561
1522
|
const selectedByFocus = useStore(store, selectors.isSelectedByFocus, listItem.index);
|
|
1562
1523
|
const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);
|
|
1563
1524
|
const index = listItem.index;
|
|
1564
1525
|
const hasRegistered = index !== -1;
|
|
1565
1526
|
const itemRef = React.useRef(null);
|
|
1566
|
-
const indexRef = useValueAsRef(index);
|
|
1567
1527
|
useIsoLayoutEffect(() => {
|
|
1568
1528
|
if (!hasRegistered) {
|
|
1569
1529
|
return undefined;
|
|
@@ -1576,34 +1536,27 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1576
1536
|
}, [hasRegistered, index, itemValue, valuesRef]);
|
|
1577
1537
|
useIsoLayoutEffect(() => {
|
|
1578
1538
|
if (!hasRegistered) {
|
|
1579
|
-
return
|
|
1539
|
+
return;
|
|
1580
1540
|
}
|
|
1581
1541
|
const selectedValue = store.state.value;
|
|
1582
1542
|
let selectedCandidate = selectedValue;
|
|
1583
|
-
if (multiple && Array.isArray(selectedValue)
|
|
1584
|
-
|
|
1543
|
+
if (multiple && Array.isArray(selectedValue)) {
|
|
1544
|
+
// Compare against the last selected item, or `undefined` when nothing is selected — never
|
|
1545
|
+
// the raw array, which a custom `isItemEqualToValue` isn't expected to receive.
|
|
1546
|
+
selectedCandidate = selectedValue.length > 0 ? selectedValue[selectedValue.length - 1] : undefined;
|
|
1585
1547
|
}
|
|
1586
1548
|
if (selectedCandidate !== undefined && compareItemEquality(itemValue, selectedCandidate, isItemEqualToValue)) {
|
|
1587
1549
|
store.set('selectedIndex', index);
|
|
1550
|
+
// Make sure SelectPopup can measure the selected item on first open.
|
|
1551
|
+
// SelectItemText can still update this ref later when focus moves.
|
|
1552
|
+
if (textRef.current) {
|
|
1553
|
+
selectedItemTextRef.current = textRef.current;
|
|
1554
|
+
}
|
|
1588
1555
|
}
|
|
1589
|
-
|
|
1590
|
-
}, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue]);
|
|
1591
|
-
const state = {
|
|
1592
|
-
disabled,
|
|
1593
|
-
selected,
|
|
1594
|
-
highlighted
|
|
1595
|
-
};
|
|
1596
|
-
const rootProps = getItemProps({
|
|
1597
|
-
active: highlighted,
|
|
1598
|
-
selected
|
|
1599
|
-
});
|
|
1600
|
-
// With our custom `focusItemOnHover` implementation, this interferes with the logic and can
|
|
1601
|
-
// cause the index state to be stuck when leaving the select popup.
|
|
1602
|
-
rootProps.onFocus = undefined;
|
|
1603
|
-
rootProps.id = undefined;
|
|
1556
|
+
}, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
|
|
1604
1557
|
const lastKeyRef = React.useRef(null);
|
|
1605
1558
|
const pointerTypeRef = React.useRef('mouse');
|
|
1606
|
-
const
|
|
1559
|
+
const allowMouseSelectionRef = React.useRef(false);
|
|
1607
1560
|
const {
|
|
1608
1561
|
getButtonProps,
|
|
1609
1562
|
buttonRef
|
|
@@ -1613,7 +1566,17 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1613
1566
|
native: nativeButton,
|
|
1614
1567
|
composite: true
|
|
1615
1568
|
});
|
|
1569
|
+
const state = {
|
|
1570
|
+
disabled,
|
|
1571
|
+
selected,
|
|
1572
|
+
highlighted
|
|
1573
|
+
};
|
|
1616
1574
|
function commitSelection(event) {
|
|
1575
|
+
// A forced-open select (`open`/`defaultOpen`) can still receive item activations even
|
|
1576
|
+
// when the root is disabled or read-only, so guard the commit here too.
|
|
1577
|
+
if (selectDisabled || readOnly) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1617
1580
|
const selectedValue = store.state.value;
|
|
1618
1581
|
if (multiple) {
|
|
1619
1582
|
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
@@ -1624,39 +1587,13 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1624
1587
|
setOpen(false, createChangeEventDetails(itemPress, event));
|
|
1625
1588
|
}
|
|
1626
1589
|
}
|
|
1590
|
+
function resetDragMovement() {
|
|
1591
|
+
selectionRef.current.dragY = 0;
|
|
1592
|
+
}
|
|
1627
1593
|
const defaultProps = {
|
|
1628
1594
|
role: 'option',
|
|
1629
1595
|
'aria-selected': selected,
|
|
1630
|
-
tabIndex: highlighted ? 0 : -1,
|
|
1631
|
-
onFocus() {
|
|
1632
|
-
store.set('activeIndex', index);
|
|
1633
|
-
},
|
|
1634
|
-
onMouseEnter() {
|
|
1635
|
-
if (!keyboardActiveRef.current && store.state.selectedIndex === null && highlightItemOnHover) {
|
|
1636
|
-
store.set('activeIndex', index);
|
|
1637
|
-
}
|
|
1638
|
-
},
|
|
1639
|
-
onMouseMove() {
|
|
1640
|
-
if (highlightItemOnHover) {
|
|
1641
|
-
store.set('activeIndex', index);
|
|
1642
|
-
}
|
|
1643
|
-
},
|
|
1644
|
-
onMouseLeave(event) {
|
|
1645
|
-
if (!highlightItemOnHover || keyboardActiveRef.current || isMouseWithinBounds(event)) {
|
|
1646
|
-
return;
|
|
1647
|
-
}
|
|
1648
|
-
highlightTimeout.start(0, () => {
|
|
1649
|
-
if (store.state.activeIndex === index) {
|
|
1650
|
-
store.set('activeIndex', null);
|
|
1651
|
-
}
|
|
1652
|
-
});
|
|
1653
|
-
},
|
|
1654
|
-
onTouchStart() {
|
|
1655
|
-
selectionRef.current = {
|
|
1656
|
-
allowSelectedMouseUp: false,
|
|
1657
|
-
allowUnselectedMouseUp: false
|
|
1658
|
-
};
|
|
1659
|
-
},
|
|
1596
|
+
tabIndex: open && highlighted ? 0 : -1,
|
|
1660
1597
|
onKeyDown(event) {
|
|
1661
1598
|
lastKeyRef.current = event.key;
|
|
1662
1599
|
store.set('activeIndex', index);
|
|
@@ -1665,13 +1602,24 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1665
1602
|
}
|
|
1666
1603
|
},
|
|
1667
1604
|
onClick(event) {
|
|
1668
|
-
|
|
1605
|
+
const isMouseClick = event.type === 'click' && pointerTypeRef.current !== 'touch';
|
|
1606
|
+
const clickPointerType = event.nativeEvent.pointerType;
|
|
1607
|
+
const isVirtualMouseClick = isMouseClick && isVirtualClick(event.nativeEvent) && (
|
|
1608
|
+
// Generic no-pointer `detail === 0` clicks stay tied to highlight state. Virtual
|
|
1609
|
+
// clicks that carry browser pointer data, including an empty string from assistive
|
|
1610
|
+
// technology, can activate unhighlighted items.
|
|
1611
|
+
clickPointerType !== undefined || highlighted);
|
|
1612
|
+
// With alignItemWithTrigger, opening can place an item under the cursor. Real mouse
|
|
1613
|
+
// clicks must start on the item, while virtual clicks represent explicit keyboard or
|
|
1614
|
+
// assistive technology activation.
|
|
1615
|
+
const isInvalidMouseClick = isMouseClick && !isVirtualMouseClick && !allowMouseSelectionRef.current;
|
|
1616
|
+
allowMouseSelectionRef.current = false;
|
|
1669
1617
|
|
|
1670
1618
|
// Prevent double commit on {Enter}
|
|
1671
1619
|
if (event.type === 'keydown' && lastKeyRef.current === null) {
|
|
1672
1620
|
return;
|
|
1673
1621
|
}
|
|
1674
|
-
if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current ||
|
|
1622
|
+
if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current || isInvalidMouseClick) {
|
|
1675
1623
|
return;
|
|
1676
1624
|
}
|
|
1677
1625
|
lastKeyRef.current = null;
|
|
@@ -1680,40 +1628,52 @@ const SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(functio
|
|
|
1680
1628
|
onPointerEnter(event) {
|
|
1681
1629
|
pointerTypeRef.current = event.pointerType;
|
|
1682
1630
|
},
|
|
1631
|
+
onPointerMove(event) {
|
|
1632
|
+
if (event.pointerType === 'mouse' && event.buttons === 1) {
|
|
1633
|
+
const selection = selectionRef.current;
|
|
1634
|
+
selection.dragY += event.movementY;
|
|
1635
|
+
if (selection.dragY ** 2 >= 64) {
|
|
1636
|
+
selection.allowUnselectedMouseUp = true;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
},
|
|
1683
1640
|
onPointerDown(event) {
|
|
1684
1641
|
pointerTypeRef.current = event.pointerType;
|
|
1685
|
-
|
|
1642
|
+
allowMouseSelectionRef.current = true;
|
|
1643
|
+
resetDragMovement();
|
|
1686
1644
|
},
|
|
1687
1645
|
onMouseUp() {
|
|
1688
|
-
|
|
1646
|
+
resetDragMovement();
|
|
1647
|
+
if (disabled || pointerTypeRef.current === 'touch') {
|
|
1689
1648
|
return;
|
|
1690
1649
|
}
|
|
1691
1650
|
|
|
1692
|
-
// Regular
|
|
1693
|
-
if (
|
|
1694
|
-
didPointerDownRef.current = false;
|
|
1651
|
+
// Regular clicks are committed by the click event.
|
|
1652
|
+
if (allowMouseSelectionRef.current) {
|
|
1695
1653
|
return;
|
|
1696
1654
|
}
|
|
1697
1655
|
const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;
|
|
1698
1656
|
const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;
|
|
1699
|
-
if (disallowSelectedMouseUp || disallowUnselectedMouseUp
|
|
1657
|
+
if (disallowSelectedMouseUp || disallowUnselectedMouseUp) {
|
|
1700
1658
|
return;
|
|
1701
1659
|
}
|
|
1660
|
+
allowMouseSelectionRef.current = true;
|
|
1702
1661
|
itemRef.current?.click();
|
|
1662
|
+
allowMouseSelectionRef.current = false;
|
|
1703
1663
|
}
|
|
1704
1664
|
};
|
|
1705
1665
|
const element = useRenderElement('div', componentProps, {
|
|
1706
1666
|
ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
|
|
1707
1667
|
state,
|
|
1708
|
-
props: [
|
|
1668
|
+
props: [itemProps, defaultProps, elementProps, getButtonProps]
|
|
1709
1669
|
});
|
|
1710
1670
|
const contextValue = React.useMemo(() => ({
|
|
1711
1671
|
selected,
|
|
1712
|
-
|
|
1672
|
+
index,
|
|
1713
1673
|
textRef,
|
|
1714
1674
|
selectedByFocus,
|
|
1715
1675
|
hasRegistered
|
|
1716
|
-
}), [selected,
|
|
1676
|
+
}), [selected, index, textRef, selectedByFocus, hasRegistered]);
|
|
1717
1677
|
return /*#__PURE__*/jsx(SelectItemContext.Provider, {
|
|
1718
1678
|
value: contextValue,
|
|
1719
1679
|
children: element
|
|
@@ -1738,13 +1698,13 @@ const SelectItemIndicator = /*#__PURE__*/React.forwardRef(function SelectItemInd
|
|
|
1738
1698
|
});
|
|
1739
1699
|
});
|
|
1740
1700
|
|
|
1741
|
-
|
|
1742
|
-
* costs unless the element needs to be mounted. */
|
|
1701
|
+
// Split the core implementation to avoid paying the hook costs unless the element needs to mount.
|
|
1743
1702
|
if (process.env.NODE_ENV !== "production") SelectItemIndicator.displayName = "SelectItemIndicator";
|
|
1744
1703
|
const Inner = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef((componentProps, forwardedRef) => {
|
|
1745
1704
|
const {
|
|
1746
1705
|
render,
|
|
1747
1706
|
className,
|
|
1707
|
+
style,
|
|
1748
1708
|
keepMounted,
|
|
1749
1709
|
...elementProps
|
|
1750
1710
|
} = componentProps;
|
|
@@ -1790,28 +1750,32 @@ if (process.env.NODE_ENV !== "production") Inner.displayName = "Inner";
|
|
|
1790
1750
|
*/
|
|
1791
1751
|
const SelectItemText = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(function SelectItemText(componentProps, forwardedRef) {
|
|
1792
1752
|
const {
|
|
1793
|
-
|
|
1753
|
+
index,
|
|
1794
1754
|
textRef,
|
|
1795
1755
|
selectedByFocus,
|
|
1796
1756
|
hasRegistered
|
|
1797
1757
|
} = useSelectItemContext();
|
|
1798
1758
|
const {
|
|
1759
|
+
firstItemTextRef,
|
|
1799
1760
|
selectedItemTextRef
|
|
1800
1761
|
} = useSelectRootContext();
|
|
1801
1762
|
const {
|
|
1802
|
-
className,
|
|
1803
1763
|
render,
|
|
1764
|
+
className,
|
|
1765
|
+
style,
|
|
1804
1766
|
...elementProps
|
|
1805
1767
|
} = componentProps;
|
|
1806
1768
|
const localRef = React.useCallback(node => {
|
|
1807
|
-
if (!node
|
|
1769
|
+
if (!node) {
|
|
1808
1770
|
return;
|
|
1809
1771
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1772
|
+
if (hasRegistered && index === 0) {
|
|
1773
|
+
firstItemTextRef.current = node;
|
|
1774
|
+
}
|
|
1775
|
+
if (hasRegistered && selectedByFocus) {
|
|
1812
1776
|
selectedItemTextRef.current = node;
|
|
1813
1777
|
}
|
|
1814
|
-
}, [selectedItemTextRef,
|
|
1778
|
+
}, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus, hasRegistered]);
|
|
1815
1779
|
const element = useRenderElement('div', componentProps, {
|
|
1816
1780
|
ref: [localRef, forwardedRef, textRef],
|
|
1817
1781
|
props: elementProps
|
|
@@ -1832,8 +1796,9 @@ function useSelectGroupContext() {
|
|
|
1832
1796
|
|
|
1833
1797
|
const SelectGroup = /*#__PURE__*/React.forwardRef(function SelectGroup(componentProps, forwardedRef) {
|
|
1834
1798
|
const {
|
|
1835
|
-
className,
|
|
1836
1799
|
render,
|
|
1800
|
+
className,
|
|
1801
|
+
style,
|
|
1837
1802
|
...elementProps
|
|
1838
1803
|
} = componentProps;
|
|
1839
1804
|
const [labelId, setLabelId] = React.useState();
|
|
@@ -1863,8 +1828,9 @@ if (process.env.NODE_ENV !== "production") SelectGroup.displayName = "SelectGrou
|
|
|
1863
1828
|
*/
|
|
1864
1829
|
const SelectGroupLabel = /*#__PURE__*/React.forwardRef(function SelectGroupLabel(componentProps, forwardedRef) {
|
|
1865
1830
|
const {
|
|
1866
|
-
className,
|
|
1867
1831
|
render,
|
|
1832
|
+
className,
|
|
1833
|
+
style,
|
|
1868
1834
|
id: idProp,
|
|
1869
1835
|
...elementProps
|
|
1870
1836
|
} = componentProps;
|
|
@@ -1885,4 +1851,4 @@ const SelectGroupLabel = /*#__PURE__*/React.forwardRef(function SelectGroupLabel
|
|
|
1885
1851
|
});
|
|
1886
1852
|
if (process.env.NODE_ENV !== "production") SelectGroupLabel.displayName = "SelectGroupLabel";
|
|
1887
1853
|
|
|
1888
|
-
export { SelectRoot as S, SelectPortal as a, SelectTrigger as b, SelectValue as c, SelectPositioner as d, SelectPopup as e, SelectList as f, SelectItem as g, SelectItemText as h, SelectItemIndicator as i, SelectGroup as j, SelectGroupLabel as k,
|
|
1854
|
+
export { SelectRoot as S, SelectPortal as a, SelectTrigger as b, SelectValue as c, SelectPositioner as d, SelectPopup as e, SelectList as f, SelectItem as g, SelectItemText as h, SelectItemIndicator as i, SelectGroup as j, SelectGroupLabel as k, useSelectRootContext as l, useSelectPositionerContext as m, selectors as s, usePreviousValue as u };
|