v-float 0.9.1 → 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.
Files changed (52) hide show
  1. package/README.md +44 -175
  2. package/dist/index.d.mts +688 -0
  3. package/dist/index.d.mts.map +1 -0
  4. package/dist/index.mjs +2038 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +74 -74
  7. package/dist/composables/index.d.ts +0 -4
  8. package/dist/composables/index.d.ts.map +0 -1
  9. package/dist/composables/interactions/index.d.ts +0 -7
  10. package/dist/composables/interactions/index.d.ts.map +0 -1
  11. package/dist/composables/interactions/navigation-strategies.d.ts +0 -36
  12. package/dist/composables/interactions/navigation-strategies.d.ts.map +0 -1
  13. package/dist/composables/interactions/polygon.d.ts +0 -38
  14. package/dist/composables/interactions/polygon.d.ts.map +0 -1
  15. package/dist/composables/interactions/use-click.d.ts +0 -120
  16. package/dist/composables/interactions/use-click.d.ts.map +0 -1
  17. package/dist/composables/interactions/use-escape-key.d.ts +0 -57
  18. package/dist/composables/interactions/use-escape-key.d.ts.map +0 -1
  19. package/dist/composables/interactions/use-focus-trap.d.ts +0 -41
  20. package/dist/composables/interactions/use-focus-trap.d.ts.map +0 -1
  21. package/dist/composables/interactions/use-focus.d.ts +0 -60
  22. package/dist/composables/interactions/use-focus.d.ts.map +0 -1
  23. package/dist/composables/interactions/use-hover.d.ts +0 -78
  24. package/dist/composables/interactions/use-hover.d.ts.map +0 -1
  25. package/dist/composables/interactions/use-list-navigation.d.ts +0 -109
  26. package/dist/composables/interactions/use-list-navigation.d.ts.map +0 -1
  27. package/dist/composables/middlewares/arrow.d.ts +0 -25
  28. package/dist/composables/middlewares/arrow.d.ts.map +0 -1
  29. package/dist/composables/middlewares/index.d.ts +0 -4
  30. package/dist/composables/middlewares/index.d.ts.map +0 -1
  31. package/dist/composables/positioning/index.d.ts +0 -5
  32. package/dist/composables/positioning/index.d.ts.map +0 -1
  33. package/dist/composables/positioning/use-arrow.d.ts +0 -55
  34. package/dist/composables/positioning/use-arrow.d.ts.map +0 -1
  35. package/dist/composables/positioning/use-client-point.d.ts +0 -218
  36. package/dist/composables/positioning/use-client-point.d.ts.map +0 -1
  37. package/dist/composables/positioning/use-floating-tree.d.ts +0 -240
  38. package/dist/composables/positioning/use-floating-tree.d.ts.map +0 -1
  39. package/dist/composables/positioning/use-floating.d.ts +0 -162
  40. package/dist/composables/positioning/use-floating.d.ts.map +0 -1
  41. package/dist/composables/utils/is-using-keyboard.d.ts +0 -2
  42. package/dist/composables/utils/is-using-keyboard.d.ts.map +0 -1
  43. package/dist/composables/utils/use-active-descendant.d.ts +0 -8
  44. package/dist/composables/utils/use-active-descendant.d.ts.map +0 -1
  45. package/dist/index.d.ts +0 -2
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/types.d.ts +0 -18
  48. package/dist/types.d.ts.map +0 -1
  49. package/dist/utils.d.ts +0 -92
  50. package/dist/utils.d.ts.map +0 -1
  51. package/dist/v-float.es.js +0 -3520
  52. package/dist/v-float.umd.js +0 -4
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