ropav 0.0.10 → 0.0.12
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 +9 -0
- package/dist/accordion.css +115 -111
- package/dist/accordion.js +60 -16
- package/dist/alert.css +110 -108
- package/dist/alert.js +48 -26
- package/dist/aspect-ratio.css +17 -15
- package/dist/aspect-ratio.js +20 -6
- package/dist/avatar.css +98 -96
- package/dist/avatar.js +27 -16
- package/dist/badge.css +95 -93
- package/dist/badge.js +29 -16
- package/dist/base.css +672 -671
- package/dist/button-group.css +39 -37
- package/dist/button-group.js +23 -12
- package/dist/button-link.css +160 -158
- package/dist/button-link.js +43 -24
- package/dist/button.css +160 -158
- package/dist/button.js +37 -19
- package/dist/card.css +109 -107
- package/dist/card.js +42 -17
- package/dist/checkbox.css +139 -137
- package/dist/checkbox.js +103 -33
- package/dist/collapse.css +29 -27
- package/dist/collapse.js +44 -10
- package/dist/color-input.css +137 -107
- package/dist/color-input.js +117 -30
- package/dist/color-picker.css +309 -303
- package/dist/color-picker.js +147 -54
- package/dist/color-picker2.js +9 -1
- package/dist/color-swatch.css +48 -46
- package/dist/color-swatch.js +15 -11
- package/dist/color-swatch2.js +4 -0
- package/dist/components/accordion/accordion-item.d.ts +13 -1
- package/dist/components/accordion/index.d.ts +2 -1
- package/dist/components/accordion/index.js +2 -2
- package/dist/components/accordion/types.d.ts +9 -4
- package/dist/components/alert/alert.d.ts +2 -2
- package/dist/components/alert/index.d.ts +2 -2
- package/dist/components/alert/index.js +2 -2
- package/dist/components/alert/types.d.ts +4 -1
- package/dist/components/aspect-ratio/index.d.ts +2 -1
- package/dist/components/aspect-ratio/index.js +2 -2
- package/dist/components/aspect-ratio/types.d.ts +4 -1
- package/dist/components/avatar/index.d.ts +2 -2
- package/dist/components/avatar/index.js +2 -2
- package/dist/components/avatar/types.d.ts +4 -1
- package/dist/components/badge/index.d.ts +2 -2
- package/dist/components/badge/index.js +2 -2
- package/dist/components/badge/types.d.ts +4 -1
- package/dist/components/button/index.d.ts +2 -1
- package/dist/components/button/index.js +2 -2
- package/dist/components/button/types.d.ts +4 -1
- package/dist/components/button-group/index.d.ts +2 -1
- package/dist/components/button-group/index.js +2 -2
- package/dist/components/button-group/types.d.ts +4 -1
- package/dist/components/button-link/index.d.ts +2 -1
- package/dist/components/button-link/index.js +2 -2
- package/dist/components/button-link/types.d.ts +4 -1
- package/dist/components/card/index.d.ts +2 -1
- package/dist/components/card/index.js +2 -2
- package/dist/components/card/types.d.ts +5 -1
- package/dist/components/checkbox/checkbox.d.ts +4 -1
- package/dist/components/checkbox/index.d.ts +2 -1
- package/dist/components/checkbox/index.js +2 -2
- package/dist/components/checkbox/types.d.ts +11 -2
- package/dist/components/checkbox/useCheckbox.d.ts +3 -1
- package/dist/components/collapse/collapse.d.ts +2 -2
- package/dist/components/collapse/index.d.ts +2 -1
- package/dist/components/collapse/index.js +2 -2
- package/dist/components/collapse/types.d.ts +9 -2
- package/dist/components/color-input/index.d.ts +2 -1
- package/dist/components/color-input/index.js +2 -2
- package/dist/components/color-input/types.d.ts +8 -2
- package/dist/components/color-input/useColorInput.d.ts +1 -1
- package/dist/components/color-input/useColorInputValue.d.ts +1 -1
- package/dist/components/color-picker/index.d.ts +2 -2
- package/dist/components/color-picker/index.js +2 -2
- package/dist/components/color-picker/types.d.ts +13 -1
- package/dist/components/color-swatch/index.d.ts +2 -1
- package/dist/components/color-swatch/index.js +2 -1
- package/dist/components/color-swatch/types.d.ts +4 -1
- package/dist/components/dialog/dialog-close.d.ts +21 -0
- package/dist/components/dialog/dialog-content.d.ts +32 -0
- package/dist/components/dialog/dialog-core.d.ts +27 -0
- package/dist/components/dialog/dialog-description.d.ts +21 -0
- package/dist/components/dialog/dialog-overlay.d.ts +21 -0
- package/dist/components/dialog/dialog-portal.d.ts +21 -0
- package/dist/components/dialog/dialog-root.d.ts +28 -0
- package/dist/components/dialog/dialog-title.d.ts +21 -0
- package/dist/components/dialog/dialog-trigger.d.ts +23 -0
- package/dist/components/dialog/index.d.ts +9 -0
- package/dist/components/dialog/index.js +2 -0
- package/dist/components/dialog/types.d.ts +61 -0
- package/dist/components/dropdown-menu/dropdown-menu-checkbox-item.d.ts +24 -0
- package/dist/components/dropdown-menu/dropdown-menu-content.d.ts +34 -0
- package/dist/components/dropdown-menu/dropdown-menu-context-trigger.d.ts +23 -0
- package/dist/components/dropdown-menu/dropdown-menu-item-indicator.d.ts +24 -0
- package/dist/components/dropdown-menu/dropdown-menu-item-row.d.ts +28 -0
- package/dist/components/dropdown-menu/dropdown-menu-item.d.ts +23 -0
- package/dist/components/dropdown-menu/dropdown-menu-items.d.ts +24 -22
- package/dist/components/dropdown-menu/dropdown-menu-label.d.ts +21 -0
- package/dist/components/dropdown-menu/dropdown-menu-outside.d.ts +3 -0
- package/dist/components/dropdown-menu/dropdown-menu-portal.d.ts +21 -0
- package/dist/components/dropdown-menu/dropdown-menu-primitive-core.d.ts +110 -0
- package/dist/components/dropdown-menu/dropdown-menu-primitives.d.ts +15 -0
- package/dist/components/dropdown-menu/dropdown-menu-radio-group.d.ts +25 -0
- package/dist/components/dropdown-menu/dropdown-menu-radio-item.d.ts +23 -0
- package/dist/components/dropdown-menu/dropdown-menu-root.d.ts +30 -0
- package/dist/components/dropdown-menu/dropdown-menu-separator.d.ts +19 -0
- package/dist/components/dropdown-menu/dropdown-menu-sub-content.d.ts +34 -0
- package/dist/components/dropdown-menu/dropdown-menu-sub-trigger.d.ts +28 -0
- package/dist/components/dropdown-menu/dropdown-menu-sub.d.ts +28 -0
- package/dist/components/dropdown-menu/dropdown-menu-trigger.d.ts +23 -0
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +6 -3
- package/dist/components/dropdown-menu/index.d.ts +32 -1
- package/dist/components/dropdown-menu/index.js +2 -2
- package/dist/components/dropdown-menu/types.d.ts +145 -6
- package/dist/components/dropdown-menu/useDropdownMenu.d.ts +15 -3
- package/dist/components/dropdown-menu/useDropdownMenuDisclosure.d.ts +10 -3
- package/dist/components/dropdown-menu/useDropdownMenuDom.d.ts +2 -1
- package/dist/components/dropdown-menu/useDropdownMenuInteractions.d.ts +4 -4
- package/dist/components/dropdown-menu/useDropdownMenuPortalPosition.d.ts +19 -0
- package/dist/components/dropdown-menu/useDropdownMenuPrimitiveContent.d.ts +27 -0
- package/dist/components/dropdown-menu/useDropdownMenuRenderItems.d.ts +14 -2
- package/dist/components/field/index.d.ts +2 -1
- package/dist/components/field/index.js +2 -2
- package/dist/components/field/types.d.ts +4 -1
- package/dist/components/floating/index.d.ts +3 -0
- package/dist/components/floating/index.js +3 -0
- package/dist/components/floating/types.d.ts +105 -0
- package/dist/components/floating/useFloatingPosition.d.ts +15 -0
- package/dist/components/floating/useHoverDisclosure.d.ts +2 -0
- package/dist/components/focus-trap/focus-trap.d.ts +31 -0
- package/dist/components/focus-trap/index.d.ts +4 -0
- package/dist/components/focus-trap/index.js +3 -0
- package/dist/components/focus-trap/types.d.ts +41 -0
- package/dist/components/focus-trap/useFocusTrap.d.ts +2 -0
- package/dist/components/icon-button/index.d.ts +2 -1
- package/dist/components/icon-button/index.js +2 -1
- package/dist/components/icon-button/types.d.ts +4 -1
- package/dist/components/input/index.d.ts +2 -1
- package/dist/components/input/index.js +2 -1
- package/dist/components/input/input.d.ts +4 -1
- package/dist/components/input/types.d.ts +7 -2
- package/dist/components/modal/index.d.ts +2 -1
- package/dist/components/modal/index.js +2 -2
- package/dist/components/modal/modal.d.ts +3 -1
- package/dist/components/modal/types.d.ts +11 -1
- package/dist/components/modal/useModal.d.ts +7 -4
- package/dist/components/number-input/index.d.ts +2 -1
- package/dist/components/number-input/index.js +2 -2
- package/dist/components/number-input/types.d.ts +7 -2
- package/dist/components/number-input/useNumberInput.d.ts +2 -1
- package/dist/components/overlay/index.d.ts +2 -1
- package/dist/components/overlay/index.js +2 -1
- package/dist/components/overlay/types.d.ts +4 -1
- package/dist/components/popover/index.d.ts +2 -2
- package/dist/components/popover/index.js +2 -2
- package/dist/components/popover/popover.d.ts +2 -2
- package/dist/components/popover/types.d.ts +20 -11
- package/dist/components/popover/usePopover.d.ts +9 -1
- package/dist/components/progress/index.d.ts +2 -2
- package/dist/components/progress/index.js +2 -2
- package/dist/components/progress/types.d.ts +4 -1
- package/dist/components/radio/index.d.ts +2 -1
- package/dist/components/radio/index.js +2 -2
- package/dist/components/radio/radio.d.ts +8 -3
- package/dist/components/radio/types.d.ts +37 -4
- package/dist/components/radio/useRadio.d.ts +12 -4
- package/dist/components/select/index.d.ts +2 -1
- package/dist/components/select/index.js +2 -2
- package/dist/components/select/select.d.ts +4 -1
- package/dist/components/select/types.d.ts +10 -2
- package/dist/components/select/useSelect.d.ts +3 -1
- package/dist/components/slider/index.d.ts +2 -2
- package/dist/components/slider/index.js +2 -2
- package/dist/components/slider/range-slider-tooltip.d.ts +3 -0
- package/dist/components/slider/range-slider.d.ts +7 -4
- package/dist/components/slider/slider.d.ts +4 -1
- package/dist/components/slider/sliderCore.d.ts +4 -4
- package/dist/components/slider/types.d.ts +22 -4
- package/dist/components/slider/useRangeSlider.d.ts +2 -2
- package/dist/components/slider/useSlider.d.ts +2 -2
- package/dist/components/switch/index.d.ts +2 -1
- package/dist/components/switch/index.js +2 -2
- package/dist/components/switch/switch.d.ts +4 -1
- package/dist/components/switch/types.d.ts +11 -2
- package/dist/components/switch/useSwitch.d.ts +3 -1
- package/dist/components/tabs/index.d.ts +2 -1
- package/dist/components/tabs/index.js +2 -2
- package/dist/components/tabs/types.d.ts +13 -4
- package/dist/components/teleport-provider/index.d.ts +3 -0
- package/dist/components/teleport-provider/index.js +3 -0
- package/dist/components/teleport-provider/teleport-provider.d.ts +21 -0
- package/dist/components/teleport-provider/types.d.ts +4 -0
- package/dist/components/teleport-provider/useTeleportTarget.d.ts +6 -0
- package/dist/components/textarea/index.d.ts +2 -1
- package/dist/components/textarea/index.js +2 -2
- package/dist/components/textarea/textarea.d.ts +4 -1
- package/dist/components/textarea/types.d.ts +10 -2
- package/dist/components/textarea/useTextarea.d.ts +2 -1
- package/dist/components/toast/index.d.ts +3 -2
- package/dist/components/toast/index.js +2 -2
- package/dist/components/toast/toast-store.d.ts +2 -0
- package/dist/components/toast/toast.d.ts +2 -2
- package/dist/components/toast/types.d.ts +20 -10
- package/dist/components/toast/useToast.d.ts +2 -2
- package/dist/components/tooltip/index.d.ts +2 -1
- package/dist/components/tooltip/index.js +2 -1
- package/dist/components/tooltip/tooltip.d.ts +2 -2
- package/dist/components/tooltip/types.d.ts +13 -12
- package/dist/components/tooltip/useTooltip.d.ts +12 -2
- package/dist/composables/useOverlayLayer.d.ts +32 -0
- package/dist/dialog.js +510 -0
- package/dist/dropdown-menu.css +191 -108
- package/dist/dropdown-menu.js +1954 -198
- package/dist/field.css +41 -39
- package/dist/field.js +40 -16
- package/dist/floating.js +403 -0
- package/dist/focus-trap.js +80 -0
- package/dist/icon-button.css +193 -189
- package/dist/icon-button.js +23 -18
- package/dist/icon-button2.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +39 -32
- package/dist/input.css +122 -120
- package/dist/input.js +63 -40
- package/dist/input2.js +9 -0
- package/dist/legacy-unlayered.css +5382 -0
- package/dist/modal.css +129 -127
- package/dist/modal.js +182 -281
- package/dist/number-input.css +87 -85
- package/dist/number-input.js +98 -26
- package/dist/overlay.css +26 -24
- package/dist/overlay.js +17 -9
- package/dist/overlay2.js +4 -0
- package/dist/popover.css +140 -107
- package/dist/popover.js +276 -196
- package/dist/popover2.js +6 -1
- package/dist/progress.css +101 -99
- package/dist/progress.js +50 -28
- package/dist/radio.css +114 -105
- package/dist/radio.js +237 -61
- package/dist/select.css +255 -242
- package/dist/select.js +257 -94
- package/dist/slider.css +1090 -1031
- package/dist/slider.js +443 -175
- package/dist/styles-api.d.ts +22 -0
- package/dist/styles-api.js +49 -0
- package/dist/switch.css +120 -108
- package/dist/switch.js +112 -38
- package/dist/tabs.css +286 -278
- package/dist/tabs.js +60 -21
- package/dist/teleport-provider.js +15 -0
- package/dist/textarea.css +132 -130
- package/dist/textarea.js +83 -31
- package/dist/toast.css +229 -225
- package/dist/toast.js +189 -118
- package/dist/tooltip.css +61 -106
- package/dist/tooltip.js +184 -228
- package/dist/tooltip2.js +8 -0
- package/dist/useControlState.js +4 -0
- package/dist/useFloatingPosition.js +1317 -0
- package/dist/useFocusTrap.js +130 -0
- package/dist/useFormControl.js +107 -0
- package/dist/useOverlayLayer.js +262 -0
- package/dist/useTeleportTarget.js +40 -0
- package/docs/public-floating-api.md +148 -0
- package/docs/public-styles-api.md +134 -0
- package/docs/public-tokens.md +545 -0
- package/package.json +37 -9
- package/src/styles/_tokens.scss +33 -28
- package/src/styles/_utilities.scss +11 -9
- package/src/styles/styles-manifest.json +4125 -0
- package/dist/rafScheduler.js +0 -34
- package/dist/useClickOutside.js +0 -24
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { onBeforeUnmount, readonly, ref, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
import { createFocusTrap } from "focus-trap";
|
|
3
|
+
//#region src/components/focus-trap/useFocusTrap.ts
|
|
4
|
+
function hasContainers(containers) {
|
|
5
|
+
return Array.isArray(containers) ? containers.length > 0 : Boolean(containers);
|
|
6
|
+
}
|
|
7
|
+
function useFocusTrap(target, options = {}) {
|
|
8
|
+
const focusTrap = shallowRef(null);
|
|
9
|
+
const isActive = ref(false);
|
|
10
|
+
const isPaused = ref(false);
|
|
11
|
+
let shouldActivate = Boolean(options.immediate);
|
|
12
|
+
let shouldPause = false;
|
|
13
|
+
function syncState(trap) {
|
|
14
|
+
isActive.value = trap.active;
|
|
15
|
+
isPaused.value = trap.paused;
|
|
16
|
+
}
|
|
17
|
+
function createTrap(containers) {
|
|
18
|
+
const { immediate: _immediate, ...focusTrapOptions } = options;
|
|
19
|
+
const { onActivate, onDeactivate, onPause, onUnpause, onPostActivate, onPostDeactivate, onPostPause, onPostUnpause } = focusTrapOptions;
|
|
20
|
+
const trap = createFocusTrap(containers, {
|
|
21
|
+
...focusTrapOptions,
|
|
22
|
+
onActivate(parameters) {
|
|
23
|
+
syncState(parameters.trap);
|
|
24
|
+
onActivate?.(parameters);
|
|
25
|
+
},
|
|
26
|
+
onPostActivate(parameters) {
|
|
27
|
+
syncState(parameters.trap);
|
|
28
|
+
onPostActivate?.(parameters);
|
|
29
|
+
},
|
|
30
|
+
onDeactivate(parameters) {
|
|
31
|
+
syncState(parameters.trap);
|
|
32
|
+
onDeactivate?.(parameters);
|
|
33
|
+
},
|
|
34
|
+
onPostDeactivate(parameters) {
|
|
35
|
+
syncState(parameters.trap);
|
|
36
|
+
onPostDeactivate?.(parameters);
|
|
37
|
+
},
|
|
38
|
+
onPause(parameters) {
|
|
39
|
+
syncState(parameters.trap);
|
|
40
|
+
onPause?.(parameters);
|
|
41
|
+
},
|
|
42
|
+
onPostPause(parameters) {
|
|
43
|
+
syncState(parameters.trap);
|
|
44
|
+
onPostPause?.(parameters);
|
|
45
|
+
},
|
|
46
|
+
onUnpause(parameters) {
|
|
47
|
+
syncState(parameters.trap);
|
|
48
|
+
onUnpause?.(parameters);
|
|
49
|
+
},
|
|
50
|
+
onPostUnpause(parameters) {
|
|
51
|
+
syncState(parameters.trap);
|
|
52
|
+
onPostUnpause?.(parameters);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
focusTrap.value = trap;
|
|
56
|
+
return trap;
|
|
57
|
+
}
|
|
58
|
+
function activate(activateOptions) {
|
|
59
|
+
shouldActivate = true;
|
|
60
|
+
shouldPause = false;
|
|
61
|
+
const trap = focusTrap.value;
|
|
62
|
+
if (!trap) return;
|
|
63
|
+
trap.activate(activateOptions);
|
|
64
|
+
syncState(trap);
|
|
65
|
+
}
|
|
66
|
+
function deactivate(deactivateOptions) {
|
|
67
|
+
shouldActivate = false;
|
|
68
|
+
shouldPause = false;
|
|
69
|
+
const trap = focusTrap.value;
|
|
70
|
+
if (!trap) return;
|
|
71
|
+
trap.deactivate(deactivateOptions);
|
|
72
|
+
syncState(trap);
|
|
73
|
+
}
|
|
74
|
+
function pause(pauseOptions) {
|
|
75
|
+
shouldPause = true;
|
|
76
|
+
const trap = focusTrap.value;
|
|
77
|
+
if (!trap) return;
|
|
78
|
+
trap.pause(pauseOptions);
|
|
79
|
+
syncState(trap);
|
|
80
|
+
}
|
|
81
|
+
function unpause(unpauseOptions) {
|
|
82
|
+
shouldPause = false;
|
|
83
|
+
const trap = focusTrap.value;
|
|
84
|
+
if (!trap) return;
|
|
85
|
+
trap.unpause(unpauseOptions);
|
|
86
|
+
syncState(trap);
|
|
87
|
+
}
|
|
88
|
+
function updateContainerElements(containers) {
|
|
89
|
+
const trap = focusTrap.value ?? createTrap(containers);
|
|
90
|
+
trap.updateContainerElements(containers);
|
|
91
|
+
syncState(trap);
|
|
92
|
+
}
|
|
93
|
+
watch(() => toValue(target), (containers) => {
|
|
94
|
+
if (!hasContainers(containers)) {
|
|
95
|
+
focusTrap.value?.deactivate({ returnFocus: false });
|
|
96
|
+
focusTrap.value = null;
|
|
97
|
+
isActive.value = false;
|
|
98
|
+
isPaused.value = false;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const trap = focusTrap.value ?? createTrap(containers);
|
|
102
|
+
trap.updateContainerElements(containers);
|
|
103
|
+
if (shouldActivate && !trap.active) trap.activate();
|
|
104
|
+
if (shouldPause && trap.active && !trap.paused) trap.pause();
|
|
105
|
+
syncState(trap);
|
|
106
|
+
}, {
|
|
107
|
+
flush: "post",
|
|
108
|
+
immediate: true
|
|
109
|
+
});
|
|
110
|
+
onBeforeUnmount(() => {
|
|
111
|
+
shouldActivate = false;
|
|
112
|
+
shouldPause = false;
|
|
113
|
+
focusTrap.value?.deactivate();
|
|
114
|
+
focusTrap.value = null;
|
|
115
|
+
isActive.value = false;
|
|
116
|
+
isPaused.value = false;
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
focusTrap,
|
|
120
|
+
isActive: readonly(isActive),
|
|
121
|
+
isPaused: readonly(isPaused),
|
|
122
|
+
activate,
|
|
123
|
+
deactivate,
|
|
124
|
+
pause,
|
|
125
|
+
unpause,
|
|
126
|
+
updateContainerElements
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { useFocusTrap as t };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { computed, inject, provide, shallowRef, watch, watchEffect } from "vue";
|
|
2
|
+
//#region src/composables/useControllableValue.ts
|
|
3
|
+
function useControllableValue(options) {
|
|
4
|
+
const initialValue = options.defaultValue();
|
|
5
|
+
const uncontrolledValue = shallowRef(initialValue);
|
|
6
|
+
const isControlled = computed(() => options.modelValue() !== void 0);
|
|
7
|
+
watch(options.modelValue, (value) => {
|
|
8
|
+
if (value !== void 0) uncontrolledValue.value = value;
|
|
9
|
+
}, { immediate: true });
|
|
10
|
+
const value = computed(() => options.modelValue() ?? uncontrolledValue.value);
|
|
11
|
+
function setValue(nextValue) {
|
|
12
|
+
if (!isControlled.value) uncontrolledValue.value = nextValue;
|
|
13
|
+
options.onChange(nextValue);
|
|
14
|
+
}
|
|
15
|
+
function resetValue(nextValue = initialValue) {
|
|
16
|
+
if (!isControlled.value) uncontrolledValue.value = nextValue;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
initialValue,
|
|
20
|
+
isControlled,
|
|
21
|
+
value,
|
|
22
|
+
setValue,
|
|
23
|
+
resetValue
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/composables/useFormControl.ts
|
|
28
|
+
var nestedFormControlOwnerKey = Symbol("nestedFormControlOwner");
|
|
29
|
+
function provideNestedFormControlOwner() {
|
|
30
|
+
let claimed = false;
|
|
31
|
+
provide(nestedFormControlOwnerKey, () => {
|
|
32
|
+
if (claimed) return false;
|
|
33
|
+
claimed = true;
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function claimNestedFormControlOwner() {
|
|
38
|
+
return inject(nestedFormControlOwnerKey, void 0)?.() ?? false;
|
|
39
|
+
}
|
|
40
|
+
function getElements(options) {
|
|
41
|
+
return options.elements().filter((element) => Boolean(element));
|
|
42
|
+
}
|
|
43
|
+
function useFormControl(options) {
|
|
44
|
+
const initialized = /* @__PURE__ */ new WeakSet();
|
|
45
|
+
watchEffect(() => {
|
|
46
|
+
const elements = getElements(options);
|
|
47
|
+
for (const [index, element] of elements.entries()) {
|
|
48
|
+
if (!initialized.has(element)) {
|
|
49
|
+
options.initializeDefault?.(element, index);
|
|
50
|
+
initialized.add(element);
|
|
51
|
+
}
|
|
52
|
+
element.setCustomValidity(options.validationMessage?.(element, index, elements) ?? "");
|
|
53
|
+
}
|
|
54
|
+
}, { flush: "post" });
|
|
55
|
+
watchEffect((onCleanup) => {
|
|
56
|
+
const documents = new Set(getElements(options).map((element) => element.ownerDocument));
|
|
57
|
+
if (documents.size === 0) return;
|
|
58
|
+
function onReset(event) {
|
|
59
|
+
const form = event.target;
|
|
60
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
61
|
+
const elements = getElements(options).filter((element) => element.form === form);
|
|
62
|
+
if (elements.length === 0) return;
|
|
63
|
+
queueMicrotask(() => {
|
|
64
|
+
if (options.isControlled()) options.syncControlledValue(elements);
|
|
65
|
+
else options.readResetValue(elements);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const document of documents) document.addEventListener("reset", onReset, true);
|
|
69
|
+
onCleanup(() => {
|
|
70
|
+
for (const document of documents) document.removeEventListener("reset", onReset, true);
|
|
71
|
+
});
|
|
72
|
+
}, { flush: "post" });
|
|
73
|
+
}
|
|
74
|
+
function useTextFormControl(element, controllable, validationMessage) {
|
|
75
|
+
useFormControl({
|
|
76
|
+
elements: () => [element()],
|
|
77
|
+
isControlled: () => controllable.isControlled.value,
|
|
78
|
+
initializeDefault(control) {
|
|
79
|
+
control.defaultValue = controllable.initialValue;
|
|
80
|
+
},
|
|
81
|
+
validationMessage,
|
|
82
|
+
readResetValue([control]) {
|
|
83
|
+
controllable.resetValue(control.value);
|
|
84
|
+
},
|
|
85
|
+
syncControlledValue([control]) {
|
|
86
|
+
control.value = controllable.value.value;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function useCheckedFormControl(element, controllable, validationMessage) {
|
|
91
|
+
useFormControl({
|
|
92
|
+
elements: () => [element()],
|
|
93
|
+
isControlled: () => controllable.isControlled.value,
|
|
94
|
+
initializeDefault(control) {
|
|
95
|
+
control.defaultChecked = controllable.initialValue;
|
|
96
|
+
},
|
|
97
|
+
validationMessage,
|
|
98
|
+
readResetValue([control]) {
|
|
99
|
+
controllable.resetValue(control.checked);
|
|
100
|
+
},
|
|
101
|
+
syncControlledValue([control]) {
|
|
102
|
+
control.checked = controllable.value.value;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { useTextFormControl as a, useFormControl as i, provideNestedFormControlOwner as n, useControllableValue as o, useCheckedFormControl as r, claimNestedFormControlOwner as t };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { computed, inject, onBeforeUnmount, provide, readonly, ref, shallowRef, toValue, watch } from "vue";
|
|
2
|
+
//#region src/composables/useOverlayLayer.ts
|
|
3
|
+
var overlayLayerKey = Symbol("overlay-layer");
|
|
4
|
+
var documentStates = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
var layerMetadata = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function createState() {
|
|
7
|
+
return {
|
|
8
|
+
layers: [],
|
|
9
|
+
inertSnapshots: /* @__PURE__ */ new Map(),
|
|
10
|
+
originalBodyOverflow: "",
|
|
11
|
+
scrollLocked: false,
|
|
12
|
+
observer: null,
|
|
13
|
+
syncQueued: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function getState(document) {
|
|
17
|
+
const existing = documentStates.get(document);
|
|
18
|
+
if (existing) return existing;
|
|
19
|
+
const state = createState();
|
|
20
|
+
documentStates.set(document, state);
|
|
21
|
+
return state;
|
|
22
|
+
}
|
|
23
|
+
function isDescendantLayer(layer, ancestor) {
|
|
24
|
+
let parent = layerMetadata.get(layer)?.parent ?? null;
|
|
25
|
+
while (parent) {
|
|
26
|
+
if (parent === ancestor) return true;
|
|
27
|
+
parent = layerMetadata.get(parent)?.parent ?? null;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function syncLayerZIndices(state) {
|
|
32
|
+
let highestZIndex = Number.NEGATIVE_INFINITY;
|
|
33
|
+
for (const layer of state.layers) {
|
|
34
|
+
const metadata = layerMetadata.get(layer);
|
|
35
|
+
if (!metadata) continue;
|
|
36
|
+
const zIndex = Math.max(metadata.baseZIndex, highestZIndex + 2);
|
|
37
|
+
metadata.setZIndex(zIndex);
|
|
38
|
+
highestZIndex = zIndex;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isEventWithinElement(event, element) {
|
|
42
|
+
const path = event.composedPath();
|
|
43
|
+
if (path.length > 0) return path.some((entry) => entry === element || typeof Node !== "undefined" && entry instanceof Node && element.contains(entry));
|
|
44
|
+
return typeof Node !== "undefined" && event.target instanceof Node && element.contains(event.target);
|
|
45
|
+
}
|
|
46
|
+
function restoreInertBackground(state) {
|
|
47
|
+
for (const [element, snapshot] of state.inertSnapshots) {
|
|
48
|
+
element.inert = snapshot.inert;
|
|
49
|
+
if (snapshot.ariaHidden == null) element.removeAttribute("aria-hidden");
|
|
50
|
+
else element.setAttribute("aria-hidden", snapshot.ariaHidden);
|
|
51
|
+
}
|
|
52
|
+
state.inertSnapshots.clear();
|
|
53
|
+
}
|
|
54
|
+
function inertElement(state, element) {
|
|
55
|
+
if (!state.inertSnapshots.has(element)) state.inertSnapshots.set(element, {
|
|
56
|
+
ariaHidden: element.getAttribute("aria-hidden"),
|
|
57
|
+
inert: Boolean(element.inert)
|
|
58
|
+
});
|
|
59
|
+
element.inert = true;
|
|
60
|
+
element.setAttribute("aria-hidden", "true");
|
|
61
|
+
}
|
|
62
|
+
function inertUnprotectedBranches(state, parent, protectedRoots) {
|
|
63
|
+
for (const child of parent.children) {
|
|
64
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
65
|
+
if (!protectedRoots.some((root) => child === root || child.contains(root))) {
|
|
66
|
+
inertElement(state, child);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!protectedRoots.includes(child)) inertUnprotectedBranches(state, child, protectedRoots);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function stopObserver(state) {
|
|
73
|
+
state.observer?.disconnect();
|
|
74
|
+
state.observer = null;
|
|
75
|
+
state.syncQueued = false;
|
|
76
|
+
}
|
|
77
|
+
function unlockScroll(document, state) {
|
|
78
|
+
if (!state.scrollLocked) return;
|
|
79
|
+
document.body.style.overflow = state.originalBodyOverflow;
|
|
80
|
+
state.scrollLocked = false;
|
|
81
|
+
}
|
|
82
|
+
function queueModalEffects(document, state) {
|
|
83
|
+
if (state.syncQueued) return;
|
|
84
|
+
state.syncQueued = true;
|
|
85
|
+
queueMicrotask(() => {
|
|
86
|
+
state.syncQueued = false;
|
|
87
|
+
syncModalEffects(document, state);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function startObserver(document, state) {
|
|
91
|
+
if (state.observer || typeof MutationObserver === "undefined") return;
|
|
92
|
+
state.observer = new MutationObserver(() => queueModalEffects(document, state));
|
|
93
|
+
state.observer.observe(document.body, {
|
|
94
|
+
childList: true,
|
|
95
|
+
subtree: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function syncModalEffects(document, state) {
|
|
99
|
+
restoreInertBackground(state);
|
|
100
|
+
let modalIndex = -1;
|
|
101
|
+
for (let index = state.layers.length - 1; index >= 0; index -= 1) {
|
|
102
|
+
const layer = state.layers[index];
|
|
103
|
+
if (layer?.modalEffects && layer.modal.value) {
|
|
104
|
+
modalIndex = index;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const shouldLock = state.layers.some((layer) => layer.modalEffects && layer.modal.value && layer.preventScroll.value);
|
|
109
|
+
if (shouldLock && !state.scrollLocked) {
|
|
110
|
+
state.originalBodyOverflow = document.body.style.overflow;
|
|
111
|
+
document.body.style.overflow = "hidden";
|
|
112
|
+
state.scrollLocked = true;
|
|
113
|
+
} else if (!shouldLock) unlockScroll(document, state);
|
|
114
|
+
if (modalIndex < 0) {
|
|
115
|
+
stopObserver(state);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const protectedRoots = [];
|
|
119
|
+
for (const layer of state.layers.slice(modalIndex)) {
|
|
120
|
+
const element = layer.element.value;
|
|
121
|
+
if (element?.isConnected) protectedRoots.push(element);
|
|
122
|
+
for (const branch of layer.branches.value) if (branch.isConnected) protectedRoots.push(branch);
|
|
123
|
+
}
|
|
124
|
+
if (protectedRoots.length > 0) {
|
|
125
|
+
inertUnprotectedBranches(state, document.body, protectedRoots);
|
|
126
|
+
startObserver(document, state);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function removeLayer(layer, document, state) {
|
|
130
|
+
const index = state.layers.indexOf(layer);
|
|
131
|
+
if (index >= 0) state.layers.splice(index, 1);
|
|
132
|
+
syncLayerZIndices(state);
|
|
133
|
+
syncModalEffects(document, state);
|
|
134
|
+
}
|
|
135
|
+
function useOverlayLayer(options) {
|
|
136
|
+
const parent = inject(overlayLayerKey, null);
|
|
137
|
+
const active = computed(() => Boolean(toValue(options.active)));
|
|
138
|
+
const modal = computed(() => Boolean(toValue(options.modal ?? false)));
|
|
139
|
+
const preventScroll = computed(() => Boolean(toValue(options.preventScroll ?? true)));
|
|
140
|
+
const branches = shallowRef([]);
|
|
141
|
+
const focusBranches = shallowRef([]);
|
|
142
|
+
const branchSet = /* @__PURE__ */ new Set();
|
|
143
|
+
const focusBranchSet = /* @__PURE__ */ new Set();
|
|
144
|
+
const insideBranchSet = /* @__PURE__ */ new Set();
|
|
145
|
+
const zIndex = ref(options.baseZIndex ?? 100);
|
|
146
|
+
let registeredDocument = null;
|
|
147
|
+
let registeredElement = null;
|
|
148
|
+
let parentBranchCleanup;
|
|
149
|
+
function syncBranches() {
|
|
150
|
+
branches.value = [...branchSet];
|
|
151
|
+
focusBranches.value = [...focusBranchSet];
|
|
152
|
+
if (registeredDocument) syncModalEffects(registeredDocument, getState(registeredDocument));
|
|
153
|
+
}
|
|
154
|
+
function registerBranch(element, branchOptions = {}) {
|
|
155
|
+
branchSet.add(element);
|
|
156
|
+
if (branchOptions.focus !== false) focusBranchSet.add(element);
|
|
157
|
+
if (branchOptions.inside !== false) insideBranchSet.add(element);
|
|
158
|
+
const parentCleanup = parent?.registerBranch(element, branchOptions);
|
|
159
|
+
syncBranches();
|
|
160
|
+
return () => {
|
|
161
|
+
branchSet.delete(element);
|
|
162
|
+
focusBranchSet.delete(element);
|
|
163
|
+
insideBranchSet.delete(element);
|
|
164
|
+
parentCleanup?.();
|
|
165
|
+
syncBranches();
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function replaceParentBranch(element) {
|
|
169
|
+
parentBranchCleanup?.();
|
|
170
|
+
parentBranchCleanup = element ? parent?.registerBranch(element) : void 0;
|
|
171
|
+
}
|
|
172
|
+
const context = {
|
|
173
|
+
id: Symbol("overlay-layer"),
|
|
174
|
+
element: options.element,
|
|
175
|
+
active,
|
|
176
|
+
modal,
|
|
177
|
+
modalEffects: options.modalEffects === true,
|
|
178
|
+
preventScroll,
|
|
179
|
+
branches,
|
|
180
|
+
focusBranches,
|
|
181
|
+
zIndex: readonly(zIndex),
|
|
182
|
+
isTopLayer() {
|
|
183
|
+
if (!registeredDocument) return false;
|
|
184
|
+
const layers = getState(registeredDocument).layers;
|
|
185
|
+
return layers[layers.length - 1] === context;
|
|
186
|
+
},
|
|
187
|
+
isInside(event, additional = []) {
|
|
188
|
+
return [
|
|
189
|
+
options.element.value,
|
|
190
|
+
...insideBranchSet,
|
|
191
|
+
...additional
|
|
192
|
+
].some((element) => element != null && isEventWithinElement(event, element));
|
|
193
|
+
},
|
|
194
|
+
registerBranch
|
|
195
|
+
};
|
|
196
|
+
layerMetadata.set(context, {
|
|
197
|
+
baseZIndex: options.baseZIndex ?? 100,
|
|
198
|
+
parent,
|
|
199
|
+
setZIndex(value) {
|
|
200
|
+
zIndex.value = value;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
function unregister() {
|
|
204
|
+
replaceParentBranch(null);
|
|
205
|
+
if (!registeredDocument) return;
|
|
206
|
+
const document = registeredDocument;
|
|
207
|
+
registeredDocument = null;
|
|
208
|
+
registeredElement = null;
|
|
209
|
+
removeLayer(context, document, getState(document));
|
|
210
|
+
}
|
|
211
|
+
function register(element) {
|
|
212
|
+
const document = element.ownerDocument;
|
|
213
|
+
if (registeredDocument === document) {
|
|
214
|
+
if (registeredElement !== element) {
|
|
215
|
+
replaceParentBranch(element);
|
|
216
|
+
registeredElement = element;
|
|
217
|
+
syncModalEffects(document, getState(document));
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
unregister();
|
|
222
|
+
const state = getState(document);
|
|
223
|
+
const currentIndex = state.layers.indexOf(context);
|
|
224
|
+
if (currentIndex >= 0) state.layers.splice(currentIndex, 1);
|
|
225
|
+
const descendantIndex = state.layers.findIndex((layer) => isDescendantLayer(layer, context));
|
|
226
|
+
if (descendantIndex >= 0) state.layers.splice(descendantIndex, 0, context);
|
|
227
|
+
else state.layers.push(context);
|
|
228
|
+
registeredDocument = document;
|
|
229
|
+
registeredElement = element;
|
|
230
|
+
replaceParentBranch(element);
|
|
231
|
+
syncLayerZIndices(state);
|
|
232
|
+
syncModalEffects(document, state);
|
|
233
|
+
}
|
|
234
|
+
watch([active, options.element], ([isActive, element]) => {
|
|
235
|
+
if (isActive && element?.isConnected) register(element);
|
|
236
|
+
else unregister();
|
|
237
|
+
}, {
|
|
238
|
+
flush: "post",
|
|
239
|
+
immediate: true
|
|
240
|
+
});
|
|
241
|
+
watch([modal, preventScroll], () => {
|
|
242
|
+
if (registeredDocument) syncModalEffects(registeredDocument, getState(registeredDocument));
|
|
243
|
+
});
|
|
244
|
+
onBeforeUnmount(unregister);
|
|
245
|
+
provide(overlayLayerKey, context);
|
|
246
|
+
return context;
|
|
247
|
+
}
|
|
248
|
+
function useOverlayLayerBranch(element, options = {}) {
|
|
249
|
+
const layer = inject(overlayLayerKey, null);
|
|
250
|
+
let cleanup;
|
|
251
|
+
watch(element, (nextElement) => {
|
|
252
|
+
cleanup?.();
|
|
253
|
+
cleanup = nextElement ? layer?.registerBranch(nextElement, options) : void 0;
|
|
254
|
+
}, {
|
|
255
|
+
flush: "post",
|
|
256
|
+
immediate: true
|
|
257
|
+
});
|
|
258
|
+
onBeforeUnmount(() => cleanup?.());
|
|
259
|
+
return layer;
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
export { useOverlayLayerBranch as n, useOverlayLayer as t };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed, inject, isRef, provide, toValue } from "vue";
|
|
2
|
+
//#region src/components/teleport-provider/useTeleportTarget.ts
|
|
3
|
+
var teleportTargetKey = Symbol("teleport-target");
|
|
4
|
+
function readTeleportTarget(target) {
|
|
5
|
+
return isRef(target) ? target.value : target;
|
|
6
|
+
}
|
|
7
|
+
function hasTarget(target) {
|
|
8
|
+
return target != null && target !== "";
|
|
9
|
+
}
|
|
10
|
+
function provideTeleportTarget(props, positioningKey) {
|
|
11
|
+
const parentContext = inject(teleportTargetKey, void 0);
|
|
12
|
+
const target = computed(() => {
|
|
13
|
+
const ownTarget = readTeleportTarget(props.teleportTo);
|
|
14
|
+
if (hasTarget(ownTarget)) return ownTarget;
|
|
15
|
+
return parentContext?.target.value ?? "body";
|
|
16
|
+
});
|
|
17
|
+
provide(teleportTargetKey, {
|
|
18
|
+
target,
|
|
19
|
+
positioningKey: computed(() => [
|
|
20
|
+
target.value,
|
|
21
|
+
toValue(positioningKey),
|
|
22
|
+
parentContext?.positioningKey.value
|
|
23
|
+
])
|
|
24
|
+
});
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
27
|
+
function useTeleportTarget(target) {
|
|
28
|
+
const providedContext = inject(teleportTargetKey, void 0);
|
|
29
|
+
return computed(() => {
|
|
30
|
+
const ownTarget = readTeleportTarget(toValue(target));
|
|
31
|
+
if (hasTarget(ownTarget)) return ownTarget;
|
|
32
|
+
return providedContext?.target.value ?? "body";
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function useTeleportPositioningKey() {
|
|
36
|
+
const providedContext = inject(teleportTargetKey, void 0);
|
|
37
|
+
return computed(() => providedContext?.positioningKey.value);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useTeleportPositioningKey as n, useTeleportTarget as r, provideTeleportTarget as t };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Public floating API
|
|
2
|
+
|
|
3
|
+
`useFloatingPosition` provides Ropav's positioning behavior without rendering component DOM or
|
|
4
|
+
styles. It applies the same offset, flip, shift and collision defaults used by Ropav overlays.
|
|
5
|
+
|
|
6
|
+
```vue
|
|
7
|
+
<template>
|
|
8
|
+
<button
|
|
9
|
+
ref="reference"
|
|
10
|
+
type="button"
|
|
11
|
+
aria-haspopup="dialog"
|
|
12
|
+
:aria-expanded="open"
|
|
13
|
+
@click="open = !open"
|
|
14
|
+
>
|
|
15
|
+
Toggle card
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
<Teleport :to="teleportTo" :disabled="!teleport">
|
|
19
|
+
<section v-if="open" ref="floating" :style="floatingStyle" :data-placement="actualPlacement">
|
|
20
|
+
Custom content
|
|
21
|
+
<span ref="arrow" :style="arrowStyle" aria-hidden="true" />
|
|
22
|
+
</section>
|
|
23
|
+
</Teleport>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { ref } from 'vue';
|
|
28
|
+
import { useFloatingPosition } from 'ropav/floating';
|
|
29
|
+
import { useTeleportTarget } from 'ropav/teleport-provider';
|
|
30
|
+
|
|
31
|
+
const reference = ref<HTMLElement | null>(null);
|
|
32
|
+
const floating = ref<HTMLElement | null>(null);
|
|
33
|
+
const arrow = ref<HTMLElement | null>(null);
|
|
34
|
+
const open = ref(false);
|
|
35
|
+
const teleport = ref(true);
|
|
36
|
+
const teleportTo = useTeleportTarget();
|
|
37
|
+
|
|
38
|
+
const { actualPlacement, arrowStyle, floatingStyle } = useFloatingPosition({
|
|
39
|
+
reference,
|
|
40
|
+
floating,
|
|
41
|
+
arrow,
|
|
42
|
+
open,
|
|
43
|
+
placement: 'bottom-start',
|
|
44
|
+
offset: 8,
|
|
45
|
+
restartKey: () => [teleport.value, teleportTo.value],
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The composable accepts plain values, refs, computed refs or getters for reactive options.
|
|
51
|
+
|
|
52
|
+
| Option | Default | Description |
|
|
53
|
+
| ------------------ | ---------- | -------------------------------------------------------------- |
|
|
54
|
+
| `reference` | required | Element or virtual element used as the anchor. |
|
|
55
|
+
| `floating` | required | Floating HTML element. |
|
|
56
|
+
| `arrow` | — | Optional arrow HTML element. |
|
|
57
|
+
| `open` | `true` | Suspends positioning and auto-updates when false. |
|
|
58
|
+
| `placement` | `bottom` | Preferred side and optional alignment. |
|
|
59
|
+
| `strategy` | `absolute` | CSS positioning strategy: `absolute` or `fixed`. |
|
|
60
|
+
| `offset` | `8` | Main/cross-axis distance from the reference. |
|
|
61
|
+
| `flip` | `true` | Tries the opposite side when space is insufficient. |
|
|
62
|
+
| `shift` | `true` | Keeps the floating element inside the collision boundary. |
|
|
63
|
+
| `collisionPadding` | `8` | Padding between the floating element and viewport boundary. |
|
|
64
|
+
| `restartKey` | — | Rebinds auto-update observers when its reactive value changes. |
|
|
65
|
+
|
|
66
|
+
The return value contains readonly `actualPlacement`, `floatingStyle`, `arrowStyle` and
|
|
67
|
+
`isPositioned` refs, plus an async `update()` method for content-driven layout changes.
|
|
68
|
+
|
|
69
|
+
`useTeleportTarget()` resolves the nearest `TeleportProvider` target and falls back to `body`. Pass
|
|
70
|
+
a target value, ref or getter to override the provider for one floating element. Include the
|
|
71
|
+
resolved target and the Teleport enabled state in `restartKey` whenever either can change while the
|
|
72
|
+
floating element is open.
|
|
73
|
+
|
|
74
|
+
Positioning does not add interaction or accessibility behavior. Custom overlays remain responsible
|
|
75
|
+
for keyboard handling, focus management, roles, labels, outside interaction and dismissal.
|
|
76
|
+
|
|
77
|
+
## Hover disclosure interactions
|
|
78
|
+
|
|
79
|
+
`useHoverDisclosure` manages hover and focus state without rendering DOM or positioning content. It
|
|
80
|
+
coordinates the trigger and floating content, so a close delay started when the pointer leaves the
|
|
81
|
+
trigger is cancelled when the pointer enters the content.
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<template>
|
|
85
|
+
<a ref="reference" href="/people/ada" v-bind="triggerProps">Ada Lovelace</a>
|
|
86
|
+
|
|
87
|
+
<Teleport to="body">
|
|
88
|
+
<section
|
|
89
|
+
v-if="isOpen"
|
|
90
|
+
ref="floating"
|
|
91
|
+
v-bind="contentProps"
|
|
92
|
+
:style="floatingStyle"
|
|
93
|
+
:data-placement="actualPlacement"
|
|
94
|
+
>
|
|
95
|
+
Mathematician and writer
|
|
96
|
+
</section>
|
|
97
|
+
</Teleport>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<script setup lang="ts">
|
|
101
|
+
import { ref } from 'vue';
|
|
102
|
+
import { useFloatingPosition, useHoverDisclosure } from 'ropav/floating';
|
|
103
|
+
|
|
104
|
+
const reference = ref<HTMLElement | null>(null);
|
|
105
|
+
const floating = ref<HTMLElement | null>(null);
|
|
106
|
+
|
|
107
|
+
const { isOpen, triggerProps, contentProps } = useHoverDisclosure({
|
|
108
|
+
openDelay: 600,
|
|
109
|
+
closeDelay: 150,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const { actualPlacement, floatingStyle } = useFloatingPosition({
|
|
113
|
+
reference,
|
|
114
|
+
floating,
|
|
115
|
+
open: isOpen,
|
|
116
|
+
placement: 'bottom-start',
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Focus opens immediately by default. Hover uses `openDelay`; leaving the combined trigger/content
|
|
122
|
+
area uses `closeDelay`. Escape dismisses an open disclosure even when keyboard focus is elsewhere.
|
|
123
|
+
The return value also contains `state`, `isDisabled`, and immediate `open()`, `close()` and
|
|
124
|
+
`toggle()` controls.
|
|
125
|
+
|
|
126
|
+
| Option | Default | Description |
|
|
127
|
+
| ------------------- | ------- | --------------------------------------------------------------------------- |
|
|
128
|
+
| `open` | — | Controlled open state. Omit it for internal state. |
|
|
129
|
+
| `defaultOpen` | `false` | Initial internal state. |
|
|
130
|
+
| `openDelay` | `0` | Delay in milliseconds before pointer hover opens the disclosure. |
|
|
131
|
+
| `closeDelay` | `0` | Delay before closing after pointer and focus leave trigger and content. |
|
|
132
|
+
| `disabled` | `false` | Prevents opening and closes pending/open internal state. |
|
|
133
|
+
| `openOnFocus` | `true` | Opens immediately when focus enters the trigger. |
|
|
134
|
+
| `closeOnEscape` | `true` | Allows Escape to dismiss the disclosure. |
|
|
135
|
+
| `touchBehavior` | `none` | `toggle` opts into tap-to-toggle and prevents the activating click default. |
|
|
136
|
+
| `interactionTarget` | — | External element, selector or virtual reference that owns trigger events. |
|
|
137
|
+
| `contentTarget` | — | External element or selector that owns content events. |
|
|
138
|
+
| `onOpenChange` | — | Receives the requested state and its interaction reason. |
|
|
139
|
+
|
|
140
|
+
When `interactionTarget` or `contentTarget` is supplied, the composable attaches the corresponding
|
|
141
|
+
listeners directly; do not also bind the matching props to that same element. An element reference
|
|
142
|
+
is used directly. A virtual reference uses its `contextElement` for events. A virtual reference
|
|
143
|
+
without `contextElement` cannot receive pointer or focus events, so control it with `open` or the
|
|
144
|
+
imperative methods instead.
|
|
145
|
+
|
|
146
|
+
The composable only owns disclosure interaction. Consumers remain responsible for content roles,
|
|
147
|
+
labels and deciding whether the revealed information is appropriate for hover/focus. Interactive
|
|
148
|
+
dialogs and focus-trapped content should use `Popover` or `Dialog` instead.
|