@octanejs/floating-ui 0.1.2
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/LICENSE +21 -0
- package/package.json +44 -0
- package/src/Composite.ts +241 -0
- package/src/FloatingArrow.ts +145 -0
- package/src/FloatingFocusManager.ts +813 -0
- package/src/FloatingList.ts +137 -0
- package/src/FloatingOverlay.ts +85 -0
- package/src/FloatingPortal.ts +257 -0
- package/src/context.ts +181 -0
- package/src/delayGroup.ts +142 -0
- package/src/index.ts +79 -0
- package/src/internal.ts +46 -0
- package/src/pubsub.ts +19 -0
- package/src/safePolygon.ts +339 -0
- package/src/transitions.ts +146 -0
- package/src/tree.ts +85 -0
- package/src/useClick.ts +139 -0
- package/src/useClientPoint.ts +193 -0
- package/src/useDismiss.ts +379 -0
- package/src/useFloating.ts +220 -0
- package/src/useFocus.ts +154 -0
- package/src/useHover.ts +406 -0
- package/src/useId.ts +6 -0
- package/src/useInteractions.ts +94 -0
- package/src/useListNavigation.ts +721 -0
- package/src/useMergeRefs.ts +59 -0
- package/src/useRole.ts +106 -0
- package/src/useTypeahead.ts +168 -0
- package/src/utils/index.ts +656 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
// Ported from @floating-ui/react useListNavigation — arrow-key navigation of a
|
|
2
|
+
// list (real or virtual focus, incl. grid). octane events are NATIVE, so the React
|
|
3
|
+
// `event.nativeEvent` accesses become `event`.
|
|
4
|
+
import { isHTMLElement } from '@floating-ui/utils/dom';
|
|
5
|
+
import { useCallback, useMemo, useRef, useState } from 'octane';
|
|
6
|
+
|
|
7
|
+
import { splitSlot, subSlot } from './internal';
|
|
8
|
+
import { useFloatingParentNodeId, useFloatingTree } from './tree';
|
|
9
|
+
import {
|
|
10
|
+
activeElement,
|
|
11
|
+
contains,
|
|
12
|
+
createGridCellMap,
|
|
13
|
+
enqueueFocus,
|
|
14
|
+
findNonDisabledListIndex,
|
|
15
|
+
getDeepestNode,
|
|
16
|
+
getDocument,
|
|
17
|
+
getFloatingFocusElement,
|
|
18
|
+
getGridCellIndexOfCorner,
|
|
19
|
+
getGridCellIndices,
|
|
20
|
+
getGridNavigatedIndex,
|
|
21
|
+
getMaxListIndex,
|
|
22
|
+
getMinListIndex,
|
|
23
|
+
isIndexOutOfListBounds,
|
|
24
|
+
isListIndexDisabled,
|
|
25
|
+
isTypeableCombobox,
|
|
26
|
+
isVirtualClick,
|
|
27
|
+
isVirtualPointerEvent,
|
|
28
|
+
stopEvent,
|
|
29
|
+
useEffectEvent,
|
|
30
|
+
useLatestRef,
|
|
31
|
+
useModernLayoutEffect,
|
|
32
|
+
} from './utils';
|
|
33
|
+
|
|
34
|
+
const ARROW_UP = 'ArrowUp';
|
|
35
|
+
const ARROW_DOWN = 'ArrowDown';
|
|
36
|
+
const ARROW_LEFT = 'ArrowLeft';
|
|
37
|
+
const ARROW_RIGHT = 'ArrowRight';
|
|
38
|
+
const ESCAPE = 'Escape';
|
|
39
|
+
|
|
40
|
+
function doSwitch(orientation: any, vertical: boolean, horizontal: boolean) {
|
|
41
|
+
switch (orientation) {
|
|
42
|
+
case 'vertical':
|
|
43
|
+
return vertical;
|
|
44
|
+
case 'horizontal':
|
|
45
|
+
return horizontal;
|
|
46
|
+
default:
|
|
47
|
+
return vertical || horizontal;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isMainOrientationKey(key: string, orientation: any) {
|
|
51
|
+
const vertical = key === ARROW_UP || key === ARROW_DOWN;
|
|
52
|
+
const horizontal = key === ARROW_LEFT || key === ARROW_RIGHT;
|
|
53
|
+
return doSwitch(orientation, vertical, horizontal);
|
|
54
|
+
}
|
|
55
|
+
function isMainOrientationToEndKey(key: string, orientation: any, rtl: boolean) {
|
|
56
|
+
const vertical = key === ARROW_DOWN;
|
|
57
|
+
const horizontal = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;
|
|
58
|
+
return (
|
|
59
|
+
doSwitch(orientation, vertical, horizontal) || key === 'Enter' || key === ' ' || key === ''
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function isCrossOrientationOpenKey(key: string, orientation: any, rtl: boolean) {
|
|
63
|
+
const vertical = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;
|
|
64
|
+
const horizontal = key === ARROW_DOWN;
|
|
65
|
+
return doSwitch(orientation, vertical, horizontal);
|
|
66
|
+
}
|
|
67
|
+
function isCrossOrientationCloseKey(key: string, orientation: any, rtl: boolean, cols?: number) {
|
|
68
|
+
const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT;
|
|
69
|
+
const horizontal = key === ARROW_UP;
|
|
70
|
+
if (orientation === 'both' || (orientation === 'horizontal' && cols && cols > 1)) {
|
|
71
|
+
return key === ESCAPE;
|
|
72
|
+
}
|
|
73
|
+
return doSwitch(orientation, vertical, horizontal);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function useListNavigation(...args: any[]): any {
|
|
77
|
+
const [user, slot] = splitSlot(args);
|
|
78
|
+
const context = user[0];
|
|
79
|
+
const props = (user[1] as any) ?? {};
|
|
80
|
+
|
|
81
|
+
const open = context.open;
|
|
82
|
+
const onOpenChange = context.onOpenChange;
|
|
83
|
+
const elements = context.elements;
|
|
84
|
+
const floatingId = context.floatingId;
|
|
85
|
+
|
|
86
|
+
const listRef = props.listRef;
|
|
87
|
+
const activeIndex = props.activeIndex;
|
|
88
|
+
const unstableOnNavigate = props.onNavigate ?? (() => {});
|
|
89
|
+
const enabled = props.enabled ?? true;
|
|
90
|
+
const selectedIndex = props.selectedIndex ?? null;
|
|
91
|
+
const allowEscape = props.allowEscape ?? false;
|
|
92
|
+
const loop = props.loop ?? false;
|
|
93
|
+
const nested = props.nested ?? false;
|
|
94
|
+
const rtl = props.rtl ?? false;
|
|
95
|
+
const virtual = props.virtual ?? false;
|
|
96
|
+
const focusItemOnOpen = props.focusItemOnOpen ?? 'auto';
|
|
97
|
+
const focusItemOnHover = props.focusItemOnHover ?? true;
|
|
98
|
+
const openOnArrowKeyDown = props.openOnArrowKeyDown ?? true;
|
|
99
|
+
const disabledIndices = props.disabledIndices ?? undefined;
|
|
100
|
+
const orientation = props.orientation ?? 'vertical';
|
|
101
|
+
const parentOrientation = props.parentOrientation;
|
|
102
|
+
const cols = props.cols ?? 1;
|
|
103
|
+
const scrollItemIntoView = props.scrollItemIntoView ?? true;
|
|
104
|
+
const virtualItemRef = props.virtualItemRef;
|
|
105
|
+
const itemSizes = props.itemSizes;
|
|
106
|
+
const dense = props.dense ?? false;
|
|
107
|
+
|
|
108
|
+
const floatingFocusElement = getFloatingFocusElement(elements.floating);
|
|
109
|
+
const floatingFocusElementRef = useLatestRef(floatingFocusElement, subSlot(slot, 'ffer'));
|
|
110
|
+
const parentId = useFloatingParentNodeId();
|
|
111
|
+
const tree = useFloatingTree();
|
|
112
|
+
|
|
113
|
+
useModernLayoutEffect(
|
|
114
|
+
() => {
|
|
115
|
+
context.dataRef.current.orientation = orientation;
|
|
116
|
+
},
|
|
117
|
+
[context, orientation],
|
|
118
|
+
subSlot(slot, 'e:orient'),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const onNavigate = useEffectEvent(
|
|
122
|
+
() => {
|
|
123
|
+
unstableOnNavigate(indexRef.current === -1 ? null : indexRef.current);
|
|
124
|
+
},
|
|
125
|
+
subSlot(slot, 'nav'),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const typeableComboboxReference = isTypeableCombobox(elements.domReference);
|
|
129
|
+
const focusItemOnOpenRef = useRef(focusItemOnOpen, subSlot(slot, 'fioo'));
|
|
130
|
+
const indexRef = useRef(selectedIndex != null ? selectedIndex : -1, subSlot(slot, 'index'));
|
|
131
|
+
const keyRef = useRef<any>(null, subSlot(slot, 'key'));
|
|
132
|
+
const isPointerModalityRef = useRef(true, subSlot(slot, 'pm'));
|
|
133
|
+
const previousOnNavigateRef = useRef(onNavigate, subSlot(slot, 'ponav'));
|
|
134
|
+
const previousMountedRef = useRef(!!elements.floating, subSlot(slot, 'pmount'));
|
|
135
|
+
const previousOpenRef = useRef(open, subSlot(slot, 'popen'));
|
|
136
|
+
const forceSyncFocusRef = useRef(false, subSlot(slot, 'fsf'));
|
|
137
|
+
const forceScrollIntoViewRef = useRef(false, subSlot(slot, 'fsiv'));
|
|
138
|
+
const disabledIndicesRef = useLatestRef(disabledIndices, subSlot(slot, 'dir'));
|
|
139
|
+
const latestOpenRef = useLatestRef(open, subSlot(slot, 'lor'));
|
|
140
|
+
const scrollItemIntoViewRef = useLatestRef(scrollItemIntoView, subSlot(slot, 'siir'));
|
|
141
|
+
const selectedIndexRef = useLatestRef(selectedIndex, subSlot(slot, 'sir'));
|
|
142
|
+
const [activeId, setActiveId] = useState<any>(undefined, subSlot(slot, 'aid'));
|
|
143
|
+
const [virtualId, setVirtualId] = useState<any>(undefined, subSlot(slot, 'vid'));
|
|
144
|
+
|
|
145
|
+
const focusItem = useEffectEvent(
|
|
146
|
+
() => {
|
|
147
|
+
function runFocus(item: any) {
|
|
148
|
+
if (virtual) {
|
|
149
|
+
if (item.id?.endsWith('-fui-option')) {
|
|
150
|
+
item.id = floatingId + '-' + Math.random().toString(16).slice(2, 10);
|
|
151
|
+
}
|
|
152
|
+
setActiveId(item.id);
|
|
153
|
+
tree?.events.emit('virtualfocus', item);
|
|
154
|
+
if (virtualItemRef) {
|
|
155
|
+
virtualItemRef.current = item;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
enqueueFocus(item, { sync: forceSyncFocusRef.current, preventScroll: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const initialItem = listRef.current[indexRef.current];
|
|
162
|
+
const forceScrollIntoView = forceScrollIntoViewRef.current;
|
|
163
|
+
if (initialItem) {
|
|
164
|
+
runFocus(initialItem);
|
|
165
|
+
}
|
|
166
|
+
const scheduler = forceSyncFocusRef.current ? (v: any) => v() : requestAnimationFrame;
|
|
167
|
+
scheduler(() => {
|
|
168
|
+
const waitedItem = listRef.current[indexRef.current] || initialItem;
|
|
169
|
+
if (!waitedItem) return;
|
|
170
|
+
if (!initialItem) {
|
|
171
|
+
runFocus(waitedItem);
|
|
172
|
+
}
|
|
173
|
+
const scrollIntoViewOptions = scrollItemIntoViewRef.current;
|
|
174
|
+
const shouldScrollIntoView =
|
|
175
|
+
scrollIntoViewOptions &&
|
|
176
|
+
waitedItem &&
|
|
177
|
+
(forceScrollIntoView || !isPointerModalityRef.current);
|
|
178
|
+
if (shouldScrollIntoView) {
|
|
179
|
+
waitedItem.scrollIntoView?.(
|
|
180
|
+
typeof scrollIntoViewOptions === 'boolean'
|
|
181
|
+
? { block: 'nearest', inline: 'nearest' }
|
|
182
|
+
: scrollIntoViewOptions,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
subSlot(slot, 'focusitem'),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
useModernLayoutEffect(
|
|
191
|
+
() => {
|
|
192
|
+
if (!enabled) return;
|
|
193
|
+
if (open && elements.floating) {
|
|
194
|
+
if (focusItemOnOpenRef.current && selectedIndex != null) {
|
|
195
|
+
forceScrollIntoViewRef.current = true;
|
|
196
|
+
indexRef.current = selectedIndex;
|
|
197
|
+
onNavigate();
|
|
198
|
+
}
|
|
199
|
+
} else if (previousMountedRef.current) {
|
|
200
|
+
indexRef.current = -1;
|
|
201
|
+
previousOnNavigateRef.current();
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
[enabled, open, elements.floating, selectedIndex, onNavigate],
|
|
205
|
+
subSlot(slot, 'e:syncsel'),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
useModernLayoutEffect(
|
|
209
|
+
() => {
|
|
210
|
+
if (!enabled) return;
|
|
211
|
+
if (!open) return;
|
|
212
|
+
if (!elements.floating) return;
|
|
213
|
+
if (activeIndex == null) {
|
|
214
|
+
forceSyncFocusRef.current = false;
|
|
215
|
+
if (selectedIndexRef.current != null) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (previousMountedRef.current) {
|
|
219
|
+
indexRef.current = -1;
|
|
220
|
+
focusItem();
|
|
221
|
+
}
|
|
222
|
+
if (
|
|
223
|
+
(!previousOpenRef.current || !previousMountedRef.current) &&
|
|
224
|
+
focusItemOnOpenRef.current &&
|
|
225
|
+
(keyRef.current != null ||
|
|
226
|
+
(focusItemOnOpenRef.current === true && keyRef.current == null))
|
|
227
|
+
) {
|
|
228
|
+
let runs = 0;
|
|
229
|
+
const waitForListPopulated = () => {
|
|
230
|
+
if (listRef.current[0] == null) {
|
|
231
|
+
if (runs < 2) {
|
|
232
|
+
const scheduler = runs ? requestAnimationFrame : queueMicrotask;
|
|
233
|
+
scheduler(waitForListPopulated);
|
|
234
|
+
}
|
|
235
|
+
runs++;
|
|
236
|
+
} else {
|
|
237
|
+
indexRef.current =
|
|
238
|
+
keyRef.current == null ||
|
|
239
|
+
isMainOrientationToEndKey(keyRef.current, orientation, rtl) ||
|
|
240
|
+
nested
|
|
241
|
+
? getMinListIndex(listRef, disabledIndicesRef.current)
|
|
242
|
+
: getMaxListIndex(listRef, disabledIndicesRef.current);
|
|
243
|
+
keyRef.current = null;
|
|
244
|
+
onNavigate();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
waitForListPopulated();
|
|
248
|
+
}
|
|
249
|
+
} else if (!isIndexOutOfListBounds(listRef, activeIndex)) {
|
|
250
|
+
indexRef.current = activeIndex;
|
|
251
|
+
focusItem();
|
|
252
|
+
forceScrollIntoViewRef.current = false;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
[
|
|
256
|
+
enabled,
|
|
257
|
+
open,
|
|
258
|
+
elements.floating,
|
|
259
|
+
activeIndex,
|
|
260
|
+
selectedIndexRef,
|
|
261
|
+
nested,
|
|
262
|
+
listRef,
|
|
263
|
+
orientation,
|
|
264
|
+
rtl,
|
|
265
|
+
onNavigate,
|
|
266
|
+
focusItem,
|
|
267
|
+
disabledIndicesRef,
|
|
268
|
+
],
|
|
269
|
+
subSlot(slot, 'e:syncactive'),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
useModernLayoutEffect(
|
|
273
|
+
() => {
|
|
274
|
+
if (!enabled || elements.floating || !tree || virtual || !previousMountedRef.current) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const nodes = tree.nodesRef.current;
|
|
278
|
+
const parent = nodes.find((node: any) => node.id === parentId)?.context?.elements.floating;
|
|
279
|
+
const activeEl = activeElement(getDocument(elements.floating));
|
|
280
|
+
const treeContainsActiveEl = nodes.some(
|
|
281
|
+
(node: any) => node.context && contains(node.context.elements.floating, activeEl as any),
|
|
282
|
+
);
|
|
283
|
+
if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {
|
|
284
|
+
parent.focus({ preventScroll: true });
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
[enabled, elements.floating, tree, parentId, virtual],
|
|
288
|
+
subSlot(slot, 'e:parentfocus'),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
useModernLayoutEffect(
|
|
292
|
+
() => {
|
|
293
|
+
if (!enabled) return;
|
|
294
|
+
if (!tree) return;
|
|
295
|
+
if (!virtual) return;
|
|
296
|
+
if (parentId) return;
|
|
297
|
+
function handleVirtualFocus(item: any) {
|
|
298
|
+
setVirtualId(item.id);
|
|
299
|
+
if (virtualItemRef) {
|
|
300
|
+
virtualItemRef.current = item;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
tree.events.on('virtualfocus', handleVirtualFocus);
|
|
304
|
+
return () => {
|
|
305
|
+
tree.events.off('virtualfocus', handleVirtualFocus);
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
[enabled, tree, virtual, parentId, virtualItemRef],
|
|
309
|
+
subSlot(slot, 'e:vfocus'),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
useModernLayoutEffect(
|
|
313
|
+
() => {
|
|
314
|
+
previousOnNavigateRef.current = onNavigate;
|
|
315
|
+
previousOpenRef.current = open;
|
|
316
|
+
previousMountedRef.current = !!elements.floating;
|
|
317
|
+
},
|
|
318
|
+
undefined,
|
|
319
|
+
subSlot(slot, 'e:prev'),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
useModernLayoutEffect(
|
|
323
|
+
() => {
|
|
324
|
+
if (!open) {
|
|
325
|
+
keyRef.current = null;
|
|
326
|
+
focusItemOnOpenRef.current = focusItemOnOpen;
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
[open, focusItemOnOpen],
|
|
330
|
+
subSlot(slot, 'e:closekey'),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const hasActiveIndex = activeIndex != null;
|
|
334
|
+
const item = useMemo(
|
|
335
|
+
() => {
|
|
336
|
+
function syncCurrentTarget(currentTarget: any) {
|
|
337
|
+
if (!latestOpenRef.current) return;
|
|
338
|
+
const index = listRef.current.indexOf(currentTarget);
|
|
339
|
+
if (index !== -1 && indexRef.current !== index) {
|
|
340
|
+
indexRef.current = index;
|
|
341
|
+
onNavigate();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
onFocus(_ref: any) {
|
|
346
|
+
const { currentTarget } = _ref;
|
|
347
|
+
forceSyncFocusRef.current = true;
|
|
348
|
+
syncCurrentTarget(currentTarget);
|
|
349
|
+
},
|
|
350
|
+
onClick: (_ref2: any) => _ref2.currentTarget.focus({ preventScroll: true }),
|
|
351
|
+
onMouseMove(_ref3: any) {
|
|
352
|
+
const { currentTarget } = _ref3;
|
|
353
|
+
forceSyncFocusRef.current = true;
|
|
354
|
+
forceScrollIntoViewRef.current = false;
|
|
355
|
+
if (focusItemOnHover) {
|
|
356
|
+
syncCurrentTarget(currentTarget);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
onPointerLeave(_ref4: any) {
|
|
360
|
+
const { pointerType } = _ref4;
|
|
361
|
+
if (!isPointerModalityRef.current || pointerType === 'touch') {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
forceSyncFocusRef.current = true;
|
|
365
|
+
if (!focusItemOnHover) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
indexRef.current = -1;
|
|
369
|
+
onNavigate();
|
|
370
|
+
if (!virtual) {
|
|
371
|
+
floatingFocusElementRef.current?.focus({ preventScroll: true });
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
[latestOpenRef, floatingFocusElementRef, focusItemOnHover, listRef, onNavigate, virtual],
|
|
377
|
+
subSlot(slot, 'm:item'),
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const getParentOrientation = useCallback(
|
|
381
|
+
() =>
|
|
382
|
+
parentOrientation != null
|
|
383
|
+
? parentOrientation
|
|
384
|
+
: tree?.nodesRef.current.find((node: any) => node.id === parentId)?.context?.dataRef
|
|
385
|
+
?.current.orientation,
|
|
386
|
+
[parentId, tree, parentOrientation],
|
|
387
|
+
subSlot(slot, 'cb:porient'),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const commonOnKeyDown = useEffectEvent(
|
|
391
|
+
(event: any) => {
|
|
392
|
+
isPointerModalityRef.current = false;
|
|
393
|
+
forceSyncFocusRef.current = true;
|
|
394
|
+
|
|
395
|
+
if (event.which === 229) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) {
|
|
402
|
+
if (!isMainOrientationKey(event.key, getParentOrientation())) {
|
|
403
|
+
stopEvent(event);
|
|
404
|
+
}
|
|
405
|
+
onOpenChange(false, event, 'list-navigation');
|
|
406
|
+
if (isHTMLElement(elements.domReference)) {
|
|
407
|
+
if (virtual) {
|
|
408
|
+
tree?.events.emit('virtualfocus', elements.domReference);
|
|
409
|
+
} else {
|
|
410
|
+
elements.domReference.focus();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const currentIndex = indexRef.current;
|
|
416
|
+
const minIndex = getMinListIndex(listRef, disabledIndices);
|
|
417
|
+
const maxIndex = getMaxListIndex(listRef, disabledIndices);
|
|
418
|
+
if (!typeableComboboxReference) {
|
|
419
|
+
if (event.key === 'Home') {
|
|
420
|
+
stopEvent(event);
|
|
421
|
+
indexRef.current = minIndex;
|
|
422
|
+
onNavigate();
|
|
423
|
+
}
|
|
424
|
+
if (event.key === 'End') {
|
|
425
|
+
stopEvent(event);
|
|
426
|
+
indexRef.current = maxIndex;
|
|
427
|
+
onNavigate();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (cols > 1) {
|
|
432
|
+
const sizes =
|
|
433
|
+
itemSizes ||
|
|
434
|
+
Array.from({ length: listRef.current.length }, () => ({ width: 1, height: 1 }));
|
|
435
|
+
const cellMap = createGridCellMap(sizes, cols, dense);
|
|
436
|
+
const minGridIndex = cellMap.findIndex(
|
|
437
|
+
(index) => index != null && !isListIndexDisabled(listRef, index, disabledIndices),
|
|
438
|
+
);
|
|
439
|
+
const maxGridIndex = cellMap.reduce(
|
|
440
|
+
(foundIndex, index, cellIndex) =>
|
|
441
|
+
index != null && !isListIndexDisabled(listRef, index, disabledIndices)
|
|
442
|
+
? cellIndex
|
|
443
|
+
: foundIndex,
|
|
444
|
+
-1,
|
|
445
|
+
);
|
|
446
|
+
const index =
|
|
447
|
+
cellMap[
|
|
448
|
+
getGridNavigatedIndex(
|
|
449
|
+
{
|
|
450
|
+
current: cellMap.map((itemIndex) =>
|
|
451
|
+
itemIndex != null ? listRef.current[itemIndex] : null,
|
|
452
|
+
),
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
event,
|
|
456
|
+
orientation,
|
|
457
|
+
loop,
|
|
458
|
+
rtl,
|
|
459
|
+
cols,
|
|
460
|
+
disabledIndices: getGridCellIndices(
|
|
461
|
+
[
|
|
462
|
+
...((typeof disabledIndices !== 'function' ? disabledIndices : null) ||
|
|
463
|
+
listRef.current.map((_: any, index: number) =>
|
|
464
|
+
isListIndexDisabled(listRef, index, disabledIndices) ? index : undefined,
|
|
465
|
+
)),
|
|
466
|
+
undefined,
|
|
467
|
+
],
|
|
468
|
+
cellMap,
|
|
469
|
+
),
|
|
470
|
+
minIndex: minGridIndex,
|
|
471
|
+
maxIndex: maxGridIndex,
|
|
472
|
+
prevIndex: getGridCellIndexOfCorner(
|
|
473
|
+
indexRef.current > maxIndex ? minIndex : indexRef.current,
|
|
474
|
+
sizes,
|
|
475
|
+
cellMap,
|
|
476
|
+
cols,
|
|
477
|
+
event.key === ARROW_DOWN
|
|
478
|
+
? 'bl'
|
|
479
|
+
: event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)
|
|
480
|
+
? 'tr'
|
|
481
|
+
: 'tl',
|
|
482
|
+
),
|
|
483
|
+
stopEvent: true,
|
|
484
|
+
},
|
|
485
|
+
)
|
|
486
|
+
];
|
|
487
|
+
if (index != null) {
|
|
488
|
+
indexRef.current = index;
|
|
489
|
+
onNavigate();
|
|
490
|
+
}
|
|
491
|
+
if (orientation === 'both') {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (isMainOrientationKey(event.key, orientation)) {
|
|
496
|
+
stopEvent(event);
|
|
497
|
+
if (
|
|
498
|
+
open &&
|
|
499
|
+
!virtual &&
|
|
500
|
+
activeElement(event.currentTarget.ownerDocument) === event.currentTarget
|
|
501
|
+
) {
|
|
502
|
+
indexRef.current = isMainOrientationToEndKey(event.key, orientation, rtl)
|
|
503
|
+
? minIndex
|
|
504
|
+
: maxIndex;
|
|
505
|
+
onNavigate();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (isMainOrientationToEndKey(event.key, orientation, rtl)) {
|
|
509
|
+
if (loop) {
|
|
510
|
+
indexRef.current =
|
|
511
|
+
currentIndex >= maxIndex
|
|
512
|
+
? allowEscape && currentIndex !== listRef.current.length
|
|
513
|
+
? -1
|
|
514
|
+
: minIndex
|
|
515
|
+
: findNonDisabledListIndex(listRef, {
|
|
516
|
+
startingIndex: currentIndex,
|
|
517
|
+
disabledIndices,
|
|
518
|
+
});
|
|
519
|
+
} else {
|
|
520
|
+
indexRef.current = Math.min(
|
|
521
|
+
maxIndex,
|
|
522
|
+
findNonDisabledListIndex(listRef, { startingIndex: currentIndex, disabledIndices }),
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
if (loop) {
|
|
527
|
+
indexRef.current =
|
|
528
|
+
currentIndex <= minIndex
|
|
529
|
+
? allowEscape && currentIndex !== -1
|
|
530
|
+
? listRef.current.length
|
|
531
|
+
: maxIndex
|
|
532
|
+
: findNonDisabledListIndex(listRef, {
|
|
533
|
+
startingIndex: currentIndex,
|
|
534
|
+
decrement: true,
|
|
535
|
+
disabledIndices,
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
indexRef.current = Math.max(
|
|
539
|
+
minIndex,
|
|
540
|
+
findNonDisabledListIndex(listRef, {
|
|
541
|
+
startingIndex: currentIndex,
|
|
542
|
+
decrement: true,
|
|
543
|
+
disabledIndices,
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (isIndexOutOfListBounds(listRef, indexRef.current)) {
|
|
549
|
+
indexRef.current = -1;
|
|
550
|
+
}
|
|
551
|
+
onNavigate();
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
subSlot(slot, 'common'),
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const ariaActiveDescendantProp = useMemo(
|
|
558
|
+
() => virtual && open && hasActiveIndex && { 'aria-activedescendant': virtualId || activeId },
|
|
559
|
+
[virtual, open, hasActiveIndex, virtualId, activeId],
|
|
560
|
+
subSlot(slot, 'm:aad'),
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
const floating = useMemo(
|
|
564
|
+
() => ({
|
|
565
|
+
'aria-orientation': orientation === 'both' ? undefined : orientation,
|
|
566
|
+
...(!typeableComboboxReference ? ariaActiveDescendantProp : {}),
|
|
567
|
+
onKeyDown: commonOnKeyDown,
|
|
568
|
+
onPointerMove() {
|
|
569
|
+
isPointerModalityRef.current = true;
|
|
570
|
+
},
|
|
571
|
+
}),
|
|
572
|
+
[ariaActiveDescendantProp, commonOnKeyDown, orientation, typeableComboboxReference],
|
|
573
|
+
subSlot(slot, 'm:flo'),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const reference = useMemo(
|
|
577
|
+
() => {
|
|
578
|
+
function checkVirtualMouse(event: any) {
|
|
579
|
+
if (focusItemOnOpen === 'auto' && isVirtualClick(event)) {
|
|
580
|
+
focusItemOnOpenRef.current = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function checkVirtualPointer(event: any) {
|
|
584
|
+
focusItemOnOpenRef.current = focusItemOnOpen;
|
|
585
|
+
if (focusItemOnOpen === 'auto' && isVirtualPointerEvent(event)) {
|
|
586
|
+
focusItemOnOpenRef.current = true;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
...ariaActiveDescendantProp,
|
|
591
|
+
onKeyDown(event: any) {
|
|
592
|
+
isPointerModalityRef.current = false;
|
|
593
|
+
const isArrowKey = event.key.startsWith('Arrow');
|
|
594
|
+
const isHomeOrEndKey = ['Home', 'End'].includes(event.key);
|
|
595
|
+
const isMoveKey = isArrowKey || isHomeOrEndKey;
|
|
596
|
+
const isCrossOpenKey = isCrossOrientationOpenKey(event.key, orientation, rtl);
|
|
597
|
+
const isCrossCloseKey = isCrossOrientationCloseKey(event.key, orientation, rtl, cols);
|
|
598
|
+
const isParentCrossOpenKey = isCrossOrientationOpenKey(
|
|
599
|
+
event.key,
|
|
600
|
+
getParentOrientation(),
|
|
601
|
+
rtl,
|
|
602
|
+
);
|
|
603
|
+
const isMainKey = isMainOrientationKey(event.key, orientation);
|
|
604
|
+
const isNavigationKey =
|
|
605
|
+
(nested ? isParentCrossOpenKey : isMainKey) ||
|
|
606
|
+
event.key === 'Enter' ||
|
|
607
|
+
event.key.trim() === '';
|
|
608
|
+
if (virtual && open) {
|
|
609
|
+
const rootNode = tree?.nodesRef.current.find((node: any) => node.parentId == null);
|
|
610
|
+
const deepestNode =
|
|
611
|
+
tree && rootNode ? getDeepestNode(tree.nodesRef.current, rootNode.id) : null;
|
|
612
|
+
if (isMoveKey && deepestNode && virtualItemRef) {
|
|
613
|
+
const eventObject = new KeyboardEvent('keydown', {
|
|
614
|
+
key: event.key,
|
|
615
|
+
bubbles: true,
|
|
616
|
+
});
|
|
617
|
+
if (isCrossOpenKey || isCrossCloseKey) {
|
|
618
|
+
const isCurrentTarget =
|
|
619
|
+
deepestNode.context?.elements.domReference === event.currentTarget;
|
|
620
|
+
const dispatchItem =
|
|
621
|
+
isCrossCloseKey && !isCurrentTarget
|
|
622
|
+
? deepestNode.context?.elements.domReference
|
|
623
|
+
: isCrossOpenKey
|
|
624
|
+
? listRef.current.find((item: any) => item?.id === activeId)
|
|
625
|
+
: null;
|
|
626
|
+
if (dispatchItem) {
|
|
627
|
+
stopEvent(event);
|
|
628
|
+
dispatchItem.dispatchEvent(eventObject);
|
|
629
|
+
setVirtualId(undefined);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if ((isMainKey || isHomeOrEndKey) && deepestNode.context) {
|
|
633
|
+
if (
|
|
634
|
+
deepestNode.context.open &&
|
|
635
|
+
deepestNode.parentId &&
|
|
636
|
+
event.currentTarget !== deepestNode.context.elements.domReference
|
|
637
|
+
) {
|
|
638
|
+
stopEvent(event);
|
|
639
|
+
deepestNode.context.elements.domReference?.dispatchEvent(eventObject);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return commonOnKeyDown(event);
|
|
645
|
+
}
|
|
646
|
+
if (!open && !openOnArrowKeyDown && isArrowKey) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (isNavigationKey) {
|
|
650
|
+
const isParentMainKey = isMainOrientationKey(event.key, getParentOrientation());
|
|
651
|
+
keyRef.current = nested && isParentMainKey ? null : event.key;
|
|
652
|
+
}
|
|
653
|
+
if (nested) {
|
|
654
|
+
if (isParentCrossOpenKey) {
|
|
655
|
+
stopEvent(event);
|
|
656
|
+
if (open) {
|
|
657
|
+
indexRef.current = getMinListIndex(listRef, disabledIndicesRef.current);
|
|
658
|
+
onNavigate();
|
|
659
|
+
} else {
|
|
660
|
+
onOpenChange(true, event, 'list-navigation');
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (isMainKey) {
|
|
666
|
+
if (selectedIndex != null) {
|
|
667
|
+
indexRef.current = selectedIndex;
|
|
668
|
+
}
|
|
669
|
+
stopEvent(event);
|
|
670
|
+
if (!open && openOnArrowKeyDown) {
|
|
671
|
+
onOpenChange(true, event, 'list-navigation');
|
|
672
|
+
} else {
|
|
673
|
+
commonOnKeyDown(event);
|
|
674
|
+
}
|
|
675
|
+
if (open) {
|
|
676
|
+
onNavigate();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
onFocus() {
|
|
681
|
+
if (open && !virtual) {
|
|
682
|
+
indexRef.current = -1;
|
|
683
|
+
onNavigate();
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
onPointerDown: checkVirtualPointer,
|
|
687
|
+
onPointerEnter: checkVirtualPointer,
|
|
688
|
+
onMouseDown: checkVirtualMouse,
|
|
689
|
+
onClick: checkVirtualMouse,
|
|
690
|
+
};
|
|
691
|
+
},
|
|
692
|
+
[
|
|
693
|
+
activeId,
|
|
694
|
+
ariaActiveDescendantProp,
|
|
695
|
+
cols,
|
|
696
|
+
commonOnKeyDown,
|
|
697
|
+
disabledIndicesRef,
|
|
698
|
+
focusItemOnOpen,
|
|
699
|
+
listRef,
|
|
700
|
+
nested,
|
|
701
|
+
onNavigate,
|
|
702
|
+
onOpenChange,
|
|
703
|
+
open,
|
|
704
|
+
openOnArrowKeyDown,
|
|
705
|
+
orientation,
|
|
706
|
+
getParentOrientation,
|
|
707
|
+
rtl,
|
|
708
|
+
selectedIndex,
|
|
709
|
+
tree,
|
|
710
|
+
virtual,
|
|
711
|
+
virtualItemRef,
|
|
712
|
+
],
|
|
713
|
+
subSlot(slot, 'm:ref'),
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
return useMemo(
|
|
717
|
+
() => (enabled ? { reference, floating, item } : {}),
|
|
718
|
+
[enabled, reference, floating, item],
|
|
719
|
+
subSlot(slot, 'm:ret'),
|
|
720
|
+
);
|
|
721
|
+
}
|