@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,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { l as useId, a as useIsoLayoutEffect, m as useValueAsRef, b as useStableCallback, n as useAnimationFrame, p as activeElement, o as ownerDocument, q as contains, s as stopEvent, d as createChangeEventDetails, r as listNavigation, g as getTarget, v as focusOut, w as isVirtualClick, x as isVirtualPointerEvent, h as useTimeout, y as jsdom } from './useButton-es.js';
|
|
2
3
|
import { b as isElement, a as isHTMLElement, o as getWindow } from './floating-ui.utils.dom-es.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { a as useRefWithInit, E as EMPTY_OBJECT } from './useRenderElement-es.js';
|
|
4
|
+
import { u as useFloatingParentNodeId, b as FloatingRootStore, P as PopupTriggerMap, d as useFloatingTree, g as getFloatingFocusElement, i as isTypeableCombobox, e as isIndexOutOfListBounds, f as getMinListIndex, h as getMaxListIndex, j as findNonDisabledListIndex, k as enqueueFocus, A as ARROW_UP, l as ARROW_DOWN, m as ARROW_LEFT, n as ARROW_RIGHT, o as isListIndexDisabled, q as isElementVisible, r as useDirection, D as DISABLED_TRANSITIONS_STYLE, p as popupStateMapping, s as useScrollLock } from './useScrollLock-es.js';
|
|
5
|
+
import { a as useRefWithInit, b as EMPTY_ARRAY, E as EMPTY_OBJECT, u as useRenderElement } from './useRenderElement-es.js';
|
|
6
6
|
import { jsx } from 'react/jsx-runtime';
|
|
7
|
-
import { e as evaluate,
|
|
7
|
+
import { e as evaluate, d as getPaddingObject, a as getAlignmentAxis, c as getAlignment, o as getAxisLength, l as clamp, b as getSide, g as getSideAxis } from './floating-ui.utils-es.js';
|
|
8
8
|
import { u as useFloating$1, h as hide$1, f as flip, d as size, b as autoUpdate, o as offset, s as shift, l as limitShift } from './floating-ui.react-dom-es.js';
|
|
9
9
|
|
|
10
10
|
function useFloatingRootContext(options) {
|
|
@@ -23,13 +23,14 @@ function useFloatingRootContext(options) {
|
|
|
23
23
|
}
|
|
24
24
|
const store = useRefWithInit(() => new FloatingRootStore({
|
|
25
25
|
open,
|
|
26
|
+
transitionStatus: undefined,
|
|
26
27
|
onOpenChange,
|
|
27
28
|
referenceElement: elements.reference ?? null,
|
|
28
29
|
floatingElement: elements.floating ?? null,
|
|
29
30
|
triggerElements: new PopupTriggerMap(),
|
|
30
31
|
floatingId,
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
syncOnly: false,
|
|
33
|
+
nested
|
|
33
34
|
})).current;
|
|
34
35
|
useIsoLayoutEffect(() => {
|
|
35
36
|
const valuesToSync = {
|
|
@@ -49,7 +50,6 @@ function useFloatingRootContext(options) {
|
|
|
49
50
|
}, [open, floatingId, elements.reference, elements.floating, store]);
|
|
50
51
|
store.context.onOpenChange = onOpenChange;
|
|
51
52
|
store.context.nested = nested;
|
|
52
|
-
store.context.noEmit = false;
|
|
53
53
|
return store;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -62,30 +62,37 @@ function useFloating(options = {}) {
|
|
|
62
62
|
nodeId,
|
|
63
63
|
externalTree
|
|
64
64
|
} = options;
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
const internalStore = useFloatingRootContext(options);
|
|
66
|
+
const store = options.rootContext || internalStore;
|
|
67
|
+
const referenceElement = store.useState('referenceElement');
|
|
68
|
+
const floatingElement = store.useState('floatingElement');
|
|
69
|
+
const domReferenceElement = store.useState('domReferenceElement');
|
|
70
|
+
const open = store.useState('open');
|
|
71
|
+
const floatingId = store.useState('floatingId');
|
|
72
72
|
const [positionReference, setPositionReferenceRaw] = React.useState(null);
|
|
73
|
+
const [localDomReference, setLocalDomReference] = React.useState(undefined);
|
|
74
|
+
const [localFloatingElement, setLocalFloatingElement] = React.useState(undefined);
|
|
73
75
|
const domReferenceRef = React.useRef(null);
|
|
74
76
|
const tree = useFloatingTree(externalTree);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}, [
|
|
77
|
+
const storeElements = React.useMemo(() => ({
|
|
78
|
+
reference: referenceElement,
|
|
79
|
+
floating: floatingElement,
|
|
80
|
+
domReference: domReferenceElement
|
|
81
|
+
}), [referenceElement, floatingElement, domReferenceElement]);
|
|
80
82
|
const position = useFloating$1({
|
|
81
83
|
...options,
|
|
82
84
|
elements: {
|
|
83
|
-
...
|
|
85
|
+
...storeElements,
|
|
84
86
|
...(positionReference && {
|
|
85
87
|
reference: positionReference
|
|
86
88
|
})
|
|
87
89
|
}
|
|
88
90
|
});
|
|
91
|
+
const localDomReferenceElement = isElement(localDomReference) ? localDomReference : null;
|
|
92
|
+
const syncedFloatingElement = localFloatingElement === undefined ? store.state.floatingElement : localFloatingElement;
|
|
93
|
+
store.useSyncedValue('referenceElement', localDomReference ?? null);
|
|
94
|
+
store.useSyncedValue('domReferenceElement', localDomReference === undefined ? domReferenceElement : localDomReferenceElement);
|
|
95
|
+
store.useSyncedValue('floatingElement', syncedFloatingElement);
|
|
89
96
|
const setPositionReference = React.useCallback(node => {
|
|
90
97
|
const computedPositionReference = isElement(node) ? {
|
|
91
98
|
getBoundingClientRect: () => node.getBoundingClientRect(),
|
|
@@ -97,11 +104,6 @@ function useFloating(options = {}) {
|
|
|
97
104
|
setPositionReferenceRaw(computedPositionReference);
|
|
98
105
|
position.refs.setReference(computedPositionReference);
|
|
99
106
|
}, [position.refs]);
|
|
100
|
-
const [localDomReference, setLocalDomReference] = React.useState(null);
|
|
101
|
-
const [localFloatingElement, setLocalFloatingElement] = React.useState(null);
|
|
102
|
-
rootContext.useSyncedValue('referenceElement', localDomReference);
|
|
103
|
-
rootContext.useSyncedValue('domReferenceElement', isElement(localDomReference) ? localDomReference : null);
|
|
104
|
-
rootContext.useSyncedValue('floatingElement', localFloatingElement);
|
|
105
107
|
const setReference = React.useCallback(node => {
|
|
106
108
|
if (isElement(node) || node === null) {
|
|
107
109
|
domReferenceRef.current = node;
|
|
@@ -131,24 +133,27 @@ function useFloating(options = {}) {
|
|
|
131
133
|
}), [position.refs, setReference, setFloating, setPositionReference]);
|
|
132
134
|
const elements = React.useMemo(() => ({
|
|
133
135
|
...position.elements,
|
|
134
|
-
domReference:
|
|
135
|
-
}), [position.elements,
|
|
136
|
-
const open = rootContext.useState('open');
|
|
137
|
-
const floatingId = rootContext.useState('floatingId');
|
|
136
|
+
domReference: domReferenceElement
|
|
137
|
+
}), [position.elements, domReferenceElement]);
|
|
138
138
|
const context = React.useMemo(() => ({
|
|
139
139
|
...position,
|
|
140
|
-
dataRef:
|
|
140
|
+
dataRef: store.context.dataRef,
|
|
141
141
|
open,
|
|
142
|
-
onOpenChange:
|
|
143
|
-
events:
|
|
142
|
+
onOpenChange: store.setOpen,
|
|
143
|
+
events: store.context.events,
|
|
144
144
|
floatingId,
|
|
145
145
|
refs,
|
|
146
146
|
elements,
|
|
147
147
|
nodeId,
|
|
148
|
-
rootStore:
|
|
149
|
-
}), [position, refs, elements, nodeId,
|
|
148
|
+
rootStore: store
|
|
149
|
+
}), [position, refs, elements, nodeId, store, open, floatingId]);
|
|
150
150
|
useIsoLayoutEffect(() => {
|
|
151
|
-
|
|
151
|
+
if (domReferenceElement) {
|
|
152
|
+
domReferenceRef.current = domReferenceElement;
|
|
153
|
+
}
|
|
154
|
+
}, [domReferenceElement]);
|
|
155
|
+
useIsoLayoutEffect(() => {
|
|
156
|
+
store.context.dataRef.current.floatingContext = context;
|
|
152
157
|
const node = tree?.nodesRef.current.find(n => n.id === nodeId);
|
|
153
158
|
if (node) {
|
|
154
159
|
node.context = context;
|
|
@@ -159,8 +164,8 @@ function useFloating(options = {}) {
|
|
|
159
164
|
context,
|
|
160
165
|
refs,
|
|
161
166
|
elements,
|
|
162
|
-
rootStore:
|
|
163
|
-
}), [position, refs, elements, context,
|
|
167
|
+
rootStore: store
|
|
168
|
+
}), [position, refs, elements, context, store]);
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
const ESCAPE = 'Escape';
|
|
@@ -189,10 +194,10 @@ function isCrossOrientationOpenKey(key, orientation, rtl) {
|
|
|
189
194
|
const horizontal = key === ARROW_DOWN;
|
|
190
195
|
return doSwitch(orientation, vertical, horizontal);
|
|
191
196
|
}
|
|
192
|
-
function isCrossOrientationCloseKey(key, orientation, rtl,
|
|
197
|
+
function isCrossOrientationCloseKey(key, orientation, rtl, grid) {
|
|
193
198
|
const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT;
|
|
194
199
|
const horizontal = key === ARROW_UP;
|
|
195
|
-
if (orientation === 'both' || orientation === 'horizontal' &&
|
|
200
|
+
if (orientation === 'both' || orientation === 'horizontal' && grid) {
|
|
196
201
|
return key === ESCAPE;
|
|
197
202
|
}
|
|
198
203
|
return doSwitch(orientation, vertical, horizontal);
|
|
@@ -203,11 +208,6 @@ function isCrossOrientationCloseKey(key, orientation, rtl, cols) {
|
|
|
203
208
|
* @see https://floating-ui.com/docs/useListNavigation
|
|
204
209
|
*/
|
|
205
210
|
function useListNavigation(context, props) {
|
|
206
|
-
const store = 'rootStore' in context ? context.rootStore : context;
|
|
207
|
-
const open = store.useState('open');
|
|
208
|
-
const floatingElement = store.useState('floatingElement');
|
|
209
|
-
const domReferenceElement = store.useState('domReferenceElement');
|
|
210
|
-
const dataRef = store.context.dataRef;
|
|
211
211
|
const {
|
|
212
212
|
listRef,
|
|
213
213
|
activeIndex,
|
|
@@ -225,11 +225,12 @@ function useListNavigation(context, props) {
|
|
|
225
225
|
disabledIndices = undefined,
|
|
226
226
|
orientation = 'vertical',
|
|
227
227
|
parentOrientation,
|
|
228
|
-
cols = 1,
|
|
229
228
|
id,
|
|
230
229
|
resetOnPointerLeave = true,
|
|
231
|
-
externalTree
|
|
230
|
+
externalTree,
|
|
231
|
+
grid: navigateGrid
|
|
232
232
|
} = props;
|
|
233
|
+
const isGrid = navigateGrid != null;
|
|
233
234
|
if (process.env.NODE_ENV !== 'production') {
|
|
234
235
|
if (allowEscape) {
|
|
235
236
|
if (!loopFocus) {
|
|
@@ -239,18 +240,20 @@ function useListNavigation(context, props) {
|
|
|
239
240
|
console.warn('`useListNavigation` must be virtual to allow escaping.');
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
|
-
if (orientation === 'vertical' &&
|
|
243
|
-
console.warn('In grid list navigation mode
|
|
243
|
+
if (orientation === 'vertical' && isGrid) {
|
|
244
|
+
console.warn('In grid list navigation mode, the `orientation` should', 'be either "horizontal" or "both".');
|
|
244
245
|
}
|
|
245
246
|
}
|
|
247
|
+
const store = 'rootStore' in context ? context.rootStore : context;
|
|
248
|
+
const open = store.useState('open');
|
|
249
|
+
const floatingElement = store.useState('floatingElement');
|
|
250
|
+
const domReferenceElement = store.useState('domReferenceElement');
|
|
251
|
+
const dataRef = store.context.dataRef;
|
|
246
252
|
const floatingFocusElement = getFloatingFocusElement(floatingElement);
|
|
253
|
+
const typeableComboboxReference = isTypeableCombobox(domReferenceElement);
|
|
247
254
|
const floatingFocusElementRef = useValueAsRef(floatingFocusElement);
|
|
248
255
|
const parentId = useFloatingParentNodeId();
|
|
249
256
|
const tree = useFloatingTree(externalTree);
|
|
250
|
-
useIsoLayoutEffect(() => {
|
|
251
|
-
dataRef.current.orientation = orientation;
|
|
252
|
-
}, [dataRef, orientation]);
|
|
253
|
-
const typeableComboboxReference = isTypeableCombobox(domReferenceElement);
|
|
254
257
|
const focusItemOnOpenRef = React.useRef(focusItemOnOpen);
|
|
255
258
|
const indexRef = React.useRef(selectedIndex ?? -1);
|
|
256
259
|
const keyRef = React.useRef(null);
|
|
@@ -258,21 +261,23 @@ function useListNavigation(context, props) {
|
|
|
258
261
|
const onNavigate = useStableCallback(event => {
|
|
259
262
|
onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event);
|
|
260
263
|
});
|
|
261
|
-
const previousOnNavigateRef = React.useRef(onNavigate);
|
|
262
264
|
const previousMountedRef = React.useRef(!!floatingElement);
|
|
263
265
|
const previousOpenRef = React.useRef(open);
|
|
264
266
|
const forceSyncFocusRef = React.useRef(false);
|
|
265
267
|
const forceScrollIntoViewRef = React.useRef(false);
|
|
268
|
+
const cancelQueuedFocusRef = React.useRef(null);
|
|
266
269
|
const disabledIndicesRef = useValueAsRef(disabledIndices);
|
|
267
270
|
const latestOpenRef = useValueAsRef(open);
|
|
268
271
|
const selectedIndexRef = useValueAsRef(selectedIndex);
|
|
269
272
|
const resetOnPointerLeaveRef = useValueAsRef(resetOnPointerLeave);
|
|
273
|
+
const focusFrame = useAnimationFrame();
|
|
274
|
+
const waitForListPopulatedFrame = useAnimationFrame();
|
|
270
275
|
const focusItem = useStableCallback(() => {
|
|
271
276
|
function runFocus(item) {
|
|
272
277
|
if (virtual) {
|
|
273
278
|
tree?.events.emit('virtualfocus', item);
|
|
274
279
|
} else {
|
|
275
|
-
enqueueFocus(item, {
|
|
280
|
+
cancelQueuedFocusRef.current = enqueueFocus(item, {
|
|
276
281
|
sync: forceSyncFocusRef.current,
|
|
277
282
|
preventScroll: true
|
|
278
283
|
});
|
|
@@ -283,7 +288,7 @@ function useListNavigation(context, props) {
|
|
|
283
288
|
if (initialItem) {
|
|
284
289
|
runFocus(initialItem);
|
|
285
290
|
}
|
|
286
|
-
const scheduler = forceSyncFocusRef.current ?
|
|
291
|
+
const scheduler = forceSyncFocusRef.current ? callback => callback() : callback => focusFrame.request(callback);
|
|
287
292
|
scheduler(() => {
|
|
288
293
|
const waitedItem = listRef.current[indexRef.current] || initialItem;
|
|
289
294
|
if (!waitedItem) {
|
|
@@ -305,6 +310,9 @@ function useListNavigation(context, props) {
|
|
|
305
310
|
}
|
|
306
311
|
});
|
|
307
312
|
});
|
|
313
|
+
useIsoLayoutEffect(() => {
|
|
314
|
+
dataRef.current.orientation = orientation;
|
|
315
|
+
}, [dataRef, orientation]);
|
|
308
316
|
|
|
309
317
|
// Sync `selectedIndex` to be the `activeIndex` upon opening the floating
|
|
310
318
|
// element. Also, reset `activeIndex` upon closing the floating element.
|
|
@@ -321,11 +329,11 @@ function useListNavigation(context, props) {
|
|
|
321
329
|
onNavigate();
|
|
322
330
|
}
|
|
323
331
|
} else if (previousMountedRef.current) {
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
332
|
+
// Reset the active index when the list is no longer open and mounted (closing or
|
|
333
|
+
// unmounting). `onNavigate` is a stable callback that always forwards to the latest
|
|
334
|
+
// `onNavigate` prop.
|
|
327
335
|
indexRef.current = -1;
|
|
328
|
-
|
|
336
|
+
onNavigate();
|
|
329
337
|
}
|
|
330
338
|
}, [enabled, open, floatingElement, selectedIndex, onNavigate]);
|
|
331
339
|
|
|
@@ -363,12 +371,15 @@ function useListNavigation(context, props) {
|
|
|
363
371
|
// otherwise use rAF. Don't try more than twice, since something
|
|
364
372
|
// is wrong otherwise.
|
|
365
373
|
if (runs < 2) {
|
|
366
|
-
const scheduler = runs ?
|
|
374
|
+
const scheduler = runs ? callback => waitForListPopulatedFrame.request(callback) : queueMicrotask;
|
|
367
375
|
scheduler(waitForListPopulated);
|
|
368
376
|
}
|
|
369
377
|
runs += 1;
|
|
370
378
|
} else {
|
|
371
|
-
//
|
|
379
|
+
// Initially focus the first non-disabled item. `disabledIndices` is deliberately
|
|
380
|
+
// omitted here so attribute-disabled items (`disabled`/`aria-disabled`) are skipped
|
|
381
|
+
// on open even when the consumer passes an empty `disabledIndices` array. Passing it
|
|
382
|
+
// would regress that behavior (see mui/base-ui#2604).
|
|
372
383
|
indexRef.current = keyRef.current == null || isMainOrientationToEndKey(keyRef.current, orientation, rtl) || nested ? getMinListIndex(listRef) : getMaxListIndex(listRef);
|
|
373
384
|
keyRef.current = null;
|
|
374
385
|
onNavigate();
|
|
@@ -376,12 +387,12 @@ function useListNavigation(context, props) {
|
|
|
376
387
|
};
|
|
377
388
|
waitForListPopulated();
|
|
378
389
|
}
|
|
379
|
-
} else if (!isIndexOutOfListBounds(listRef, activeIndex)) {
|
|
390
|
+
} else if (!isIndexOutOfListBounds(listRef.current, activeIndex)) {
|
|
380
391
|
indexRef.current = activeIndex;
|
|
381
392
|
focusItem();
|
|
382
393
|
forceScrollIntoViewRef.current = false;
|
|
383
394
|
}
|
|
384
|
-
}, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem,
|
|
395
|
+
}, [enabled, open, floatingElement, activeIndex, selectedIndexRef, nested, listRef, orientation, rtl, onNavigate, focusItem, waitForListPopulatedFrame]);
|
|
385
396
|
|
|
386
397
|
// Ensure the parent floating element has focus when a nested child closes
|
|
387
398
|
// to allow arrow key navigation to work after the pointer leaves the child.
|
|
@@ -391,16 +402,18 @@ function useListNavigation(context, props) {
|
|
|
391
402
|
}
|
|
392
403
|
const nodes = tree.nodesRef.current;
|
|
393
404
|
const parent = nodes.find(node => node.id === parentId)?.context?.elements.floating;
|
|
394
|
-
|
|
405
|
+
// `floatingElement` is null here (see the guard above), so resolve the owner document from an
|
|
406
|
+
// in-DOM element for realm-safety (shadow DOM/iframes): the reference element, falling back to
|
|
407
|
+
// the parent floating element when the reference is virtual (`domReferenceElement` is null).
|
|
408
|
+
const activeEl = activeElement(ownerDocument(domReferenceElement ?? parent ?? null));
|
|
395
409
|
const treeContainsActiveEl = nodes.some(node => node.context && contains(node.context.elements.floating, activeEl));
|
|
396
410
|
if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {
|
|
397
411
|
parent.focus({
|
|
398
412
|
preventScroll: true
|
|
399
413
|
});
|
|
400
414
|
}
|
|
401
|
-
}, [enabled, floatingElement, tree, parentId, virtual]);
|
|
415
|
+
}, [enabled, floatingElement, domReferenceElement, tree, parentId, virtual]);
|
|
402
416
|
useIsoLayoutEffect(() => {
|
|
403
|
-
previousOnNavigateRef.current = onNavigate;
|
|
404
417
|
previousOpenRef.current = open;
|
|
405
418
|
previousMountedRef.current = !!floatingElement;
|
|
406
419
|
});
|
|
@@ -411,68 +424,22 @@ function useListNavigation(context, props) {
|
|
|
411
424
|
}
|
|
412
425
|
}, [open, focusItemOnOpen]);
|
|
413
426
|
const hasActiveIndex = activeIndex != null;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
const index = listRef.current.indexOf(event.currentTarget);
|
|
420
|
-
if (index !== -1 && indexRef.current !== index) {
|
|
421
|
-
indexRef.current = index;
|
|
422
|
-
onNavigate(event);
|
|
423
|
-
}
|
|
427
|
+
const syncCurrentTarget = useStableCallback(event => {
|
|
428
|
+
if (!latestOpenRef.current) {
|
|
429
|
+
return;
|
|
424
430
|
}
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}) => currentTarget.focus({
|
|
433
|
-
preventScroll: true
|
|
434
|
-
}),
|
|
435
|
-
// Safari
|
|
436
|
-
onMouseMove(event) {
|
|
437
|
-
forceSyncFocusRef.current = true;
|
|
438
|
-
forceScrollIntoViewRef.current = false;
|
|
439
|
-
if (focusItemOnHover) {
|
|
440
|
-
syncCurrentTarget(event);
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
onPointerLeave(event) {
|
|
444
|
-
if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
forceSyncFocusRef.current = true;
|
|
448
|
-
const relatedTarget = event.relatedTarget;
|
|
449
|
-
if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
if (!resetOnPointerLeaveRef.current) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
enqueueFocus(null, {
|
|
456
|
-
sync: true
|
|
457
|
-
});
|
|
458
|
-
indexRef.current = -1;
|
|
459
|
-
onNavigate(event);
|
|
460
|
-
if (!virtual) {
|
|
461
|
-
const floatingFocusEl = floatingFocusElementRef.current;
|
|
462
|
-
const activeEl = activeElement(ownerDocument(floatingFocusEl));
|
|
463
|
-
if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {
|
|
464
|
-
floatingFocusEl.focus({
|
|
465
|
-
preventScroll: true
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
return itemProps;
|
|
472
|
-
}, [latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
|
|
473
|
-
const getParentOrientation = React.useCallback(() => {
|
|
431
|
+
const index = listRef.current.indexOf(event.currentTarget);
|
|
432
|
+
if (index !== -1 && (indexRef.current !== index || activeIndex !== index)) {
|
|
433
|
+
indexRef.current = index;
|
|
434
|
+
onNavigate(event);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const getParentOrientation = useStableCallback(() => {
|
|
474
438
|
return parentOrientation ?? tree?.nodesRef.current.find(node => node.id === parentId)?.context?.dataRef?.current.orientation;
|
|
475
|
-
}
|
|
439
|
+
});
|
|
440
|
+
const getMinEnabledIndex = useStableCallback(() => {
|
|
441
|
+
return getMinListIndex(listRef, disabledIndicesRef.current);
|
|
442
|
+
});
|
|
476
443
|
const commonOnKeyDown = useStableCallback(event => {
|
|
477
444
|
isPointerModalityRef.current = false;
|
|
478
445
|
forceSyncFocusRef.current = true;
|
|
@@ -491,7 +458,7 @@ function useListNavigation(context, props) {
|
|
|
491
458
|
if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {
|
|
492
459
|
return;
|
|
493
460
|
}
|
|
494
|
-
if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl,
|
|
461
|
+
if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, isGrid)) {
|
|
495
462
|
// If the nested list's close key is also the parent navigation key,
|
|
496
463
|
// let the parent navigate. Otherwise, stop propagating the event.
|
|
497
464
|
if (!isMainOrientationKey(event.key, getParentOrientation())) {
|
|
@@ -523,41 +490,10 @@ function useListNavigation(context, props) {
|
|
|
523
490
|
}
|
|
524
491
|
}
|
|
525
492
|
|
|
526
|
-
// Grid navigation
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}, () => ({
|
|
531
|
-
width: 1,
|
|
532
|
-
height: 1
|
|
533
|
-
}));
|
|
534
|
-
// To calculate movements on the grid, we use hypothetical cell indices
|
|
535
|
-
// as if every item was 1x1, then convert back to real indices.
|
|
536
|
-
const cellMap = createGridCellMap(sizes, cols);
|
|
537
|
-
const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(listRef, index, disabledIndices));
|
|
538
|
-
// last enabled index
|
|
539
|
-
const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(listRef, index, disabledIndices) ? cellIndex : foundIndex, -1);
|
|
540
|
-
const index = cellMap[getGridNavigatedIndex({
|
|
541
|
-
current: cellMap.map(itemIndex => itemIndex != null ? listRef.current[itemIndex] : null)
|
|
542
|
-
}, {
|
|
543
|
-
event,
|
|
544
|
-
orientation,
|
|
545
|
-
loopFocus,
|
|
546
|
-
rtl,
|
|
547
|
-
cols,
|
|
548
|
-
// treat undefined (empty grid spaces) as disabled indices so we
|
|
549
|
-
// don't end up in them
|
|
550
|
-
disabledIndices: getGridCellIndices([...((typeof disabledIndices !== 'function' ? disabledIndices : null) || listRef.current.map((_, listIndex) => isListIndexDisabled(listRef, listIndex, disabledIndices) ? listIndex : undefined)), undefined], cellMap),
|
|
551
|
-
minIndex: minGridIndex,
|
|
552
|
-
maxIndex: maxGridIndex,
|
|
553
|
-
prevIndex: getGridCellIndexOfCorner(indexRef.current > maxIndex ? minIndex : indexRef.current, sizes, cellMap, cols,
|
|
554
|
-
// use a corner matching the edge closest to the direction
|
|
555
|
-
// we're moving in so we don't end up in the same item. Prefer
|
|
556
|
-
// top/left over bottom/right.
|
|
557
|
-
// eslint-disable-next-line no-nested-ternary
|
|
558
|
-
event.key === ARROW_DOWN ? 'bl' : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT) ? 'tr' : 'tl'),
|
|
559
|
-
stopEvent: true
|
|
560
|
-
})];
|
|
493
|
+
// Grid navigation is injected by grid-capable consumers so non-grid
|
|
494
|
+
// consumers (menu, select) tree-shake the grid helpers out.
|
|
495
|
+
if (navigateGrid != null) {
|
|
496
|
+
const index = navigateGrid(event, indexRef.current, listRef, orientation, loopFocus, rtl, disabledIndices, minIndex, maxIndex);
|
|
561
497
|
if (index != null) {
|
|
562
498
|
indexRef.current = index;
|
|
563
499
|
onNavigate(event);
|
|
@@ -586,13 +522,13 @@ function useListNavigation(context, props) {
|
|
|
586
522
|
indexRef.current = minIndex;
|
|
587
523
|
}
|
|
588
524
|
} else {
|
|
589
|
-
indexRef.current = findNonDisabledListIndex(listRef, {
|
|
525
|
+
indexRef.current = findNonDisabledListIndex(listRef.current, {
|
|
590
526
|
startingIndex: currentIndex,
|
|
591
527
|
disabledIndices
|
|
592
528
|
});
|
|
593
529
|
}
|
|
594
530
|
} else {
|
|
595
|
-
indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef, {
|
|
531
|
+
indexRef.current = Math.min(maxIndex, findNonDisabledListIndex(listRef.current, {
|
|
596
532
|
startingIndex: currentIndex,
|
|
597
533
|
disabledIndices
|
|
598
534
|
}));
|
|
@@ -607,25 +543,73 @@ function useListNavigation(context, props) {
|
|
|
607
543
|
indexRef.current = maxIndex;
|
|
608
544
|
}
|
|
609
545
|
} else {
|
|
610
|
-
indexRef.current = findNonDisabledListIndex(listRef, {
|
|
546
|
+
indexRef.current = findNonDisabledListIndex(listRef.current, {
|
|
611
547
|
startingIndex: currentIndex,
|
|
612
548
|
decrement: true,
|
|
613
549
|
disabledIndices
|
|
614
550
|
});
|
|
615
551
|
}
|
|
616
552
|
} else {
|
|
617
|
-
indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef, {
|
|
553
|
+
indexRef.current = Math.max(minIndex, findNonDisabledListIndex(listRef.current, {
|
|
618
554
|
startingIndex: currentIndex,
|
|
619
555
|
decrement: true,
|
|
620
556
|
disabledIndices
|
|
621
557
|
}));
|
|
622
558
|
}
|
|
623
|
-
if (isIndexOutOfListBounds(listRef, indexRef.current)) {
|
|
559
|
+
if (isIndexOutOfListBounds(listRef.current, indexRef.current)) {
|
|
624
560
|
indexRef.current = -1;
|
|
625
561
|
}
|
|
626
562
|
onNavigate(event);
|
|
627
563
|
}
|
|
628
564
|
});
|
|
565
|
+
const item = React.useMemo(() => {
|
|
566
|
+
const itemProps = {
|
|
567
|
+
onFocus(event) {
|
|
568
|
+
forceSyncFocusRef.current = true;
|
|
569
|
+
syncCurrentTarget(event);
|
|
570
|
+
},
|
|
571
|
+
onClick: ({
|
|
572
|
+
currentTarget
|
|
573
|
+
}) => currentTarget.focus({
|
|
574
|
+
preventScroll: true
|
|
575
|
+
}),
|
|
576
|
+
// Safari
|
|
577
|
+
onMouseMove(event) {
|
|
578
|
+
forceSyncFocusRef.current = true;
|
|
579
|
+
forceScrollIntoViewRef.current = false;
|
|
580
|
+
if (focusItemOnHover) {
|
|
581
|
+
syncCurrentTarget(event);
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
onPointerLeave(event) {
|
|
585
|
+
if (!latestOpenRef.current || !isPointerModalityRef.current || event.pointerType === 'touch') {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
forceSyncFocusRef.current = true;
|
|
589
|
+
const relatedTarget = event.relatedTarget;
|
|
590
|
+
if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (!resetOnPointerLeaveRef.current) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
cancelQueuedFocusRef.current?.();
|
|
597
|
+
cancelQueuedFocusRef.current = null;
|
|
598
|
+
indexRef.current = -1;
|
|
599
|
+
onNavigate(event);
|
|
600
|
+
if (!virtual) {
|
|
601
|
+
const floatingFocusEl = floatingFocusElementRef.current;
|
|
602
|
+
const activeEl = activeElement(ownerDocument(floatingFocusEl));
|
|
603
|
+
if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {
|
|
604
|
+
floatingFocusEl.focus({
|
|
605
|
+
preventScroll: true
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
return itemProps;
|
|
612
|
+
}, [syncCurrentTarget, latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, resetOnPointerLeaveRef, virtual]);
|
|
629
613
|
const ariaActiveDescendantProp = React.useMemo(() => {
|
|
630
614
|
return virtual && open && hasActiveIndex && {
|
|
631
615
|
'aria-activedescendant': `${id}-${activeIndex}`
|
|
@@ -660,6 +644,9 @@ function useListNavigation(context, props) {
|
|
|
660
644
|
};
|
|
661
645
|
}, [ariaActiveDescendantProp, commonOnKeyDown, floatingFocusElementRef, orientation, typeableComboboxReference, store, open, virtual, domReferenceElement]);
|
|
662
646
|
const trigger = React.useMemo(() => {
|
|
647
|
+
function openOnNavigationKeyDown(event) {
|
|
648
|
+
store.setOpen(true, createChangeEventDetails(listNavigation, event.nativeEvent, event.currentTarget));
|
|
649
|
+
}
|
|
663
650
|
function checkVirtualMouse(event) {
|
|
664
651
|
if (focusItemOnOpen === 'auto' && isVirtualClick(event.nativeEvent)) {
|
|
665
652
|
focusItemOnOpenRef.current = !virtual;
|
|
@@ -698,10 +685,10 @@ function useListNavigation(context, props) {
|
|
|
698
685
|
if (isParentCrossOpenKey) {
|
|
699
686
|
stopEvent(event);
|
|
700
687
|
if (currentOpen) {
|
|
701
|
-
indexRef.current =
|
|
688
|
+
indexRef.current = getMinEnabledIndex();
|
|
702
689
|
onNavigate(event);
|
|
703
690
|
} else {
|
|
704
|
-
|
|
691
|
+
openOnNavigationKeyDown(event);
|
|
705
692
|
}
|
|
706
693
|
}
|
|
707
694
|
return undefined;
|
|
@@ -712,7 +699,7 @@ function useListNavigation(context, props) {
|
|
|
712
699
|
}
|
|
713
700
|
stopEvent(event);
|
|
714
701
|
if (!currentOpen && openOnArrowKeyDown) {
|
|
715
|
-
|
|
702
|
+
openOnNavigationKeyDown(event);
|
|
716
703
|
} else {
|
|
717
704
|
commonOnKeyDown(event);
|
|
718
705
|
}
|
|
@@ -733,7 +720,7 @@ function useListNavigation(context, props) {
|
|
|
733
720
|
onMouseDown: checkVirtualMouse,
|
|
734
721
|
onClick: checkVirtualMouse
|
|
735
722
|
};
|
|
736
|
-
}, [commonOnKeyDown,
|
|
723
|
+
}, [commonOnKeyDown, focusItemOnOpen, getMinEnabledIndex, nested, onNavigate, store, openOnArrowKeyDown, orientation, getParentOrientation, rtl, selectedIndexRef, virtual]);
|
|
737
724
|
const reference = React.useMemo(() => {
|
|
738
725
|
return {
|
|
739
726
|
...ariaActiveDescendantProp,
|
|
@@ -754,65 +741,48 @@ function useListNavigation(context, props) {
|
|
|
754
741
|
* @see https://floating-ui.com/docs/useTypeahead
|
|
755
742
|
*/
|
|
756
743
|
function useTypeahead(context, props) {
|
|
757
|
-
const store = 'rootStore' in context ? context.rootStore : context;
|
|
758
|
-
const dataRef = store.context.dataRef;
|
|
759
|
-
const open = store.useState('open');
|
|
760
744
|
const {
|
|
761
745
|
listRef,
|
|
762
746
|
elementsRef,
|
|
763
747
|
activeIndex,
|
|
764
748
|
onMatch: onMatchProp,
|
|
765
|
-
|
|
749
|
+
disabledIndices,
|
|
750
|
+
onTyping,
|
|
766
751
|
enabled = true,
|
|
767
752
|
resetMs = 750,
|
|
768
753
|
selectedIndex = null
|
|
769
754
|
} = props;
|
|
755
|
+
const store = 'rootStore' in context ? context.rootStore : context;
|
|
756
|
+
const open = store.useState('open');
|
|
770
757
|
const timeout = useTimeout();
|
|
771
758
|
const stringRef = React.useRef('');
|
|
772
759
|
const prevIndexRef = React.useRef(selectedIndex ?? activeIndex ?? -1);
|
|
773
760
|
const matchIndexRef = React.useRef(null);
|
|
774
|
-
useIsoLayoutEffect(() => {
|
|
775
|
-
if (!open && selectedIndex !== null) {
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
timeout.clear();
|
|
779
|
-
matchIndexRef.current = null;
|
|
780
|
-
if (stringRef.current !== '') {
|
|
781
|
-
stringRef.current = '';
|
|
782
|
-
}
|
|
783
|
-
}, [open, selectedIndex, timeout]);
|
|
784
|
-
useIsoLayoutEffect(() => {
|
|
785
|
-
// Sync arrow key navigation but not typeahead navigation.
|
|
786
|
-
if (open && stringRef.current === '') {
|
|
787
|
-
prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
|
|
788
|
-
}
|
|
789
|
-
}, [open, selectedIndex, activeIndex]);
|
|
790
|
-
const setTypingChange = useStableCallback(value => {
|
|
791
|
-
if (value) {
|
|
792
|
-
if (!dataRef.current.typing) {
|
|
793
|
-
dataRef.current.typing = value;
|
|
794
|
-
onTypingChange?.(value);
|
|
795
|
-
}
|
|
796
|
-
} else if (dataRef.current.typing) {
|
|
797
|
-
dataRef.current.typing = value;
|
|
798
|
-
onTypingChange?.(value);
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
761
|
const onKeyDown = useStableCallback(event => {
|
|
802
762
|
function isVisible(index) {
|
|
803
763
|
const element = elementsRef?.current[index];
|
|
804
764
|
return !element || isElementVisible(element);
|
|
805
765
|
}
|
|
766
|
+
function isItemAvailable(index) {
|
|
767
|
+
if (!isVisible(index)) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
// Visibility is handled above; pass an empty element list so `isListIndexDisabled`
|
|
771
|
+
// resolves only the explicit `disabledIndices` (array/predicate) and skips its own
|
|
772
|
+
// visibility/attribute fallbacks. Consumers that don't opt in keep matching every
|
|
773
|
+
// visible item.
|
|
774
|
+
return disabledIndices == null || !isListIndexDisabled(EMPTY_ARRAY, index, disabledIndices);
|
|
775
|
+
}
|
|
806
776
|
function getMatchingIndex(list, string, startIndex = 0) {
|
|
807
777
|
if (list.length === 0) {
|
|
808
778
|
return -1;
|
|
809
779
|
}
|
|
810
780
|
const normalizedStartIndex = (startIndex % list.length + list.length) % list.length;
|
|
811
|
-
const lowerString = string.
|
|
781
|
+
const lowerString = string.toLowerCase();
|
|
812
782
|
for (let offset = 0; offset < list.length; offset += 1) {
|
|
813
783
|
const index = (normalizedStartIndex + offset) % list.length;
|
|
814
784
|
const text = list[index];
|
|
815
|
-
if (!text?.
|
|
785
|
+
if (!text?.toLowerCase().startsWith(lowerString) || !isItemAvailable(index)) {
|
|
816
786
|
continue;
|
|
817
787
|
}
|
|
818
788
|
return index;
|
|
@@ -823,11 +793,11 @@ function useTypeahead(context, props) {
|
|
|
823
793
|
if (stringRef.current.length > 0 && event.key === ' ') {
|
|
824
794
|
// Space should continue the in-progress typeahead session.
|
|
825
795
|
stopEvent(event);
|
|
826
|
-
|
|
796
|
+
onTyping?.(true);
|
|
827
797
|
}
|
|
828
798
|
if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') {
|
|
829
799
|
if (getMatchingIndex(listContent, stringRef.current) === -1 && event.key !== ' ') {
|
|
830
|
-
|
|
800
|
+
onTyping?.(false);
|
|
831
801
|
}
|
|
832
802
|
}
|
|
833
803
|
if (listContent == null ||
|
|
@@ -839,7 +809,7 @@ function useTypeahead(context, props) {
|
|
|
839
809
|
}
|
|
840
810
|
if (open && event.key !== ' ') {
|
|
841
811
|
stopEvent(event);
|
|
842
|
-
|
|
812
|
+
onTyping?.(true);
|
|
843
813
|
}
|
|
844
814
|
|
|
845
815
|
// Capture whether this is a new typing session before mutating the string.
|
|
@@ -849,8 +819,10 @@ function useTypeahead(context, props) {
|
|
|
849
819
|
}
|
|
850
820
|
|
|
851
821
|
// Bail out if the list contains a word like "llama" or "aaron". TODO:
|
|
852
|
-
// allow it in this case, too.
|
|
853
|
-
|
|
822
|
+
// allow it in this case, too. Unavailable items are skipped while matching, so
|
|
823
|
+
// they must be ignored here as well — otherwise a hidden or disabled double-letter
|
|
824
|
+
// label would block rapid cycling through the available items.
|
|
825
|
+
const allowRapidSuccessionOfFirstLetter = listContent.every((text, index) => text && isItemAvailable(index) ? text[0]?.toLowerCase() !== text[1]?.toLowerCase() : true);
|
|
854
826
|
|
|
855
827
|
// Allows the user to cycle through items that start with the same letter
|
|
856
828
|
// in rapid succession.
|
|
@@ -862,7 +834,7 @@ function useTypeahead(context, props) {
|
|
|
862
834
|
timeout.start(resetMs, () => {
|
|
863
835
|
stringRef.current = '';
|
|
864
836
|
prevIndexRef.current = matchIndexRef.current;
|
|
865
|
-
|
|
837
|
+
onTyping?.(false);
|
|
866
838
|
});
|
|
867
839
|
|
|
868
840
|
// Compute the starting index for this search.
|
|
@@ -876,18 +848,17 @@ function useTypeahead(context, props) {
|
|
|
876
848
|
matchIndexRef.current = index;
|
|
877
849
|
} else if (event.key !== ' ') {
|
|
878
850
|
stringRef.current = '';
|
|
879
|
-
|
|
851
|
+
onTyping?.(false);
|
|
880
852
|
}
|
|
881
853
|
});
|
|
882
854
|
const onBlur = useStableCallback(event => {
|
|
883
855
|
const next = event.relatedTarget;
|
|
884
856
|
const currentDomReferenceElement = store.select('domReferenceElement');
|
|
885
857
|
const currentFloatingElement = store.select('floatingElement');
|
|
886
|
-
const
|
|
887
|
-
const withinFloating = contains(currentFloatingElement, next);
|
|
858
|
+
const withinComposite = contains(currentDomReferenceElement, next) || contains(currentFloatingElement, next);
|
|
888
859
|
|
|
889
860
|
// Keep the session if focus moves within the composite (reference <-> floating).
|
|
890
|
-
if (
|
|
861
|
+
if (withinComposite) {
|
|
891
862
|
return;
|
|
892
863
|
}
|
|
893
864
|
|
|
@@ -895,33 +866,44 @@ function useTypeahead(context, props) {
|
|
|
895
866
|
timeout.clear();
|
|
896
867
|
stringRef.current = '';
|
|
897
868
|
prevIndexRef.current = matchIndexRef.current;
|
|
898
|
-
|
|
869
|
+
onTyping?.(false);
|
|
899
870
|
});
|
|
900
|
-
|
|
871
|
+
useIsoLayoutEffect(() => {
|
|
872
|
+
if (!open && selectedIndex !== null) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
timeout.clear();
|
|
876
|
+
matchIndexRef.current = null;
|
|
877
|
+
if (stringRef.current !== '') {
|
|
878
|
+
stringRef.current = '';
|
|
879
|
+
}
|
|
880
|
+
}, [open, selectedIndex, timeout]);
|
|
881
|
+
useIsoLayoutEffect(() => {
|
|
882
|
+
// Sync arrow key navigation but not typeahead navigation.
|
|
883
|
+
if (open && stringRef.current === '') {
|
|
884
|
+
prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;
|
|
885
|
+
}
|
|
886
|
+
}, [open, selectedIndex, activeIndex]);
|
|
887
|
+
const sharedProps = React.useMemo(() => ({
|
|
901
888
|
onKeyDown,
|
|
902
889
|
onBlur
|
|
903
890
|
}), [onKeyDown, onBlur]);
|
|
904
|
-
const floating = React.useMemo(() => {
|
|
905
|
-
return {
|
|
906
|
-
onKeyDown,
|
|
907
|
-
onBlur
|
|
908
|
-
};
|
|
909
|
-
}, [onKeyDown, onBlur]);
|
|
910
891
|
return React.useMemo(() => enabled ? {
|
|
911
|
-
reference,
|
|
912
|
-
floating
|
|
913
|
-
} : {}, [enabled,
|
|
892
|
+
reference: sharedProps,
|
|
893
|
+
floating: sharedProps
|
|
894
|
+
} : {}, [enabled, sharedProps]);
|
|
914
895
|
}
|
|
915
896
|
|
|
916
897
|
function getPseudoElementBounds(element) {
|
|
917
898
|
const elementRect = element.getBoundingClientRect();
|
|
899
|
+
const win = getWindow(element);
|
|
918
900
|
|
|
919
|
-
// Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
|
|
920
|
-
if (
|
|
901
|
+
// Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)" in jsdom.
|
|
902
|
+
if (jsdom) {
|
|
921
903
|
return elementRect;
|
|
922
904
|
}
|
|
923
|
-
const beforeStyles =
|
|
924
|
-
const afterStyles =
|
|
905
|
+
const beforeStyles = win.getComputedStyle(element, '::before');
|
|
906
|
+
const afterStyles = win.getComputedStyle(element, '::after');
|
|
925
907
|
const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
|
|
926
908
|
if (!hasPseudoElements) {
|
|
927
909
|
return elementRect;
|
|
@@ -1203,6 +1185,7 @@ const arrow = (options, deps) => ({
|
|
|
1203
1185
|
options: [options, deps]
|
|
1204
1186
|
});
|
|
1205
1187
|
|
|
1188
|
+
const nativeHideFn = hide$1().fn;
|
|
1206
1189
|
const hide = {
|
|
1207
1190
|
name: 'hide',
|
|
1208
1191
|
async fn(state) {
|
|
@@ -1213,7 +1196,7 @@ const hide = {
|
|
|
1213
1196
|
y
|
|
1214
1197
|
} = state.rects.reference;
|
|
1215
1198
|
const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;
|
|
1216
|
-
const nativeHideResult = await
|
|
1199
|
+
const nativeHideResult = await nativeHideFn(state);
|
|
1217
1200
|
return {
|
|
1218
1201
|
data: {
|
|
1219
1202
|
referenceHidden: nativeHideResult.data?.referenceHidden || anchorHidden
|
|
@@ -1343,6 +1326,7 @@ function useAnchorPositioning(params) {
|
|
|
1343
1326
|
sticky = false,
|
|
1344
1327
|
arrowPadding = 5,
|
|
1345
1328
|
disableAnchorTracking = false,
|
|
1329
|
+
inline: inlineMiddleware,
|
|
1346
1330
|
// Private parameters
|
|
1347
1331
|
keepMounted = false,
|
|
1348
1332
|
floatingRootContext,
|
|
@@ -1365,6 +1349,7 @@ function useAnchorPositioning(params) {
|
|
|
1365
1349
|
const anchorFnCallback = useStableCallback(anchorFn);
|
|
1366
1350
|
const anchorDep = anchorFn ? anchorFnCallback : anchor;
|
|
1367
1351
|
const anchorValueRef = useValueAsRef(anchor);
|
|
1352
|
+
const mountedRef = useValueAsRef(mounted);
|
|
1368
1353
|
const direction = useDirection();
|
|
1369
1354
|
const isRtl = direction === 'rtl';
|
|
1370
1355
|
const side = mountSide || {
|
|
@@ -1416,7 +1401,11 @@ function useAnchorPositioning(params) {
|
|
|
1416
1401
|
const alignOffsetRef = useValueAsRef(alignOffset);
|
|
1417
1402
|
const sideOffsetDep = typeof sideOffset !== 'function' ? sideOffset : 0;
|
|
1418
1403
|
const alignOffsetDep = typeof alignOffset !== 'function' ? alignOffset : 0;
|
|
1419
|
-
const middleware = [
|
|
1404
|
+
const middleware = [];
|
|
1405
|
+
if (inlineMiddleware) {
|
|
1406
|
+
middleware.push(inlineMiddleware);
|
|
1407
|
+
}
|
|
1408
|
+
middleware.push(offset(state => {
|
|
1420
1409
|
const data = getOffsetData(state, sideParam, isRtl);
|
|
1421
1410
|
const sideAxis = typeof sideOffsetRef.current === 'function' ? sideOffsetRef.current(data) : sideOffsetRef.current;
|
|
1422
1411
|
const alignAxis = typeof alignOffsetRef.current === 'function' ? alignOffsetRef.current(data) : alignOffsetRef.current;
|
|
@@ -1425,7 +1414,7 @@ function useAnchorPositioning(params) {
|
|
|
1425
1414
|
crossAxis: alignAxis,
|
|
1426
1415
|
alignmentAxis: alignAxis
|
|
1427
1416
|
};
|
|
1428
|
-
}, [sideOffsetDep, alignOffsetDep, isRtl, sideParam])
|
|
1417
|
+
}, [sideOffsetDep, alignOffsetDep, isRtl, sideParam]));
|
|
1429
1418
|
const shiftDisabled = collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift';
|
|
1430
1419
|
const crossAxisShiftEnabled = !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift');
|
|
1431
1420
|
const flipMiddleware = collisionAvoidanceSide === 'none' ? null : flip({
|
|
@@ -1490,12 +1479,15 @@ function useAnchorPositioning(params) {
|
|
|
1490
1479
|
availableHeight,
|
|
1491
1480
|
rects
|
|
1492
1481
|
}) {
|
|
1482
|
+
if (!mountedRef.current) {
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1493
1485
|
const floatingStyle = floating.style;
|
|
1494
1486
|
floatingStyle.setProperty('--available-width', `${availableWidth}px`);
|
|
1495
1487
|
floatingStyle.setProperty('--available-height', `${availableHeight}px`);
|
|
1496
1488
|
|
|
1497
1489
|
// Snap anchor dimensions to device pixels to ensure the popup's visual width matches the anchor's one.
|
|
1498
|
-
const dpr =
|
|
1490
|
+
const dpr = getWindow(floating).devicePixelRatio || 1;
|
|
1499
1491
|
const {
|
|
1500
1492
|
x,
|
|
1501
1493
|
y,
|
|
@@ -1507,10 +1499,10 @@ function useAnchorPositioning(params) {
|
|
|
1507
1499
|
floatingStyle.setProperty('--anchor-width', `${anchorWidth}px`);
|
|
1508
1500
|
floatingStyle.setProperty('--anchor-height', `${anchorHeight}px`);
|
|
1509
1501
|
}
|
|
1510
|
-
}), arrow(
|
|
1502
|
+
}), arrow(state => ({
|
|
1511
1503
|
// `transform-origin` calculations rely on an element existing. If the arrow hasn't been set,
|
|
1512
1504
|
// we'll create a fake element.
|
|
1513
|
-
element: arrowRef.current ||
|
|
1505
|
+
element: arrowRef.current || ownerDocument(state.elements.floating).createElement('div'),
|
|
1514
1506
|
padding: arrowPadding,
|
|
1515
1507
|
offsetParent: 'floating'
|
|
1516
1508
|
}), [arrowPadding]), {
|
|
@@ -1554,7 +1546,8 @@ function useAnchorPositioning(params) {
|
|
|
1554
1546
|
floatingRootContext.update({
|
|
1555
1547
|
referenceElement: null,
|
|
1556
1548
|
floatingElement: null,
|
|
1557
|
-
domReferenceElement: null
|
|
1549
|
+
domReferenceElement: null,
|
|
1550
|
+
positionReference: null
|
|
1558
1551
|
});
|
|
1559
1552
|
}
|
|
1560
1553
|
}, [mounted, floatingRootContext]);
|
|
@@ -1575,6 +1568,7 @@ function useAnchorPositioning(params) {
|
|
|
1575
1568
|
floatingStyles: originalFloatingStyles
|
|
1576
1569
|
} = useFloating({
|
|
1577
1570
|
rootContext: floatingRootContext,
|
|
1571
|
+
open: keepMounted ? mounted : undefined,
|
|
1578
1572
|
placement,
|
|
1579
1573
|
middleware,
|
|
1580
1574
|
strategy: positionMethod,
|
|
@@ -1635,8 +1629,8 @@ function useAnchorPositioning(params) {
|
|
|
1635
1629
|
}
|
|
1636
1630
|
}, [mounted, refs, anchorDep, anchorValueRef]);
|
|
1637
1631
|
React.useEffect(() => {
|
|
1638
|
-
if (keepMounted && mounted && elements.
|
|
1639
|
-
return autoUpdate(elements.
|
|
1632
|
+
if (keepMounted && mounted && elements.reference && elements.floating) {
|
|
1633
|
+
return autoUpdate(elements.reference, elements.floating, update, autoUpdateOptions);
|
|
1640
1634
|
}
|
|
1641
1635
|
return undefined;
|
|
1642
1636
|
}, [keepMounted, mounted, elements, update, autoUpdateOptions]);
|
|
@@ -1645,11 +1639,9 @@ function useAnchorPositioning(params) {
|
|
|
1645
1639
|
const renderedAlign = getAlignment(renderedPlacement) || 'center';
|
|
1646
1640
|
const anchorHidden = Boolean(middlewareData.hide?.referenceHidden);
|
|
1647
1641
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
* the size of the popup dynamically to avoid unwanted flipping when typing.
|
|
1652
|
-
*/
|
|
1642
|
+
// Locks the flip (makes it "sticky") so it doesn't prefer a given placement
|
|
1643
|
+
// and flips back lazily, not eagerly. Ideal for filtered lists that change
|
|
1644
|
+
// the size of the popup dynamically to avoid unwanted flipping when typing.
|
|
1653
1645
|
useIsoLayoutEffect(() => {
|
|
1654
1646
|
if (lazyFlip && mounted && isPositioned) {
|
|
1655
1647
|
setMountSide(renderedSide);
|
|
@@ -1684,6 +1676,61 @@ function getDisabledMountTransitionStyles(transitionStatus) {
|
|
|
1684
1676
|
return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;
|
|
1685
1677
|
}
|
|
1686
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* Renders the shared outer Positioner element used by popup components.
|
|
1681
|
+
* Applies the common role, hidden state, transition styles, state attributes, and optional inert styling.
|
|
1682
|
+
*/
|
|
1683
|
+
function usePositioner(componentProps, state, {
|
|
1684
|
+
styles,
|
|
1685
|
+
transitionStatus,
|
|
1686
|
+
props,
|
|
1687
|
+
refs,
|
|
1688
|
+
hidden,
|
|
1689
|
+
inert = false
|
|
1690
|
+
}) {
|
|
1691
|
+
const style = {
|
|
1692
|
+
...styles
|
|
1693
|
+
};
|
|
1694
|
+
if (inert) {
|
|
1695
|
+
style.pointerEvents = 'none';
|
|
1696
|
+
}
|
|
1697
|
+
return useRenderElement('div', componentProps, {
|
|
1698
|
+
state,
|
|
1699
|
+
ref: refs,
|
|
1700
|
+
props: [{
|
|
1701
|
+
role: 'presentation',
|
|
1702
|
+
hidden,
|
|
1703
|
+
style
|
|
1704
|
+
}, getDisabledMountTransitionStyles(transitionStatus), props],
|
|
1705
|
+
stateAttributesMapping: popupStateMapping
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Touch-opened popups normally avoid scroll locking so users can still swipe outside to dismiss.
|
|
1710
|
+
// This hook re-enables scroll lock only when the popup is effectively full-width.
|
|
1711
|
+
// Treat popups with up to 20px of total horizontal gutter as full-width so common ~10px side
|
|
1712
|
+
// padding still locks scroll, since that leaves too little outside space for a reliable swipe.
|
|
1713
|
+
const VIEWPORT_WIDTH_TOLERANCE_PX = 20;
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* Manages scroll lock for anchored popups. For non-touch opens, scroll lock is applied when
|
|
1717
|
+
* enabled. For touch opens, scroll lock is applied only when the positioner width is effectively
|
|
1718
|
+
* viewport-sized.
|
|
1719
|
+
*/
|
|
1720
|
+
function useAnchoredPopupScrollLock(enabled, touchOpen, positionerElement, referenceElement) {
|
|
1721
|
+
const [touchOpenShouldLockScroll, setTouchOpenShouldLockScroll] = React.useState(false);
|
|
1722
|
+
useIsoLayoutEffect(() => {
|
|
1723
|
+
if (!enabled || !touchOpen || positionerElement == null) {
|
|
1724
|
+
setTouchOpenShouldLockScroll(false);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const viewportWidth = ownerDocument(positionerElement).documentElement.clientWidth;
|
|
1728
|
+
const popupWidth = positionerElement.offsetWidth;
|
|
1729
|
+
setTouchOpenShouldLockScroll(viewportWidth > 0 && popupWidth > 0 && popupWidth >= viewportWidth - VIEWPORT_WIDTH_TOLERANCE_PX);
|
|
1730
|
+
}, [enabled, touchOpen, positionerElement]);
|
|
1731
|
+
useScrollLock(enabled && (!touchOpen || touchOpenShouldLockScroll), referenceElement);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1687
1734
|
let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
|
|
1688
1735
|
IndexGuessBehavior[IndexGuessBehavior["None"] = 0] = "None";
|
|
1689
1736
|
IndexGuessBehavior[IndexGuessBehavior["GuessFromOrder"] = 1] = "GuessFromOrder";
|
|
@@ -1753,10 +1800,10 @@ function useCompositeListItem(params = {}) {
|
|
|
1753
1800
|
}
|
|
1754
1801
|
});
|
|
1755
1802
|
}, [externalIndex, subscribeMapChange, setIndex]);
|
|
1756
|
-
return
|
|
1803
|
+
return {
|
|
1757
1804
|
ref,
|
|
1758
1805
|
index
|
|
1759
|
-
}
|
|
1806
|
+
};
|
|
1760
1807
|
}
|
|
1761
1808
|
|
|
1762
|
-
export { CompositeList as C, IndexGuessBehavior as I, useListNavigation as a, useTypeahead as b,
|
|
1809
|
+
export { CompositeList as C, IndexGuessBehavior as I, useListNavigation as a, useTypeahead as b, useAnchoredPopupScrollLock as c, useAnchorPositioning as d, usePositioner as e, getDisabledMountTransitionStyles as f, getPseudoElementBounds as g, useCompositeListItem as h, adaptiveOrigin as i, useFloatingRootContext as u };
|