@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.
@@ -0,0 +1,154 @@
1
+ // Ported from @floating-ui/react useFocus. octane events are NATIVE, so
2
+ // `event.nativeEvent` → `event`, and `getTarget(event)` reads the native target.
3
+ import { getWindow, isElement, isHTMLElement } from '@floating-ui/utils/dom';
4
+ import { useEffect, useMemo, useRef } from 'octane';
5
+
6
+ import { splitSlot, subSlot } from './internal';
7
+ import {
8
+ activeElement,
9
+ clearTimeoutIfSet,
10
+ contains,
11
+ createAttribute,
12
+ getDocument,
13
+ getTarget,
14
+ isMacSafari,
15
+ isTypeableElement,
16
+ matchesFocusVisible,
17
+ } from './utils';
18
+
19
+ export function useFocus(...args: any[]): any {
20
+ const [user, slot] = splitSlot(args);
21
+ const context = user[0];
22
+ const props = (user[1] as any) ?? {};
23
+
24
+ const open = context.open;
25
+ const onOpenChange = context.onOpenChange;
26
+ const events = context.events;
27
+ const dataRef = context.dataRef;
28
+ const elements = context.elements;
29
+
30
+ const enabled = props.enabled ?? true;
31
+ const visibleOnly = props.visibleOnly ?? true;
32
+
33
+ const blockFocusRef = useRef(false, subSlot(slot, 'block'));
34
+ const timeoutRef = useRef(-1, subSlot(slot, 'timeout'));
35
+ const keyboardModalityRef = useRef(true, subSlot(slot, 'kbd'));
36
+
37
+ useEffect(
38
+ () => {
39
+ if (!enabled) return;
40
+ const win = getWindow(elements.domReference);
41
+ function onBlur() {
42
+ if (
43
+ !open &&
44
+ isHTMLElement(elements.domReference) &&
45
+ elements.domReference === activeElement(getDocument(elements.domReference))
46
+ ) {
47
+ blockFocusRef.current = true;
48
+ }
49
+ }
50
+ function onKeyDown() {
51
+ keyboardModalityRef.current = true;
52
+ }
53
+ function onPointerDown() {
54
+ keyboardModalityRef.current = false;
55
+ }
56
+ win.addEventListener('blur', onBlur);
57
+ if (isMacSafari()) {
58
+ win.addEventListener('keydown', onKeyDown, true);
59
+ win.addEventListener('pointerdown', onPointerDown, true);
60
+ }
61
+ return () => {
62
+ win.removeEventListener('blur', onBlur);
63
+ if (isMacSafari()) {
64
+ win.removeEventListener('keydown', onKeyDown, true);
65
+ win.removeEventListener('pointerdown', onPointerDown, true);
66
+ }
67
+ };
68
+ },
69
+ [elements.domReference, open, enabled],
70
+ subSlot(slot, 'e:win'),
71
+ );
72
+
73
+ useEffect(
74
+ () => {
75
+ if (!enabled) return;
76
+ function onOpenChangeLocal(_ref: any) {
77
+ const { reason } = _ref;
78
+ if (reason === 'reference-press' || reason === 'escape-key') {
79
+ blockFocusRef.current = true;
80
+ }
81
+ }
82
+ events.on('openchange', onOpenChangeLocal);
83
+ return () => {
84
+ events.off('openchange', onOpenChangeLocal);
85
+ };
86
+ },
87
+ [events, enabled],
88
+ subSlot(slot, 'e:oc'),
89
+ );
90
+
91
+ useEffect(
92
+ () => {
93
+ return () => {
94
+ clearTimeoutIfSet(timeoutRef);
95
+ };
96
+ },
97
+ [],
98
+ subSlot(slot, 'e:cleanup'),
99
+ );
100
+
101
+ const reference = useMemo(
102
+ () => ({
103
+ onMouseLeave() {
104
+ blockFocusRef.current = false;
105
+ },
106
+ onFocus(event: any) {
107
+ if (blockFocusRef.current) return;
108
+ const target = getTarget(event);
109
+ if (visibleOnly && isElement(target)) {
110
+ if (isMacSafari() && !event.relatedTarget) {
111
+ if (!keyboardModalityRef.current && !isTypeableElement(target)) {
112
+ return;
113
+ }
114
+ } else if (!matchesFocusVisible(target)) {
115
+ return;
116
+ }
117
+ }
118
+ onOpenChange(true, event, 'focus');
119
+ },
120
+ onBlur(event: any) {
121
+ blockFocusRef.current = false;
122
+ const relatedTarget = event.relatedTarget;
123
+
124
+ const movedToFocusGuard =
125
+ isElement(relatedTarget) &&
126
+ relatedTarget.hasAttribute(createAttribute('focus-guard')) &&
127
+ relatedTarget.getAttribute('data-type') === 'outside';
128
+
129
+ timeoutRef.current = window.setTimeout(() => {
130
+ const activeEl = activeElement(
131
+ elements.domReference ? elements.domReference.ownerDocument : document,
132
+ );
133
+ if (!relatedTarget && activeEl === elements.domReference) return;
134
+ if (
135
+ contains(dataRef.current.floatingContext?.refs.floating.current, activeEl as any) ||
136
+ contains(elements.domReference, activeEl as any) ||
137
+ movedToFocusGuard
138
+ ) {
139
+ return;
140
+ }
141
+ onOpenChange(false, event, 'focus');
142
+ });
143
+ },
144
+ }),
145
+ [dataRef, elements.domReference, onOpenChange, visibleOnly],
146
+ subSlot(slot, 'm:ref'),
147
+ );
148
+
149
+ return useMemo(
150
+ () => (enabled ? { reference } : {}),
151
+ [enabled, reference],
152
+ subSlot(slot, 'm:ret'),
153
+ );
154
+ }
@@ -0,0 +1,406 @@
1
+ // Ported from @floating-ui/react useHover — opens on hover (like CSS :hover),
2
+ // with optional safePolygon close handling. Mouse listeners are bound directly to
3
+ // the DOM (native), so octane's delegation isn't involved. `event.nativeEvent` →
4
+ // `event`.
5
+ import { isElement } from '@floating-ui/utils/dom';
6
+ import { useCallback, useEffect, useMemo, useRef } from 'octane';
7
+
8
+ import { splitSlot, subSlot } from './internal';
9
+ import { useFloatingParentNodeId, useFloatingTree } from './tree';
10
+ import {
11
+ clearTimeoutIfSet,
12
+ contains,
13
+ createAttribute,
14
+ getDelay,
15
+ getDocument,
16
+ isMouseLikePointerType,
17
+ useEffectEvent,
18
+ useLatestRef,
19
+ useModernLayoutEffect,
20
+ } from './utils';
21
+
22
+ const safePolygonIdentifier = createAttribute('safe-polygon');
23
+
24
+ function getRestMs(value: any): any {
25
+ if (typeof value === 'function') {
26
+ return value();
27
+ }
28
+ return value;
29
+ }
30
+
31
+ export function useHover(...args: any[]): any {
32
+ const [user, slot] = splitSlot(args);
33
+ const context = user[0];
34
+ const props = (user[1] as any) ?? {};
35
+
36
+ const open = context.open;
37
+ const onOpenChange = context.onOpenChange;
38
+ const dataRef = context.dataRef;
39
+ const events = context.events;
40
+ const elements = context.elements;
41
+
42
+ const enabled = props.enabled ?? true;
43
+ const delay = props.delay ?? 0;
44
+ const handleClose = props.handleClose ?? null;
45
+ const mouseOnly = props.mouseOnly ?? false;
46
+ const restMs = props.restMs ?? 0;
47
+ const move = props.move ?? true;
48
+
49
+ const tree = useFloatingTree();
50
+ const parentId = useFloatingParentNodeId();
51
+ const handleCloseRef = useLatestRef(handleClose, subSlot(slot, 'hc'));
52
+ const delayRef = useLatestRef(delay, subSlot(slot, 'delay'));
53
+ const openRef = useLatestRef(open, subSlot(slot, 'open'));
54
+ const restMsRef = useLatestRef(restMs, subSlot(slot, 'restms'));
55
+
56
+ const pointerTypeRef = useRef<any>(undefined, subSlot(slot, 'ptype'));
57
+ const timeoutRef = useRef(-1, subSlot(slot, 'timeout'));
58
+ const handlerRef = useRef<any>(undefined, subSlot(slot, 'handler'));
59
+ const restTimeoutRef = useRef(-1, subSlot(slot, 'resttimeout'));
60
+ const blockMouseMoveRef = useRef(true, subSlot(slot, 'block'));
61
+ const performedPointerEventsMutationRef = useRef(false, subSlot(slot, 'ppem'));
62
+ const unbindMouseMoveRef = useRef<any>(() => {}, subSlot(slot, 'unbind'));
63
+ const restTimeoutPendingRef = useRef(false, subSlot(slot, 'rtp'));
64
+
65
+ const isHoverOpen = useEffectEvent(
66
+ () => {
67
+ const type = dataRef.current.openEvent?.type;
68
+ return type?.includes('mouse') && type !== 'mousedown';
69
+ },
70
+ subSlot(slot, 'ishover'),
71
+ );
72
+
73
+ useEffect(
74
+ () => {
75
+ if (!enabled) return;
76
+ function onOpenChangeLocal(_ref: any) {
77
+ const { open: o } = _ref;
78
+ if (!o) {
79
+ clearTimeoutIfSet(timeoutRef);
80
+ clearTimeoutIfSet(restTimeoutRef);
81
+ blockMouseMoveRef.current = true;
82
+ restTimeoutPendingRef.current = false;
83
+ }
84
+ }
85
+ events.on('openchange', onOpenChangeLocal);
86
+ return () => {
87
+ events.off('openchange', onOpenChangeLocal);
88
+ };
89
+ },
90
+ [enabled, events],
91
+ subSlot(slot, 'e:oc'),
92
+ );
93
+
94
+ useEffect(
95
+ () => {
96
+ if (!enabled) return;
97
+ if (!handleCloseRef.current) return;
98
+ if (!open) return;
99
+ function onLeave(event: any) {
100
+ if (isHoverOpen()) {
101
+ onOpenChange(false, event, 'hover');
102
+ }
103
+ }
104
+ const html = getDocument(elements.floating).documentElement;
105
+ html.addEventListener('mouseleave', onLeave);
106
+ return () => {
107
+ html.removeEventListener('mouseleave', onLeave);
108
+ };
109
+ },
110
+ [elements.floating, open, onOpenChange, enabled, handleCloseRef, isHoverOpen],
111
+ subSlot(slot, 'e:leave'),
112
+ );
113
+
114
+ const closeWithDelay = useCallback(
115
+ (event: any, runElseBranch = true, reason = 'hover') => {
116
+ const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current);
117
+ if (closeDelay && !handlerRef.current) {
118
+ clearTimeoutIfSet(timeoutRef);
119
+ timeoutRef.current = window.setTimeout(
120
+ () => onOpenChange(false, event, reason),
121
+ closeDelay,
122
+ );
123
+ } else if (runElseBranch) {
124
+ clearTimeoutIfSet(timeoutRef);
125
+ onOpenChange(false, event, reason);
126
+ }
127
+ },
128
+ [delayRef, onOpenChange],
129
+ subSlot(slot, 'cwd'),
130
+ );
131
+
132
+ const cleanupMouseMoveHandler = useEffectEvent(
133
+ () => {
134
+ unbindMouseMoveRef.current();
135
+ handlerRef.current = undefined;
136
+ },
137
+ subSlot(slot, 'cleanupmm'),
138
+ );
139
+
140
+ const clearPointerEvents = useEffectEvent(
141
+ () => {
142
+ if (performedPointerEventsMutationRef.current) {
143
+ const body = getDocument(elements.floating).body;
144
+ body.style.pointerEvents = '';
145
+ body.removeAttribute(safePolygonIdentifier);
146
+ performedPointerEventsMutationRef.current = false;
147
+ }
148
+ },
149
+ subSlot(slot, 'clearpe'),
150
+ );
151
+
152
+ const isClickLikeOpenEvent = useEffectEvent(
153
+ () => {
154
+ return dataRef.current.openEvent
155
+ ? ['click', 'mousedown'].includes(dataRef.current.openEvent.type)
156
+ : false;
157
+ },
158
+ subSlot(slot, 'isclick'),
159
+ );
160
+
161
+ useEffect(
162
+ () => {
163
+ if (!enabled) return;
164
+ function onReferenceMouseEnter(event: any) {
165
+ clearTimeoutIfSet(timeoutRef);
166
+ blockMouseMoveRef.current = false;
167
+ if (
168
+ (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current)) ||
169
+ (getRestMs(restMsRef.current) > 0 && !getDelay(delayRef.current, 'open'))
170
+ ) {
171
+ return;
172
+ }
173
+ const openDelay = getDelay(delayRef.current, 'open', pointerTypeRef.current);
174
+ if (openDelay) {
175
+ timeoutRef.current = window.setTimeout(() => {
176
+ if (!openRef.current) {
177
+ onOpenChange(true, event, 'hover');
178
+ }
179
+ }, openDelay);
180
+ } else if (!open) {
181
+ onOpenChange(true, event, 'hover');
182
+ }
183
+ }
184
+ function onReferenceMouseLeave(event: any) {
185
+ if (isClickLikeOpenEvent()) {
186
+ clearPointerEvents();
187
+ return;
188
+ }
189
+ unbindMouseMoveRef.current();
190
+ const doc = getDocument(elements.floating);
191
+ clearTimeoutIfSet(restTimeoutRef);
192
+ restTimeoutPendingRef.current = false;
193
+ if (handleCloseRef.current && dataRef.current.floatingContext) {
194
+ if (!open) {
195
+ clearTimeoutIfSet(timeoutRef);
196
+ }
197
+ handlerRef.current = handleCloseRef.current({
198
+ ...dataRef.current.floatingContext,
199
+ tree,
200
+ x: event.clientX,
201
+ y: event.clientY,
202
+ onClose() {
203
+ clearPointerEvents();
204
+ cleanupMouseMoveHandler();
205
+ if (!isClickLikeOpenEvent()) {
206
+ closeWithDelay(event, true, 'safe-polygon');
207
+ }
208
+ },
209
+ });
210
+ const handler = handlerRef.current;
211
+ doc.addEventListener('mousemove', handler);
212
+ unbindMouseMoveRef.current = () => {
213
+ doc.removeEventListener('mousemove', handler);
214
+ };
215
+ return;
216
+ }
217
+ const shouldClose =
218
+ pointerTypeRef.current === 'touch'
219
+ ? !contains(elements.floating, event.relatedTarget)
220
+ : true;
221
+ if (shouldClose) {
222
+ closeWithDelay(event);
223
+ }
224
+ }
225
+ function onScrollMouseLeave(event: any) {
226
+ if (isClickLikeOpenEvent()) return;
227
+ if (!dataRef.current.floatingContext) return;
228
+ handleCloseRef.current?.({
229
+ ...dataRef.current.floatingContext,
230
+ tree,
231
+ x: event.clientX,
232
+ y: event.clientY,
233
+ onClose() {
234
+ clearPointerEvents();
235
+ cleanupMouseMoveHandler();
236
+ if (!isClickLikeOpenEvent()) {
237
+ closeWithDelay(event);
238
+ }
239
+ },
240
+ })(event);
241
+ }
242
+ function onFloatingMouseEnter() {
243
+ clearTimeoutIfSet(timeoutRef);
244
+ }
245
+ function onFloatingMouseLeave(event: any) {
246
+ if (!isClickLikeOpenEvent()) {
247
+ closeWithDelay(event, false);
248
+ }
249
+ }
250
+ if (isElement(elements.domReference)) {
251
+ const reference = elements.domReference;
252
+ const floating = elements.floating;
253
+ if (open) {
254
+ reference.addEventListener('mouseleave', onScrollMouseLeave);
255
+ }
256
+ if (move) {
257
+ reference.addEventListener('mousemove', onReferenceMouseEnter, { once: true });
258
+ }
259
+ reference.addEventListener('mouseenter', onReferenceMouseEnter);
260
+ reference.addEventListener('mouseleave', onReferenceMouseLeave);
261
+ if (floating) {
262
+ floating.addEventListener('mouseleave', onScrollMouseLeave);
263
+ floating.addEventListener('mouseenter', onFloatingMouseEnter);
264
+ floating.addEventListener('mouseleave', onFloatingMouseLeave);
265
+ }
266
+ return () => {
267
+ if (open) {
268
+ reference.removeEventListener('mouseleave', onScrollMouseLeave);
269
+ }
270
+ if (move) {
271
+ reference.removeEventListener('mousemove', onReferenceMouseEnter);
272
+ }
273
+ reference.removeEventListener('mouseenter', onReferenceMouseEnter);
274
+ reference.removeEventListener('mouseleave', onReferenceMouseLeave);
275
+ if (floating) {
276
+ floating.removeEventListener('mouseleave', onScrollMouseLeave);
277
+ floating.removeEventListener('mouseenter', onFloatingMouseEnter);
278
+ floating.removeEventListener('mouseleave', onFloatingMouseLeave);
279
+ }
280
+ };
281
+ }
282
+ },
283
+ [
284
+ elements,
285
+ enabled,
286
+ context,
287
+ mouseOnly,
288
+ move,
289
+ closeWithDelay,
290
+ cleanupMouseMoveHandler,
291
+ clearPointerEvents,
292
+ onOpenChange,
293
+ open,
294
+ openRef,
295
+ tree,
296
+ delayRef,
297
+ handleCloseRef,
298
+ dataRef,
299
+ isClickLikeOpenEvent,
300
+ restMsRef,
301
+ ],
302
+ subSlot(slot, 'e:refevents'),
303
+ );
304
+
305
+ useModernLayoutEffect(
306
+ () => {
307
+ if (!enabled) return;
308
+ if (open && handleCloseRef.current?.__options?.blockPointerEvents && isHoverOpen()) {
309
+ performedPointerEventsMutationRef.current = true;
310
+ const floatingEl = elements.floating;
311
+ if (isElement(elements.domReference) && floatingEl) {
312
+ const body = getDocument(elements.floating).body;
313
+ body.setAttribute(safePolygonIdentifier, '');
314
+ const ref = elements.domReference;
315
+ const parentFloating = tree?.nodesRef.current.find((node: any) => node.id === parentId)
316
+ ?.context?.elements.floating;
317
+ if (parentFloating) {
318
+ parentFloating.style.pointerEvents = '';
319
+ }
320
+ body.style.pointerEvents = 'none';
321
+ ref.style.pointerEvents = 'auto';
322
+ floatingEl.style.pointerEvents = 'auto';
323
+ return () => {
324
+ body.style.pointerEvents = '';
325
+ ref.style.pointerEvents = '';
326
+ floatingEl.style.pointerEvents = '';
327
+ };
328
+ }
329
+ }
330
+ },
331
+ [enabled, open, parentId, elements, tree, handleCloseRef, isHoverOpen],
332
+ subSlot(slot, 'e:blockpe'),
333
+ );
334
+
335
+ useModernLayoutEffect(
336
+ () => {
337
+ if (!open) {
338
+ pointerTypeRef.current = undefined;
339
+ restTimeoutPendingRef.current = false;
340
+ cleanupMouseMoveHandler();
341
+ clearPointerEvents();
342
+ }
343
+ },
344
+ [open, cleanupMouseMoveHandler, clearPointerEvents],
345
+ subSlot(slot, 'e:reset'),
346
+ );
347
+
348
+ useEffect(
349
+ () => {
350
+ return () => {
351
+ cleanupMouseMoveHandler();
352
+ clearTimeoutIfSet(timeoutRef);
353
+ clearTimeoutIfSet(restTimeoutRef);
354
+ clearPointerEvents();
355
+ };
356
+ },
357
+ [enabled, elements.domReference, cleanupMouseMoveHandler, clearPointerEvents],
358
+ subSlot(slot, 'e:cleanup'),
359
+ );
360
+
361
+ const reference = useMemo(
362
+ () => {
363
+ function setPointerRef(event: any) {
364
+ pointerTypeRef.current = event.pointerType;
365
+ }
366
+ return {
367
+ onPointerDown: setPointerRef,
368
+ onPointerEnter: setPointerRef,
369
+ onMouseMove(event: any) {
370
+ function handleMouseMove() {
371
+ if (!blockMouseMoveRef.current && !openRef.current) {
372
+ onOpenChange(true, event, 'hover');
373
+ }
374
+ }
375
+ if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current)) {
376
+ return;
377
+ }
378
+ if (open || getRestMs(restMsRef.current) === 0) {
379
+ return;
380
+ }
381
+ if (restTimeoutPendingRef.current && event.movementX ** 2 + event.movementY ** 2 < 2) {
382
+ return;
383
+ }
384
+ clearTimeoutIfSet(restTimeoutRef);
385
+ if (pointerTypeRef.current === 'touch') {
386
+ handleMouseMove();
387
+ } else {
388
+ restTimeoutPendingRef.current = true;
389
+ restTimeoutRef.current = window.setTimeout(
390
+ handleMouseMove,
391
+ getRestMs(restMsRef.current),
392
+ );
393
+ }
394
+ },
395
+ };
396
+ },
397
+ [mouseOnly, onOpenChange, open, openRef, restMsRef],
398
+ subSlot(slot, 'm:ref'),
399
+ );
400
+
401
+ return useMemo(
402
+ () => (enabled ? { reference } : {}),
403
+ [enabled, reference],
404
+ subSlot(slot, 'm:ret'),
405
+ );
406
+ }
package/src/useId.ts ADDED
@@ -0,0 +1,6 @@
1
+ // @floating-ui/react's useId, backed by octane's SSR-stable useId.
2
+ import { useId as octaneUseId } from 'octane';
3
+
4
+ export function useId(slot: symbol | undefined): string {
5
+ return octaneUseId(slot);
6
+ }
@@ -0,0 +1,94 @@
1
+ // Ported from @floating-ui/react useInteractions + mergeProps. mergeProps is pure
2
+ // JS (composes prop getters, chaining `on*` handlers so none overwrite another).
3
+ import { useCallback, useMemo } from 'octane';
4
+
5
+ import { splitSlot, subSlot } from './internal';
6
+ import { FOCUSABLE_ATTRIBUTE } from './utils';
7
+
8
+ const ACTIVE_KEY = 'active';
9
+ const SELECTED_KEY = 'selected';
10
+
11
+ function mergeProps(userProps: any, propsList: any[], elementKey: string): any {
12
+ const map = new Map<string, any[]>();
13
+ const isItem = elementKey === 'item';
14
+ let domUserProps = userProps;
15
+ if (isItem && userProps) {
16
+ const { [ACTIVE_KEY]: _a, [SELECTED_KEY]: _s, ...validProps } = userProps;
17
+ domUserProps = validProps;
18
+ }
19
+ return {
20
+ ...(elementKey === 'floating' && {
21
+ tabIndex: -1,
22
+ [FOCUSABLE_ATTRIBUTE]: '',
23
+ }),
24
+ ...domUserProps,
25
+ ...propsList
26
+ .map((value) => {
27
+ const propsOrGetProps = value ? value[elementKey] : null;
28
+ if (typeof propsOrGetProps === 'function') {
29
+ return userProps ? propsOrGetProps(userProps) : null;
30
+ }
31
+ return propsOrGetProps;
32
+ })
33
+ .concat(userProps)
34
+ .reduce((acc: any, props: any) => {
35
+ if (!props) {
36
+ return acc;
37
+ }
38
+ Object.entries(props).forEach((_ref) => {
39
+ const [key, value] = _ref as [string, any];
40
+ if (isItem && [ACTIVE_KEY, SELECTED_KEY].includes(key)) {
41
+ return;
42
+ }
43
+ if (key.indexOf('on') === 0) {
44
+ if (!map.has(key)) {
45
+ map.set(key, []);
46
+ }
47
+ if (typeof value === 'function') {
48
+ map.get(key)?.push(value);
49
+ acc[key] = function (...args: any[]) {
50
+ return map
51
+ .get(key)
52
+ ?.map((fn) => fn(...args))
53
+ .find((val) => val !== undefined);
54
+ };
55
+ }
56
+ } else {
57
+ acc[key] = value;
58
+ }
59
+ });
60
+ return acc;
61
+ }, {}),
62
+ };
63
+ }
64
+
65
+ export function useInteractions(...args: any[]): any {
66
+ const [user, slot] = splitSlot(args);
67
+ const propsList = (user[0] as any[]) ?? [];
68
+
69
+ const referenceDeps = propsList.map((key) => (key == null ? undefined : key.reference));
70
+ const floatingDeps = propsList.map((key) => (key == null ? undefined : key.floating));
71
+ const itemDeps = propsList.map((key) => (key == null ? undefined : key.item));
72
+
73
+ const getReferenceProps = useCallback(
74
+ (userProps: any) => mergeProps(userProps, propsList, 'reference'),
75
+ referenceDeps,
76
+ subSlot(slot, 'ref'),
77
+ );
78
+ const getFloatingProps = useCallback(
79
+ (userProps: any) => mergeProps(userProps, propsList, 'floating'),
80
+ floatingDeps,
81
+ subSlot(slot, 'flo'),
82
+ );
83
+ const getItemProps = useCallback(
84
+ (userProps: any) => mergeProps(userProps, propsList, 'item'),
85
+ itemDeps,
86
+ subSlot(slot, 'item'),
87
+ );
88
+
89
+ return useMemo(
90
+ () => ({ getReferenceProps, getFloatingProps, getItemProps }),
91
+ [getReferenceProps, getFloatingProps, getItemProps],
92
+ subSlot(slot, 'ret'),
93
+ );
94
+ }