@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
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
var React = require('react');
|
|
4
4
|
var useButton = require('./useButton-cjs.js');
|
|
5
5
|
var useRenderElement = require('./useRenderElement-cjs.js');
|
|
6
|
-
var
|
|
6
|
+
var useScrollLock = require('./useScrollLock-cjs.js');
|
|
7
|
+
var isElementDisabled = require('./isElementDisabled-cjs.js');
|
|
7
8
|
var useLabelableId = require('./useLabelableId-cjs.js');
|
|
8
9
|
var resolveAriaLabelledBy = require('./resolveAriaLabelledBy-cjs.js');
|
|
10
|
+
var scrollEdges = require('./scrollEdges-cjs.js');
|
|
9
11
|
var jsxRuntime = require('react/jsx-runtime');
|
|
10
12
|
var useCompositeListItem = require('./useCompositeListItem-cjs.js');
|
|
11
|
-
var
|
|
12
|
-
var index_esm = require('./index.esm-cjs.js');
|
|
13
|
-
var CSPContext = require('./CSPContext-cjs.js');
|
|
14
|
-
var ToolbarRootContext = require('./ToolbarRootContext-cjs.js');
|
|
13
|
+
var floatingUi_utils = require('./floating-ui.utils-cjs.js');
|
|
15
14
|
var clamp = require('./clamp-cjs.js');
|
|
16
15
|
var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
|
|
17
16
|
var floatingUi_reactDom = require('./floating-ui.react-dom-cjs.js');
|
|
@@ -34,7 +33,25 @@ function _interopNamespaceDefault(e) {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
37
|
-
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a previous value of its argument.
|
|
39
|
+
* @param value Current value.
|
|
40
|
+
* @returns Previous value, or null if there is no previous value.
|
|
41
|
+
*/
|
|
42
|
+
function usePreviousValue(value) {
|
|
43
|
+
const [state, setState] = React__namespace.useState({
|
|
44
|
+
current: value,
|
|
45
|
+
previous: null
|
|
46
|
+
});
|
|
47
|
+
if (value !== state.current) {
|
|
48
|
+
setState({
|
|
49
|
+
current: value,
|
|
50
|
+
previous: state.current
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return state.previous;
|
|
54
|
+
}
|
|
38
55
|
|
|
39
56
|
const SelectRootContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
40
57
|
if (process.env.NODE_ENV !== "production") SelectRootContext.displayName = "SelectRootContext";
|
|
@@ -56,16 +73,16 @@ function useSelectFloatingContext() {
|
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
const selectors = {
|
|
59
|
-
id:
|
|
60
|
-
labelId:
|
|
61
|
-
modal:
|
|
62
|
-
multiple:
|
|
63
|
-
items:
|
|
64
|
-
itemToStringLabel:
|
|
65
|
-
itemToStringValue:
|
|
66
|
-
isItemEqualToValue:
|
|
67
|
-
value:
|
|
68
|
-
hasSelectedValue:
|
|
76
|
+
id: useScrollLock.createSelector(state => state.id),
|
|
77
|
+
labelId: useScrollLock.createSelector(state => state.labelId),
|
|
78
|
+
modal: useScrollLock.createSelector(state => state.modal),
|
|
79
|
+
multiple: useScrollLock.createSelector(state => state.multiple),
|
|
80
|
+
items: useScrollLock.createSelector(state => state.items),
|
|
81
|
+
itemToStringLabel: useScrollLock.createSelector(state => state.itemToStringLabel),
|
|
82
|
+
itemToStringValue: useScrollLock.createSelector(state => state.itemToStringValue),
|
|
83
|
+
isItemEqualToValue: useScrollLock.createSelector(state => state.isItemEqualToValue),
|
|
84
|
+
value: useScrollLock.createSelector(state => state.value),
|
|
85
|
+
hasSelectedValue: useScrollLock.createSelector(state => {
|
|
69
86
|
const {
|
|
70
87
|
value,
|
|
71
88
|
multiple,
|
|
@@ -79,42 +96,41 @@ const selectors = {
|
|
|
79
96
|
}
|
|
80
97
|
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue) !== '';
|
|
81
98
|
}),
|
|
82
|
-
hasNullItemLabel:
|
|
99
|
+
hasNullItemLabel: useScrollLock.createSelector((state, enabled) => {
|
|
83
100
|
return enabled ? resolveAriaLabelledBy.hasNullItemLabel(state.items) : false;
|
|
84
101
|
}),
|
|
85
|
-
open:
|
|
86
|
-
mounted:
|
|
87
|
-
forceMount:
|
|
88
|
-
transitionStatus:
|
|
89
|
-
openMethod:
|
|
90
|
-
activeIndex:
|
|
91
|
-
selectedIndex:
|
|
92
|
-
isActive:
|
|
93
|
-
isSelected:
|
|
102
|
+
open: useScrollLock.createSelector(state => state.open),
|
|
103
|
+
mounted: useScrollLock.createSelector(state => state.mounted),
|
|
104
|
+
forceMount: useScrollLock.createSelector(state => state.forceMount),
|
|
105
|
+
transitionStatus: useScrollLock.createSelector(state => state.transitionStatus),
|
|
106
|
+
openMethod: useScrollLock.createSelector(state => state.openMethod),
|
|
107
|
+
activeIndex: useScrollLock.createSelector(state => state.activeIndex),
|
|
108
|
+
selectedIndex: useScrollLock.createSelector(state => state.selectedIndex),
|
|
109
|
+
isActive: useScrollLock.createSelector((state, index) => state.activeIndex === index),
|
|
110
|
+
isSelected: useScrollLock.createSelector((state, itemValue) => {
|
|
94
111
|
const comparer = state.isItemEqualToValue;
|
|
95
112
|
const storeValue = state.value;
|
|
96
113
|
if (state.multiple) {
|
|
97
114
|
return Array.isArray(storeValue) && storeValue.some(selectedItem => resolveAriaLabelledBy.compareItemEquality(itemValue, selectedItem, comparer));
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
117
|
+
// The value is the source of truth: a stale `selectedIndex` (e.g. the controlled
|
|
118
|
+
// value changes while the popup is open, where the index sync is deferred) must not
|
|
119
|
+
// keep a previously selected item marked as selected.
|
|
105
120
|
return resolveAriaLabelledBy.compareItemEquality(itemValue, storeValue, comparer);
|
|
106
121
|
}),
|
|
107
|
-
isSelectedByFocus:
|
|
122
|
+
isSelectedByFocus: useScrollLock.createSelector((state, index) => {
|
|
108
123
|
return state.selectedIndex === index;
|
|
109
124
|
}),
|
|
110
|
-
popupProps:
|
|
111
|
-
triggerProps:
|
|
112
|
-
triggerElement:
|
|
113
|
-
positionerElement:
|
|
114
|
-
listElement:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
popupProps: useScrollLock.createSelector(state => state.popupProps),
|
|
126
|
+
triggerProps: useScrollLock.createSelector(state => state.triggerProps),
|
|
127
|
+
triggerElement: useScrollLock.createSelector(state => state.triggerElement),
|
|
128
|
+
positionerElement: useScrollLock.createSelector(state => state.positionerElement),
|
|
129
|
+
listElement: useScrollLock.createSelector(state => state.listElement),
|
|
130
|
+
popupSide: useScrollLock.createSelector(state => state.popupSide),
|
|
131
|
+
scrollUpArrowVisible: useScrollLock.createSelector(state => state.scrollUpArrowVisible),
|
|
132
|
+
scrollDownArrowVisible: useScrollLock.createSelector(state => state.scrollDownArrowVisible),
|
|
133
|
+
hasScrollArrows: useScrollLock.createSelector(state => state.hasScrollArrows)
|
|
118
134
|
};
|
|
119
135
|
|
|
120
136
|
function SelectRoot(props) {
|
|
@@ -127,6 +143,7 @@ function SelectRoot(props) {
|
|
|
127
143
|
defaultOpen = false,
|
|
128
144
|
onOpenChange,
|
|
129
145
|
name: nameProp,
|
|
146
|
+
form,
|
|
130
147
|
autoComplete,
|
|
131
148
|
disabled: disabledProp = false,
|
|
132
149
|
readOnly = false,
|
|
@@ -150,7 +167,6 @@ function SelectRoot(props) {
|
|
|
150
167
|
setDirty,
|
|
151
168
|
setTouched,
|
|
152
169
|
setFocused,
|
|
153
|
-
shouldValidateOnChange,
|
|
154
170
|
validityData,
|
|
155
171
|
setFilled,
|
|
156
172
|
name: fieldName,
|
|
@@ -183,11 +199,12 @@ function SelectRoot(props) {
|
|
|
183
199
|
const valueRef = React__namespace.useRef(null);
|
|
184
200
|
const valuesRef = React__namespace.useRef([]);
|
|
185
201
|
const typingRef = React__namespace.useRef(false);
|
|
186
|
-
const
|
|
202
|
+
const firstItemTextRef = React__namespace.useRef(null);
|
|
187
203
|
const selectedItemTextRef = React__namespace.useRef(null);
|
|
188
204
|
const selectionRef = React__namespace.useRef({
|
|
189
205
|
allowSelectedMouseUp: false,
|
|
190
|
-
allowUnselectedMouseUp: false
|
|
206
|
+
allowUnselectedMouseUp: false,
|
|
207
|
+
dragY: 0
|
|
191
208
|
});
|
|
192
209
|
const alignItemWithTriggerActiveRef = React__namespace.useRef(false);
|
|
193
210
|
const {
|
|
@@ -198,8 +215,8 @@ function SelectRoot(props) {
|
|
|
198
215
|
const {
|
|
199
216
|
openMethod,
|
|
200
217
|
triggerProps: interactionTypeProps
|
|
201
|
-
} =
|
|
202
|
-
const store = useRenderElement.useRefWithInit(() => new
|
|
218
|
+
} = useScrollLock.useOpenInteractionType(open);
|
|
219
|
+
const store = useRenderElement.useRefWithInit(() => new useScrollLock.Store({
|
|
203
220
|
id: generatedId,
|
|
204
221
|
labelId: undefined,
|
|
205
222
|
modal,
|
|
@@ -221,16 +238,22 @@ function SelectRoot(props) {
|
|
|
221
238
|
triggerElement: null,
|
|
222
239
|
positionerElement: null,
|
|
223
240
|
listElement: null,
|
|
241
|
+
popupSide: null,
|
|
224
242
|
scrollUpArrowVisible: false,
|
|
225
243
|
scrollDownArrowVisible: false,
|
|
226
244
|
hasScrollArrows: false
|
|
227
245
|
})).current;
|
|
228
|
-
const activeIndex =
|
|
229
|
-
const selectedIndex =
|
|
230
|
-
const triggerElement =
|
|
231
|
-
const positionerElement =
|
|
246
|
+
const activeIndex = useScrollLock.useStore(store, selectors.activeIndex);
|
|
247
|
+
const selectedIndex = useScrollLock.useStore(store, selectors.selectedIndex);
|
|
248
|
+
const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
|
|
249
|
+
const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
|
|
250
|
+
const previousOpenMethod = usePreviousValue(openMethod);
|
|
251
|
+
const renderedOpenMethod = openMethod ?? previousOpenMethod ?? null;
|
|
232
252
|
const serializedValue = React__namespace.useMemo(() => {
|
|
233
|
-
|
|
253
|
+
// In multiple mode the shared input is nameless; per-value entries are submitted via
|
|
254
|
+
// `hiddenInputs`. Its value is therefore irrelevant, and passing the whole array to
|
|
255
|
+
// `stringifyAsValue` would invoke a user `itemToStringValue` with an array it doesn't expect.
|
|
256
|
+
if (multiple) {
|
|
234
257
|
return '';
|
|
235
258
|
}
|
|
236
259
|
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue);
|
|
@@ -242,15 +265,12 @@ function SelectRoot(props) {
|
|
|
242
265
|
return resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue);
|
|
243
266
|
}, [multiple, value, itemToStringValue]);
|
|
244
267
|
const controlRef = useButton.useValueAsRef(store.state.triggerElement);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
commit: validation.commit,
|
|
248
|
-
value,
|
|
249
|
-
controlRef,
|
|
250
|
-
name,
|
|
251
|
-
getValue: () => fieldStringValue
|
|
252
|
-
});
|
|
268
|
+
const getStringifiedValueForForm = useButton.useStableCallback(() => fieldStringValue);
|
|
269
|
+
useLabelableId.useRegisterFieldControl(controlRef, generatedId, value, getStringifiedValueForForm, !disabled, nameProp);
|
|
253
270
|
const initialValueRef = React__namespace.useRef(value);
|
|
271
|
+
// Mirror the `hasSelectedValue` store selector so the Field's filled state agrees with the
|
|
272
|
+
// trigger/value placeholder semantics (a value serializing to `''` counts as empty).
|
|
273
|
+
const hasSelectedValue = multiple ? Array.isArray(value) && value.length > 0 : value != null && resolveAriaLabelledBy.stringifyAsValue(value, itemToStringValue) !== '';
|
|
254
274
|
useButton.useIsoLayoutEffect(() => {
|
|
255
275
|
// Ensure the values and labels are registered for programmatic value changes.
|
|
256
276
|
if (value !== initialValueRef.current) {
|
|
@@ -258,35 +278,43 @@ function SelectRoot(props) {
|
|
|
258
278
|
}
|
|
259
279
|
}, [store, value]);
|
|
260
280
|
useButton.useIsoLayoutEffect(() => {
|
|
261
|
-
setFilled(
|
|
262
|
-
}, [
|
|
281
|
+
setFilled(hasSelectedValue);
|
|
282
|
+
}, [hasSelectedValue, setFilled]);
|
|
263
283
|
useButton.useIsoLayoutEffect(function syncSelectedIndex() {
|
|
264
|
-
if (open) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
284
|
const registry = valuesRef.current;
|
|
285
|
+
let nextIndex;
|
|
268
286
|
if (multiple) {
|
|
269
287
|
const currentValue = Array.isArray(value) ? value : [];
|
|
270
288
|
if (currentValue.length === 0) {
|
|
271
|
-
|
|
272
|
-
|
|
289
|
+
nextIndex = null;
|
|
290
|
+
} else {
|
|
291
|
+
const lastValue = currentValue[currentValue.length - 1];
|
|
292
|
+
const lastIndex = resolveAriaLabelledBy.findItemIndex(registry, lastValue, isItemEqualToValue);
|
|
293
|
+
nextIndex = lastIndex === -1 ? null : lastIndex;
|
|
273
294
|
}
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
295
|
+
} else {
|
|
296
|
+
const index = resolveAriaLabelledBy.findItemIndex(registry, value, isItemEqualToValue);
|
|
297
|
+
nextIndex = index === -1 ? null : index;
|
|
298
|
+
}
|
|
299
|
+
if (nextIndex === null) {
|
|
300
|
+
selectedItemTextRef.current = null;
|
|
301
|
+
}
|
|
302
|
+
if (open) {
|
|
277
303
|
return;
|
|
278
304
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
305
|
+
store.set('selectedIndex', nextIndex);
|
|
306
|
+
}, [hasSelectedValue, multiple, open, value, valuesRef, isItemEqualToValue, store, selectedItemTextRef]);
|
|
307
|
+
function isSelectedValueDirty(currentValue) {
|
|
308
|
+
const initialValue = validityData.initialValue;
|
|
309
|
+
if (Array.isArray(currentValue) && Array.isArray(initialValue)) {
|
|
310
|
+
return !resolveAriaLabelledBy.areArraysEqual(currentValue, initialValue, (itemValue, initialItemValue) => resolveAriaLabelledBy.compareItemEquality(itemValue, initialItemValue, isItemEqualToValue));
|
|
311
|
+
}
|
|
312
|
+
return currentValue !== initialValue;
|
|
313
|
+
}
|
|
282
314
|
useButton.useValueChanged(value, () => {
|
|
283
315
|
clearErrors(name);
|
|
284
|
-
setDirty(value
|
|
285
|
-
|
|
286
|
-
validation.commit(value);
|
|
287
|
-
} else {
|
|
288
|
-
validation.commit(value, true);
|
|
289
|
-
}
|
|
316
|
+
setDirty(isSelectedValueDirty(value));
|
|
317
|
+
validation.change(value);
|
|
290
318
|
});
|
|
291
319
|
const setOpen = useButton.useStableCallback((nextOpen, eventDetails) => {
|
|
292
320
|
onOpenChange?.(nextOpen, eventDetails);
|
|
@@ -301,24 +329,13 @@ function SelectRoot(props) {
|
|
|
301
329
|
validation.commit(value);
|
|
302
330
|
}
|
|
303
331
|
}
|
|
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
332
|
});
|
|
319
333
|
const handleUnmount = useButton.useStableCallback(() => {
|
|
320
334
|
setMounted(false);
|
|
321
|
-
store.
|
|
335
|
+
store.update({
|
|
336
|
+
activeIndex: null,
|
|
337
|
+
openMethod: null
|
|
338
|
+
});
|
|
322
339
|
onOpenChangeComplete?.(false);
|
|
323
340
|
});
|
|
324
341
|
useButton.useOpenChangeComplete({
|
|
@@ -346,10 +363,10 @@ function SelectRoot(props) {
|
|
|
346
363
|
if (!scroller) {
|
|
347
364
|
return;
|
|
348
365
|
}
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
const shouldShowUp =
|
|
352
|
-
const shouldShowDown =
|
|
366
|
+
const maxScrollTop = scrollEdges.getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
367
|
+
const scrollTop = scrollEdges.normalizeScrollOffset(scroller.scrollTop, maxScrollTop);
|
|
368
|
+
const shouldShowUp = scrollTop > 0;
|
|
369
|
+
const shouldShowDown = scrollTop < maxScrollTop;
|
|
353
370
|
if (store.state.scrollUpArrowVisible !== shouldShowUp) {
|
|
354
371
|
store.set('scrollUpArrowVisible', shouldShowUp);
|
|
355
372
|
}
|
|
@@ -365,13 +382,11 @@ function SelectRoot(props) {
|
|
|
365
382
|
floating: positionerElement
|
|
366
383
|
}
|
|
367
384
|
});
|
|
368
|
-
const click =
|
|
385
|
+
const click = useScrollLock.useClick(floatingContext, {
|
|
369
386
|
enabled: !readOnly && !disabled,
|
|
370
387
|
event: 'mousedown'
|
|
371
388
|
});
|
|
372
|
-
const dismiss =
|
|
373
|
-
bubbles: false
|
|
374
|
-
});
|
|
389
|
+
const dismiss = useScrollLock.useDismiss(floatingContext);
|
|
375
390
|
const listNavigation = useCompositeListItem.useListNavigation(floatingContext, {
|
|
376
391
|
enabled: !readOnly && !disabled,
|
|
377
392
|
listRef,
|
|
@@ -385,15 +400,19 @@ function SelectRoot(props) {
|
|
|
385
400
|
}
|
|
386
401
|
store.set('activeIndex', nextActiveIndex);
|
|
387
402
|
},
|
|
388
|
-
|
|
389
|
-
// the `alignItemWithTrigger=true`, causing a performance issue on Chrome.
|
|
390
|
-
focusItemOnHover: false
|
|
403
|
+
focusItemOnHover: highlightItemOnHover
|
|
391
404
|
});
|
|
392
405
|
const typeahead = useCompositeListItem.useTypeahead(floatingContext, {
|
|
393
406
|
enabled: !readOnly && !disabled && (open || !multiple),
|
|
394
407
|
listRef: labelsRef,
|
|
395
408
|
activeIndex,
|
|
396
409
|
selectedIndex,
|
|
410
|
+
// Skip disabled items while matching so typeahead advances to the next selectable item
|
|
411
|
+
// (a click can never select a disabled item and native `<select>` skips them too). Resolve
|
|
412
|
+
// the disabled state from the element via the attribute-only `isElementDisabled` so the
|
|
413
|
+
// hidden, force-mounted items used for closed-trigger typeahead aren't dropped by the
|
|
414
|
+
// `elementsRef`/visibility filter that `disabledIndices` deliberately sidesteps.
|
|
415
|
+
disabledIndices: index => isElementDisabled.isElementDisabled(listRef.current[index]),
|
|
397
416
|
onMatch(index) {
|
|
398
417
|
if (open) {
|
|
399
418
|
store.set('activeIndex', index);
|
|
@@ -401,23 +420,22 @@ function SelectRoot(props) {
|
|
|
401
420
|
setValue(valuesRef.current[index], useButton.createChangeEventDetails('none'));
|
|
402
421
|
}
|
|
403
422
|
},
|
|
404
|
-
|
|
423
|
+
onTyping(typing) {
|
|
405
424
|
typingRef.current = typing;
|
|
406
425
|
}
|
|
407
426
|
});
|
|
408
|
-
const {
|
|
409
|
-
getReferenceProps,
|
|
410
|
-
getFloatingProps,
|
|
411
|
-
getItemProps
|
|
412
|
-
} = InternalBackdrop.useInteractions([click, dismiss, listNavigation, typeahead]);
|
|
413
427
|
const mergedTriggerProps = React__namespace.useMemo(() => {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
428
|
+
const triggerInteractionProps = useRenderElement.mergeProps(typeahead.reference, listNavigation.reference, dismiss.reference, click.reference, interactionTypeProps);
|
|
429
|
+
if (generatedId) {
|
|
430
|
+
triggerInteractionProps.id = generatedId;
|
|
431
|
+
}
|
|
432
|
+
return triggerInteractionProps;
|
|
433
|
+
}, [click.reference, typeahead.reference, listNavigation.reference, dismiss.reference, interactionTypeProps, generatedId]);
|
|
434
|
+
const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, typeahead.floating, listNavigation.floating, dismiss.floating), [typeahead.floating, listNavigation.floating, dismiss.floating]);
|
|
435
|
+
const itemProps = listNavigation.item ?? useRenderElement.EMPTY_OBJECT;
|
|
436
|
+
useScrollLock.useOnFirstRender(() => {
|
|
419
437
|
store.update({
|
|
420
|
-
popupProps
|
|
438
|
+
popupProps,
|
|
421
439
|
triggerProps: mergedTriggerProps
|
|
422
440
|
});
|
|
423
441
|
});
|
|
@@ -430,15 +448,15 @@ function SelectRoot(props) {
|
|
|
430
448
|
open,
|
|
431
449
|
mounted,
|
|
432
450
|
transitionStatus,
|
|
433
|
-
popupProps
|
|
451
|
+
popupProps,
|
|
434
452
|
triggerProps: mergedTriggerProps,
|
|
435
453
|
items,
|
|
436
454
|
itemToStringLabel,
|
|
437
455
|
itemToStringValue,
|
|
438
456
|
isItemEqualToValue,
|
|
439
|
-
openMethod
|
|
457
|
+
openMethod: renderedOpenMethod
|
|
440
458
|
});
|
|
441
|
-
}, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus,
|
|
459
|
+
}, [store, generatedId, modal, multiple, value, open, mounted, transitionStatus, popupProps, mergedTriggerProps, items, itemToStringLabel, itemToStringValue, isItemEqualToValue, renderedOpenMethod]);
|
|
442
460
|
const contextValue = React__namespace.useMemo(() => ({
|
|
443
461
|
store,
|
|
444
462
|
name,
|
|
@@ -446,8 +464,6 @@ function SelectRoot(props) {
|
|
|
446
464
|
disabled,
|
|
447
465
|
readOnly,
|
|
448
466
|
multiple,
|
|
449
|
-
itemToStringLabel,
|
|
450
|
-
itemToStringValue,
|
|
451
467
|
highlightItemOnHover,
|
|
452
468
|
setValue,
|
|
453
469
|
setOpen,
|
|
@@ -456,20 +472,19 @@ function SelectRoot(props) {
|
|
|
456
472
|
scrollHandlerRef,
|
|
457
473
|
handleScrollArrowVisibility,
|
|
458
474
|
scrollArrowsMountedCountRef,
|
|
459
|
-
|
|
460
|
-
events: floatingContext.context.events,
|
|
475
|
+
itemProps,
|
|
461
476
|
valueRef,
|
|
462
477
|
valuesRef,
|
|
463
478
|
labelsRef,
|
|
464
479
|
typingRef,
|
|
465
480
|
selectionRef,
|
|
481
|
+
firstItemTextRef,
|
|
466
482
|
selectedItemTextRef,
|
|
467
483
|
validation,
|
|
468
484
|
onOpenChangeComplete,
|
|
469
|
-
keyboardActiveRef,
|
|
470
485
|
alignItemWithTriggerActiveRef,
|
|
471
486
|
initialValueRef
|
|
472
|
-
}), [store, name, required, disabled, readOnly, multiple,
|
|
487
|
+
}), [store, name, required, disabled, readOnly, multiple, highlightItemOnHover, setValue, setOpen, itemProps, validation, onOpenChangeComplete, handleScrollArrowVisibility]);
|
|
473
488
|
const ref = useRenderElement.useMergedRefs(inputRef, validation.inputRef);
|
|
474
489
|
const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;
|
|
475
490
|
const hiddenInputName = multiple ? undefined : name;
|
|
@@ -481,32 +496,33 @@ function SelectRoot(props) {
|
|
|
481
496
|
const currentSerializedValue = resolveAriaLabelledBy.stringifyAsValue(v, itemToStringValue);
|
|
482
497
|
return /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
483
498
|
type: "hidden",
|
|
499
|
+
form: form,
|
|
484
500
|
name: name,
|
|
485
|
-
value: currentSerializedValue
|
|
501
|
+
value: currentSerializedValue,
|
|
502
|
+
disabled: disabled
|
|
486
503
|
}, currentSerializedValue);
|
|
487
504
|
});
|
|
488
|
-
}, [multiple, value, name, itemToStringValue]);
|
|
505
|
+
}, [multiple, value, form, name, itemToStringValue, disabled]);
|
|
489
506
|
return /*#__PURE__*/jsxRuntime.jsx(SelectRootContext.Provider, {
|
|
490
507
|
value: contextValue,
|
|
491
508
|
children: /*#__PURE__*/jsxRuntime.jsxs(SelectFloatingContext.Provider, {
|
|
492
509
|
value: floatingContext,
|
|
493
510
|
children: [children, /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
494
|
-
...validation.
|
|
511
|
+
...validation.getValidationProps(disabled, {
|
|
495
512
|
onFocus() {
|
|
496
513
|
// Move focus to the trigger element when the hidden input is focused.
|
|
497
514
|
store.state.triggerElement?.focus({
|
|
498
515
|
// Supported in Chrome from 144 (January 2026)
|
|
499
|
-
// @ts-expect-error - focusVisible is not yet in the lib.dom.d.ts
|
|
500
516
|
focusVisible: true
|
|
501
517
|
});
|
|
502
518
|
},
|
|
503
519
|
// Handle browser autofill.
|
|
504
520
|
onChange(event) {
|
|
505
521
|
// Workaround for https://github.com/facebook/react/issues/9023
|
|
506
|
-
if (event.nativeEvent.defaultPrevented) {
|
|
522
|
+
if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
|
|
507
523
|
return;
|
|
508
524
|
}
|
|
509
|
-
const nextValue = event.
|
|
525
|
+
const nextValue = event.currentTarget.value;
|
|
510
526
|
const details = useButton.createChangeEventDetails(useButton.none, event.nativeEvent);
|
|
511
527
|
function handleChange() {
|
|
512
528
|
if (multiple) {
|
|
@@ -514,20 +530,21 @@ function SelectRoot(props) {
|
|
|
514
530
|
return;
|
|
515
531
|
}
|
|
516
532
|
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
533
|
+
// Preserve the original serialized matching, then fall back to rendered text,
|
|
534
|
+
// which browsers can autofill for primitive values like `value="US">United States`.
|
|
535
|
+
const nextValueLower = nextValue.toLowerCase();
|
|
536
|
+
let matchingIndex = valuesRef.current.findIndex(candidate => resolveAriaLabelledBy.stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower || resolveAriaLabelledBy.stringifyAsLabel(candidate, itemToStringLabel).toLowerCase() === nextValueLower);
|
|
537
|
+
if (matchingIndex === -1) {
|
|
538
|
+
matchingIndex = valuesRef.current.findIndex((_, index) => {
|
|
539
|
+
const renderedLabel = labelsRef.current[index];
|
|
540
|
+
return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const matchingValue = matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
|
|
525
544
|
if (matchingValue != null) {
|
|
526
|
-
|
|
545
|
+
// `setValue` may be canceled by `onValueChange`; rely on `useValueChanged` to
|
|
546
|
+
// mark the field dirty and run validation only when the value actually changes.
|
|
527
547
|
setValue(matchingValue, details);
|
|
528
|
-
if (shouldValidateOnChange()) {
|
|
529
|
-
validation.commit(matchingValue);
|
|
530
|
-
}
|
|
531
548
|
}
|
|
532
549
|
}
|
|
533
550
|
store.set('forceMount', true);
|
|
@@ -535,6 +552,7 @@ function SelectRoot(props) {
|
|
|
535
552
|
}
|
|
536
553
|
}),
|
|
537
554
|
id: generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined,
|
|
555
|
+
form: form,
|
|
538
556
|
name: hiddenInputName,
|
|
539
557
|
autoComplete: autoComplete,
|
|
540
558
|
value: serializedValue,
|
|
@@ -544,7 +562,8 @@ function SelectRoot(props) {
|
|
|
544
562
|
ref: ref,
|
|
545
563
|
style: name ? useButton.visuallyHiddenInput : useButton.visuallyHidden,
|
|
546
564
|
tabIndex: -1,
|
|
547
|
-
"aria-hidden": true
|
|
565
|
+
"aria-hidden": true,
|
|
566
|
+
suppressHydrationWarning: true
|
|
548
567
|
}), hiddenInputs]
|
|
549
568
|
})
|
|
550
569
|
});
|
|
@@ -552,10 +571,12 @@ function SelectRoot(props) {
|
|
|
552
571
|
|
|
553
572
|
const BOUNDARY_OFFSET = 2;
|
|
554
573
|
const SELECTED_DELAY = 400;
|
|
555
|
-
const UNSELECTED_DELAY = 200;
|
|
556
574
|
const stateAttributesMapping$2 = {
|
|
557
|
-
...
|
|
575
|
+
...useScrollLock.pressableTriggerOpenStateMapping,
|
|
558
576
|
...useLabelableId.fieldValidityMapping,
|
|
577
|
+
popupSide: side => side ? {
|
|
578
|
+
'data-popup-side': side
|
|
579
|
+
} : null,
|
|
559
580
|
value: () => null
|
|
560
581
|
};
|
|
561
582
|
|
|
@@ -572,6 +593,7 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
572
593
|
id: idProp,
|
|
573
594
|
disabled: disabledProp = false,
|
|
574
595
|
nativeButton = true,
|
|
596
|
+
style,
|
|
575
597
|
...elementProps
|
|
576
598
|
} = componentProps;
|
|
577
599
|
const {
|
|
@@ -592,20 +614,20 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
592
614
|
readOnly,
|
|
593
615
|
required,
|
|
594
616
|
alignItemWithTriggerActiveRef,
|
|
595
|
-
disabled: selectDisabled
|
|
596
|
-
keyboardActiveRef
|
|
617
|
+
disabled: selectDisabled
|
|
597
618
|
} = useSelectRootContext();
|
|
598
619
|
const disabled = fieldDisabled || selectDisabled || disabledProp;
|
|
599
|
-
const open =
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
const
|
|
620
|
+
const open = useScrollLock.useStore(store, selectors.open);
|
|
621
|
+
const mounted = useScrollLock.useStore(store, selectors.mounted);
|
|
622
|
+
const value = useScrollLock.useStore(store, selectors.value);
|
|
623
|
+
const triggerProps = useScrollLock.useStore(store, selectors.triggerProps);
|
|
624
|
+
const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
|
|
625
|
+
const listElement = useScrollLock.useStore(store, selectors.listElement);
|
|
626
|
+
const popupSideValue = useScrollLock.useStore(store, selectors.popupSide);
|
|
627
|
+
const rootId = useScrollLock.useStore(store, selectors.id);
|
|
628
|
+
const selectLabelId = useScrollLock.useStore(store, selectors.labelId);
|
|
629
|
+
const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
|
|
630
|
+
const popupSide = mounted && positionerElement ? popupSideValue : null;
|
|
609
631
|
const id = idProp ?? rootId;
|
|
610
632
|
const ariaLabelledBy = resolveAriaLabelledBy.resolveAriaLabelledBy(fieldLabelId, selectLabelId);
|
|
611
633
|
useLabelableId.useLabelableId({
|
|
@@ -623,61 +645,41 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
623
645
|
const setTriggerElement = useButton.useStableCallback(element => {
|
|
624
646
|
store.set('triggerElement', element);
|
|
625
647
|
});
|
|
626
|
-
const mergedRef = useRenderElement.useMergedRefs(forwardedRef, triggerRef, buttonRef, setTriggerElement);
|
|
627
648
|
const timeoutFocus = useButton.useTimeout();
|
|
628
649
|
const timeoutMouseDown = useButton.useTimeout();
|
|
629
650
|
const selectedDelayTimeout = useButton.useTimeout();
|
|
630
|
-
const unselectedDelayTimeout = useButton.useTimeout();
|
|
631
651
|
React__namespace.useEffect(() => {
|
|
632
652
|
if (open) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
}
|
|
653
|
+
// A mousedown on the trigger can open the popup under the cursor. Keep mouseup selection
|
|
654
|
+
// disabled briefly so releasing over either the selected item or a neighboring item doesn't
|
|
655
|
+
// commit an accidental selection. SelectItem can still opt into unselected mouseup sooner
|
|
656
|
+
// after a real drag over the item.
|
|
657
|
+
selectedDelayTimeout.start(SELECTED_DELAY, () => {
|
|
658
|
+
selectionRef.current.allowUnselectedMouseUp = true;
|
|
659
|
+
selectionRef.current.allowSelectedMouseUp = true;
|
|
660
|
+
});
|
|
655
661
|
return () => {
|
|
656
662
|
selectedDelayTimeout.clear();
|
|
657
|
-
unselectedDelayTimeout.clear();
|
|
658
663
|
};
|
|
659
664
|
}
|
|
660
665
|
selectionRef.current = {
|
|
661
666
|
allowSelectedMouseUp: false,
|
|
662
|
-
allowUnselectedMouseUp: false
|
|
667
|
+
allowUnselectedMouseUp: false,
|
|
668
|
+
dragY: 0
|
|
663
669
|
};
|
|
664
670
|
timeoutMouseDown.clear();
|
|
665
671
|
return undefined;
|
|
666
|
-
}, [open,
|
|
667
|
-
const
|
|
668
|
-
return listElement?.id ?? useButton.getFloatingFocusElement(positionerElement)?.id;
|
|
669
|
-
}, [listElement, positionerElement]);
|
|
670
|
-
const props = useRenderElement.mergeProps(triggerProps, {
|
|
672
|
+
}, [open, selectionRef, timeoutMouseDown, selectedDelayTimeout]);
|
|
673
|
+
const mergedProps = useRenderElement.mergeProps(triggerProps, {
|
|
671
674
|
id,
|
|
672
675
|
role: 'combobox',
|
|
673
676
|
'aria-expanded': open ? 'true' : 'false',
|
|
674
677
|
'aria-haspopup': 'listbox',
|
|
675
|
-
'aria-controls': open ?
|
|
678
|
+
'aria-controls': open ? listElement?.id ?? useScrollLock.getFloatingFocusElement(positionerElement)?.id : undefined,
|
|
676
679
|
'aria-labelledby': ariaLabelledBy,
|
|
677
680
|
'aria-readonly': readOnly || undefined,
|
|
678
681
|
'aria-required': required || undefined,
|
|
679
682
|
tabIndex: disabled ? -1 : 0,
|
|
680
|
-
ref: mergedRef,
|
|
681
683
|
onFocus(event) {
|
|
682
684
|
setFocused(true);
|
|
683
685
|
|
|
@@ -689,8 +691,6 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
689
691
|
// Saves a re-render on initial click: `forceMount === true` mounts
|
|
690
692
|
// the items before `open === true`. We could sync those cycles better
|
|
691
693
|
// without a timeout, but this is enough for now.
|
|
692
|
-
//
|
|
693
|
-
// XXX: might be causing `act()` warnings.
|
|
694
694
|
timeoutFocus.start(0, () => {
|
|
695
695
|
store.set('forceMount', true);
|
|
696
696
|
});
|
|
@@ -706,12 +706,6 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
706
706
|
validation.commit(value);
|
|
707
707
|
}
|
|
708
708
|
},
|
|
709
|
-
onPointerMove() {
|
|
710
|
-
keyboardActiveRef.current = false;
|
|
711
|
-
},
|
|
712
|
-
onKeyDown() {
|
|
713
|
-
keyboardActiveRef.current = true;
|
|
714
|
-
},
|
|
715
709
|
onMouseDown(event) {
|
|
716
710
|
if (open) {
|
|
717
711
|
return;
|
|
@@ -723,8 +717,9 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
723
717
|
}
|
|
724
718
|
const mouseUpTarget = mouseEvent.target;
|
|
725
719
|
|
|
726
|
-
//
|
|
727
|
-
|
|
720
|
+
// Don't treat the release as an outside press when it lands on the trigger or inside
|
|
721
|
+
// the popup positioner (or their children).
|
|
722
|
+
if (useButton.contains(triggerRef.current, mouseUpTarget) || useButton.contains(positionerRef.current, mouseUpTarget)) {
|
|
728
723
|
return;
|
|
729
724
|
}
|
|
730
725
|
const bounds = useCompositeListItem.getPseudoElementBounds(triggerRef.current);
|
|
@@ -741,7 +736,8 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
741
736
|
});
|
|
742
737
|
});
|
|
743
738
|
}
|
|
744
|
-
},
|
|
739
|
+
}, elementProps, getButtonProps);
|
|
740
|
+
const props = validation.getValidationProps(disabled, mergedProps);
|
|
745
741
|
|
|
746
742
|
// ensure nested useButton does not overwrite the combobox role:
|
|
747
743
|
// <Toolbar.Button render={<Select.Trigger />} />
|
|
@@ -752,10 +748,11 @@ const SelectTrigger = /*#__PURE__*/React__namespace.forwardRef(function SelectTr
|
|
|
752
748
|
disabled,
|
|
753
749
|
value,
|
|
754
750
|
readOnly,
|
|
751
|
+
popupSide,
|
|
755
752
|
placeholder: !hasSelectedValue
|
|
756
753
|
};
|
|
757
754
|
return useRenderElement.useRenderElement('button', componentProps, {
|
|
758
|
-
ref: [forwardedRef, triggerRef],
|
|
755
|
+
ref: [forwardedRef, triggerRef, buttonRef, setTriggerElement],
|
|
759
756
|
state,
|
|
760
757
|
stateAttributesMapping: stateAttributesMapping$2,
|
|
761
758
|
props
|
|
@@ -779,18 +776,19 @@ const SelectValue = /*#__PURE__*/React__namespace.forwardRef(function SelectValu
|
|
|
779
776
|
render,
|
|
780
777
|
children: childrenProp,
|
|
781
778
|
placeholder,
|
|
779
|
+
style,
|
|
782
780
|
...elementProps
|
|
783
781
|
} = componentProps;
|
|
784
782
|
const {
|
|
785
783
|
store,
|
|
786
784
|
valueRef
|
|
787
785
|
} = useSelectRootContext();
|
|
788
|
-
const value =
|
|
789
|
-
const items =
|
|
790
|
-
const itemToStringLabel =
|
|
791
|
-
const hasSelectedValue =
|
|
786
|
+
const value = useScrollLock.useStore(store, selectors.value);
|
|
787
|
+
const items = useScrollLock.useStore(store, selectors.items);
|
|
788
|
+
const itemToStringLabel = useScrollLock.useStore(store, selectors.itemToStringLabel);
|
|
789
|
+
const hasSelectedValue = useScrollLock.useStore(store, selectors.hasSelectedValue);
|
|
792
790
|
const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;
|
|
793
|
-
const hasNullLabel =
|
|
791
|
+
const hasNullLabel = useScrollLock.useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);
|
|
794
792
|
const state = {
|
|
795
793
|
value,
|
|
796
794
|
placeholder: !hasSelectedValue
|
|
@@ -826,15 +824,15 @@ const SelectPortal = /*#__PURE__*/React__namespace.forwardRef(function SelectPor
|
|
|
826
824
|
const {
|
|
827
825
|
store
|
|
828
826
|
} = useSelectRootContext();
|
|
829
|
-
const mounted =
|
|
830
|
-
const forceMount =
|
|
827
|
+
const mounted = useScrollLock.useStore(store, selectors.mounted);
|
|
828
|
+
const forceMount = useScrollLock.useStore(store, selectors.forceMount);
|
|
831
829
|
const shouldRender = mounted || forceMount;
|
|
832
830
|
if (!shouldRender) {
|
|
833
831
|
return null;
|
|
834
832
|
}
|
|
835
833
|
return /*#__PURE__*/jsxRuntime.jsx(SelectPortalContext.Provider, {
|
|
836
834
|
value: true,
|
|
837
|
-
children: /*#__PURE__*/jsxRuntime.jsx(
|
|
835
|
+
children: /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingPortal, {
|
|
838
836
|
ref: forwardedRef,
|
|
839
837
|
...portalProps
|
|
840
838
|
})
|
|
@@ -890,7 +888,8 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
890
888
|
sticky = false,
|
|
891
889
|
disableAnchorTracking,
|
|
892
890
|
alignItemWithTrigger = true,
|
|
893
|
-
collisionAvoidance =
|
|
891
|
+
collisionAvoidance = useScrollLock.DROPDOWN_COLLISION_AVOIDANCE,
|
|
892
|
+
style,
|
|
894
893
|
...elementProps
|
|
895
894
|
} = componentProps;
|
|
896
895
|
const {
|
|
@@ -905,15 +904,15 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
905
904
|
setValue
|
|
906
905
|
} = useSelectRootContext();
|
|
907
906
|
const floatingRootContext = useSelectFloatingContext();
|
|
908
|
-
const open =
|
|
909
|
-
const mounted =
|
|
910
|
-
const modal =
|
|
911
|
-
const value =
|
|
912
|
-
const openMethod =
|
|
913
|
-
const positionerElement =
|
|
914
|
-
const triggerElement =
|
|
915
|
-
const isItemEqualToValue =
|
|
916
|
-
const transitionStatus =
|
|
907
|
+
const open = useScrollLock.useStore(store, selectors.open);
|
|
908
|
+
const mounted = useScrollLock.useStore(store, selectors.mounted);
|
|
909
|
+
const modal = useScrollLock.useStore(store, selectors.modal);
|
|
910
|
+
const value = useScrollLock.useStore(store, selectors.value);
|
|
911
|
+
const openMethod = useScrollLock.useStore(store, selectors.openMethod);
|
|
912
|
+
const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
|
|
913
|
+
const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
|
|
914
|
+
const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
|
|
915
|
+
const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
|
|
917
916
|
const scrollUpArrowRef = React__namespace.useRef(null);
|
|
918
917
|
const scrollDownArrowRef = React__namespace.useRef(null);
|
|
919
918
|
const [controlledAlignItemWithTrigger, setControlledAlignItemWithTrigger] = React__namespace.useState(alignItemWithTrigger);
|
|
@@ -932,7 +931,7 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
932
931
|
}
|
|
933
932
|
}, [store, mounted]);
|
|
934
933
|
React__namespace.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);
|
|
935
|
-
|
|
934
|
+
useCompositeListItem.useAnchoredPopupScrollLock((alignItemWithTriggerActive || modal) && open, openMethod === 'touch', positionerElement, triggerElement);
|
|
936
935
|
const positioning = useCompositeListItem.useAnchorPositioning({
|
|
937
936
|
anchor,
|
|
938
937
|
floatingRootContext,
|
|
@@ -952,34 +951,25 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
952
951
|
});
|
|
953
952
|
const renderedSide = alignItemWithTriggerActive ? 'none' : positioning.side;
|
|
954
953
|
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
954
|
const state = {
|
|
970
955
|
open,
|
|
971
956
|
side: renderedSide,
|
|
972
957
|
align: positioning.align,
|
|
973
958
|
anchorHidden: positioning.anchorHidden
|
|
974
959
|
};
|
|
960
|
+
useButton.useIsoLayoutEffect(() => {
|
|
961
|
+
store.set('popupSide', positioning.side);
|
|
962
|
+
}, [store, positioning.side]);
|
|
975
963
|
const setPositionerElement = useButton.useStableCallback(element => {
|
|
976
964
|
store.set('positionerElement', element);
|
|
977
965
|
});
|
|
978
|
-
const element =
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
966
|
+
const element = useCompositeListItem.usePositioner(componentProps, state, {
|
|
967
|
+
styles: positionerStyles,
|
|
968
|
+
transitionStatus,
|
|
969
|
+
props: elementProps,
|
|
970
|
+
refs: [forwardedRef, setPositionerElement],
|
|
971
|
+
hidden: !mounted,
|
|
972
|
+
inert: !open
|
|
983
973
|
});
|
|
984
974
|
const prevMapSizeRef = React__namespace.useRef(0);
|
|
985
975
|
const onMapChange = useButton.useStableCallback(map => {
|
|
@@ -1045,8 +1035,8 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
1045
1035
|
onMapChange: onMapChange,
|
|
1046
1036
|
children: /*#__PURE__*/jsxRuntime.jsxs(SelectPositionerContext.Provider, {
|
|
1047
1037
|
value: contextValue,
|
|
1048
|
-
children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(
|
|
1049
|
-
inert:
|
|
1038
|
+
children: [mounted && modal && /*#__PURE__*/jsxRuntime.jsx(useScrollLock.InternalBackdrop, {
|
|
1039
|
+
inert: useScrollLock.inertValue(!open),
|
|
1050
1040
|
cutout: triggerElement
|
|
1051
1041
|
}), element]
|
|
1052
1042
|
})
|
|
@@ -1054,20 +1044,8 @@ const SelectPositioner = /*#__PURE__*/React__namespace.forwardRef(function Selec
|
|
|
1054
1044
|
});
|
|
1055
1045
|
if (process.env.NODE_ENV !== "production") SelectPositioner.displayName = "SelectPositioner";
|
|
1056
1046
|
|
|
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
1047
|
const stateAttributesMapping = {
|
|
1070
|
-
...
|
|
1048
|
+
...useScrollLock.popupStateMapping,
|
|
1071
1049
|
...useButton.transitionStatusMapping
|
|
1072
1050
|
};
|
|
1073
1051
|
|
|
@@ -1081,6 +1059,7 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1081
1059
|
const {
|
|
1082
1060
|
render,
|
|
1083
1061
|
className,
|
|
1062
|
+
style,
|
|
1084
1063
|
finalFocus,
|
|
1085
1064
|
...elementProps
|
|
1086
1065
|
} = componentProps;
|
|
@@ -1090,39 +1069,38 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1090
1069
|
onOpenChangeComplete,
|
|
1091
1070
|
setOpen,
|
|
1092
1071
|
valueRef,
|
|
1072
|
+
firstItemTextRef,
|
|
1093
1073
|
selectedItemTextRef,
|
|
1094
|
-
keyboardActiveRef,
|
|
1095
1074
|
multiple,
|
|
1096
1075
|
handleScrollArrowVisibility,
|
|
1097
1076
|
scrollHandlerRef,
|
|
1077
|
+
listRef,
|
|
1098
1078
|
highlightItemOnHover
|
|
1099
1079
|
} = useSelectRootContext();
|
|
1100
1080
|
const {
|
|
1101
1081
|
side,
|
|
1102
1082
|
align,
|
|
1103
1083
|
alignItemWithTriggerActive,
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
scrollUpArrowRef
|
|
1084
|
+
isPositioned,
|
|
1085
|
+
setControlledAlignItemWithTrigger
|
|
1107
1086
|
} = useSelectPositionerContext();
|
|
1108
|
-
const insideToolbar =
|
|
1087
|
+
const insideToolbar = isElementDisabled.useToolbarRootContext(true) != null;
|
|
1109
1088
|
const floatingRootContext = useSelectFloatingContext();
|
|
1089
|
+
const direction = useScrollLock.useDirection();
|
|
1110
1090
|
const {
|
|
1111
1091
|
nonce,
|
|
1112
1092
|
disableStyleElements
|
|
1113
|
-
} =
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
1117
|
-
const mounted =
|
|
1118
|
-
const popupProps =
|
|
1119
|
-
const transitionStatus =
|
|
1120
|
-
const triggerElement =
|
|
1121
|
-
const positionerElement =
|
|
1122
|
-
const listElement =
|
|
1123
|
-
const initialHeightRef = React__namespace.useRef(0);
|
|
1093
|
+
} = scrollEdges.useCSPContext();
|
|
1094
|
+
const id = useScrollLock.useStore(store, selectors.id);
|
|
1095
|
+
const open = useScrollLock.useStore(store, selectors.open);
|
|
1096
|
+
const openMethod = useScrollLock.useStore(store, selectors.openMethod);
|
|
1097
|
+
const mounted = useScrollLock.useStore(store, selectors.mounted);
|
|
1098
|
+
const popupProps = useScrollLock.useStore(store, selectors.popupProps);
|
|
1099
|
+
const transitionStatus = useScrollLock.useStore(store, selectors.transitionStatus);
|
|
1100
|
+
const triggerElement = useScrollLock.useStore(store, selectors.triggerElement);
|
|
1101
|
+
const positionerElement = useScrollLock.useStore(store, selectors.positionerElement);
|
|
1102
|
+
const listElement = useScrollLock.useStore(store, selectors.listElement);
|
|
1124
1103
|
const reachedMaxHeightRef = React__namespace.useRef(false);
|
|
1125
|
-
const maxHeightRef = React__namespace.useRef(0);
|
|
1126
1104
|
const initialPlacedRef = React__namespace.useRef(false);
|
|
1127
1105
|
const originalPositionerStylesRef = React__namespace.useRef({});
|
|
1128
1106
|
const scrollArrowFrame = useButton.useAnimationFrame();
|
|
@@ -1136,12 +1114,18 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1136
1114
|
}
|
|
1137
1115
|
const isTopPositioned = positionerElement.style.top === '0px';
|
|
1138
1116
|
const isBottomPositioned = positionerElement.style.bottom === '0px';
|
|
1139
|
-
|
|
1117
|
+
if (!isTopPositioned && !isBottomPositioned) {
|
|
1118
|
+
handleScrollArrowVisibility();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const scale = getScale(positionerElement);
|
|
1122
|
+
const currentHeight = normalizeSize(positionerElement.getBoundingClientRect().height, 'y', scale);
|
|
1140
1123
|
const doc = useButton.ownerDocument(positionerElement);
|
|
1141
|
-
const
|
|
1124
|
+
const win = floatingUi_utils_dom.getWindow(positionerElement);
|
|
1125
|
+
const positionerStyles = win.getComputedStyle(positionerElement);
|
|
1142
1126
|
const marginTop = parseFloat(positionerStyles.marginTop);
|
|
1143
1127
|
const marginBottom = parseFloat(positionerStyles.marginBottom);
|
|
1144
|
-
const maxPopupHeight = getMaxPopupHeight(getComputedStyle(popupRef.current));
|
|
1128
|
+
const maxPopupHeight = getMaxPopupHeight(win.getComputedStyle(popupRef.current));
|
|
1145
1129
|
const maxAvailableHeight = Math.min(doc.documentElement.clientHeight - marginTop - marginBottom, maxPopupHeight);
|
|
1146
1130
|
const scrollTop = scroller.scrollTop;
|
|
1147
1131
|
const maxScrollTop = getMaxScrollTop(scroller);
|
|
@@ -1159,42 +1143,29 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1159
1143
|
setHeight(currentHeight + heightDelta);
|
|
1160
1144
|
}
|
|
1161
1145
|
scroller.scrollTop = targetScrollTop;
|
|
1162
|
-
if (maxAvailableHeight - (currentHeight + heightDelta) <=
|
|
1146
|
+
if (maxAvailableHeight - (currentHeight + heightDelta) <= scrollEdges.SCROLL_EDGE_TOLERANCE_PX) {
|
|
1163
1147
|
reachedMaxHeightRef.current = true;
|
|
1164
1148
|
}
|
|
1165
1149
|
handleScrollArrowVisibility();
|
|
1166
1150
|
};
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {
|
|
1151
|
+
const diff = isTopPositioned ? maxScrollTop - scrollTop : scrollTop;
|
|
1152
|
+
const nextHeight = Math.min(currentHeight + diff, maxAvailableHeight);
|
|
1153
|
+
nextPositionerHeight = nextHeight;
|
|
1154
|
+
if (diff <= scrollEdges.SCROLL_EDGE_TOLERANCE_PX) {
|
|
1155
|
+
handleSmallDiff(diff, isTopPositioned ? maxScrollTop : 0);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (maxAvailableHeight - nextHeight > scrollEdges.SCROLL_EDGE_TOLERANCE_PX) {
|
|
1159
|
+
if (isTopPositioned) {
|
|
1177
1160
|
scrollToMax = true;
|
|
1178
1161
|
} 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
1162
|
nextScrollTop = 0;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
setReachedMax = true;
|
|
1166
|
+
if (isBottomPositioned && scrollTop < maxScrollTop) {
|
|
1167
|
+
const overshoot = currentHeight + diff - maxAvailableHeight;
|
|
1168
|
+
nextScrollTop = scrollTop - (diff - overshoot);
|
|
1198
1169
|
}
|
|
1199
1170
|
}
|
|
1200
1171
|
nextPositionerHeight = Math.ceil(nextPositionerHeight);
|
|
@@ -1207,11 +1178,11 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1207
1178
|
const target = scrollToMax ? nextMaxScrollTop : clamp.clamp(nextScrollTop, 0, nextMaxScrollTop);
|
|
1208
1179
|
|
|
1209
1180
|
// Avoid adjustments that re-trigger scroll events forever.
|
|
1210
|
-
if (Math.abs(scroller.scrollTop - target) >
|
|
1181
|
+
if (Math.abs(scroller.scrollTop - target) > scrollEdges.SCROLL_EDGE_TOLERANCE_PX) {
|
|
1211
1182
|
scroller.scrollTop = target;
|
|
1212
1183
|
}
|
|
1213
1184
|
}
|
|
1214
|
-
if (setReachedMax || nextPositionerHeight >= maxAvailableHeight -
|
|
1185
|
+
if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - scrollEdges.SCROLL_EDGE_TOLERANCE_PX) {
|
|
1215
1186
|
reachedMaxHeightRef.current = true;
|
|
1216
1187
|
}
|
|
1217
1188
|
handleScrollArrowVisibility();
|
|
@@ -1254,13 +1225,15 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1254
1225
|
}
|
|
1255
1226
|
initialPlacedRef.current = false;
|
|
1256
1227
|
reachedMaxHeightRef.current = false;
|
|
1257
|
-
initialHeightRef.current = 0;
|
|
1258
|
-
maxHeightRef.current = 0;
|
|
1259
1228
|
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1260
1229
|
}, [open, alignItemWithTriggerActive, positionerElement, popupRef]);
|
|
1261
1230
|
useButton.useIsoLayoutEffect(() => {
|
|
1262
1231
|
const popupElement = popupRef.current;
|
|
1263
|
-
|
|
1232
|
+
|
|
1233
|
+
// Wait for Floating UI's first positioning pass before reading DOM geometry.
|
|
1234
|
+
// We replace the final coordinates for aligned selects, but still need middleware
|
|
1235
|
+
// like `size()` to set CSS variables such as `--anchor-width`.
|
|
1236
|
+
if (!open || !triggerElement || !positionerElement || !popupElement || alignItemWithTriggerActive && !isPositioned || store.state.transitionStatus === 'ending') {
|
|
1264
1237
|
return;
|
|
1265
1238
|
}
|
|
1266
1239
|
if (!alignItemWithTriggerActive) {
|
|
@@ -1270,114 +1243,113 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1270
1243
|
return;
|
|
1271
1244
|
}
|
|
1272
1245
|
|
|
1273
|
-
//
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const
|
|
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
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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);
|
|
1246
|
+
// Ensure we remove any transforms that can affect the location of the popup
|
|
1247
|
+
// and therefore the calculations.
|
|
1248
|
+
const restoreTransformStyles = unsetTransformStyles(popupElement);
|
|
1249
|
+
popupElement.style.removeProperty('--transform-origin');
|
|
1250
|
+
try {
|
|
1251
|
+
let textElement = selectedItemTextRef.current;
|
|
1252
|
+
if (!textElement?.isConnected) {
|
|
1253
|
+
const hasSelectedValue = selectors.hasSelectedValue(store.state);
|
|
1254
|
+
textElement = !hasSelectedValue && firstItemTextRef.current?.isConnected ? firstItemTextRef.current : null;
|
|
1255
|
+
}
|
|
1256
|
+
const valueElement = valueRef.current;
|
|
1257
|
+
const win = floatingUi_utils_dom.getWindow(positionerElement);
|
|
1258
|
+
const positionerStyles = win.getComputedStyle(positionerElement);
|
|
1259
|
+
const popupStyles = win.getComputedStyle(popupElement);
|
|
1260
|
+
const doc = useButton.ownerDocument(triggerElement);
|
|
1261
|
+
const scale = getScale(triggerElement);
|
|
1262
|
+
const triggerRect = normalizeRect(triggerElement.getBoundingClientRect(), scale);
|
|
1263
|
+
const positionerRect = normalizeRect(positionerElement.getBoundingClientRect(), scale);
|
|
1264
|
+
const triggerHeight = triggerRect.height;
|
|
1265
|
+
const scroller = listElement || popupElement;
|
|
1266
|
+
const scrollHeight = scroller.scrollHeight;
|
|
1267
|
+
const borderBottom = parseFloat(popupStyles.borderBottomWidth);
|
|
1268
|
+
// The `|| N` fallbacks cover an unset/`auto` value (parses to `NaN`). Note a literal `0`
|
|
1269
|
+
// also resolves to the fallback, so an explicit `margin: 0` or `min-height: 0` still takes
|
|
1270
|
+
// the default below.
|
|
1271
|
+
const marginTop = parseFloat(positionerStyles.marginTop) || 10;
|
|
1272
|
+
const marginBottom = parseFloat(positionerStyles.marginBottom) || 10;
|
|
1273
|
+
const minHeight = parseFloat(positionerStyles.minHeight) || 100;
|
|
1274
|
+
const maxPopupHeight = getMaxPopupHeight(popupStyles);
|
|
1275
|
+
const paddingLeft = 5;
|
|
1276
|
+
const paddingRight = 5;
|
|
1277
|
+
const triggerCollisionThreshold = 20;
|
|
1278
|
+
const viewportHeight = doc.documentElement.clientHeight - marginTop - marginBottom;
|
|
1279
|
+
const viewportWidth = doc.documentElement.clientWidth;
|
|
1280
|
+
const availableSpaceBeneathTrigger = viewportHeight - triggerRect.bottom + triggerHeight;
|
|
1281
|
+
let textRect;
|
|
1282
|
+
let alignedLeft = direction === 'rtl' ? triggerRect.right - positionerRect.width : triggerRect.left;
|
|
1283
|
+
let offsetY = 0;
|
|
1284
|
+
if (textElement && valueElement) {
|
|
1285
|
+
const valueRect = normalizeRect(valueElement.getBoundingClientRect(), scale);
|
|
1286
|
+
textRect = normalizeRect(textElement.getBoundingClientRect(), scale);
|
|
1287
|
+
alignedLeft = positionerRect.left + (direction === 'rtl' ? valueRect.right - textRect.right : valueRect.left - textRect.left);
|
|
1288
|
+
const valueCenterFromTriggerTop = valueRect.top - triggerRect.top + valueRect.height / 2;
|
|
1289
|
+
const textCenterFromPositionerTop = textRect.top - positionerRect.top + textRect.height / 2;
|
|
1290
|
+
offsetY = textCenterFromPositionerTop - valueCenterFromTriggerTop;
|
|
1291
|
+
}
|
|
1292
|
+
const idealHeight = availableSpaceBeneathTrigger + offsetY + marginBottom + borderBottom;
|
|
1293
|
+
let height = Math.min(viewportHeight, idealHeight);
|
|
1294
|
+
const maxHeight = viewportHeight - marginTop - marginBottom;
|
|
1295
|
+
const scrollTop = idealHeight - height;
|
|
1296
|
+
const maxRight = viewportWidth - paddingRight;
|
|
1297
|
+
positionerElement.style.left = `${clamp.clamp(alignedLeft, paddingLeft, maxRight - positionerRect.width)}px`;
|
|
1298
|
+
positionerElement.style.height = `${height}px`;
|
|
1299
|
+
// `none` (not the invalid `auto`) so the explicit height governs in align mode and isn't
|
|
1300
|
+
// clamped by a `max-height` from user CSS.
|
|
1301
|
+
positionerElement.style.maxHeight = 'none';
|
|
1302
|
+
positionerElement.style.marginTop = `${marginTop}px`;
|
|
1303
|
+
positionerElement.style.marginBottom = `${marginBottom}px`;
|
|
1304
|
+
popupElement.style.height = '100%';
|
|
1305
|
+
const maxScrollTop = getMaxScrollTop(scroller);
|
|
1306
|
+
const isTopPositioned = scrollTop >= maxScrollTop - scrollEdges.SCROLL_EDGE_TOLERANCE_PX;
|
|
1307
|
+
if (isTopPositioned) {
|
|
1308
|
+
height = Math.min(viewportHeight, positionerRect.height) - (scrollTop - maxScrollTop);
|
|
1309
|
+
}
|
|
1339
1310
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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();
|
|
1311
|
+
// When the trigger is too close to the top or bottom of the viewport, or the minHeight is
|
|
1312
|
+
// reached, we fallback to aligning the popup to the trigger as the UX is poor otherwise.
|
|
1313
|
+
const fallbackToAlignPopupToTrigger = triggerRect.top < triggerCollisionThreshold || triggerRect.bottom > viewportHeight - triggerCollisionThreshold || Math.ceil(height) + scrollEdges.SCROLL_EDGE_TOLERANCE_PX < Math.min(scrollHeight, minHeight);
|
|
1371
1314
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1315
|
+
// Safari doesn't position the popup correctly when pinch-zoomed.
|
|
1316
|
+
const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && useScrollLock.webkit;
|
|
1317
|
+
if (fallbackToAlignPopupToTrigger || isPinchZoomed) {
|
|
1318
|
+
initialPlacedRef.current = true;
|
|
1319
|
+
clearStyles(positionerElement, originalPositionerStylesRef.current);
|
|
1320
|
+
setControlledAlignItemWithTrigger(false);
|
|
1321
|
+
return;
|
|
1378
1322
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1323
|
+
const initialHeight = Math.max(minHeight, height);
|
|
1324
|
+
if (isTopPositioned) {
|
|
1325
|
+
const topOffset = Math.max(0, viewportHeight - idealHeight);
|
|
1326
|
+
positionerElement.style.top = positionerRect.height >= maxHeight ? '0' : `${topOffset}px`;
|
|
1327
|
+
positionerElement.style.height = `${height}px`;
|
|
1328
|
+
scroller.scrollTop = getMaxScrollTop(scroller);
|
|
1329
|
+
} else {
|
|
1330
|
+
positionerElement.style.bottom = '0';
|
|
1331
|
+
scroller.scrollTop = scrollTop;
|
|
1332
|
+
}
|
|
1333
|
+
if (textRect) {
|
|
1334
|
+
const popupTop = positionerRect.top;
|
|
1335
|
+
const popupHeight = positionerRect.height;
|
|
1336
|
+
const textCenterY = textRect.top + textRect.height / 2;
|
|
1337
|
+
const transformOriginY = popupHeight > 0 ? (textCenterY - popupTop) / popupHeight * 100 : 50;
|
|
1338
|
+
const clampedY = clamp.clamp(transformOriginY, 0, 100);
|
|
1339
|
+
popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);
|
|
1340
|
+
}
|
|
1341
|
+
if (initialHeight === viewportHeight || height >= maxPopupHeight) {
|
|
1342
|
+
reachedMaxHeightRef.current = true;
|
|
1343
|
+
}
|
|
1344
|
+
handleScrollArrowVisibility();
|
|
1345
|
+
if (highlightItemOnHover && store.state.selectedIndex === null && store.state.activeIndex === null && listRef.current[0] != null) {
|
|
1346
|
+
store.set('activeIndex', 0);
|
|
1347
|
+
}
|
|
1348
|
+
initialPlacedRef.current = true;
|
|
1349
|
+
} finally {
|
|
1350
|
+
restoreTransformStyles();
|
|
1351
|
+
}
|
|
1352
|
+
}, [store, open, positionerElement, triggerElement, valueRef, firstItemTextRef, selectedItemTextRef, popupRef, handleScrollArrowVisibility, alignItemWithTriggerActive, setControlledAlignItemWithTrigger, scrollArrowFrame, listElement, listRef, highlightItemOnHover, direction, isPositioned]);
|
|
1381
1353
|
React__namespace.useEffect(() => {
|
|
1382
1354
|
if (!alignItemWithTriggerActive || !positionerElement || !open) {
|
|
1383
1355
|
return undefined;
|
|
@@ -1386,10 +1358,7 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1386
1358
|
function handleResize(event) {
|
|
1387
1359
|
setOpen(false, useButton.createChangeEventDetails(useButton.windowResize, event));
|
|
1388
1360
|
}
|
|
1389
|
-
|
|
1390
|
-
return () => {
|
|
1391
|
-
win.removeEventListener('resize', handleResize);
|
|
1392
|
-
};
|
|
1361
|
+
return useButton.addEventListener(win, 'resize', handleResize);
|
|
1393
1362
|
}, [setOpen, alignItemWithTriggerActive, positionerElement, open]);
|
|
1394
1363
|
const defaultProps = {
|
|
1395
1364
|
...(listElement ? {
|
|
@@ -1401,26 +1370,10 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1401
1370
|
id: `${id}-list`
|
|
1402
1371
|
}),
|
|
1403
1372
|
onKeyDown(event) {
|
|
1404
|
-
|
|
1405
|
-
if (insideToolbar && CSPContext.COMPOSITE_KEYS.has(event.key)) {
|
|
1373
|
+
if (insideToolbar && scrollEdges.COMPOSITE_KEYS.has(event.key)) {
|
|
1406
1374
|
event.stopPropagation();
|
|
1407
1375
|
}
|
|
1408
1376
|
},
|
|
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
1377
|
onScroll(event) {
|
|
1425
1378
|
if (listElement) {
|
|
1426
1379
|
return;
|
|
@@ -1438,14 +1391,15 @@ const SelectPopup = /*#__PURE__*/React__namespace.forwardRef(function SelectPopu
|
|
|
1438
1391
|
state,
|
|
1439
1392
|
stateAttributesMapping,
|
|
1440
1393
|
props: [popupProps, defaultProps, useCompositeListItem.getDisabledMountTransitionStyles(transitionStatus), {
|
|
1441
|
-
className: !listElement && alignItemWithTriggerActive ?
|
|
1394
|
+
className: !listElement && alignItemWithTriggerActive ? scrollEdges.styleDisableScrollbar.className : undefined
|
|
1442
1395
|
}, elementProps]
|
|
1443
1396
|
});
|
|
1444
1397
|
return /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
|
|
1445
|
-
children: [!disableStyleElements &&
|
|
1398
|
+
children: [!disableStyleElements && scrollEdges.styleDisableScrollbar.getElement(nonce), /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingFocusManager, {
|
|
1446
1399
|
context: floatingRootContext,
|
|
1447
1400
|
modal: false,
|
|
1448
1401
|
disabled: !mounted,
|
|
1402
|
+
openInteractionType: openMethod,
|
|
1449
1403
|
returnFocus: finalFocus,
|
|
1450
1404
|
restoreFocus: true,
|
|
1451
1405
|
children: element
|
|
@@ -1458,18 +1412,21 @@ function getMaxPopupHeight(popupStyles) {
|
|
|
1458
1412
|
return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;
|
|
1459
1413
|
}
|
|
1460
1414
|
function getMaxScrollTop(scroller) {
|
|
1461
|
-
return
|
|
1415
|
+
return scrollEdges.getMaxScrollOffset(scroller.scrollHeight, scroller.clientHeight);
|
|
1462
1416
|
}
|
|
1463
1417
|
function getScale(element) {
|
|
1464
1418
|
// The platform API is async-capable, but the DOM platform returns a plain scale object.
|
|
1465
1419
|
return floatingUi_reactDom.platform.getScale(element);
|
|
1466
1420
|
}
|
|
1421
|
+
function normalizeSize(size, axis, scale) {
|
|
1422
|
+
return size / scale[axis];
|
|
1423
|
+
}
|
|
1467
1424
|
function normalizeRect(rect, scale) {
|
|
1468
|
-
return
|
|
1469
|
-
x: rect.x
|
|
1470
|
-
y: rect.y
|
|
1471
|
-
width: rect.width
|
|
1472
|
-
height: rect.height
|
|
1425
|
+
return floatingUi_utils.rectToClientRect({
|
|
1426
|
+
x: normalizeSize(rect.x, 'x', scale),
|
|
1427
|
+
y: normalizeSize(rect.y, 'y', scale),
|
|
1428
|
+
width: normalizeSize(rect.width, 'x', scale),
|
|
1429
|
+
height: normalizeSize(rect.height, 'y', scale)
|
|
1473
1430
|
});
|
|
1474
1431
|
}
|
|
1475
1432
|
const TRANSFORM_STYLE_RESETS = [['transform', 'none'], ['scale', '1'], ['translate', '0 0']];
|
|
@@ -1502,8 +1459,9 @@ function unsetTransformStyles(popupElement) {
|
|
|
1502
1459
|
*/
|
|
1503
1460
|
const SelectList = /*#__PURE__*/React__namespace.forwardRef(function SelectList(componentProps, forwardedRef) {
|
|
1504
1461
|
const {
|
|
1505
|
-
className,
|
|
1506
1462
|
render,
|
|
1463
|
+
className,
|
|
1464
|
+
style,
|
|
1507
1465
|
...elementProps
|
|
1508
1466
|
} = componentProps;
|
|
1509
1467
|
const {
|
|
@@ -1513,10 +1471,10 @@ const SelectList = /*#__PURE__*/React__namespace.forwardRef(function SelectList(
|
|
|
1513
1471
|
const {
|
|
1514
1472
|
alignItemWithTriggerActive
|
|
1515
1473
|
} = useSelectPositionerContext();
|
|
1516
|
-
const hasScrollArrows =
|
|
1517
|
-
const openMethod =
|
|
1518
|
-
const multiple =
|
|
1519
|
-
const id =
|
|
1474
|
+
const hasScrollArrows = useScrollLock.useStore(store, selectors.hasScrollArrows);
|
|
1475
|
+
const openMethod = useScrollLock.useStore(store, selectors.openMethod);
|
|
1476
|
+
const multiple = useScrollLock.useStore(store, selectors.multiple);
|
|
1477
|
+
const id = useScrollLock.useStore(store, selectors.id);
|
|
1520
1478
|
const defaultProps = {
|
|
1521
1479
|
id: `${id}-list`,
|
|
1522
1480
|
role: 'listbox',
|
|
@@ -1527,7 +1485,7 @@ const SelectList = /*#__PURE__*/React__namespace.forwardRef(function SelectList(
|
|
|
1527
1485
|
...(alignItemWithTriggerActive && {
|
|
1528
1486
|
style: LIST_FUNCTIONAL_STYLES
|
|
1529
1487
|
}),
|
|
1530
|
-
className: hasScrollArrows && openMethod !== 'touch' ?
|
|
1488
|
+
className: hasScrollArrows && openMethod !== 'touch' ? scrollEdges.styleDisableScrollbar.className : undefined
|
|
1531
1489
|
};
|
|
1532
1490
|
const setListElement = useButton.useStableCallback(element => {
|
|
1533
1491
|
store.set('listElement', element);
|
|
@@ -1553,6 +1511,7 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1553
1511
|
const {
|
|
1554
1512
|
render,
|
|
1555
1513
|
className,
|
|
1514
|
+
style,
|
|
1556
1515
|
value: itemValue = null,
|
|
1557
1516
|
label,
|
|
1558
1517
|
disabled = false,
|
|
@@ -1567,25 +1526,25 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1567
1526
|
});
|
|
1568
1527
|
const {
|
|
1569
1528
|
store,
|
|
1570
|
-
|
|
1529
|
+
itemProps,
|
|
1571
1530
|
setOpen,
|
|
1572
1531
|
setValue,
|
|
1573
1532
|
selectionRef,
|
|
1574
1533
|
typingRef,
|
|
1575
1534
|
valuesRef,
|
|
1576
|
-
keyboardActiveRef,
|
|
1577
1535
|
multiple,
|
|
1578
|
-
|
|
1536
|
+
selectedItemTextRef,
|
|
1537
|
+
disabled: selectDisabled,
|
|
1538
|
+
readOnly
|
|
1579
1539
|
} = useSelectRootContext();
|
|
1580
|
-
const
|
|
1581
|
-
const
|
|
1582
|
-
const selected =
|
|
1583
|
-
const selectedByFocus =
|
|
1584
|
-
const isItemEqualToValue =
|
|
1540
|
+
const highlighted = useScrollLock.useStore(store, selectors.isActive, listItem.index);
|
|
1541
|
+
const open = useScrollLock.useStore(store, selectors.open);
|
|
1542
|
+
const selected = useScrollLock.useStore(store, selectors.isSelected, itemValue);
|
|
1543
|
+
const selectedByFocus = useScrollLock.useStore(store, selectors.isSelectedByFocus, listItem.index);
|
|
1544
|
+
const isItemEqualToValue = useScrollLock.useStore(store, selectors.isItemEqualToValue);
|
|
1585
1545
|
const index = listItem.index;
|
|
1586
1546
|
const hasRegistered = index !== -1;
|
|
1587
1547
|
const itemRef = React__namespace.useRef(null);
|
|
1588
|
-
const indexRef = useButton.useValueAsRef(index);
|
|
1589
1548
|
useButton.useIsoLayoutEffect(() => {
|
|
1590
1549
|
if (!hasRegistered) {
|
|
1591
1550
|
return undefined;
|
|
@@ -1598,34 +1557,27 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1598
1557
|
}, [hasRegistered, index, itemValue, valuesRef]);
|
|
1599
1558
|
useButton.useIsoLayoutEffect(() => {
|
|
1600
1559
|
if (!hasRegistered) {
|
|
1601
|
-
return
|
|
1560
|
+
return;
|
|
1602
1561
|
}
|
|
1603
1562
|
const selectedValue = store.state.value;
|
|
1604
1563
|
let selectedCandidate = selectedValue;
|
|
1605
|
-
if (multiple && Array.isArray(selectedValue)
|
|
1606
|
-
|
|
1564
|
+
if (multiple && Array.isArray(selectedValue)) {
|
|
1565
|
+
// Compare against the last selected item, or `undefined` when nothing is selected — never
|
|
1566
|
+
// the raw array, which a custom `isItemEqualToValue` isn't expected to receive.
|
|
1567
|
+
selectedCandidate = selectedValue.length > 0 ? selectedValue[selectedValue.length - 1] : undefined;
|
|
1607
1568
|
}
|
|
1608
1569
|
if (selectedCandidate !== undefined && resolveAriaLabelledBy.compareItemEquality(itemValue, selectedCandidate, isItemEqualToValue)) {
|
|
1609
1570
|
store.set('selectedIndex', index);
|
|
1571
|
+
// Make sure SelectPopup can measure the selected item on first open.
|
|
1572
|
+
// SelectItemText can still update this ref later when focus moves.
|
|
1573
|
+
if (textRef.current) {
|
|
1574
|
+
selectedItemTextRef.current = textRef.current;
|
|
1575
|
+
}
|
|
1610
1576
|
}
|
|
1611
|
-
|
|
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;
|
|
1577
|
+
}, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue, selectedItemTextRef]);
|
|
1626
1578
|
const lastKeyRef = React__namespace.useRef(null);
|
|
1627
1579
|
const pointerTypeRef = React__namespace.useRef('mouse');
|
|
1628
|
-
const
|
|
1580
|
+
const allowMouseSelectionRef = React__namespace.useRef(false);
|
|
1629
1581
|
const {
|
|
1630
1582
|
getButtonProps,
|
|
1631
1583
|
buttonRef
|
|
@@ -1635,7 +1587,17 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1635
1587
|
native: nativeButton,
|
|
1636
1588
|
composite: true
|
|
1637
1589
|
});
|
|
1590
|
+
const state = {
|
|
1591
|
+
disabled,
|
|
1592
|
+
selected,
|
|
1593
|
+
highlighted
|
|
1594
|
+
};
|
|
1638
1595
|
function commitSelection(event) {
|
|
1596
|
+
// A forced-open select (`open`/`defaultOpen`) can still receive item activations even
|
|
1597
|
+
// when the root is disabled or read-only, so guard the commit here too.
|
|
1598
|
+
if (selectDisabled || readOnly) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1639
1601
|
const selectedValue = store.state.value;
|
|
1640
1602
|
if (multiple) {
|
|
1641
1603
|
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
@@ -1646,39 +1608,13 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1646
1608
|
setOpen(false, useButton.createChangeEventDetails(useButton.itemPress, event));
|
|
1647
1609
|
}
|
|
1648
1610
|
}
|
|
1611
|
+
function resetDragMovement() {
|
|
1612
|
+
selectionRef.current.dragY = 0;
|
|
1613
|
+
}
|
|
1649
1614
|
const defaultProps = {
|
|
1650
1615
|
role: 'option',
|
|
1651
1616
|
'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
|
-
},
|
|
1617
|
+
tabIndex: open && highlighted ? 0 : -1,
|
|
1682
1618
|
onKeyDown(event) {
|
|
1683
1619
|
lastKeyRef.current = event.key;
|
|
1684
1620
|
store.set('activeIndex', index);
|
|
@@ -1687,13 +1623,24 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1687
1623
|
}
|
|
1688
1624
|
},
|
|
1689
1625
|
onClick(event) {
|
|
1690
|
-
|
|
1626
|
+
const isMouseClick = event.type === 'click' && pointerTypeRef.current !== 'touch';
|
|
1627
|
+
const clickPointerType = event.nativeEvent.pointerType;
|
|
1628
|
+
const isVirtualMouseClick = isMouseClick && useButton.isVirtualClick(event.nativeEvent) && (
|
|
1629
|
+
// Generic no-pointer `detail === 0` clicks stay tied to highlight state. Virtual
|
|
1630
|
+
// clicks that carry browser pointer data, including an empty string from assistive
|
|
1631
|
+
// technology, can activate unhighlighted items.
|
|
1632
|
+
clickPointerType !== undefined || highlighted);
|
|
1633
|
+
// With alignItemWithTrigger, opening can place an item under the cursor. Real mouse
|
|
1634
|
+
// clicks must start on the item, while virtual clicks represent explicit keyboard or
|
|
1635
|
+
// assistive technology activation.
|
|
1636
|
+
const isInvalidMouseClick = isMouseClick && !isVirtualMouseClick && !allowMouseSelectionRef.current;
|
|
1637
|
+
allowMouseSelectionRef.current = false;
|
|
1691
1638
|
|
|
1692
1639
|
// Prevent double commit on {Enter}
|
|
1693
1640
|
if (event.type === 'keydown' && lastKeyRef.current === null) {
|
|
1694
1641
|
return;
|
|
1695
1642
|
}
|
|
1696
|
-
if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current ||
|
|
1643
|
+
if (disabled || event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current || isInvalidMouseClick) {
|
|
1697
1644
|
return;
|
|
1698
1645
|
}
|
|
1699
1646
|
lastKeyRef.current = null;
|
|
@@ -1702,40 +1649,52 @@ const SelectItem = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namesp
|
|
|
1702
1649
|
onPointerEnter(event) {
|
|
1703
1650
|
pointerTypeRef.current = event.pointerType;
|
|
1704
1651
|
},
|
|
1652
|
+
onPointerMove(event) {
|
|
1653
|
+
if (event.pointerType === 'mouse' && event.buttons === 1) {
|
|
1654
|
+
const selection = selectionRef.current;
|
|
1655
|
+
selection.dragY += event.movementY;
|
|
1656
|
+
if (selection.dragY ** 2 >= 64) {
|
|
1657
|
+
selection.allowUnselectedMouseUp = true;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
},
|
|
1705
1661
|
onPointerDown(event) {
|
|
1706
1662
|
pointerTypeRef.current = event.pointerType;
|
|
1707
|
-
|
|
1663
|
+
allowMouseSelectionRef.current = true;
|
|
1664
|
+
resetDragMovement();
|
|
1708
1665
|
},
|
|
1709
1666
|
onMouseUp() {
|
|
1710
|
-
|
|
1667
|
+
resetDragMovement();
|
|
1668
|
+
if (disabled || pointerTypeRef.current === 'touch') {
|
|
1711
1669
|
return;
|
|
1712
1670
|
}
|
|
1713
1671
|
|
|
1714
|
-
// Regular
|
|
1715
|
-
if (
|
|
1716
|
-
didPointerDownRef.current = false;
|
|
1672
|
+
// Regular clicks are committed by the click event.
|
|
1673
|
+
if (allowMouseSelectionRef.current) {
|
|
1717
1674
|
return;
|
|
1718
1675
|
}
|
|
1719
1676
|
const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;
|
|
1720
1677
|
const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;
|
|
1721
|
-
if (disallowSelectedMouseUp || disallowUnselectedMouseUp
|
|
1678
|
+
if (disallowSelectedMouseUp || disallowUnselectedMouseUp) {
|
|
1722
1679
|
return;
|
|
1723
1680
|
}
|
|
1681
|
+
allowMouseSelectionRef.current = true;
|
|
1724
1682
|
itemRef.current?.click();
|
|
1683
|
+
allowMouseSelectionRef.current = false;
|
|
1725
1684
|
}
|
|
1726
1685
|
};
|
|
1727
1686
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1728
1687
|
ref: [buttonRef, forwardedRef, listItem.ref, itemRef],
|
|
1729
1688
|
state,
|
|
1730
|
-
props: [
|
|
1689
|
+
props: [itemProps, defaultProps, elementProps, getButtonProps]
|
|
1731
1690
|
});
|
|
1732
1691
|
const contextValue = React__namespace.useMemo(() => ({
|
|
1733
1692
|
selected,
|
|
1734
|
-
|
|
1693
|
+
index,
|
|
1735
1694
|
textRef,
|
|
1736
1695
|
selectedByFocus,
|
|
1737
1696
|
hasRegistered
|
|
1738
|
-
}), [selected,
|
|
1697
|
+
}), [selected, index, textRef, selectedByFocus, hasRegistered]);
|
|
1739
1698
|
return /*#__PURE__*/jsxRuntime.jsx(SelectItemContext.Provider, {
|
|
1740
1699
|
value: contextValue,
|
|
1741
1700
|
children: element
|
|
@@ -1760,13 +1719,13 @@ const SelectItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function Se
|
|
|
1760
1719
|
});
|
|
1761
1720
|
});
|
|
1762
1721
|
|
|
1763
|
-
|
|
1764
|
-
* costs unless the element needs to be mounted. */
|
|
1722
|
+
// Split the core implementation to avoid paying the hook costs unless the element needs to mount.
|
|
1765
1723
|
if (process.env.NODE_ENV !== "production") SelectItemIndicator.displayName = "SelectItemIndicator";
|
|
1766
1724
|
const Inner = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef((componentProps, forwardedRef) => {
|
|
1767
1725
|
const {
|
|
1768
1726
|
render,
|
|
1769
1727
|
className,
|
|
1728
|
+
style,
|
|
1770
1729
|
keepMounted,
|
|
1771
1730
|
...elementProps
|
|
1772
1731
|
} = componentProps;
|
|
@@ -1812,28 +1771,32 @@ if (process.env.NODE_ENV !== "production") Inner.displayName = "Inner";
|
|
|
1812
1771
|
*/
|
|
1813
1772
|
const SelectItemText = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__namespace.forwardRef(function SelectItemText(componentProps, forwardedRef) {
|
|
1814
1773
|
const {
|
|
1815
|
-
|
|
1774
|
+
index,
|
|
1816
1775
|
textRef,
|
|
1817
1776
|
selectedByFocus,
|
|
1818
1777
|
hasRegistered
|
|
1819
1778
|
} = useSelectItemContext();
|
|
1820
1779
|
const {
|
|
1780
|
+
firstItemTextRef,
|
|
1821
1781
|
selectedItemTextRef
|
|
1822
1782
|
} = useSelectRootContext();
|
|
1823
1783
|
const {
|
|
1824
|
-
className,
|
|
1825
1784
|
render,
|
|
1785
|
+
className,
|
|
1786
|
+
style,
|
|
1826
1787
|
...elementProps
|
|
1827
1788
|
} = componentProps;
|
|
1828
1789
|
const localRef = React__namespace.useCallback(node => {
|
|
1829
|
-
if (!node
|
|
1790
|
+
if (!node) {
|
|
1830
1791
|
return;
|
|
1831
1792
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1793
|
+
if (hasRegistered && index === 0) {
|
|
1794
|
+
firstItemTextRef.current = node;
|
|
1795
|
+
}
|
|
1796
|
+
if (hasRegistered && selectedByFocus) {
|
|
1834
1797
|
selectedItemTextRef.current = node;
|
|
1835
1798
|
}
|
|
1836
|
-
}, [selectedItemTextRef,
|
|
1799
|
+
}, [firstItemTextRef, selectedItemTextRef, index, selectedByFocus, hasRegistered]);
|
|
1837
1800
|
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
1838
1801
|
ref: [localRef, forwardedRef, textRef],
|
|
1839
1802
|
props: elementProps
|
|
@@ -1854,8 +1817,9 @@ function useSelectGroupContext() {
|
|
|
1854
1817
|
|
|
1855
1818
|
const SelectGroup = /*#__PURE__*/React__namespace.forwardRef(function SelectGroup(componentProps, forwardedRef) {
|
|
1856
1819
|
const {
|
|
1857
|
-
className,
|
|
1858
1820
|
render,
|
|
1821
|
+
className,
|
|
1822
|
+
style,
|
|
1859
1823
|
...elementProps
|
|
1860
1824
|
} = componentProps;
|
|
1861
1825
|
const [labelId, setLabelId] = React__namespace.useState();
|
|
@@ -1885,8 +1849,9 @@ if (process.env.NODE_ENV !== "production") SelectGroup.displayName = "SelectGrou
|
|
|
1885
1849
|
*/
|
|
1886
1850
|
const SelectGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function SelectGroupLabel(componentProps, forwardedRef) {
|
|
1887
1851
|
const {
|
|
1888
|
-
className,
|
|
1889
1852
|
render,
|
|
1853
|
+
className,
|
|
1854
|
+
style,
|
|
1890
1855
|
id: idProp,
|
|
1891
1856
|
...elementProps
|
|
1892
1857
|
} = componentProps;
|
|
@@ -1920,5 +1885,6 @@ exports.SelectRoot = SelectRoot;
|
|
|
1920
1885
|
exports.SelectTrigger = SelectTrigger;
|
|
1921
1886
|
exports.SelectValue = SelectValue;
|
|
1922
1887
|
exports.selectors = selectors;
|
|
1888
|
+
exports.usePreviousValue = usePreviousValue;
|
|
1923
1889
|
exports.useSelectPositionerContext = useSelectPositionerContext;
|
|
1924
1890
|
exports.useSelectRootContext = useSelectRootContext;
|