v-float 0.10.0 → 0.11.0
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/README.md +44 -175
- package/dist/index.d.mts +688 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2038 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -75
- package/dist/composables/index.d.ts +0 -4
- package/dist/composables/index.d.ts.map +0 -1
- package/dist/composables/interactions/index.d.ts +0 -7
- package/dist/composables/interactions/index.d.ts.map +0 -1
- package/dist/composables/interactions/polygon.d.ts +0 -38
- package/dist/composables/interactions/polygon.d.ts.map +0 -1
- package/dist/composables/interactions/use-click.d.ts +0 -120
- package/dist/composables/interactions/use-click.d.ts.map +0 -1
- package/dist/composables/interactions/use-escape-key.d.ts +0 -57
- package/dist/composables/interactions/use-escape-key.d.ts.map +0 -1
- package/dist/composables/interactions/use-focus-trap.d.ts +0 -78
- package/dist/composables/interactions/use-focus-trap.d.ts.map +0 -1
- package/dist/composables/interactions/use-focus.d.ts +0 -60
- package/dist/composables/interactions/use-focus.d.ts.map +0 -1
- package/dist/composables/interactions/use-hover.d.ts +0 -78
- package/dist/composables/interactions/use-hover.d.ts.map +0 -1
- package/dist/composables/interactions/use-list-navigation.d.ts +0 -144
- package/dist/composables/interactions/use-list-navigation.d.ts.map +0 -1
- package/dist/composables/middlewares/arrow.d.ts +0 -25
- package/dist/composables/middlewares/arrow.d.ts.map +0 -1
- package/dist/composables/middlewares/index.d.ts +0 -4
- package/dist/composables/middlewares/index.d.ts.map +0 -1
- package/dist/composables/positioning/index.d.ts +0 -5
- package/dist/composables/positioning/index.d.ts.map +0 -1
- package/dist/composables/positioning/use-arrow.d.ts +0 -55
- package/dist/composables/positioning/use-arrow.d.ts.map +0 -1
- package/dist/composables/positioning/use-client-point.d.ts +0 -218
- package/dist/composables/positioning/use-client-point.d.ts.map +0 -1
- package/dist/composables/positioning/use-floating-tree.d.ts +0 -240
- package/dist/composables/positioning/use-floating-tree.d.ts.map +0 -1
- package/dist/composables/positioning/use-floating.d.ts +0 -162
- package/dist/composables/positioning/use-floating.d.ts.map +0 -1
- package/dist/composables/utils/is-using-keyboard.d.ts +0 -2
- package/dist/composables/utils/is-using-keyboard.d.ts.map +0 -1
- package/dist/composables/utils/use-active-descendant.d.ts +0 -8
- package/dist/composables/utils/use-active-descendant.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -18
- package/dist/types.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -92
- package/dist/utils.d.ts.map +0 -1
- package/dist/v-float.es.js +0 -3827
- package/dist/v-float.umd.js +0 -8
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2038 @@
|
|
|
1
|
+
import { computed, effectScope, getCurrentScope, onScopeDispose, onWatcherCleanup, readonly, ref, shallowRef, toValue, watch, watchEffect, watchPostEffect } from "vue";
|
|
2
|
+
import { createFocusTrap } from "focus-trap";
|
|
3
|
+
import { arrow as arrow$1, autoPlacement, autoUpdate, computePosition, flip, hide, offset, shift, size } from "@floating-ui/dom";
|
|
4
|
+
//#region src/composables/utils/use-event-listener.ts
|
|
5
|
+
function useEventListener(target, event, listener, options) {
|
|
6
|
+
let removeListener;
|
|
7
|
+
const cleanup = () => {
|
|
8
|
+
removeListener?.();
|
|
9
|
+
removeListener = void 0;
|
|
10
|
+
};
|
|
11
|
+
const stopWatch = watch(() => [toValue(target), toValue(event)], ([currentTarget, currentEvent], _, onCleanup) => {
|
|
12
|
+
cleanup();
|
|
13
|
+
if (!currentTarget || !currentEvent) return;
|
|
14
|
+
const listenerOptions = typeof options === "object" && options !== null ? { ...options } : options;
|
|
15
|
+
currentTarget.addEventListener(currentEvent, listener, listenerOptions);
|
|
16
|
+
removeListener = () => {
|
|
17
|
+
currentTarget.removeEventListener(currentEvent, listener, listenerOptions);
|
|
18
|
+
};
|
|
19
|
+
onCleanup(cleanup);
|
|
20
|
+
}, {
|
|
21
|
+
immediate: true,
|
|
22
|
+
flush: "post"
|
|
23
|
+
});
|
|
24
|
+
return () => {
|
|
25
|
+
stopWatch();
|
|
26
|
+
cleanup();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/core/floating-accessors.ts
|
|
31
|
+
function getFloatingRefs(context) {
|
|
32
|
+
if (context.refs) return context.refs;
|
|
33
|
+
throw new Error("[VFloat] Floating refs are missing from the provided context.");
|
|
34
|
+
}
|
|
35
|
+
function getFloatingState(context) {
|
|
36
|
+
if (context.state) return context.state;
|
|
37
|
+
if (context.open && context.setOpen) return {
|
|
38
|
+
open: context.open,
|
|
39
|
+
setOpen: context.setOpen
|
|
40
|
+
};
|
|
41
|
+
throw new Error("[VFloat] Floating state is missing from the provided context.");
|
|
42
|
+
}
|
|
43
|
+
function getFloatingPosition(context) {
|
|
44
|
+
if (context.position) return context.position;
|
|
45
|
+
if (context.x && context.y && context.strategy && context.placement && context.middlewareData && context.isPositioned && context.floatingStyles && context.update) return {
|
|
46
|
+
x: context.x,
|
|
47
|
+
y: context.y,
|
|
48
|
+
strategy: context.strategy,
|
|
49
|
+
placement: context.placement,
|
|
50
|
+
middlewareData: context.middlewareData,
|
|
51
|
+
isPositioned: context.isPositioned,
|
|
52
|
+
styles: context.floatingStyles,
|
|
53
|
+
update: context.update
|
|
54
|
+
};
|
|
55
|
+
throw new Error("[VFloat] Floating position data is missing from the provided context.");
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/core/lifecycle.ts
|
|
59
|
+
function tryOnScopeDispose(cleanup) {
|
|
60
|
+
if (!getCurrentScope()) return false;
|
|
61
|
+
onScopeDispose(cleanup);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils.ts
|
|
66
|
+
/**
|
|
67
|
+
* Type guard for HTMLElement
|
|
68
|
+
*/
|
|
69
|
+
function isHTMLElement(value) {
|
|
70
|
+
return value instanceof Element && value instanceof HTMLElement;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Checks if the user agent is on a Mac.
|
|
74
|
+
*/
|
|
75
|
+
function isMac() {
|
|
76
|
+
if (typeof navigator === "undefined") return false;
|
|
77
|
+
return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Checks if the browser is Safari.
|
|
81
|
+
*/
|
|
82
|
+
function isSafari() {
|
|
83
|
+
if (typeof navigator === "undefined") return false;
|
|
84
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A simple utility to check if an element matches `:focus-visible`.
|
|
88
|
+
*/
|
|
89
|
+
function matchesFocusVisible(element) {
|
|
90
|
+
if (typeof element?.matches !== "function") return false;
|
|
91
|
+
return element.matches(":focus-visible");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Checks if the pointer type is mouse-like (mouse or pen).
|
|
95
|
+
*/
|
|
96
|
+
function isMouseLikePointerType(pointerType, strict) {
|
|
97
|
+
if (pointerType === void 0) return false;
|
|
98
|
+
const isMouse = pointerType === "mouse";
|
|
99
|
+
return strict ? isMouse : isMouse || pointerType === "pen";
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Checks if the element is an input, textarea, or contenteditable element.
|
|
103
|
+
*/
|
|
104
|
+
function isTypeableElement(element) {
|
|
105
|
+
if (!element || !(element instanceof HTMLElement)) return false;
|
|
106
|
+
return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element.isContentEditable && element.contentEditable !== "false";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Checks if the event target is a button-like element.
|
|
110
|
+
*/
|
|
111
|
+
function isButtonTarget(event) {
|
|
112
|
+
const target = event.target;
|
|
113
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
114
|
+
return target.tagName === "BUTTON" || target.tagName === "INPUT" && target.getAttribute("type") === "button" || target.getAttribute("role") === "button";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Checks if the Space key press should be ignored for the given element.
|
|
118
|
+
*/
|
|
119
|
+
function isSpaceIgnored(element) {
|
|
120
|
+
return isTypeableElement(element);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Checks if the event target is within the given element.
|
|
124
|
+
*/
|
|
125
|
+
function isEventTargetWithin(event, element) {
|
|
126
|
+
if (!element) return false;
|
|
127
|
+
if ("composedPath" in event && typeof event.composedPath === "function") return event.composedPath().includes(element);
|
|
128
|
+
return element.contains(event.target);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Checks if a click event occurred on a scrollbar.
|
|
132
|
+
*/
|
|
133
|
+
function isClickOnScrollbar(event, target) {
|
|
134
|
+
const rect = target.getBoundingClientRect();
|
|
135
|
+
const scrollbarWidth = target.offsetWidth - target.clientWidth;
|
|
136
|
+
const scrollbarHeight = target.offsetHeight - target.clientHeight;
|
|
137
|
+
const elementX = event.clientX - rect.left;
|
|
138
|
+
const elementY = event.clientY - rect.top;
|
|
139
|
+
if (scrollbarWidth > 0) {
|
|
140
|
+
if (elementX >= target.clientWidth && elementX <= target.offsetWidth) return true;
|
|
141
|
+
}
|
|
142
|
+
if (scrollbarHeight > 0) {
|
|
143
|
+
if (elementY >= target.clientHeight && elementY <= target.offsetHeight) return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Simple element containment wrapper.
|
|
149
|
+
*/
|
|
150
|
+
function contains(el, target) {
|
|
151
|
+
return el.contains(target);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Event target extraction utility.
|
|
155
|
+
*/
|
|
156
|
+
function getTarget(event) {
|
|
157
|
+
return event.target;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Safe performance timing that handles environments without performance API.
|
|
161
|
+
*/
|
|
162
|
+
function getCurrentTime() {
|
|
163
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Centralized timeout management to prevent memory leaks.
|
|
167
|
+
*/
|
|
168
|
+
function clearTimeoutIfSet(timeoutId) {
|
|
169
|
+
if (timeoutId !== -1) clearTimeout(timeoutId);
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/composables/interactions/use-click.ts
|
|
173
|
+
/**
|
|
174
|
+
* Enables showing/hiding the floating element when clicking the anchor element
|
|
175
|
+
* and optionally when clicking outside both the anchor and floating elements.
|
|
176
|
+
*
|
|
177
|
+
* This composable provides unified event handlers for both inside click interactions
|
|
178
|
+
* (to open/toggle floating elements) and outside click interactions (to close them).
|
|
179
|
+
*
|
|
180
|
+
* @param context - The floating context with open state and change handler.
|
|
181
|
+
* @param options - Configuration options for click behavior.
|
|
182
|
+
*
|
|
183
|
+
* @example Basic usage with outside click enabled
|
|
184
|
+
* ```ts
|
|
185
|
+
* const context = useFloating(...)
|
|
186
|
+
* useClick(context, {
|
|
187
|
+
* toggle: true,
|
|
188
|
+
* closeOnOutsideClick: true,
|
|
189
|
+
* outsideClickEvent: 'pointerdown'
|
|
190
|
+
* })
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example Custom outside click handler
|
|
194
|
+
* ```ts
|
|
195
|
+
* useClick(context, {
|
|
196
|
+
* closeOnOutsideClick: true,
|
|
197
|
+
* onOutsideClick: (event) => {
|
|
198
|
+
* if (confirm("Close dialog?")) {
|
|
199
|
+
* context.setOpen(false)
|
|
200
|
+
* }
|
|
201
|
+
* },
|
|
202
|
+
* })
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
function useClick(context, options = {}) {
|
|
206
|
+
const { open, setOpen } = getFloatingState(context);
|
|
207
|
+
const refs = getFloatingRefs(context);
|
|
208
|
+
const { enabled = true, event: eventOption = "click", toggle = true, ignoreMouse = false, ignoreKeyboard = false, ignoreTouch = false, closeOnOutsideClick = false, outsideClickEvent = "pointerdown", outsideCapture = true, onOutsideClick, ignoreScrollbar = true, ignoreDrag = true } = options;
|
|
209
|
+
const interactionState = {
|
|
210
|
+
pointerType: void 0,
|
|
211
|
+
didKeyDown: false,
|
|
212
|
+
dragStartedInside: false,
|
|
213
|
+
interactionInProgress: false
|
|
214
|
+
};
|
|
215
|
+
let dragResetTimeoutId;
|
|
216
|
+
const isEnabled = computed(() => toValue(enabled));
|
|
217
|
+
const isOutsideClickEnabled = computed(() => toValue(closeOnOutsideClick));
|
|
218
|
+
const anchorEl = computed(() => {
|
|
219
|
+
const el = refs.anchorEl.value;
|
|
220
|
+
if (el instanceof HTMLElement) return el;
|
|
221
|
+
return null;
|
|
222
|
+
});
|
|
223
|
+
const floatingEl = computed(() => refs.floatingEl.value);
|
|
224
|
+
function clearDragResetTimeout() {
|
|
225
|
+
if (dragResetTimeoutId == null) return;
|
|
226
|
+
clearTimeout(dragResetTimeoutId);
|
|
227
|
+
dragResetTimeoutId = void 0;
|
|
228
|
+
}
|
|
229
|
+
function handleOpenChange(reason, event) {
|
|
230
|
+
interactionState.interactionInProgress = true;
|
|
231
|
+
try {
|
|
232
|
+
if (open.value) toValue(toggle) && setOpen(false, reason, event);
|
|
233
|
+
else setOpen(true, reason, event);
|
|
234
|
+
} finally {
|
|
235
|
+
queueMicrotask(() => {
|
|
236
|
+
interactionState.interactionInProgress = false;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function resetInteractionState() {
|
|
241
|
+
interactionState.pointerType = void 0;
|
|
242
|
+
interactionState.didKeyDown = false;
|
|
243
|
+
interactionState.dragStartedInside = false;
|
|
244
|
+
clearDragResetTimeout();
|
|
245
|
+
}
|
|
246
|
+
function onPointerDown(e) {
|
|
247
|
+
interactionState.pointerType = e.pointerType;
|
|
248
|
+
}
|
|
249
|
+
function isSyntheticKeyboardClick(e) {
|
|
250
|
+
return toValue(ignoreKeyboard) && e.detail === 0;
|
|
251
|
+
}
|
|
252
|
+
function onMouseDown(e) {
|
|
253
|
+
if (e.button !== 0) return;
|
|
254
|
+
if (toValue(eventOption) === "click") return;
|
|
255
|
+
if (shouldIgnorePointerType(interactionState.pointerType)) return;
|
|
256
|
+
handleOpenChange("anchor-click", e);
|
|
257
|
+
}
|
|
258
|
+
function onClick(e) {
|
|
259
|
+
if (isSyntheticKeyboardClick(e)) {
|
|
260
|
+
resetInteractionState();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (toValue(eventOption) === "mousedown" && interactionState.pointerType) {
|
|
264
|
+
resetInteractionState();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (shouldIgnorePointerType(interactionState.pointerType)) {
|
|
268
|
+
resetInteractionState();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
handleOpenChange("anchor-click", e);
|
|
272
|
+
resetInteractionState();
|
|
273
|
+
}
|
|
274
|
+
function onKeyDown(e) {
|
|
275
|
+
interactionState.pointerType = void 0;
|
|
276
|
+
if (e.defaultPrevented || toValue(ignoreKeyboard) || isButtonTarget(e)) return;
|
|
277
|
+
const el = anchorEl.value;
|
|
278
|
+
if (!el) return;
|
|
279
|
+
if (e.key === " " && !isSpaceIgnored(el)) {
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
interactionState.didKeyDown = true;
|
|
282
|
+
}
|
|
283
|
+
if (e.key === "Enter") handleOpenChange("keyboard-activate", e);
|
|
284
|
+
}
|
|
285
|
+
function onKeyUp(e) {
|
|
286
|
+
const el = anchorEl.value;
|
|
287
|
+
if (!el) return;
|
|
288
|
+
if (e.defaultPrevented || toValue(ignoreKeyboard) || isButtonTarget(e) || isSpaceIgnored(el)) return;
|
|
289
|
+
if (e.key === " " && interactionState.didKeyDown) {
|
|
290
|
+
interactionState.didKeyDown = false;
|
|
291
|
+
handleOpenChange("keyboard-activate", e);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function onOutsideClickHandler(event) {
|
|
295
|
+
if (!isEnabled.value || !isOutsideClickEnabled.value || !open.value) return;
|
|
296
|
+
if (suppressOutsideClickForDragSequence()) return;
|
|
297
|
+
if (interactionState.interactionInProgress) return;
|
|
298
|
+
const target = event.target;
|
|
299
|
+
if (!target) return;
|
|
300
|
+
if (toValue(ignoreScrollbar) && isHTMLElement(target) && floatingEl.value && isClickOnScrollbar(event, target)) return;
|
|
301
|
+
if (isEventTargetWithin(event, anchorEl.value) || isEventTargetWithin(event, floatingEl.value)) return;
|
|
302
|
+
if (onOutsideClick) onOutsideClick(event);
|
|
303
|
+
else setOpen(false, "outside-pointer", event);
|
|
304
|
+
}
|
|
305
|
+
function suppressOutsideClickForDragSequence() {
|
|
306
|
+
if (toValue(outsideClickEvent) !== "click") return false;
|
|
307
|
+
if (!toValue(ignoreDrag)) return false;
|
|
308
|
+
if (!interactionState.dragStartedInside) return false;
|
|
309
|
+
interactionState.dragStartedInside = false;
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
function onFloatingMouseDown() {
|
|
313
|
+
interactionState.dragStartedInside = true;
|
|
314
|
+
}
|
|
315
|
+
function onFloatingMouseUp() {
|
|
316
|
+
clearDragResetTimeout();
|
|
317
|
+
dragResetTimeoutId = setTimeout(() => {
|
|
318
|
+
interactionState.dragStartedInside = false;
|
|
319
|
+
}, 0);
|
|
320
|
+
}
|
|
321
|
+
function shouldIgnorePointerType(type) {
|
|
322
|
+
if (isMouseLikePointerType(type, true) && toValue(ignoreMouse)) return true;
|
|
323
|
+
if (type === "touch" && toValue(ignoreTouch)) return true;
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
tryOnScopeDispose(() => {
|
|
327
|
+
clearDragResetTimeout();
|
|
328
|
+
});
|
|
329
|
+
watchPostEffect(() => {
|
|
330
|
+
const el = anchorEl.value;
|
|
331
|
+
if (!isEnabled.value || !el) return;
|
|
332
|
+
el.addEventListener("pointerdown", onPointerDown);
|
|
333
|
+
el.addEventListener("mousedown", onMouseDown);
|
|
334
|
+
el.addEventListener("click", onClick);
|
|
335
|
+
el.addEventListener("keydown", onKeyDown);
|
|
336
|
+
el.addEventListener("keyup", onKeyUp);
|
|
337
|
+
onWatcherCleanup(() => {
|
|
338
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
339
|
+
el.removeEventListener("mousedown", onMouseDown);
|
|
340
|
+
el.removeEventListener("click", onClick);
|
|
341
|
+
el.removeEventListener("keydown", onKeyDown);
|
|
342
|
+
el.removeEventListener("keyup", onKeyUp);
|
|
343
|
+
resetInteractionState();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
useEventListener(() => isEnabled.value && isOutsideClickEnabled.value ? document : null, outsideClickEvent, onOutsideClickHandler, { capture: toValue(outsideCapture) });
|
|
347
|
+
useEventListener(() => isEnabled.value && isOutsideClickEnabled.value && toValue(ignoreDrag) ? floatingEl.value : null, "mousedown", onFloatingMouseDown, { capture: true });
|
|
348
|
+
useEventListener(() => isEnabled.value && isOutsideClickEnabled.value && toValue(ignoreDrag) ? floatingEl.value : null, "mouseup", onFloatingMouseUp, { capture: true });
|
|
349
|
+
}
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/composables/utils/use-composition.ts
|
|
352
|
+
let sharedCompositionState;
|
|
353
|
+
function getSharedCompositionState() {
|
|
354
|
+
if (sharedCompositionState) return sharedCompositionState;
|
|
355
|
+
const scope = effectScope(true);
|
|
356
|
+
const isComposing = ref(false);
|
|
357
|
+
scope.run(() => {
|
|
358
|
+
useEventListener(document, "compositionstart", () => {
|
|
359
|
+
isComposing.value = true;
|
|
360
|
+
});
|
|
361
|
+
useEventListener(document, "compositionend", () => {
|
|
362
|
+
isComposing.value = false;
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
sharedCompositionState = {
|
|
366
|
+
scope,
|
|
367
|
+
isComposing,
|
|
368
|
+
consumers: 0
|
|
369
|
+
};
|
|
370
|
+
return sharedCompositionState;
|
|
371
|
+
}
|
|
372
|
+
function useComposition() {
|
|
373
|
+
const state = getSharedCompositionState();
|
|
374
|
+
state.consumers += 1;
|
|
375
|
+
if (getCurrentScope()) onScopeDispose(() => {
|
|
376
|
+
state.consumers -= 1;
|
|
377
|
+
if (state.consumers <= 0) {
|
|
378
|
+
state.scope.stop();
|
|
379
|
+
sharedCompositionState = void 0;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return { isComposing: state.isComposing };
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/composables/interactions/use-escape-key.ts
|
|
386
|
+
/**
|
|
387
|
+
* A composable to handle the escape key press with composition event handling.
|
|
388
|
+
*
|
|
389
|
+
* When triggered, it will close the floating element by setting open to false.
|
|
390
|
+
*
|
|
391
|
+
* @param context - The floating context with open state and change handler.
|
|
392
|
+
* @param options - {@link UseEscapeKeyOptions}
|
|
393
|
+
*
|
|
394
|
+
* @example Basic usage
|
|
395
|
+
* ```ts
|
|
396
|
+
* const context = useFloating(...)
|
|
397
|
+
* useEscapeKey(context) // Closes the floating element on escape
|
|
398
|
+
* ```
|
|
399
|
+
*
|
|
400
|
+
* @example Custom handler
|
|
401
|
+
* ```ts
|
|
402
|
+
* useEscapeKey(context, {
|
|
403
|
+
* onEscape: (event) => {
|
|
404
|
+
* if (hasUnsavedChanges.value) {
|
|
405
|
+
* showConfirmDialog.value = true
|
|
406
|
+
* } else {
|
|
407
|
+
* context.setOpen(false)
|
|
408
|
+
* }
|
|
409
|
+
* }
|
|
410
|
+
* })
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
function useEscapeKey(context, options = {}) {
|
|
414
|
+
const { enabled = true, capture = false, preventDefault = false, onEscape } = options;
|
|
415
|
+
const { isComposing } = useComposition();
|
|
416
|
+
const { open, setOpen } = getFloatingState(context);
|
|
417
|
+
const handleEscape = (event) => {
|
|
418
|
+
if (event.key !== "Escape" || event.defaultPrevented || !toValue(enabled) || !open.value || isComposing.value) return;
|
|
419
|
+
if (preventDefault) event.preventDefault();
|
|
420
|
+
if (onEscape) {
|
|
421
|
+
onEscape(event);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
setOpen(false, "escape-key", event);
|
|
425
|
+
};
|
|
426
|
+
useEventListener(document, "keydown", handleEscape, capture);
|
|
427
|
+
}
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/composables/utils/is-using-keyboard.ts
|
|
430
|
+
const mutableRef = ref(false);
|
|
431
|
+
if (typeof window !== "undefined") {
|
|
432
|
+
const options = { capture: true };
|
|
433
|
+
function switchToKeyboard() {
|
|
434
|
+
mutableRef.value = true;
|
|
435
|
+
window.removeEventListener("keydown", switchToKeyboard, options);
|
|
436
|
+
window.addEventListener("pointerdown", switchToPointer, options);
|
|
437
|
+
}
|
|
438
|
+
function switchToPointer() {
|
|
439
|
+
mutableRef.value = false;
|
|
440
|
+
window.removeEventListener("pointerdown", switchToPointer, options);
|
|
441
|
+
window.addEventListener("keydown", switchToKeyboard, options);
|
|
442
|
+
}
|
|
443
|
+
switchToPointer();
|
|
444
|
+
}
|
|
445
|
+
const isUsingKeyboard = readonly(mutableRef);
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/composables/interactions/use-focus.ts
|
|
448
|
+
/**
|
|
449
|
+
* Delay in milliseconds for checking active element after blur event.
|
|
450
|
+
* Using 0ms ensures check happens in next event loop tick, which is more
|
|
451
|
+
* reliable than relatedTarget for Shadow DOM and programmatic focus changes.
|
|
452
|
+
*/
|
|
453
|
+
const BLUR_CHECK_DELAY = 0;
|
|
454
|
+
/**
|
|
455
|
+
* Enables showing/hiding the floating element when the reference element receives or loses focus.
|
|
456
|
+
*
|
|
457
|
+
* Keyboard-only interaction hook. Compose with `useClick`, `useHover`, `useEscapeKey` for a complete UX.
|
|
458
|
+
*
|
|
459
|
+
* @param context - The floating context with open state and change handler
|
|
460
|
+
* @param options - Configuration options
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```ts
|
|
464
|
+
* const ctx = useFloating(...)
|
|
465
|
+
* useFocus(ctx)
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
function useFocus(context, options = {}) {
|
|
469
|
+
const { open, setOpen } = getFloatingState(context);
|
|
470
|
+
const { floatingEl, anchorEl: _anchorEl } = getFloatingRefs(context);
|
|
471
|
+
const { enabled = true, requireFocusVisible = true } = options;
|
|
472
|
+
/**
|
|
473
|
+
* Computed anchor element that handles both HTMLElement and virtual element references.
|
|
474
|
+
* Virtual elements (from libraries like Floating UI) use a `contextElement` property
|
|
475
|
+
* to reference the actual DOM element for positioning calculations.
|
|
476
|
+
*/
|
|
477
|
+
const anchorEl = computed(() => {
|
|
478
|
+
if (!_anchorEl.value) return null;
|
|
479
|
+
return _anchorEl.value instanceof HTMLElement ? _anchorEl.value : _anchorEl.value.contextElement;
|
|
480
|
+
});
|
|
481
|
+
let isFocusBlocked = false;
|
|
482
|
+
const isSafariOnMac = isMac() && isSafari();
|
|
483
|
+
let timeoutId;
|
|
484
|
+
useEventListener(window, "blur", () => {
|
|
485
|
+
if (!open.value && anchorEl.value && anchorEl.value === document.activeElement) isFocusBlocked = true;
|
|
486
|
+
});
|
|
487
|
+
useEventListener(window, "focus", () => {
|
|
488
|
+
isFocusBlocked = false;
|
|
489
|
+
});
|
|
490
|
+
function onFocus(event) {
|
|
491
|
+
if (isFocusBlocked) {
|
|
492
|
+
isFocusBlocked = false;
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
496
|
+
if (toValue(requireFocusVisible) && target) {
|
|
497
|
+
if (isSafariOnMac && !event.relatedTarget) {
|
|
498
|
+
if (!isUsingKeyboard.value && !isTypeableElement(target)) return;
|
|
499
|
+
} else if (!matchesFocusVisible(target)) return;
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
setOpen(true, "focus", event);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error("[useFocus] Error in onFocus handler:", error);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function onBlur(event) {
|
|
508
|
+
clearTimeout(timeoutId);
|
|
509
|
+
timeoutId = window.setTimeout(() => {
|
|
510
|
+
const activeEl = (anchorEl.value?.ownerDocument ?? document).activeElement;
|
|
511
|
+
if (!event.relatedTarget && activeEl === anchorEl.value) return;
|
|
512
|
+
if (activeEl && floatingEl.value?.contains(activeEl)) return;
|
|
513
|
+
try {
|
|
514
|
+
setOpen(false, "blur", event);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error("[useFocus] Error in onBlur handler:", error);
|
|
517
|
+
}
|
|
518
|
+
}, BLUR_CHECK_DELAY);
|
|
519
|
+
}
|
|
520
|
+
useEventListener(document, "focusin", (evt) => {
|
|
521
|
+
if (!toValue(enabled)) return;
|
|
522
|
+
if (!(evt.target instanceof Element)) return;
|
|
523
|
+
if (isEventTargetWithin(evt, anchorEl.value)) return;
|
|
524
|
+
if (isEventTargetWithin(evt, floatingEl.value)) return;
|
|
525
|
+
try {
|
|
526
|
+
setOpen(false, "blur", evt);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.error("[useFocus] Error in document focusin handler:", error);
|
|
529
|
+
}
|
|
530
|
+
}, { capture: true });
|
|
531
|
+
const removeWatcher = watchPostEffect(() => {
|
|
532
|
+
if (!toValue(enabled)) return;
|
|
533
|
+
const el = anchorEl.value;
|
|
534
|
+
if (!el || !(el instanceof HTMLElement)) return;
|
|
535
|
+
el.addEventListener("focus", onFocus);
|
|
536
|
+
el.addEventListener("blur", onBlur);
|
|
537
|
+
onWatcherCleanup(() => {
|
|
538
|
+
el.removeEventListener("focus", onFocus);
|
|
539
|
+
el.removeEventListener("blur", onBlur);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
tryOnScopeDispose(() => {
|
|
543
|
+
clearTimeout(timeoutId);
|
|
544
|
+
});
|
|
545
|
+
return { cleanup: () => {
|
|
546
|
+
clearTimeout(timeoutId);
|
|
547
|
+
removeWatcher();
|
|
548
|
+
} };
|
|
549
|
+
}
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/composables/interactions/use-focus-trap.ts
|
|
552
|
+
const supportsInert = typeof HTMLElement !== "undefined" && "inert" in HTMLElement.prototype;
|
|
553
|
+
function resolveInitialFocus(initialFocus) {
|
|
554
|
+
return () => typeof initialFocus === "function" ? initialFocus() : initialFocus;
|
|
555
|
+
}
|
|
556
|
+
function resolveIsolationMode(modal, outsideElementsInert) {
|
|
557
|
+
if (!modal) return false;
|
|
558
|
+
if (outsideElementsInert && supportsInert) return "inert";
|
|
559
|
+
return "aria-hidden";
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Creates a focus trap for a floating element using focus-trap library.
|
|
563
|
+
* Manages focus containment, modal behavior, and accessibility features.
|
|
564
|
+
*
|
|
565
|
+
* @param context - Floating context containing floating element refs
|
|
566
|
+
* @param options - Configuration options for the focus trap
|
|
567
|
+
* @returns Object with isActive state, and manual control methods
|
|
568
|
+
*/
|
|
569
|
+
function useFocusTrap(context, options = {}) {
|
|
570
|
+
const { floatingEl } = getFloatingRefs(context);
|
|
571
|
+
const { open, setOpen } = getFloatingState(context);
|
|
572
|
+
const { enabled = true, modal = false, initialFocus, returnFocus = true, closeOnFocusOut = false, preventScroll = true, outsideElementsInert = false, onError } = options;
|
|
573
|
+
const isEnabled = computed(() => !!toValue(enabled));
|
|
574
|
+
const isModal = computed(() => !!toValue(modal));
|
|
575
|
+
const shouldCloseOnFocusOut = computed(() => !isModal.value && !!toValue(closeOnFocusOut));
|
|
576
|
+
const shouldInertOutside = computed(() => !!toValue(outsideElementsInert));
|
|
577
|
+
const trapRef = shallowRef(null);
|
|
578
|
+
const isActive = computed(() => trapRef.value !== null);
|
|
579
|
+
let shouldCloseOnDeactivate = false;
|
|
580
|
+
let isDeactivating = false;
|
|
581
|
+
/**
|
|
582
|
+
* Deactivates the focus trap and cleans up state.
|
|
583
|
+
*/
|
|
584
|
+
const deactivateTrap = (options = {}) => {
|
|
585
|
+
if (isDeactivating || !trapRef.value) return;
|
|
586
|
+
isDeactivating = true;
|
|
587
|
+
if (options.closeFloating) shouldCloseOnDeactivate = true;
|
|
588
|
+
try {
|
|
589
|
+
trapRef.value.deactivate({ returnFocus: options.returnFocus ?? toValue(returnFocus) });
|
|
590
|
+
trapRef.value = null;
|
|
591
|
+
} finally {
|
|
592
|
+
isDeactivating = false;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Creates and activates the focus trap.
|
|
597
|
+
*/
|
|
598
|
+
const createTrap = () => {
|
|
599
|
+
if (!isEnabled.value || !open.value) return;
|
|
600
|
+
deactivateTrap({ returnFocus: false });
|
|
601
|
+
const container = floatingEl.value;
|
|
602
|
+
if (!container) {
|
|
603
|
+
if (import.meta.env.DEV) console.warn("[useFocusTrap] No floating element available for focus trap");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
trapRef.value = createFocusTrap(container, {
|
|
607
|
+
onActivate: () => {
|
|
608
|
+
shouldCloseOnDeactivate = false;
|
|
609
|
+
},
|
|
610
|
+
onDeactivate: () => {
|
|
611
|
+
trapRef.value = null;
|
|
612
|
+
if (shouldCloseOnDeactivate) {
|
|
613
|
+
shouldCloseOnDeactivate = false;
|
|
614
|
+
setOpen(false);
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
initialFocus: resolveInitialFocus(initialFocus),
|
|
618
|
+
fallbackFocus: () => container,
|
|
619
|
+
returnFocusOnDeactivate: toValue(returnFocus),
|
|
620
|
+
clickOutsideDeactivates: () => {
|
|
621
|
+
if (!shouldCloseOnFocusOut.value) return false;
|
|
622
|
+
shouldCloseOnDeactivate = true;
|
|
623
|
+
return true;
|
|
624
|
+
},
|
|
625
|
+
allowOutsideClick: !isModal.value,
|
|
626
|
+
escapeDeactivates: false,
|
|
627
|
+
preventScroll: toValue(preventScroll),
|
|
628
|
+
isolateSubtrees: resolveIsolationMode(isModal.value, shouldInertOutside.value),
|
|
629
|
+
tabbableOptions: { displayCheck: "none" }
|
|
630
|
+
});
|
|
631
|
+
try {
|
|
632
|
+
trapRef.value.activate();
|
|
633
|
+
} catch (error) {
|
|
634
|
+
shouldCloseOnDeactivate = false;
|
|
635
|
+
trapRef.value = null;
|
|
636
|
+
if (onError) onError(error);
|
|
637
|
+
else if (import.meta.env.DEV) console.error("[useFocusTrap] Failed to activate focus trap:", error);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
watchPostEffect(() => {
|
|
641
|
+
if (isEnabled.value && open.value && floatingEl.value) createTrap();
|
|
642
|
+
else deactivateTrap();
|
|
643
|
+
});
|
|
644
|
+
tryOnScopeDispose(() => {
|
|
645
|
+
deactivateTrap();
|
|
646
|
+
});
|
|
647
|
+
return {
|
|
648
|
+
isActive,
|
|
649
|
+
activate: createTrap,
|
|
650
|
+
deactivate: () => deactivateTrap({ closeFloating: true })
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/composables/interactions/polygon/geometry.ts
|
|
655
|
+
function isInside(point, rect) {
|
|
656
|
+
return point[0] >= rect.x && point[0] <= rect.x + rect.width && point[1] >= rect.y && point[1] <= rect.y + rect.height;
|
|
657
|
+
}
|
|
658
|
+
function isPointInPolygon(point, polygon) {
|
|
659
|
+
const [x, y] = point;
|
|
660
|
+
let isInsidePolygon = false;
|
|
661
|
+
const length = polygon.length;
|
|
662
|
+
for (let i = 0, j = length - 1; i < length; j = i++) {
|
|
663
|
+
const [xi, yi] = polygon[i] || [0, 0];
|
|
664
|
+
const [xj, yj] = polygon[j] || [0, 0];
|
|
665
|
+
if (yi >= y !== yj >= y && x <= (xj - xi) * (y - yi) / (yj - yi) + xi) isInsidePolygon = !isInsidePolygon;
|
|
666
|
+
}
|
|
667
|
+
return isInsidePolygon;
|
|
668
|
+
}
|
|
669
|
+
function getCursorSpeed(x, y, lastX, lastY, lastCursorTime, currentTime) {
|
|
670
|
+
const elapsedTime = currentTime - lastCursorTime;
|
|
671
|
+
if (lastX === null || lastY === null || elapsedTime === 0) return {
|
|
672
|
+
speed: null,
|
|
673
|
+
lastX: x,
|
|
674
|
+
lastY: y,
|
|
675
|
+
lastCursorTime: currentTime
|
|
676
|
+
};
|
|
677
|
+
const deltaX = x - lastX;
|
|
678
|
+
const deltaY = y - lastY;
|
|
679
|
+
return {
|
|
680
|
+
speed: Math.sqrt(deltaX * deltaX + deltaY * deltaY) / elapsedTime,
|
|
681
|
+
lastX: x,
|
|
682
|
+
lastY: y,
|
|
683
|
+
lastCursorTime: currentTime
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function isPointerLeavingOppositeSide(side, leaveX, leaveY, refRect) {
|
|
687
|
+
return side === "top" && leaveY >= (refRect?.bottom ?? 0) - 1 || side === "bottom" && leaveY <= (refRect?.top ?? 0) + 1 || side === "left" && leaveX >= (refRect?.right ?? 0) - 1 || side === "right" && leaveX <= (refRect?.left ?? 0) + 1;
|
|
688
|
+
}
|
|
689
|
+
function buildRectangularTrough(side, rect, refRect) {
|
|
690
|
+
const isFloatingWider = (rect?.width ?? 0) > (refRect?.width ?? 0);
|
|
691
|
+
const isFloatingTaller = (rect?.height ?? 0) > (refRect?.height ?? 0);
|
|
692
|
+
const left = (isFloatingWider ? refRect : rect)?.left ?? 0;
|
|
693
|
+
const right = (isFloatingWider ? refRect : rect)?.right ?? 0;
|
|
694
|
+
const top = (isFloatingTaller ? refRect : rect)?.top ?? 0;
|
|
695
|
+
const bottom = (isFloatingTaller ? refRect : rect)?.bottom ?? 0;
|
|
696
|
+
switch (side) {
|
|
697
|
+
case "top": return [
|
|
698
|
+
[left, (refRect?.top ?? 0) + 1],
|
|
699
|
+
[left, (rect?.bottom ?? 0) - 1],
|
|
700
|
+
[right, (rect?.bottom ?? 0) - 1],
|
|
701
|
+
[right, (refRect?.top ?? 0) + 1]
|
|
702
|
+
];
|
|
703
|
+
case "bottom": return [
|
|
704
|
+
[left, (rect?.top ?? 0) + 1],
|
|
705
|
+
[left, (refRect?.bottom ?? 0) - 1],
|
|
706
|
+
[right, (refRect?.bottom ?? 0) - 1],
|
|
707
|
+
[right, (rect?.top ?? 0) + 1]
|
|
708
|
+
];
|
|
709
|
+
case "left": return [
|
|
710
|
+
[(rect?.right ?? 0) - 1, bottom],
|
|
711
|
+
[(rect?.right ?? 0) - 1, top],
|
|
712
|
+
[(refRect?.left ?? 0) + 1, top],
|
|
713
|
+
[(refRect?.left ?? 0) + 1, bottom]
|
|
714
|
+
];
|
|
715
|
+
case "right": return [
|
|
716
|
+
[(refRect?.right ?? 0) - 1, bottom],
|
|
717
|
+
[(refRect?.right ?? 0) - 1, top],
|
|
718
|
+
[(rect?.left ?? 0) + 1, top],
|
|
719
|
+
[(rect?.left ?? 0) + 1, bottom]
|
|
720
|
+
];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function buildSafePolygon(side, leaveX, leaveY, rect, refRect, buffer) {
|
|
724
|
+
const isFloatingWider = (rect?.width ?? 0) > (refRect?.width ?? 0);
|
|
725
|
+
const isFloatingTaller = (rect?.height ?? 0) > (refRect?.height ?? 0);
|
|
726
|
+
const cursorLeaveFromRight = leaveX > (rect?.right ?? 0) - (rect?.width ?? 0) / 2;
|
|
727
|
+
const cursorLeaveFromBottom = leaveY > (rect?.bottom ?? 0) - (rect?.height ?? 0) / 2;
|
|
728
|
+
switch (side) {
|
|
729
|
+
case "top": return [
|
|
730
|
+
[isFloatingWider ? leaveX + buffer / 2 : cursorLeaveFromRight ? leaveX + buffer * 4 : leaveX - buffer * 4, leaveY + buffer + 1],
|
|
731
|
+
[isFloatingWider ? leaveX - buffer / 2 : cursorLeaveFromRight ? leaveX + buffer * 4 : leaveX - buffer * 4, leaveY + buffer + 1],
|
|
732
|
+
...[[rect?.left ?? 0, cursorLeaveFromRight ? (rect?.bottom ?? 0) - buffer : isFloatingWider ? (rect?.bottom ?? 0) - buffer : rect?.top ?? 0], [rect?.right ?? 0, cursorLeaveFromRight ? isFloatingWider ? (rect?.bottom ?? 0) - buffer : rect?.top ?? 0 : (rect?.bottom ?? 0) - buffer]]
|
|
733
|
+
];
|
|
734
|
+
case "bottom": return [
|
|
735
|
+
[isFloatingWider ? leaveX + buffer / 2 : cursorLeaveFromRight ? leaveX + buffer * 4 : leaveX - buffer * 4, leaveY - buffer],
|
|
736
|
+
[isFloatingWider ? leaveX - buffer / 2 : cursorLeaveFromRight ? leaveX + buffer * 4 : leaveX - buffer * 4, leaveY - buffer],
|
|
737
|
+
...[[rect?.left ?? 0, cursorLeaveFromRight ? (rect?.top ?? 0) + buffer : isFloatingWider ? (rect?.top ?? 0) + buffer : rect?.bottom ?? 0], [rect?.right ?? 0, cursorLeaveFromRight ? isFloatingWider ? (rect?.top ?? 0) + buffer : rect?.bottom ?? 0 : (rect?.top ?? 0) + buffer]]
|
|
738
|
+
];
|
|
739
|
+
case "left": {
|
|
740
|
+
const cursorPointOne = [leaveX + buffer + 1, isFloatingTaller ? leaveY + buffer / 2 : cursorLeaveFromBottom ? leaveY + buffer * 4 : leaveY - buffer * 4];
|
|
741
|
+
const cursorPointTwo = [leaveX + buffer + 1, isFloatingTaller ? leaveY - buffer / 2 : cursorLeaveFromBottom ? leaveY + buffer * 4 : leaveY - buffer * 4];
|
|
742
|
+
return [
|
|
743
|
+
...[[cursorLeaveFromBottom ? (rect?.right ?? 0) - buffer : isFloatingTaller ? (rect?.right ?? 0) - buffer : rect?.left ?? 0, rect?.top ?? 0], [cursorLeaveFromBottom ? isFloatingTaller ? (rect?.right ?? 0) - buffer : rect?.left ?? 0 : (rect?.right ?? 0) - buffer, rect?.bottom ?? 0]],
|
|
744
|
+
cursorPointOne,
|
|
745
|
+
cursorPointTwo
|
|
746
|
+
];
|
|
747
|
+
}
|
|
748
|
+
case "right": return [
|
|
749
|
+
[leaveX - buffer, isFloatingTaller ? leaveY + buffer / 2 : cursorLeaveFromBottom ? leaveY + buffer * 4 : leaveY - buffer * 4],
|
|
750
|
+
[leaveX - buffer, isFloatingTaller ? leaveY - buffer / 2 : cursorLeaveFromBottom ? leaveY + buffer * 4 : leaveY - buffer * 4],
|
|
751
|
+
...[[cursorLeaveFromBottom ? (rect?.left ?? 0) + buffer : isFloatingTaller ? (rect?.left ?? 0) + buffer : rect?.right ?? 0, rect?.top ?? 0], [cursorLeaveFromBottom ? isFloatingTaller ? (rect?.left ?? 0) + buffer : rect?.right ?? 0 : (rect?.left ?? 0) + buffer, rect?.bottom ?? 0]]
|
|
752
|
+
];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
756
|
+
//#region src/composables/interactions/polygon/bridge.ts
|
|
757
|
+
function safePolygon(options = {}) {
|
|
758
|
+
const { requireIntent = true } = options;
|
|
759
|
+
let timeoutId = -1;
|
|
760
|
+
let hasLanded = false;
|
|
761
|
+
return function createSafePolygonHandler(context) {
|
|
762
|
+
const { x, y, placement, elements, buffer: contextBuffer, onClose } = context;
|
|
763
|
+
const referenceEl = computed(() => {
|
|
764
|
+
const domReference = elements.domReference;
|
|
765
|
+
if (isHTMLElement(domReference)) return domReference;
|
|
766
|
+
return domReference?.contextElement ?? null;
|
|
767
|
+
});
|
|
768
|
+
let lastX = null;
|
|
769
|
+
let lastY = null;
|
|
770
|
+
let lastCursorTime = getCurrentTime();
|
|
771
|
+
const close = () => {
|
|
772
|
+
clearTimeoutIfSet(timeoutId);
|
|
773
|
+
timeoutId = -1;
|
|
774
|
+
onClose();
|
|
775
|
+
};
|
|
776
|
+
return function onMouseMove(event) {
|
|
777
|
+
clearTimeoutIfSet(timeoutId);
|
|
778
|
+
timeoutId = -1;
|
|
779
|
+
if (!elements.domReference || !elements.floating || placement == null || x == null || y == null) return;
|
|
780
|
+
const { clientX, clientY } = event;
|
|
781
|
+
const clientPoint = [clientX, clientY];
|
|
782
|
+
const target = getTarget(event);
|
|
783
|
+
const isLeave = event.type === "mouseleave";
|
|
784
|
+
const isOverFloatingEl = elements.floating && contains(elements.floating, target);
|
|
785
|
+
const isOverReferenceEl = referenceEl.value && contains(referenceEl.value, target);
|
|
786
|
+
const refRect = referenceEl.value?.getBoundingClientRect();
|
|
787
|
+
const rect = elements.floating?.getBoundingClientRect();
|
|
788
|
+
const side = placement.split("-")[0];
|
|
789
|
+
const isOverReferenceRect = refRect ? isInside(clientPoint, refRect) : false;
|
|
790
|
+
if (isOverFloatingEl) {
|
|
791
|
+
hasLanded = true;
|
|
792
|
+
if (!isLeave) return;
|
|
793
|
+
}
|
|
794
|
+
if (isOverReferenceEl) hasLanded = false;
|
|
795
|
+
if (isOverReferenceEl && !isLeave) {
|
|
796
|
+
hasLanded = true;
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (isLeave && isHTMLElement(event.relatedTarget) && elements.floating && contains(elements.floating, event.relatedTarget)) return;
|
|
800
|
+
if (isPointerLeavingOppositeSide(side, x, y, refRect)) {
|
|
801
|
+
close();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const rectPoly = buildRectangularTrough(side, rect, refRect);
|
|
805
|
+
const polygon = buildSafePolygon(side, x, y, rect, refRect, contextBuffer);
|
|
806
|
+
options.onPolygonChange?.(polygon);
|
|
807
|
+
if (isPointInPolygon(clientPoint, rectPoly)) return;
|
|
808
|
+
if (isPointInPolygon(clientPoint, polygon)) {
|
|
809
|
+
if (!hasLanded && requireIntent) {
|
|
810
|
+
const speedResult = getCursorSpeed(event.clientX, event.clientY, lastX, lastY, lastCursorTime, getCurrentTime());
|
|
811
|
+
lastX = speedResult.lastX;
|
|
812
|
+
lastY = speedResult.lastY;
|
|
813
|
+
lastCursorTime = speedResult.lastCursorTime;
|
|
814
|
+
if (speedResult.speed !== null && speedResult.speed < .1) timeoutId = window.setTimeout(close, 40);
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (hasLanded && !isOverReferenceRect) {
|
|
819
|
+
close();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
close();
|
|
823
|
+
};
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/composables/interactions/use-hover.ts
|
|
828
|
+
const POINTER_MOVE_THRESHOLD = 10;
|
|
829
|
+
function useDelayedOpen(show, hide, options) {
|
|
830
|
+
const { delay } = options;
|
|
831
|
+
const showDelay = computed(() => {
|
|
832
|
+
const delayVal = toValue(delay);
|
|
833
|
+
return (typeof delayVal === "number" ? delayVal : delayVal.open) ?? 0;
|
|
834
|
+
});
|
|
835
|
+
const hideDelay = computed(() => {
|
|
836
|
+
const delayVal = toValue(delay);
|
|
837
|
+
return (typeof delayVal === "number" ? delayVal : delayVal.close) ?? 0;
|
|
838
|
+
});
|
|
839
|
+
let showTimeoutID;
|
|
840
|
+
let hideTimeoutID;
|
|
841
|
+
const clearTimeouts = () => {
|
|
842
|
+
clearTimeout(showTimeoutID);
|
|
843
|
+
clearTimeout(hideTimeoutID);
|
|
844
|
+
};
|
|
845
|
+
tryOnScopeDispose(clearTimeouts);
|
|
846
|
+
return {
|
|
847
|
+
show: (overrideDelay, event) => {
|
|
848
|
+
clearTimeouts();
|
|
849
|
+
const resolvedDelay = overrideDelay ?? showDelay.value;
|
|
850
|
+
if (resolvedDelay === 0) show(event);
|
|
851
|
+
else showTimeoutID = setTimeout(() => show(event), resolvedDelay);
|
|
852
|
+
},
|
|
853
|
+
hide: (overrideDelay, event) => {
|
|
854
|
+
clearTimeouts();
|
|
855
|
+
const resolvedDelay = overrideDelay ?? hideDelay.value;
|
|
856
|
+
if (resolvedDelay === 0) hide(event);
|
|
857
|
+
else hideTimeoutID = setTimeout(() => hide(event), resolvedDelay);
|
|
858
|
+
},
|
|
859
|
+
showDelay,
|
|
860
|
+
hideDelay,
|
|
861
|
+
clearTimeouts
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Enables showing/hiding the floating element when hovering the reference element
|
|
866
|
+
* with enhanced behaviors like delayed open/close, rest detection, and custom
|
|
867
|
+
* exit handling.
|
|
868
|
+
*
|
|
869
|
+
* @param context - The floating context with open state and change handler
|
|
870
|
+
* @param options - Configuration options for hover behavior
|
|
871
|
+
*
|
|
872
|
+
* @example Basic usage
|
|
873
|
+
* ```ts
|
|
874
|
+
* const context = useFloating(...)
|
|
875
|
+
* useHover(context, {
|
|
876
|
+
* delay: { open: 100, close: 300 },
|
|
877
|
+
* restMs: 150
|
|
878
|
+
* })
|
|
879
|
+
* ```
|
|
880
|
+
*/
|
|
881
|
+
function useHover(context, options = {}) {
|
|
882
|
+
const { open, setOpen } = getFloatingState(context);
|
|
883
|
+
const { placement } = getFloatingPosition(context);
|
|
884
|
+
const { anchorEl, floatingEl } = getFloatingRefs(context);
|
|
885
|
+
const { enabled: enabledOption = true, delay = 0, restMs: restMsOption = 0, mouseOnly = false, safePolygon: safePolygonOption = false } = options;
|
|
886
|
+
const enabled = computed(() => toValue(enabledOption));
|
|
887
|
+
const restMs = computed(() => toValue(restMsOption));
|
|
888
|
+
const reference = computed(() => {
|
|
889
|
+
const el = anchorEl.value;
|
|
890
|
+
if (!el || el instanceof HTMLElement) return el;
|
|
891
|
+
return el.contextElement ?? null;
|
|
892
|
+
});
|
|
893
|
+
const { hide, show, showDelay, clearTimeouts } = useDelayedOpen((event) => {
|
|
894
|
+
open.value || setOpen(true, "hover", event);
|
|
895
|
+
}, (event) => {
|
|
896
|
+
open.value && setOpen(false, "hover", event);
|
|
897
|
+
}, { delay });
|
|
898
|
+
let restCoords = null;
|
|
899
|
+
let restTimeoutId;
|
|
900
|
+
const isRestMsEnabled = computed(() => showDelay.value === 0 && restMs.value > 0);
|
|
901
|
+
function onPointerMoveForRest(e) {
|
|
902
|
+
if (!enabled.value || !isValidPointerType(e) || !isRestMsEnabled.value) return;
|
|
903
|
+
if (!restCoords) return;
|
|
904
|
+
const newCoords = {
|
|
905
|
+
x: e.clientX,
|
|
906
|
+
y: e.clientY
|
|
907
|
+
};
|
|
908
|
+
const dx = Math.abs(newCoords.x - restCoords.x);
|
|
909
|
+
const dy = Math.abs(newCoords.y - restCoords.y);
|
|
910
|
+
if (dx > POINTER_MOVE_THRESHOLD || dy > POINTER_MOVE_THRESHOLD) {
|
|
911
|
+
restCoords = newCoords;
|
|
912
|
+
clearTimeout(restTimeoutId);
|
|
913
|
+
restTimeoutId = setTimeout(() => {
|
|
914
|
+
show(0, e);
|
|
915
|
+
}, restMs.value);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function onPointerEnterForRest(e) {
|
|
919
|
+
if (!enabled.value || !isValidPointerType(e) || !isRestMsEnabled.value) return;
|
|
920
|
+
restCoords = {
|
|
921
|
+
x: e.clientX,
|
|
922
|
+
y: e.clientY
|
|
923
|
+
};
|
|
924
|
+
restTimeoutId = setTimeout(() => {
|
|
925
|
+
show(0, e);
|
|
926
|
+
}, restMs.value);
|
|
927
|
+
}
|
|
928
|
+
function onPointerLeaveForRest() {
|
|
929
|
+
clearTimeout(restTimeoutId);
|
|
930
|
+
restCoords = null;
|
|
931
|
+
}
|
|
932
|
+
watchPostEffect(() => {
|
|
933
|
+
const el = reference.value;
|
|
934
|
+
if (!el || !enabled.value || !isRestMsEnabled.value) return;
|
|
935
|
+
el.addEventListener("pointerenter", onPointerEnterForRest);
|
|
936
|
+
el.addEventListener("pointermove", onPointerMoveForRest);
|
|
937
|
+
el.addEventListener("pointerleave", onPointerLeaveForRest);
|
|
938
|
+
onWatcherCleanup(() => {
|
|
939
|
+
el.removeEventListener("pointerenter", onPointerEnterForRest);
|
|
940
|
+
el.removeEventListener("pointermove", onPointerMoveForRest);
|
|
941
|
+
el.removeEventListener("pointerleave", onPointerLeaveForRest);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
tryOnScopeDispose(() => {
|
|
945
|
+
clearTimeout(restTimeoutId);
|
|
946
|
+
});
|
|
947
|
+
function isValidPointerType(e) {
|
|
948
|
+
if (toValue(mouseOnly)) return e.pointerType === "mouse";
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
function onPointerEnterReference(e) {
|
|
952
|
+
if (!enabled.value || !isValidPointerType(e) || isRestMsEnabled.value) return;
|
|
953
|
+
cleanupPolygon();
|
|
954
|
+
show(void 0, e);
|
|
955
|
+
}
|
|
956
|
+
function onPointerEnterFloating(e) {
|
|
957
|
+
if (!enabled.value || !isValidPointerType(e)) return;
|
|
958
|
+
clearTimeouts();
|
|
959
|
+
cleanupPolygon();
|
|
960
|
+
}
|
|
961
|
+
let polygonMouseMoveHandler = null;
|
|
962
|
+
function cleanupPolygon() {
|
|
963
|
+
if (polygonMouseMoveHandler) {
|
|
964
|
+
document.removeEventListener("pointermove", polygonMouseMoveHandler);
|
|
965
|
+
polygonMouseMoveHandler = null;
|
|
966
|
+
}
|
|
967
|
+
const polygonOptions = safePolygonOptions.value;
|
|
968
|
+
if (polygonOptions?.onPolygonChange) polygonOptions.onPolygonChange([]);
|
|
969
|
+
}
|
|
970
|
+
const safePolygonEnabled = computed(() => !!toValue(safePolygonOption));
|
|
971
|
+
const safePolygonOptions = computed(() => {
|
|
972
|
+
const val = toValue(safePolygonOption);
|
|
973
|
+
if (typeof val === "object" && val) return val;
|
|
974
|
+
if (val === true) return {};
|
|
975
|
+
});
|
|
976
|
+
function onPointerLeave(e) {
|
|
977
|
+
if (!enabled.value || !isValidPointerType(e)) return;
|
|
978
|
+
const { clientX, clientY } = e;
|
|
979
|
+
const relatedTarget = e.relatedTarget;
|
|
980
|
+
if (safePolygonEnabled.value) setTimeout(() => {
|
|
981
|
+
cleanupPolygon();
|
|
982
|
+
const refEl = reference.value;
|
|
983
|
+
const floatEl = floatingEl.value;
|
|
984
|
+
if (!refEl || !floatEl) {
|
|
985
|
+
hide(void 0, e);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
polygonMouseMoveHandler = safePolygon(safePolygonOptions.value)({
|
|
989
|
+
x: clientX,
|
|
990
|
+
y: clientY,
|
|
991
|
+
placement: placement.value,
|
|
992
|
+
elements: {
|
|
993
|
+
domReference: refEl,
|
|
994
|
+
floating: floatEl
|
|
995
|
+
},
|
|
996
|
+
buffer: safePolygonOptions.value?.buffer ?? 1,
|
|
997
|
+
onClose: () => {
|
|
998
|
+
cleanupPolygon();
|
|
999
|
+
hide(void 0);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
if (polygonMouseMoveHandler) document.addEventListener("pointermove", polygonMouseMoveHandler);
|
|
1003
|
+
}, 0);
|
|
1004
|
+
else {
|
|
1005
|
+
if (floatingEl.value?.contains(relatedTarget)) return;
|
|
1006
|
+
hide(void 0, e);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
watchPostEffect(() => {
|
|
1010
|
+
const el = reference.value;
|
|
1011
|
+
if (!el || !enabled.value) return;
|
|
1012
|
+
el.addEventListener("pointerenter", onPointerEnterReference);
|
|
1013
|
+
el.addEventListener("pointerleave", onPointerLeave);
|
|
1014
|
+
onWatcherCleanup(() => {
|
|
1015
|
+
el.removeEventListener("pointerenter", onPointerEnterReference);
|
|
1016
|
+
el.removeEventListener("pointerleave", onPointerLeave);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
watchPostEffect(() => {
|
|
1020
|
+
const el = floatingEl.value;
|
|
1021
|
+
if (!el || !enabled.value) return;
|
|
1022
|
+
el.addEventListener("pointerenter", onPointerEnterFloating);
|
|
1023
|
+
el.addEventListener("pointerleave", onPointerLeave);
|
|
1024
|
+
onWatcherCleanup(() => {
|
|
1025
|
+
el.removeEventListener("pointerenter", onPointerEnterFloating);
|
|
1026
|
+
el.removeEventListener("pointerleave", onPointerLeave);
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
tryOnScopeDispose(() => {
|
|
1030
|
+
cleanupPolygon();
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region src/composables/utils/use-active-descendant.ts
|
|
1035
|
+
/**
|
|
1036
|
+
* Manages "Virtual Focus" (via aria-activedescendant) for a list of items.
|
|
1037
|
+
*
|
|
1038
|
+
* **Important:** All items in `listRef` MUST have an `id` attribute set in the template.
|
|
1039
|
+
* The composable will not auto-generate IDs to avoid hydration mismatches and re-render issues.
|
|
1040
|
+
*
|
|
1041
|
+
* @param anchorEl - The element that will receive the aria-activedescendant attribute
|
|
1042
|
+
* @param listRef - Array of list item elements (each must have an id attribute)
|
|
1043
|
+
* @param activeIndex - The currently active index in the list
|
|
1044
|
+
* @param options - Configuration options
|
|
1045
|
+
* @returns Object containing the activeItem ref
|
|
1046
|
+
*/
|
|
1047
|
+
function useActiveDescendant(anchorEl, listRef, activeIndex, options) {
|
|
1048
|
+
const activeItem = ref(null);
|
|
1049
|
+
watch([
|
|
1050
|
+
() => toValue(options.virtual),
|
|
1051
|
+
options.open,
|
|
1052
|
+
() => toValue(activeIndex),
|
|
1053
|
+
anchorEl,
|
|
1054
|
+
listRef
|
|
1055
|
+
], ([isVirtual, isOpen, idx], _oldValues, onCleanup) => {
|
|
1056
|
+
const anchor = anchorEl.value;
|
|
1057
|
+
onCleanup(() => {
|
|
1058
|
+
anchor?.removeAttribute("aria-activedescendant");
|
|
1059
|
+
});
|
|
1060
|
+
if (!anchor || !isVirtual || !isOpen || idx == null) {
|
|
1061
|
+
activeItem.value = null;
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const el = listRef.value[idx];
|
|
1065
|
+
if (!el) {
|
|
1066
|
+
activeItem.value = null;
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (!el.id) {
|
|
1070
|
+
if (import.meta.env.DEV) console.warn("[useActiveDescendant] List item at index", idx, "is missing an 'id' attribute. All list items must have stable IDs for proper accessibility.");
|
|
1071
|
+
activeItem.value = null;
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
anchor.setAttribute("aria-activedescendant", el.id);
|
|
1075
|
+
activeItem.value = el;
|
|
1076
|
+
}, { flush: "post" });
|
|
1077
|
+
return { activeItem };
|
|
1078
|
+
}
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region src/composables/interactions/list-navigation/strategies.ts
|
|
1081
|
+
function resolveLinearMove(current, dir, context) {
|
|
1082
|
+
const { items, loop, allowEscape, isVirtual, findNextEnabled, getFirstEnabledIndex, getLastEnabledIndex } = context;
|
|
1083
|
+
const itemCount = items.length;
|
|
1084
|
+
let next = findNextEnabled(current == null ? dir === 1 ? 0 : itemCount - 1 : current + dir, dir, loop);
|
|
1085
|
+
if (next == null && loop) {
|
|
1086
|
+
if (allowEscape && isVirtual) return {
|
|
1087
|
+
type: "navigate",
|
|
1088
|
+
index: null
|
|
1089
|
+
};
|
|
1090
|
+
next = dir === 1 ? getFirstEnabledIndex() : getLastEnabledIndex();
|
|
1091
|
+
}
|
|
1092
|
+
if (next != null) return {
|
|
1093
|
+
type: "navigate",
|
|
1094
|
+
index: next
|
|
1095
|
+
};
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
var VerticalNavigationStrategy = class {
|
|
1099
|
+
handleKey(key, context) {
|
|
1100
|
+
const { isRtl, nested } = context;
|
|
1101
|
+
if (key === "ArrowDown") return resolveLinearMove(context.current, 1, context);
|
|
1102
|
+
if (key === "ArrowUp") return resolveLinearMove(context.current, -1, context);
|
|
1103
|
+
if (nested) {
|
|
1104
|
+
if (key === (isRtl ? "ArrowRight" : "ArrowLeft")) return { type: "close" };
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
var HorizontalNavigationStrategy = class {
|
|
1110
|
+
handleKey(key, context) {
|
|
1111
|
+
const { isRtl, nested } = context;
|
|
1112
|
+
if (key === "ArrowRight") return resolveLinearMove(context.current, isRtl ? -1 : 1, context);
|
|
1113
|
+
if (key === "ArrowLeft") return resolveLinearMove(context.current, isRtl ? 1 : -1, context);
|
|
1114
|
+
if (nested && key === "ArrowUp") return { type: "close" };
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
var GridNavigationStrategy = class {
|
|
1119
|
+
constructor(fallbackToLinear = false, loopDirection = "row") {
|
|
1120
|
+
this.fallbackToLinear = fallbackToLinear;
|
|
1121
|
+
this.loopDirection = loopDirection;
|
|
1122
|
+
}
|
|
1123
|
+
handleKey(key, context) {
|
|
1124
|
+
const { current, items, isDisabled, loop, allowEscape, isVirtual, getFirstEnabledIndex, getLastEnabledIndex, cols } = context;
|
|
1125
|
+
if (key === "ArrowRight" || key === "ArrowLeft") {
|
|
1126
|
+
const dir = key === "ArrowRight" ? 1 : -1;
|
|
1127
|
+
if (current === null) return resolveLinearMove(current, dir, context);
|
|
1128
|
+
const next = current + dir;
|
|
1129
|
+
const currentRow = Math.floor(current / cols);
|
|
1130
|
+
const nextRow = Math.floor(next / cols);
|
|
1131
|
+
if (next >= 0 && next < items.length && currentRow === nextRow && !isDisabled(next)) return {
|
|
1132
|
+
type: "navigate",
|
|
1133
|
+
index: next
|
|
1134
|
+
};
|
|
1135
|
+
if (loop) {
|
|
1136
|
+
if (allowEscape && isVirtual) {
|
|
1137
|
+
const isAtStart = current === 0;
|
|
1138
|
+
const isAtEnd = current === items.length - 1;
|
|
1139
|
+
if (dir === -1 && isAtStart || dir === 1 && isAtEnd) return {
|
|
1140
|
+
type: "navigate",
|
|
1141
|
+
index: null
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
let wrapCandidate;
|
|
1145
|
+
if (this.loopDirection === "next") {
|
|
1146
|
+
wrapCandidate = dir === 1 ? current + 1 : current - 1;
|
|
1147
|
+
if (wrapCandidate >= items.length) wrapCandidate = 0;
|
|
1148
|
+
if (wrapCandidate < 0) wrapCandidate = items.length - 1;
|
|
1149
|
+
} else wrapCandidate = dir === 1 ? currentRow * cols : Math.min((currentRow + 1) * cols - 1, items.length - 1);
|
|
1150
|
+
if (this.loopDirection === "next") {
|
|
1151
|
+
let candidate = wrapCandidate;
|
|
1152
|
+
for (let i = 0; i < items.length; i++) {
|
|
1153
|
+
if (!isDisabled(candidate)) return {
|
|
1154
|
+
type: "navigate",
|
|
1155
|
+
index: candidate
|
|
1156
|
+
};
|
|
1157
|
+
candidate += dir;
|
|
1158
|
+
if (candidate >= items.length) candidate = 0;
|
|
1159
|
+
if (candidate < 0) candidate = items.length - 1;
|
|
1160
|
+
if (candidate === wrapCandidate) break;
|
|
1161
|
+
}
|
|
1162
|
+
} else {
|
|
1163
|
+
const rowStart = currentRow * cols;
|
|
1164
|
+
const rowEnd = Math.min((currentRow + 1) * cols - 1, items.length - 1);
|
|
1165
|
+
while (wrapCandidate >= rowStart && wrapCandidate <= rowEnd) {
|
|
1166
|
+
if (!isDisabled(wrapCandidate)) return {
|
|
1167
|
+
type: "navigate",
|
|
1168
|
+
index: wrapCandidate
|
|
1169
|
+
};
|
|
1170
|
+
wrapCandidate += dir;
|
|
1171
|
+
if (wrapCandidate < rowStart || wrapCandidate > rowEnd) break;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return resolveLinearMove(current, dir, context);
|
|
1176
|
+
}
|
|
1177
|
+
if (key === "ArrowDown" || key === "ArrowUp") {
|
|
1178
|
+
if (cols <= 1) return null;
|
|
1179
|
+
const offset = key === "ArrowDown" ? cols : -cols;
|
|
1180
|
+
const step = offset > 0 ? cols : -cols;
|
|
1181
|
+
let candidate = (current == null ? offset > 0 ? -cols : items.length : current) + offset;
|
|
1182
|
+
while (candidate >= 0 && candidate < items.length && isDisabled(candidate)) candidate += step;
|
|
1183
|
+
if (candidate >= 0 && candidate < items.length) return {
|
|
1184
|
+
type: "navigate",
|
|
1185
|
+
index: candidate
|
|
1186
|
+
};
|
|
1187
|
+
if (loop) {
|
|
1188
|
+
if (allowEscape && isVirtual) return {
|
|
1189
|
+
type: "navigate",
|
|
1190
|
+
index: null
|
|
1191
|
+
};
|
|
1192
|
+
if (current === null) {
|
|
1193
|
+
const boundary = offset > 0 ? getFirstEnabledIndex() : getLastEnabledIndex();
|
|
1194
|
+
return boundary != null ? {
|
|
1195
|
+
type: "navigate",
|
|
1196
|
+
index: boundary
|
|
1197
|
+
} : null;
|
|
1198
|
+
}
|
|
1199
|
+
const col = current % cols;
|
|
1200
|
+
let wrapCandidate;
|
|
1201
|
+
if (offset > 0) wrapCandidate = col;
|
|
1202
|
+
else {
|
|
1203
|
+
wrapCandidate = (Math.ceil(items.length / cols) - 1) * cols + col;
|
|
1204
|
+
if (wrapCandidate >= items.length) wrapCandidate -= cols;
|
|
1205
|
+
}
|
|
1206
|
+
while (wrapCandidate >= 0 && wrapCandidate < items.length && isDisabled(wrapCandidate)) wrapCandidate += step;
|
|
1207
|
+
if (wrapCandidate >= 0 && wrapCandidate < items.length) return {
|
|
1208
|
+
type: "navigate",
|
|
1209
|
+
index: wrapCandidate
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
if (this.fallbackToLinear) return resolveLinearMove(current, key === "ArrowDown" ? 1 : -1, context);
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
function doSwitch(orientation, vertical, horizontal) {
|
|
1218
|
+
switch (orientation) {
|
|
1219
|
+
case "vertical": return vertical;
|
|
1220
|
+
case "horizontal": return horizontal;
|
|
1221
|
+
default: return vertical || horizontal;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function isMainOrientationKey(key, orientation) {
|
|
1225
|
+
return doSwitch(orientation, key === "ArrowUp" || key === "ArrowDown", key === "ArrowLeft" || key === "ArrowRight");
|
|
1226
|
+
}
|
|
1227
|
+
function isMainOrientationToEndKey(key, orientation, isRtl) {
|
|
1228
|
+
return doSwitch(orientation, key === "ArrowDown", isRtl ? key === "ArrowLeft" : key === "ArrowRight") || key === "Enter" || key === " " || key === "";
|
|
1229
|
+
}
|
|
1230
|
+
function createNavigationStrategy(orientation, cols, gridLoopDirection) {
|
|
1231
|
+
if (orientation === "vertical") return new VerticalNavigationStrategy();
|
|
1232
|
+
if (orientation === "horizontal") return cols > 1 ? new GridNavigationStrategy(false, gridLoopDirection) : new HorizontalNavigationStrategy();
|
|
1233
|
+
return new GridNavigationStrategy(true, gridLoopDirection);
|
|
1234
|
+
}
|
|
1235
|
+
//#endregion
|
|
1236
|
+
//#region src/composables/interactions/use-list-navigation.ts
|
|
1237
|
+
function useListNavigation(context, options) {
|
|
1238
|
+
const { refs, open, setOpen } = context;
|
|
1239
|
+
const { listRef, activeIndex, onNavigate, enabled = true, loop = false, orientation = "vertical", disabledIndices, focusItemOnHover = true, openOnArrowKeyDown = true, scrollItemIntoView = true, selectedIndex = null, focusItemOnOpen = "auto", nested = false, rtl = false, virtual = false, virtualItemRef, cols = 1, allowEscape = false } = options;
|
|
1240
|
+
const isEnabled = computed(() => toValue(enabled));
|
|
1241
|
+
const anchorEl = computed(() => {
|
|
1242
|
+
const el = refs.anchorEl.value;
|
|
1243
|
+
if (el instanceof HTMLElement) return el;
|
|
1244
|
+
if (el && "contextElement" in el && el.contextElement instanceof HTMLElement) return el.contextElement;
|
|
1245
|
+
return null;
|
|
1246
|
+
});
|
|
1247
|
+
const floatingEl = computed(() => refs.floatingEl.value);
|
|
1248
|
+
const isVirtual = computed(() => !!toValue(virtual));
|
|
1249
|
+
const isRtl = computed(() => !!toValue(rtl));
|
|
1250
|
+
const gridCols = computed(() => Math.max(1, Number(toValue(cols) ?? 1)));
|
|
1251
|
+
const cleanupFns = [];
|
|
1252
|
+
const registerCleanup = (fn) => {
|
|
1253
|
+
cleanupFns.push(fn);
|
|
1254
|
+
return fn;
|
|
1255
|
+
};
|
|
1256
|
+
const runCleanups = () => {
|
|
1257
|
+
while (cleanupFns.length) cleanupFns.pop()?.();
|
|
1258
|
+
};
|
|
1259
|
+
const getActiveIndex = () => activeIndex !== void 0 ? toValue(activeIndex) : null;
|
|
1260
|
+
const isDisabled = (idx) => {
|
|
1261
|
+
if (!disabledIndices) return false;
|
|
1262
|
+
return Array.isArray(disabledIndices) ? disabledIndices.includes(idx) : !!disabledIndices(idx);
|
|
1263
|
+
};
|
|
1264
|
+
const getFirstEnabledIndex = () => {
|
|
1265
|
+
const items = listRef.value;
|
|
1266
|
+
for (let i = 0; i < items.length; i++) if (items[i] && !isDisabled(i)) return i;
|
|
1267
|
+
return null;
|
|
1268
|
+
};
|
|
1269
|
+
const getLastEnabledIndex = () => {
|
|
1270
|
+
const items = listRef.value;
|
|
1271
|
+
for (let i = items.length - 1; i >= 0; i--) if (items[i] && !isDisabled(i)) return i;
|
|
1272
|
+
return null;
|
|
1273
|
+
};
|
|
1274
|
+
const findNextEnabled = (start, dir, wrap) => {
|
|
1275
|
+
const items = listRef.value;
|
|
1276
|
+
const len = items.length;
|
|
1277
|
+
let i = start;
|
|
1278
|
+
for (let step = 0; step < len; step++) {
|
|
1279
|
+
if (i < 0 || i >= len) {
|
|
1280
|
+
if (!wrap) return null;
|
|
1281
|
+
i = (i + len) % len;
|
|
1282
|
+
}
|
|
1283
|
+
if (items[i] && !isDisabled(i)) return i;
|
|
1284
|
+
i += dir;
|
|
1285
|
+
}
|
|
1286
|
+
return null;
|
|
1287
|
+
};
|
|
1288
|
+
const focusItem = (index, forceScroll = false) => {
|
|
1289
|
+
if (index == null) return;
|
|
1290
|
+
const el = listRef.value[index];
|
|
1291
|
+
if (!el) return;
|
|
1292
|
+
if (isVirtual.value) return;
|
|
1293
|
+
el.focus({ preventScroll: true });
|
|
1294
|
+
const opts = scrollItemIntoView;
|
|
1295
|
+
if (!!opts && (forceScroll || isUsingKeyboard.value)) el.scrollIntoView?.(typeof opts === "boolean" ? {
|
|
1296
|
+
block: "nearest",
|
|
1297
|
+
inline: "nearest"
|
|
1298
|
+
} : opts);
|
|
1299
|
+
};
|
|
1300
|
+
let lastKey = null;
|
|
1301
|
+
const handleAnchorKeyDown = (e) => {
|
|
1302
|
+
if (e.defaultPrevented) return;
|
|
1303
|
+
const target = e.target;
|
|
1304
|
+
if (target && isTypeableElement(target) && target !== anchorEl.value) return;
|
|
1305
|
+
const key = e.key;
|
|
1306
|
+
const ori = toValue(orientation);
|
|
1307
|
+
lastKey = key;
|
|
1308
|
+
if (open.value && isVirtual.value) {
|
|
1309
|
+
handleFloatingKeyDown(e);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (!isMainOrientationKey(key, ori)) return;
|
|
1313
|
+
if (open.value || !toValue(openOnArrowKeyDown)) return;
|
|
1314
|
+
e.preventDefault();
|
|
1315
|
+
setOpen(true, "keyboard-activate", e);
|
|
1316
|
+
const initial = (selectedIndex !== void 0 ? toValue(selectedIndex) : null) ?? (isMainOrientationToEndKey(key, ori, isRtl.value) ? getFirstEnabledIndex() : getLastEnabledIndex());
|
|
1317
|
+
if (initial != null) onNavigate?.(initial);
|
|
1318
|
+
};
|
|
1319
|
+
const handleFloatingKeyDown = (e) => {
|
|
1320
|
+
if (e.defaultPrevented) return;
|
|
1321
|
+
const key = e.key;
|
|
1322
|
+
const ori = toValue(orientation);
|
|
1323
|
+
const items = listRef.value;
|
|
1324
|
+
if (!items.length) return;
|
|
1325
|
+
if (!isVirtual.value) {
|
|
1326
|
+
if (key === "Home") {
|
|
1327
|
+
e.preventDefault();
|
|
1328
|
+
const idx = getFirstEnabledIndex();
|
|
1329
|
+
if (idx != null) onNavigate?.(idx);
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (key === "End") {
|
|
1333
|
+
e.preventDefault();
|
|
1334
|
+
const idx = getLastEnabledIndex();
|
|
1335
|
+
if (idx != null) onNavigate?.(idx);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
const strategy = createNavigationStrategy(ori, gridCols.value, toValue(options.gridLoopDirection) ?? "row");
|
|
1340
|
+
const strategyContext = {
|
|
1341
|
+
current: getActiveIndex(),
|
|
1342
|
+
items,
|
|
1343
|
+
isRtl: isRtl.value,
|
|
1344
|
+
loop: !!toValue(loop),
|
|
1345
|
+
allowEscape: !!toValue(allowEscape),
|
|
1346
|
+
isVirtual: isVirtual.value,
|
|
1347
|
+
cols: gridCols.value,
|
|
1348
|
+
nested: !!toValue(nested),
|
|
1349
|
+
isDisabled,
|
|
1350
|
+
findNextEnabled,
|
|
1351
|
+
getFirstEnabledIndex,
|
|
1352
|
+
getLastEnabledIndex
|
|
1353
|
+
};
|
|
1354
|
+
const result = strategy.handleKey(key, strategyContext);
|
|
1355
|
+
if (!result) return;
|
|
1356
|
+
e.preventDefault();
|
|
1357
|
+
if (result.type === "navigate") onNavigate?.(result.index);
|
|
1358
|
+
else if (result.type === "close") setOpen(false, "programmatic", e);
|
|
1359
|
+
};
|
|
1360
|
+
registerCleanup(watch([open, computed(() => getActiveIndex())], ([isOpen, idx]) => {
|
|
1361
|
+
if (!isEnabled.value || !isOpen || idx == null) return;
|
|
1362
|
+
focusItem(idx);
|
|
1363
|
+
}, { flush: "post" }));
|
|
1364
|
+
registerCleanup(useEventListener(() => isEnabled.value ? anchorEl.value : null, "keydown", handleAnchorKeyDown));
|
|
1365
|
+
registerCleanup(useEventListener(() => isEnabled.value ? floatingEl.value : null, "keydown", handleFloatingKeyDown));
|
|
1366
|
+
registerCleanup(watchPostEffect(() => {
|
|
1367
|
+
if (!isEnabled.value || !toValue(focusItemOnHover)) return;
|
|
1368
|
+
const container = floatingEl.value;
|
|
1369
|
+
if (!container) return;
|
|
1370
|
+
let lastX = null;
|
|
1371
|
+
let lastY = null;
|
|
1372
|
+
const onMove = (evt) => {
|
|
1373
|
+
if (lastX === evt.clientX && lastY === evt.clientY) return;
|
|
1374
|
+
lastX = evt.clientX;
|
|
1375
|
+
lastY = evt.clientY;
|
|
1376
|
+
const target = evt.target;
|
|
1377
|
+
if (!target) return;
|
|
1378
|
+
const idx = findItemIndexFromTarget(target);
|
|
1379
|
+
if (idx >= 0) onNavigate?.(idx);
|
|
1380
|
+
};
|
|
1381
|
+
container.addEventListener("mousemove", onMove);
|
|
1382
|
+
onWatcherCleanup(() => container.removeEventListener("mousemove", onMove));
|
|
1383
|
+
}));
|
|
1384
|
+
const prevOpen = ref(false);
|
|
1385
|
+
registerCleanup(watch(() => open.value, (isOpen) => {
|
|
1386
|
+
if (!isEnabled.value) return;
|
|
1387
|
+
if (isOpen && !prevOpen.value) {
|
|
1388
|
+
const f = toValue(focusItemOnOpen);
|
|
1389
|
+
if (f === true || f === "auto" && lastKey != null) {
|
|
1390
|
+
const idx = (selectedIndex !== void 0 ? toValue(selectedIndex) : null) ?? (lastKey && isMainOrientationToEndKey(lastKey, toValue(orientation), isRtl.value) ? getFirstEnabledIndex() : getLastEnabledIndex());
|
|
1391
|
+
if (idx != null) {
|
|
1392
|
+
onNavigate?.(idx);
|
|
1393
|
+
focusItem(idx, true);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
prevOpen.value = isOpen;
|
|
1398
|
+
}, {
|
|
1399
|
+
flush: "post",
|
|
1400
|
+
immediate: true
|
|
1401
|
+
}));
|
|
1402
|
+
const findItemIndexFromTarget = (target) => {
|
|
1403
|
+
const items = listRef.value;
|
|
1404
|
+
for (let i = 0; i < items.length; i++) {
|
|
1405
|
+
const el = items[i];
|
|
1406
|
+
if (el && (el === target || el.contains(target))) return i;
|
|
1407
|
+
}
|
|
1408
|
+
return -1;
|
|
1409
|
+
};
|
|
1410
|
+
const { activeItem } = useActiveDescendant(anchorEl, listRef, computed(() => getActiveIndex()), {
|
|
1411
|
+
virtual,
|
|
1412
|
+
open
|
|
1413
|
+
});
|
|
1414
|
+
if (virtualItemRef) registerCleanup(watch(activeItem, (item) => {
|
|
1415
|
+
virtualItemRef.value = item;
|
|
1416
|
+
}, { flush: "post" }));
|
|
1417
|
+
return { cleanup: runCleanups };
|
|
1418
|
+
}
|
|
1419
|
+
//#endregion
|
|
1420
|
+
//#region src/composables/middlewares/arrow.ts
|
|
1421
|
+
/**
|
|
1422
|
+
* Positions an inner element of the floating element such that it is centered to the anchor element.
|
|
1423
|
+
*
|
|
1424
|
+
* This middleware is used to position arrow elements within floating elements.
|
|
1425
|
+
*
|
|
1426
|
+
* @param options - The arrow options including padding and element reference
|
|
1427
|
+
* @returns A middleware function for arrow positioning
|
|
1428
|
+
*/
|
|
1429
|
+
function arrow(options) {
|
|
1430
|
+
return {
|
|
1431
|
+
name: "arrow",
|
|
1432
|
+
options,
|
|
1433
|
+
fn(args) {
|
|
1434
|
+
const element = toValue(options.element);
|
|
1435
|
+
if (element == null) return {};
|
|
1436
|
+
return arrow$1({
|
|
1437
|
+
element,
|
|
1438
|
+
padding: options.padding
|
|
1439
|
+
}).fn(args);
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
//#endregion
|
|
1444
|
+
//#region src/core/floating-internals.ts
|
|
1445
|
+
const floatingInternals = /* @__PURE__ */ new WeakMap();
|
|
1446
|
+
function setFloatingInternals(target, internals) {
|
|
1447
|
+
floatingInternals.set(target, internals);
|
|
1448
|
+
}
|
|
1449
|
+
function getFloatingInternals(target) {
|
|
1450
|
+
return floatingInternals.get(target);
|
|
1451
|
+
}
|
|
1452
|
+
//#endregion
|
|
1453
|
+
//#region src/composables/positioning/use-arrow.ts
|
|
1454
|
+
function useArrow(contextOrArrow, contextOrOptions, maybeOptions = {}) {
|
|
1455
|
+
const { context, arrowEl, options } = normalizeArrowArgs(contextOrArrow, contextOrOptions, maybeOptions);
|
|
1456
|
+
const { refs } = context;
|
|
1457
|
+
const { middlewareData, placement } = getFloatingPosition(context);
|
|
1458
|
+
const { offset = "-4px" } = options;
|
|
1459
|
+
watch(arrowEl, (element) => {
|
|
1460
|
+
refs.setArrow(element);
|
|
1461
|
+
}, { immediate: true });
|
|
1462
|
+
getFloatingInternals(context)?.middlewareRegistry.register(computed(() => {
|
|
1463
|
+
if (!arrowEl.value) return null;
|
|
1464
|
+
return arrow({ element: arrowEl });
|
|
1465
|
+
}));
|
|
1466
|
+
const arrowX = computed(() => middlewareData.value.arrow?.x ?? 0);
|
|
1467
|
+
const arrowY = computed(() => middlewareData.value.arrow?.y ?? 0);
|
|
1468
|
+
return {
|
|
1469
|
+
arrowX,
|
|
1470
|
+
arrowY,
|
|
1471
|
+
arrowStyles: computed(() => {
|
|
1472
|
+
if (!(arrowEl.value || refs.arrowEl.value) || !middlewareData.value.arrow) return {};
|
|
1473
|
+
const side = toValue(placement).split("-")[0];
|
|
1474
|
+
if (side === "bottom") return {
|
|
1475
|
+
"inset-inline-start": `${arrowX.value}px`,
|
|
1476
|
+
"inset-block-start": offset
|
|
1477
|
+
};
|
|
1478
|
+
if (side === "top") return {
|
|
1479
|
+
"inset-inline-start": `${arrowX.value}px`,
|
|
1480
|
+
"inset-block-end": offset
|
|
1481
|
+
};
|
|
1482
|
+
if (side === "right") return {
|
|
1483
|
+
"inset-block-start": `${arrowY.value}px`,
|
|
1484
|
+
"inset-inline-start": offset
|
|
1485
|
+
};
|
|
1486
|
+
return {
|
|
1487
|
+
"inset-block-start": `${arrowY.value}px`,
|
|
1488
|
+
"inset-inline-end": offset
|
|
1489
|
+
};
|
|
1490
|
+
})
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function normalizeArrowArgs(contextOrArrow, contextOrOptions, maybeOptions = {}) {
|
|
1494
|
+
if (isFloatingContext(contextOrArrow)) {
|
|
1495
|
+
const context = contextOrArrow;
|
|
1496
|
+
const options = contextOrOptions ?? {};
|
|
1497
|
+
if (!options.element) throw new Error("[useArrow] `options.element` is required when calling useArrow(context, options).");
|
|
1498
|
+
getFloatingRefs(context);
|
|
1499
|
+
return {
|
|
1500
|
+
context,
|
|
1501
|
+
arrowEl: options.element,
|
|
1502
|
+
options
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
context: contextOrOptions,
|
|
1507
|
+
arrowEl: contextOrArrow,
|
|
1508
|
+
options: maybeOptions
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function isFloatingContext(value) {
|
|
1512
|
+
return typeof value === "object" && value !== null && "refs" in value;
|
|
1513
|
+
}
|
|
1514
|
+
//#endregion
|
|
1515
|
+
//#region src/composables/positioning/client-point/tracking-strategies.ts
|
|
1516
|
+
var TrackingStrategy = class {
|
|
1517
|
+
lastKnownCoordinates = null;
|
|
1518
|
+
getCoordinatesForOpening() {
|
|
1519
|
+
return this.lastKnownCoordinates;
|
|
1520
|
+
}
|
|
1521
|
+
onClose() {
|
|
1522
|
+
this.lastKnownCoordinates = null;
|
|
1523
|
+
}
|
|
1524
|
+
reset() {
|
|
1525
|
+
this.lastKnownCoordinates = null;
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
var FollowTracker = class extends TrackingStrategy {
|
|
1529
|
+
name = "follow";
|
|
1530
|
+
getRequiredEvents() {
|
|
1531
|
+
return [
|
|
1532
|
+
"pointerdown",
|
|
1533
|
+
"pointermove",
|
|
1534
|
+
"pointerenter"
|
|
1535
|
+
];
|
|
1536
|
+
}
|
|
1537
|
+
process(event, context) {
|
|
1538
|
+
const coordinates = event.coordinates;
|
|
1539
|
+
this.lastKnownCoordinates = coordinates;
|
|
1540
|
+
switch (event.type) {
|
|
1541
|
+
case "pointerdown": return coordinates;
|
|
1542
|
+
case "pointermove":
|
|
1543
|
+
if (context.isOpen && isMouseLikePointerType(event.originalEvent.pointerType, true)) return coordinates;
|
|
1544
|
+
return null;
|
|
1545
|
+
case "pointerenter": return coordinates;
|
|
1546
|
+
default: return null;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
var StaticTracker = class extends TrackingStrategy {
|
|
1551
|
+
name = "static";
|
|
1552
|
+
triggerCoordinates = null;
|
|
1553
|
+
getRequiredEvents() {
|
|
1554
|
+
return ["pointerdown", "pointermove"];
|
|
1555
|
+
}
|
|
1556
|
+
process(event, context) {
|
|
1557
|
+
const coordinates = event.coordinates;
|
|
1558
|
+
this.lastKnownCoordinates = coordinates;
|
|
1559
|
+
if (event.type === "pointerdown") {
|
|
1560
|
+
this.triggerCoordinates = coordinates;
|
|
1561
|
+
return context.isOpen ? coordinates : null;
|
|
1562
|
+
}
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
getCoordinatesForOpening() {
|
|
1566
|
+
if (this.triggerCoordinates) return this.triggerCoordinates;
|
|
1567
|
+
return this.lastKnownCoordinates;
|
|
1568
|
+
}
|
|
1569
|
+
reset() {
|
|
1570
|
+
super.reset();
|
|
1571
|
+
this.triggerCoordinates = null;
|
|
1572
|
+
}
|
|
1573
|
+
onClose() {
|
|
1574
|
+
this.triggerCoordinates = null;
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
//#endregion
|
|
1578
|
+
//#region src/composables/positioning/client-point/virtual-element-factory.ts
|
|
1579
|
+
var VirtualElementFactory = class VirtualElementFactory {
|
|
1580
|
+
static DEFAULT_DIMENSIONS = {
|
|
1581
|
+
width: 100,
|
|
1582
|
+
height: 30
|
|
1583
|
+
};
|
|
1584
|
+
create(options) {
|
|
1585
|
+
const config = this.buildConfiguration(options);
|
|
1586
|
+
return {
|
|
1587
|
+
contextElement: config.referenceElement || void 0,
|
|
1588
|
+
getBoundingClientRect: () => this.buildBoundingRect(config)
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
buildConfiguration(options) {
|
|
1592
|
+
return {
|
|
1593
|
+
coordinates: options.coordinates,
|
|
1594
|
+
referenceElement: options.referenceElement ?? null,
|
|
1595
|
+
baselineCoordinates: options.baselineCoordinates ?? null,
|
|
1596
|
+
axis: options.axis ?? "both"
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
buildBoundingRect(config) {
|
|
1600
|
+
const referenceRect = this.getReferenceRect(config.referenceElement);
|
|
1601
|
+
const position = this.resolvePosition(config, referenceRect);
|
|
1602
|
+
const size = this.calculateSize(config.axis, referenceRect);
|
|
1603
|
+
return this.buildDOMRect({
|
|
1604
|
+
x: position.x,
|
|
1605
|
+
y: position.y,
|
|
1606
|
+
width: size.width,
|
|
1607
|
+
height: size.height
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
getReferenceRect(element) {
|
|
1611
|
+
if (element) try {
|
|
1612
|
+
return element.getBoundingClientRect();
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
if (import.meta.env.DEV) console.warn("VirtualElementFactory: Failed to get element bounds", {
|
|
1615
|
+
element,
|
|
1616
|
+
error
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
return this.buildDOMRect({
|
|
1620
|
+
x: 0,
|
|
1621
|
+
y: 0,
|
|
1622
|
+
width: VirtualElementFactory.DEFAULT_DIMENSIONS.width,
|
|
1623
|
+
height: VirtualElementFactory.DEFAULT_DIMENSIONS.height
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
resolvePosition(config, referenceRect) {
|
|
1627
|
+
return {
|
|
1628
|
+
x: this.resolveAxisCoordinate({
|
|
1629
|
+
current: config.coordinates.x,
|
|
1630
|
+
baseline: config.baselineCoordinates?.x ?? null,
|
|
1631
|
+
fallback: referenceRect.x,
|
|
1632
|
+
isAxisEnabled: config.axis === "x" || config.axis === "both"
|
|
1633
|
+
}),
|
|
1634
|
+
y: this.resolveAxisCoordinate({
|
|
1635
|
+
current: config.coordinates.y,
|
|
1636
|
+
baseline: config.baselineCoordinates?.y ?? null,
|
|
1637
|
+
fallback: referenceRect.y,
|
|
1638
|
+
isAxisEnabled: config.axis === "y" || config.axis === "both"
|
|
1639
|
+
})
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
resolveAxisCoordinate(sources) {
|
|
1643
|
+
const { current, baseline, fallback, isAxisEnabled } = sources;
|
|
1644
|
+
if (isAxisEnabled && current !== null) return current;
|
|
1645
|
+
if (baseline !== null) return baseline;
|
|
1646
|
+
return fallback;
|
|
1647
|
+
}
|
|
1648
|
+
calculateSize(axis, referenceRect) {
|
|
1649
|
+
const ensurePositive = (value, fallback) => Math.max(0, value || fallback);
|
|
1650
|
+
switch (axis) {
|
|
1651
|
+
case "both": return {
|
|
1652
|
+
width: 0,
|
|
1653
|
+
height: 0
|
|
1654
|
+
};
|
|
1655
|
+
case "x": return {
|
|
1656
|
+
width: ensurePositive(referenceRect.width, VirtualElementFactory.DEFAULT_DIMENSIONS.width),
|
|
1657
|
+
height: 0
|
|
1658
|
+
};
|
|
1659
|
+
case "y": return {
|
|
1660
|
+
width: 0,
|
|
1661
|
+
height: ensurePositive(referenceRect.height, VirtualElementFactory.DEFAULT_DIMENSIONS.height)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
buildDOMRect(rect) {
|
|
1666
|
+
const { x, y, width, height } = rect;
|
|
1667
|
+
const safeWidth = Math.max(0, width);
|
|
1668
|
+
const safeHeight = Math.max(0, height);
|
|
1669
|
+
return {
|
|
1670
|
+
x,
|
|
1671
|
+
y,
|
|
1672
|
+
width: safeWidth,
|
|
1673
|
+
height: safeHeight,
|
|
1674
|
+
top: y,
|
|
1675
|
+
right: x + safeWidth,
|
|
1676
|
+
bottom: y + safeHeight,
|
|
1677
|
+
left: x,
|
|
1678
|
+
toJSON: () => ({
|
|
1679
|
+
x,
|
|
1680
|
+
y,
|
|
1681
|
+
width: safeWidth,
|
|
1682
|
+
height: safeHeight
|
|
1683
|
+
})
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
//#endregion
|
|
1688
|
+
//#region src/composables/positioning/use-client-point.ts
|
|
1689
|
+
const sanitizeCoordinate = (value) => {
|
|
1690
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1691
|
+
};
|
|
1692
|
+
function useClientPoint(contextOrPointerTarget, contextOrOptions = {}, maybeOptions = {}) {
|
|
1693
|
+
const { pointerTarget, context, options } = normalizeClientPointArgs(contextOrPointerTarget, contextOrOptions, maybeOptions);
|
|
1694
|
+
const { open } = getFloatingState(context);
|
|
1695
|
+
const refs = context.refs;
|
|
1696
|
+
const update = "position" in context && context.position ? context.position.update : context.update;
|
|
1697
|
+
const virtualElementFactory = new VirtualElementFactory();
|
|
1698
|
+
const internalCoordinates = ref({
|
|
1699
|
+
x: null,
|
|
1700
|
+
y: null
|
|
1701
|
+
});
|
|
1702
|
+
const lockedCoordinates = ref(null);
|
|
1703
|
+
const axis = computed(() => toValue(options.axis ?? "both"));
|
|
1704
|
+
const enabled = computed(() => toValue(options.enabled ?? true));
|
|
1705
|
+
const externalX = computed(() => sanitizeCoordinate(toValue(options.x ?? null)));
|
|
1706
|
+
const externalY = computed(() => sanitizeCoordinate(toValue(options.y ?? null)));
|
|
1707
|
+
const isExternallyControlled = computed(() => externalX.value !== null && externalY.value !== null);
|
|
1708
|
+
const coordinates = computed(() => {
|
|
1709
|
+
if (isExternallyControlled.value) return {
|
|
1710
|
+
x: externalX.value,
|
|
1711
|
+
y: externalY.value
|
|
1712
|
+
};
|
|
1713
|
+
return internalCoordinates.value;
|
|
1714
|
+
});
|
|
1715
|
+
const constrainedCoordinates = computed(() => {
|
|
1716
|
+
const coords = coordinates.value;
|
|
1717
|
+
switch (axis.value) {
|
|
1718
|
+
case "x": return {
|
|
1719
|
+
x: coords.x,
|
|
1720
|
+
y: null
|
|
1721
|
+
};
|
|
1722
|
+
case "y": return {
|
|
1723
|
+
x: null,
|
|
1724
|
+
y: coords.y
|
|
1725
|
+
};
|
|
1726
|
+
case "both": return coords;
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
const trackingStrategy = createTrackingStrategy(options.trackingMode ?? "follow");
|
|
1730
|
+
const setCoordinates = (x, y) => {
|
|
1731
|
+
if (isExternallyControlled.value) return;
|
|
1732
|
+
internalCoordinates.value = {
|
|
1733
|
+
x: sanitizeCoordinate(x),
|
|
1734
|
+
y: sanitizeCoordinate(y)
|
|
1735
|
+
};
|
|
1736
|
+
};
|
|
1737
|
+
const resetCoordinates = () => {
|
|
1738
|
+
if (!isExternallyControlled.value) internalCoordinates.value = {
|
|
1739
|
+
x: null,
|
|
1740
|
+
y: null
|
|
1741
|
+
};
|
|
1742
|
+
};
|
|
1743
|
+
const processPointerEvent = (event, type) => {
|
|
1744
|
+
const nextCoordinates = trackingStrategy.process({
|
|
1745
|
+
type,
|
|
1746
|
+
coordinates: {
|
|
1747
|
+
x: event.clientX,
|
|
1748
|
+
y: event.clientY
|
|
1749
|
+
},
|
|
1750
|
+
originalEvent: event
|
|
1751
|
+
}, { isOpen: open.value });
|
|
1752
|
+
if (nextCoordinates) setCoordinates(nextCoordinates.x, nextCoordinates.y);
|
|
1753
|
+
};
|
|
1754
|
+
watch([
|
|
1755
|
+
constrainedCoordinates,
|
|
1756
|
+
lockedCoordinates,
|
|
1757
|
+
axis,
|
|
1758
|
+
pointerTarget
|
|
1759
|
+
], () => {
|
|
1760
|
+
refs.anchorEl.value = virtualElementFactory.create({
|
|
1761
|
+
coordinates: constrainedCoordinates.value,
|
|
1762
|
+
referenceElement: pointerTarget.value,
|
|
1763
|
+
baselineCoordinates: lockedCoordinates.value,
|
|
1764
|
+
axis: axis.value
|
|
1765
|
+
});
|
|
1766
|
+
if (open.value) update?.();
|
|
1767
|
+
}, { immediate: true });
|
|
1768
|
+
watch(open, (isOpen) => {
|
|
1769
|
+
if (!enabled.value || isExternallyControlled.value) return;
|
|
1770
|
+
if (isOpen) {
|
|
1771
|
+
const openingCoordinates = trackingStrategy.getCoordinatesForOpening();
|
|
1772
|
+
if (openingCoordinates) {
|
|
1773
|
+
setCoordinates(openingCoordinates.x, openingCoordinates.y);
|
|
1774
|
+
lockedCoordinates.value = { ...openingCoordinates };
|
|
1775
|
+
} else lockedCoordinates.value = { ...internalCoordinates.value };
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
trackingStrategy.onClose();
|
|
1779
|
+
resetCoordinates();
|
|
1780
|
+
lockedCoordinates.value = null;
|
|
1781
|
+
});
|
|
1782
|
+
const handlers = {
|
|
1783
|
+
pointerdown: (event) => processPointerEvent(event, "pointerdown"),
|
|
1784
|
+
pointerenter: (event) => processPointerEvent(event, "pointerenter"),
|
|
1785
|
+
pointermove: (event) => processPointerEvent(event, "pointermove")
|
|
1786
|
+
};
|
|
1787
|
+
watchEffect((onCleanup) => {
|
|
1788
|
+
if (isExternallyControlled.value || !enabled.value) return;
|
|
1789
|
+
const target = pointerTarget.value;
|
|
1790
|
+
if (!target) return;
|
|
1791
|
+
const requiredEvents = trackingStrategy.getRequiredEvents();
|
|
1792
|
+
for (const eventType of requiredEvents) target.addEventListener(eventType, handlers[eventType]);
|
|
1793
|
+
onCleanup(() => {
|
|
1794
|
+
for (const eventType of requiredEvents) target.removeEventListener(eventType, handlers[eventType]);
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
return {
|
|
1798
|
+
coordinates: readonly(constrainedCoordinates),
|
|
1799
|
+
updatePosition: (x, y) => setCoordinates(x, y)
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function createTrackingStrategy(trackingMode) {
|
|
1803
|
+
return trackingMode === "follow" ? new FollowTracker() : new StaticTracker();
|
|
1804
|
+
}
|
|
1805
|
+
function normalizeClientPointArgs(contextOrPointerTarget, contextOrOptions, maybeOptions) {
|
|
1806
|
+
if (isClientPointContext(contextOrPointerTarget)) {
|
|
1807
|
+
const context = contextOrPointerTarget;
|
|
1808
|
+
const options = contextOrOptions;
|
|
1809
|
+
if (!options.pointerTarget) throw new Error("[useClientPoint] `options.pointerTarget` is required when calling useClientPoint(context, options).");
|
|
1810
|
+
return {
|
|
1811
|
+
context,
|
|
1812
|
+
pointerTarget: options.pointerTarget,
|
|
1813
|
+
options
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
return {
|
|
1817
|
+
pointerTarget: contextOrPointerTarget,
|
|
1818
|
+
context: contextOrOptions,
|
|
1819
|
+
options: maybeOptions
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function isClientPointContext(value) {
|
|
1823
|
+
return typeof value === "object" && value !== null && "refs" in value;
|
|
1824
|
+
}
|
|
1825
|
+
//#endregion
|
|
1826
|
+
//#region src/core/elements.ts
|
|
1827
|
+
function createRefSetter(target) {
|
|
1828
|
+
return (value) => {
|
|
1829
|
+
target.value = value;
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
//#endregion
|
|
1833
|
+
//#region src/composables/positioning/internal/open-state.ts
|
|
1834
|
+
function createOpenStateController(options = {}) {
|
|
1835
|
+
const open = options.open ?? ref(false);
|
|
1836
|
+
const setOpen = (value, reason, event) => {
|
|
1837
|
+
if (open.value === value) return;
|
|
1838
|
+
open.value = value;
|
|
1839
|
+
options.onOpenChange?.(value, reason ?? "programmatic", event);
|
|
1840
|
+
};
|
|
1841
|
+
return {
|
|
1842
|
+
open,
|
|
1843
|
+
setOpen
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
//#endregion
|
|
1847
|
+
//#region src/composables/positioning/internal/middleware-registry.ts
|
|
1848
|
+
function createMiddlewareRegistry(baseMiddlewares) {
|
|
1849
|
+
const registrations = ref([]);
|
|
1850
|
+
let nextRegistrationId = 0;
|
|
1851
|
+
const middlewares = computed(() => {
|
|
1852
|
+
const merged = [...toValue(baseMiddlewares) ?? []];
|
|
1853
|
+
for (const registration of registrations.value) {
|
|
1854
|
+
const middleware = toValue(registration.middleware);
|
|
1855
|
+
if (!middleware) continue;
|
|
1856
|
+
const existingIndex = merged.findIndex((candidate) => candidate.name === middleware.name);
|
|
1857
|
+
if (existingIndex === -1) merged.push(middleware);
|
|
1858
|
+
else merged[existingIndex] = middleware;
|
|
1859
|
+
}
|
|
1860
|
+
return merged;
|
|
1861
|
+
});
|
|
1862
|
+
const register = (middleware) => {
|
|
1863
|
+
const registration = {
|
|
1864
|
+
id: nextRegistrationId++,
|
|
1865
|
+
middleware
|
|
1866
|
+
};
|
|
1867
|
+
registrations.value = [...registrations.value, registration];
|
|
1868
|
+
const unregister = () => {
|
|
1869
|
+
registrations.value = registrations.value.filter((candidate) => candidate.id !== registration.id);
|
|
1870
|
+
};
|
|
1871
|
+
tryOnScopeDispose(unregister);
|
|
1872
|
+
return unregister;
|
|
1873
|
+
};
|
|
1874
|
+
return {
|
|
1875
|
+
middlewares,
|
|
1876
|
+
register
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region src/composables/positioning/internal/position-controller.ts
|
|
1881
|
+
function createPositionController(options) {
|
|
1882
|
+
const { anchorEl, floatingEl, open, transform = true, middlewares = [], autoUpdate: autoUpdateOptions = true } = options;
|
|
1883
|
+
const preferredPlacement = computed(() => toValue(options.placement) ?? "bottom");
|
|
1884
|
+
const preferredStrategy = computed(() => toValue(options.strategy) ?? "absolute");
|
|
1885
|
+
const middlewareRegistry = createMiddlewareRegistry(middlewares);
|
|
1886
|
+
const x = ref(0);
|
|
1887
|
+
const y = ref(0);
|
|
1888
|
+
const placement = ref(preferredPlacement.value);
|
|
1889
|
+
const strategy = ref(preferredStrategy.value);
|
|
1890
|
+
const middlewareData = shallowRef({});
|
|
1891
|
+
const isPositioned = ref(false);
|
|
1892
|
+
const update = async () => {
|
|
1893
|
+
if (!anchorEl.value || !floatingEl.value) return;
|
|
1894
|
+
try {
|
|
1895
|
+
const result = await computePosition(anchorEl.value, floatingEl.value, {
|
|
1896
|
+
placement: preferredPlacement.value,
|
|
1897
|
+
strategy: preferredStrategy.value,
|
|
1898
|
+
middleware: middlewareRegistry.middlewares.value
|
|
1899
|
+
});
|
|
1900
|
+
x.value = result.x;
|
|
1901
|
+
y.value = result.y;
|
|
1902
|
+
placement.value = result.placement;
|
|
1903
|
+
strategy.value = result.strategy;
|
|
1904
|
+
middlewareData.value = result.middlewareData;
|
|
1905
|
+
isPositioned.value = open.value;
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
if (import.meta.env.DEV) console.error("[useFloating] Failed to compute position:", error);
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
watch([
|
|
1911
|
+
preferredPlacement,
|
|
1912
|
+
preferredStrategy,
|
|
1913
|
+
middlewareRegistry.middlewares
|
|
1914
|
+
], () => {
|
|
1915
|
+
if (open.value) update();
|
|
1916
|
+
});
|
|
1917
|
+
watch([
|
|
1918
|
+
anchorEl,
|
|
1919
|
+
floatingEl,
|
|
1920
|
+
open
|
|
1921
|
+
], ([currentAnchor, currentFloating, isOpen], _oldValue, onCleanup) => {
|
|
1922
|
+
if (!isOpen || !currentAnchor || !currentFloating) return;
|
|
1923
|
+
update();
|
|
1924
|
+
if (!autoUpdateOptions) return;
|
|
1925
|
+
onCleanup(autoUpdate(currentAnchor, currentFloating, update, typeof autoUpdateOptions === "object" ? autoUpdateOptions : void 0));
|
|
1926
|
+
}, { immediate: true });
|
|
1927
|
+
watch(open, (isOpen) => {
|
|
1928
|
+
if (!isOpen) isPositioned.value = false;
|
|
1929
|
+
});
|
|
1930
|
+
const styles = computed(() => {
|
|
1931
|
+
const floatingElement = floatingEl.value;
|
|
1932
|
+
const baseStyles = {
|
|
1933
|
+
position: strategy.value,
|
|
1934
|
+
left: "0",
|
|
1935
|
+
top: "0"
|
|
1936
|
+
};
|
|
1937
|
+
if (!floatingElement) return baseStyles;
|
|
1938
|
+
const resolvedTransform = toValue(transform);
|
|
1939
|
+
const roundedX = roundByDPR(floatingElement, x.value);
|
|
1940
|
+
const roundedY = roundByDPR(floatingElement, y.value);
|
|
1941
|
+
if (!resolvedTransform) return {
|
|
1942
|
+
...baseStyles,
|
|
1943
|
+
left: `${roundedX}px`,
|
|
1944
|
+
top: `${roundedY}px`
|
|
1945
|
+
};
|
|
1946
|
+
return {
|
|
1947
|
+
...baseStyles,
|
|
1948
|
+
transform: `translate(${roundedX}px, ${roundedY}px)`,
|
|
1949
|
+
...getDPR(floatingElement) >= 1.5 ? { "will-change": "transform" } : {}
|
|
1950
|
+
};
|
|
1951
|
+
});
|
|
1952
|
+
tryOnScopeDispose(() => {
|
|
1953
|
+
isPositioned.value = false;
|
|
1954
|
+
});
|
|
1955
|
+
return {
|
|
1956
|
+
middlewareRegistry,
|
|
1957
|
+
position: {
|
|
1958
|
+
x,
|
|
1959
|
+
y,
|
|
1960
|
+
strategy,
|
|
1961
|
+
placement,
|
|
1962
|
+
middlewareData,
|
|
1963
|
+
isPositioned,
|
|
1964
|
+
styles,
|
|
1965
|
+
update
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function roundByDPR(el, value) {
|
|
1970
|
+
const dpr = getDPR(el);
|
|
1971
|
+
return Math.round(value * dpr) / dpr;
|
|
1972
|
+
}
|
|
1973
|
+
function getDPR(el) {
|
|
1974
|
+
if (typeof window === "undefined") return 1;
|
|
1975
|
+
return (el.ownerDocument.defaultView || window).devicePixelRatio || 1;
|
|
1976
|
+
}
|
|
1977
|
+
//#endregion
|
|
1978
|
+
//#region src/composables/positioning/use-floating.ts
|
|
1979
|
+
function useFloating(anchorEl, floatingEl, options = {}) {
|
|
1980
|
+
const stateController = createOpenStateController({
|
|
1981
|
+
open: options.open,
|
|
1982
|
+
onOpenChange: options.onOpenChange
|
|
1983
|
+
});
|
|
1984
|
+
const arrowEl = ref(null);
|
|
1985
|
+
const refs = {
|
|
1986
|
+
anchorEl,
|
|
1987
|
+
floatingEl,
|
|
1988
|
+
arrowEl,
|
|
1989
|
+
setAnchor: createRefSetter(anchorEl),
|
|
1990
|
+
setFloating: createRefSetter(floatingEl),
|
|
1991
|
+
setArrow: createRefSetter(arrowEl)
|
|
1992
|
+
};
|
|
1993
|
+
const positionController = createPositionController({
|
|
1994
|
+
anchorEl,
|
|
1995
|
+
floatingEl,
|
|
1996
|
+
open: stateController.open,
|
|
1997
|
+
placement: options.placement,
|
|
1998
|
+
strategy: options.strategy,
|
|
1999
|
+
transform: options.transform,
|
|
2000
|
+
middlewares: options.middlewares,
|
|
2001
|
+
autoUpdate: options.autoUpdate
|
|
2002
|
+
});
|
|
2003
|
+
const state = {
|
|
2004
|
+
open: stateController.open,
|
|
2005
|
+
setOpen: stateController.setOpen
|
|
2006
|
+
};
|
|
2007
|
+
const position = {
|
|
2008
|
+
x: positionController.position.x,
|
|
2009
|
+
y: positionController.position.y,
|
|
2010
|
+
strategy: positionController.position.strategy,
|
|
2011
|
+
placement: positionController.position.placement,
|
|
2012
|
+
middlewareData: positionController.position.middlewareData,
|
|
2013
|
+
isPositioned: positionController.position.isPositioned,
|
|
2014
|
+
styles: positionController.position.styles,
|
|
2015
|
+
update: positionController.position.update
|
|
2016
|
+
};
|
|
2017
|
+
const context = {
|
|
2018
|
+
refs,
|
|
2019
|
+
state,
|
|
2020
|
+
position,
|
|
2021
|
+
open: state.open,
|
|
2022
|
+
setOpen: state.setOpen,
|
|
2023
|
+
x: position.x,
|
|
2024
|
+
y: position.y,
|
|
2025
|
+
strategy: position.strategy,
|
|
2026
|
+
placement: position.placement,
|
|
2027
|
+
middlewareData: position.middlewareData,
|
|
2028
|
+
isPositioned: position.isPositioned,
|
|
2029
|
+
floatingStyles: position.styles,
|
|
2030
|
+
update: position.update
|
|
2031
|
+
};
|
|
2032
|
+
setFloatingInternals(context, { middlewareRegistry: positionController.middlewareRegistry });
|
|
2033
|
+
return context;
|
|
2034
|
+
}
|
|
2035
|
+
//#endregion
|
|
2036
|
+
export { FollowTracker, StaticTracker, TrackingStrategy, VirtualElementFactory, arrow, autoPlacement, flip, hide, offset, shift, size, useArrow, useClick, useClientPoint, useEscapeKey, useFloating, useFocus, useFocusTrap, useHover, useListNavigation };
|
|
2037
|
+
|
|
2038
|
+
//# sourceMappingURL=index.mjs.map
|