@sit-onyx/headless 1.0.0-beta.0 → 1.0.0-beta.10
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/package.json +6 -2
- package/src/composables/comboBox/SelectOnlyCombobox.vue +6 -2
- package/src/composables/comboBox/TestCombobox.ct.tsx +1 -1
- package/src/composables/comboBox/TestCombobox.vue +9 -6
- package/src/composables/comboBox/createComboBox.ts +20 -15
- package/src/composables/helpers/useDismissible.ts +19 -0
- package/src/composables/helpers/useGlobalListener.spec.ts +93 -0
- package/src/composables/helpers/useGlobalListener.ts +64 -0
- package/src/composables/helpers/useOutsideClick.spec.ts +83 -0
- package/src/composables/helpers/useOutsideClick.ts +40 -0
- package/src/composables/{typeAhead.spec.ts → helpers/useTypeAhead.spec.ts} +1 -1
- package/src/composables/{typeAhead.ts → helpers/useTypeAhead.ts} +2 -2
- package/src/composables/listbox/TestListbox.ct.tsx +1 -1
- package/src/composables/listbox/TestListbox.vue +2 -0
- package/src/composables/listbox/createListbox.ts +26 -9
- package/src/composables/menuButton/TestMenuButton.ct.tsx +2 -2
- package/src/composables/menuButton/TestMenuButton.vue +7 -6
- package/src/composables/menuButton/{createMenuButton.ct.ts → createMenuButton.testing.ts} +17 -16
- package/src/composables/menuButton/createMenuButton.ts +121 -101
- package/src/composables/navigationMenu/TestMenu.ct.tsx +12 -0
- package/src/composables/navigationMenu/TestMenu.vue +16 -0
- package/src/composables/navigationMenu/createMenu.testing.ts +37 -0
- package/src/composables/navigationMenu/createMenu.ts +55 -0
- package/src/composables/tabs/TestTabs.ct.tsx +13 -0
- package/src/composables/tabs/TestTabs.vue +26 -0
- package/src/composables/tabs/createTabs.testing.ts +79 -0
- package/src/composables/tabs/createTabs.ts +72 -0
- package/src/composables/tooltip/createToggletip.ts +61 -0
- package/src/composables/tooltip/createTooltip.ts +37 -92
- package/src/index.ts +6 -1
- package/src/playwright.ts +5 -3
- package/src/utils/builder.ts +111 -13
- package/src/utils/math.spec.ts +14 -0
- package/src/utils/math.ts +6 -0
- package/src/utils/types.ts +7 -0
- package/src/utils/vitest.ts +36 -0
- package/src/composables/outsideClick.ts +0 -52
- package/src/utils/id.ts +0 -14
- /package/src/composables/comboBox/{createComboBox.ct.ts → createComboBox.testing.ts} +0 -0
- /package/src/composables/listbox/{createListbox.ct.ts → createListbox.testing.ts} +0 -0
|
@@ -1,43 +1,26 @@
|
|
|
1
|
-
import { computed,
|
|
2
|
-
import { createId } from "../..";
|
|
1
|
+
import { computed, toRef, toValue, useId, type MaybeRefOrGetter, type Ref } from "vue";
|
|
3
2
|
import { createBuilder } from "../../utils/builder";
|
|
4
|
-
import {
|
|
3
|
+
import { useDismissible } from "../helpers/useDismissible";
|
|
5
4
|
|
|
6
5
|
export type CreateTooltipOptions = {
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Number of milliseconds to use as debounce when showing/hiding the tooltip.
|
|
8
|
+
*/
|
|
9
|
+
debounce: MaybeRefOrGetter<number>;
|
|
10
|
+
isVisible?: Ref<boolean>;
|
|
8
11
|
};
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const TOOLTIP_TRIGGERS = ["hover", "click"] as const;
|
|
22
|
-
export type TooltipTrigger = (typeof TOOLTIP_TRIGGERS)[number];
|
|
23
|
-
|
|
24
|
-
export const createTooltip = createBuilder((options: CreateTooltipOptions) => {
|
|
25
|
-
const tooltipId = createId("tooltip");
|
|
26
|
-
const _isVisible = ref(false);
|
|
13
|
+
/**
|
|
14
|
+
* Create a tooltip as described in https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tooltip_role
|
|
15
|
+
* Its visibility is toggled on hover or focus.
|
|
16
|
+
* A tooltip MUST be used to describe the associated trigger element. E.g. The usage with the ⓘ would be incorrect.
|
|
17
|
+
* To provide contextual information use the `createToggletip`.
|
|
18
|
+
*/
|
|
19
|
+
export const createTooltip = createBuilder(({ debounce, isVisible }: CreateTooltipOptions) => {
|
|
20
|
+
const tooltipId = useId();
|
|
21
|
+
const _isVisible = toRef(isVisible ?? false);
|
|
27
22
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
28
23
|
|
|
29
|
-
const debounce = computed(() => {
|
|
30
|
-
const open = unref(options.open);
|
|
31
|
-
if (typeof open !== "object") return 200;
|
|
32
|
-
return open.debounce;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const openType = computed(() => {
|
|
36
|
-
const open = unref(options.open);
|
|
37
|
-
if (typeof open !== "object") return open;
|
|
38
|
-
return open.type;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
24
|
/**
|
|
42
25
|
* Debounced visible state that will only be toggled after a given timeout.
|
|
43
26
|
*/
|
|
@@ -47,79 +30,41 @@ export const createTooltip = createBuilder((options: CreateTooltipOptions) => {
|
|
|
47
30
|
clearTimeout(timeout);
|
|
48
31
|
timeout = setTimeout(() => {
|
|
49
32
|
_isVisible.value = newValue;
|
|
50
|
-
}, debounce
|
|
33
|
+
}, toValue(debounce));
|
|
51
34
|
},
|
|
52
35
|
});
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (typeof openType.value === "boolean") return openType.value;
|
|
60
|
-
return debouncedVisible.value;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Toggles the tooltip if element is clicked.
|
|
65
|
-
*/
|
|
66
|
-
const handleClick = () => {
|
|
67
|
-
_isVisible.value = !_isVisible.value;
|
|
37
|
+
const hoverEvents = {
|
|
38
|
+
onMouseover: () => (debouncedVisible.value = true),
|
|
39
|
+
onMouseout: () => (debouncedVisible.value = false),
|
|
40
|
+
onFocusin: () => (_isVisible.value = true),
|
|
41
|
+
onFocusout: () => (_isVisible.value = false),
|
|
68
42
|
};
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
if (openType.value !== "hover") return;
|
|
72
|
-
return {
|
|
73
|
-
onMouseover: () => (debouncedVisible.value = true),
|
|
74
|
-
onMouseout: () => (debouncedVisible.value = false),
|
|
75
|
-
onFocusin: () => (_isVisible.value = true),
|
|
76
|
-
onFocusout: () => (_isVisible.value = false),
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Closes the tooltip if Escape is pressed.
|
|
82
|
-
*/
|
|
83
|
-
const handleDocumentKeydown = (event: KeyboardEvent) => {
|
|
84
|
-
if (event.key !== "Escape") return;
|
|
85
|
-
_isVisible.value = false;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// close tooltip on outside click
|
|
89
|
-
useOutsideClick({
|
|
90
|
-
queryComponent: () => document.getElementById(tooltipId)?.parentElement,
|
|
91
|
-
onOutsideClick: () => (_isVisible.value = false),
|
|
92
|
-
disabled: computed(() => openType.value !== "click"),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// add global document event listeners only on/before mounted to also work in server side rendering
|
|
96
|
-
onBeforeMount(() => {
|
|
97
|
-
document.addEventListener("keydown", handleDocumentKeydown);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Clean up global event listeners to prevent dangling events.
|
|
102
|
-
*/
|
|
103
|
-
onBeforeUnmount(() => {
|
|
104
|
-
document.removeEventListener("keydown", handleDocumentKeydown);
|
|
105
|
-
});
|
|
44
|
+
useDismissible({ isExpanded: _isVisible });
|
|
106
45
|
|
|
107
46
|
return {
|
|
108
47
|
elements: {
|
|
109
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The element which controls the tooltip visibility on hover.
|
|
50
|
+
*/
|
|
51
|
+
trigger: {
|
|
110
52
|
"aria-describedby": tooltipId,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
53
|
+
...hoverEvents,
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* The element describing the tooltip.
|
|
57
|
+
* Only simple, textual and non-focusable content is allowed.
|
|
58
|
+
*/
|
|
59
|
+
tooltip: {
|
|
115
60
|
role: "tooltip",
|
|
116
61
|
id: tooltipId,
|
|
117
62
|
tabindex: "-1",
|
|
118
|
-
...hoverEvents
|
|
119
|
-
}
|
|
63
|
+
...hoverEvents,
|
|
64
|
+
},
|
|
120
65
|
},
|
|
121
66
|
state: {
|
|
122
|
-
isVisible,
|
|
67
|
+
isVisible: _isVisible,
|
|
123
68
|
},
|
|
124
69
|
};
|
|
125
70
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export * from "./composables/comboBox/createComboBox";
|
|
2
|
+
export * from "./composables/helpers/useGlobalListener";
|
|
2
3
|
export * from "./composables/listbox/createListbox";
|
|
3
4
|
export * from "./composables/menuButton/createMenuButton";
|
|
5
|
+
export * from "./composables/navigationMenu/createMenu";
|
|
6
|
+
export * from "./composables/tabs/createTabs";
|
|
7
|
+
export * from "./composables/tooltip/createToggletip";
|
|
4
8
|
export * from "./composables/tooltip/createTooltip";
|
|
5
|
-
export
|
|
9
|
+
export * from "./utils/builder";
|
|
6
10
|
export { isPrintableCharacter, wasKeyPressed } from "./utils/keyboard";
|
|
11
|
+
export { debounce } from "./utils/timer";
|
package/src/playwright.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export * from "./composables/comboBox/createComboBox.
|
|
2
|
-
export * from "./composables/listbox/createListbox.
|
|
3
|
-
export * from "./composables/menuButton/createMenuButton.
|
|
1
|
+
export * from "./composables/comboBox/createComboBox.testing";
|
|
2
|
+
export * from "./composables/listbox/createListbox.testing";
|
|
3
|
+
export * from "./composables/menuButton/createMenuButton.testing";
|
|
4
|
+
export * from "./composables/navigationMenu/createMenu.testing";
|
|
5
|
+
export * from "./composables/tabs/createTabs.testing";
|
package/src/utils/builder.ts
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
shallowRef,
|
|
4
|
+
type ComponentPublicInstance,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
type MaybeRef,
|
|
7
|
+
type Ref,
|
|
8
|
+
type WritableComputedOptions,
|
|
9
|
+
type WritableComputedRef,
|
|
10
|
+
} from "vue";
|
|
2
11
|
import type { IfDefined } from "./types";
|
|
3
12
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Properties as they can be used by `v-bind` on an HTML element.
|
|
15
|
+
* This includes generic html attributes and the vue reserved `ref` property.
|
|
16
|
+
* `ref` is restricted to be a `HeadlessElRef` which only can by created through `createElRef`.
|
|
17
|
+
*/
|
|
18
|
+
export type VBindAttributes<
|
|
19
|
+
A extends HTMLAttributes = HTMLAttributes,
|
|
20
|
+
E extends Element = Element,
|
|
21
|
+
> = A & {
|
|
22
|
+
ref?: VueTemplateRef<E>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type IteratedHeadlessElementFunc<
|
|
26
|
+
A extends HTMLAttributes,
|
|
27
|
+
T extends Record<string, unknown>,
|
|
28
|
+
> = (opts: T) => VBindAttributes<A>;
|
|
7
29
|
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
export type HeadlessElementAttributes<A extends HTMLAttributes> =
|
|
31
|
+
| VBindAttributes<A>
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
| IteratedHeadlessElementFunc<A, any>;
|
|
10
34
|
|
|
11
|
-
export type HeadlessElements = Record<
|
|
12
|
-
string,
|
|
13
|
-
HeadlessElementAttributes | ComputedRef<HeadlessElementAttributes>
|
|
14
|
-
>;
|
|
35
|
+
export type HeadlessElements = Record<string, MaybeRef<HeadlessElementAttributes<HTMLAttributes>>>;
|
|
15
36
|
|
|
16
37
|
export type HeadlessState = Record<string, Ref>;
|
|
17
38
|
|
|
@@ -26,12 +47,89 @@ export type HeadlessComposable<
|
|
|
26
47
|
|
|
27
48
|
/**
|
|
28
49
|
* We use this identity function to ensure the correct typings of the headless composables
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* export const createTooltip = createBuilder(({ initialVisible }: CreateTooltipOptions) => {
|
|
53
|
+
* const tooltipId = useId();
|
|
54
|
+
* const isVisible = ref(initialVisible);
|
|
55
|
+
*
|
|
56
|
+
* const hoverEvents = {
|
|
57
|
+
* onMouseover: () => (isVisible.value = true),
|
|
58
|
+
* onMouseout: () => (isVisible.value = false),
|
|
59
|
+
* onFocusin: () => (isVisible.value = true),
|
|
60
|
+
* onFocusout: () => (isVisible.value = false),
|
|
61
|
+
* };
|
|
62
|
+
*
|
|
63
|
+
* return {
|
|
64
|
+
* elements: {
|
|
65
|
+
* trigger: {
|
|
66
|
+
* "aria-describedby": tooltipId,
|
|
67
|
+
* ...hoverEvents,
|
|
68
|
+
* },
|
|
69
|
+
* tooltip: {
|
|
70
|
+
* role: "tooltip",
|
|
71
|
+
* id: tooltipId,
|
|
72
|
+
* tabindex: "-1",
|
|
73
|
+
* ...hoverEvents,
|
|
74
|
+
* },
|
|
75
|
+
* },
|
|
76
|
+
* state: {
|
|
77
|
+
* isVisible,
|
|
78
|
+
* },
|
|
79
|
+
* };
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* ```
|
|
29
83
|
*/
|
|
30
84
|
export const createBuilder = <
|
|
31
|
-
|
|
32
|
-
Elements extends HeadlessElements,
|
|
85
|
+
Args extends unknown[] = unknown[],
|
|
86
|
+
Elements extends HeadlessElements = HeadlessElements,
|
|
33
87
|
State extends HeadlessState | undefined = undefined,
|
|
34
88
|
Internals extends object | undefined = undefined,
|
|
35
89
|
>(
|
|
36
|
-
builder: (
|
|
90
|
+
builder: (...args: Args) => HeadlessComposable<Elements, State, Internals>,
|
|
37
91
|
) => builder;
|
|
92
|
+
|
|
93
|
+
type VueTemplateRefElement<E extends Element> = E | (ComponentPublicInstance & { $el: E }) | null;
|
|
94
|
+
type VueTemplateRef<E extends Element> = Ref<VueTemplateRefElement<E>>;
|
|
95
|
+
|
|
96
|
+
declare const HeadlessElRefSymbol: unique symbol;
|
|
97
|
+
type HeadlessElRef<E extends Element> = WritableComputedRef<E> & {
|
|
98
|
+
/**
|
|
99
|
+
* type differentiator
|
|
100
|
+
* ensures that only `createElRef` can be used for headless element ref bindings
|
|
101
|
+
*/
|
|
102
|
+
[HeadlessElRefSymbol]: true;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a special writeable computed that references a DOM Element.
|
|
107
|
+
* Vue Component references will be unwrapped.
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* createBuilder() => {
|
|
111
|
+
* const buttonRef = createElRef<HtmlButtonElement>();
|
|
112
|
+
* return {
|
|
113
|
+
* elements: {
|
|
114
|
+
* button: {
|
|
115
|
+
* ref: buttonRef,
|
|
116
|
+
* },
|
|
117
|
+
* }
|
|
118
|
+
* };
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function createElRef<E extends Element>(): HeadlessElRef<E>;
|
|
123
|
+
export function createElRef<
|
|
124
|
+
E extends Element,
|
|
125
|
+
V extends VueTemplateRefElement<E> = VueTemplateRefElement<E>,
|
|
126
|
+
>() {
|
|
127
|
+
const elementRef = shallowRef<E>();
|
|
128
|
+
|
|
129
|
+
return computed({
|
|
130
|
+
set: (element: V) => {
|
|
131
|
+
elementRef.value = element != null && "$el" in element ? element.$el : (element as E);
|
|
132
|
+
},
|
|
133
|
+
get: () => elementRef.value,
|
|
134
|
+
} as WritableComputedOptions<E>);
|
|
135
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { MathUtils } from "./math";
|
|
3
|
+
|
|
4
|
+
describe("MathUtils.clamp", () => {
|
|
5
|
+
test.each([
|
|
6
|
+
{ number: 1, min: 1, max: 2, result: 1 },
|
|
7
|
+
{ number: 1, min: 2, max: 2, result: 2 },
|
|
8
|
+
{ number: 1, min: 0, max: 0, result: 0 },
|
|
9
|
+
{ number: 1, min: 1, max: 1, result: 1 },
|
|
10
|
+
])(
|
|
11
|
+
"should return $result for key number:$number min:$min max:$max",
|
|
12
|
+
({ number, min, max, result }) => expect(MathUtils.clamp(number, min, max)).toBe(result),
|
|
13
|
+
);
|
|
14
|
+
});
|
package/src/utils/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter } from "vue";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Adds the entry with the key `Key` and the value of type `TValue` to a record when it is defined.
|
|
3
5
|
* Then the entry is either undefined or exists without being optional.
|
|
@@ -21,3 +23,8 @@ export type IfDefined<Key extends string, TValue> =
|
|
|
21
23
|
export type IsArray<TValue, TMultiple extends boolean = false> = TMultiple extends true
|
|
22
24
|
? TValue[]
|
|
23
25
|
: TValue;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Type for any kind of ref source. Preferably used in combination with vue's `toValue` method
|
|
29
|
+
*/
|
|
30
|
+
export type MaybeReactiveSource<T> = MaybeRefOrGetter<T> | ComputedRef<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
type Callback = () => void | (() => Promise<void>);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mocks the following vue lifecycle functions:
|
|
7
|
+
* - onBeforeMount
|
|
8
|
+
* - onMounted
|
|
9
|
+
* - onBeforeUnmount
|
|
10
|
+
* - onUnmounted
|
|
11
|
+
*
|
|
12
|
+
* `onBeforeMount` and `onMounted` callbacks are executed immediately.
|
|
13
|
+
* `onBeforeUnmount` and `onUnmounted` are executed when the returned callback is run.
|
|
14
|
+
* @returns a callback to trigger the run of `onBeforeUnmount` and `onUnmounted`
|
|
15
|
+
*/
|
|
16
|
+
export const mockVueLifecycle = () => {
|
|
17
|
+
const { callbacks } = vi.hoisted(() => ({
|
|
18
|
+
callbacks: {
|
|
19
|
+
onBeforeUnmountedCb: null as Callback | null,
|
|
20
|
+
onUnmountedCb: null as Callback | null,
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("vue", async (original) => ({
|
|
25
|
+
...((await original()) as typeof import("vue")),
|
|
26
|
+
onBeforeMount: vi.fn((cb: Callback) => cb()),
|
|
27
|
+
onMounted: vi.fn((cb: Callback) => cb()),
|
|
28
|
+
onBeforeUnmount: vi.fn((cb: Callback) => (callbacks.onBeforeUnmountedCb = cb)),
|
|
29
|
+
onUnmounted: vi.fn((cb: Callback) => (callbacks.onUnmountedCb = cb)),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
return async () => {
|
|
33
|
+
await callbacks.onBeforeUnmountedCb?.();
|
|
34
|
+
await callbacks.onUnmountedCb?.();
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { onBeforeMount, onBeforeUnmount, watchEffect, type Ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export type UseOutsideClickOptions = {
|
|
4
|
-
/**
|
|
5
|
-
* Function that returns the HTML element of the component where outside clicks should be listened to.
|
|
6
|
-
*/
|
|
7
|
-
queryComponent: () => ReturnType<typeof document.querySelector> | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Callback when an outside click occurred.
|
|
10
|
-
*/
|
|
11
|
-
onOutsideClick: () => void;
|
|
12
|
-
/**
|
|
13
|
-
* If `true`, event listeners will be removed and no outside clicks will be captured.
|
|
14
|
-
*/
|
|
15
|
-
disabled?: Ref<boolean>;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Composable for listening to click events that occur outside of a component.
|
|
20
|
-
* Useful to e.g. close flyouts or tooltips.
|
|
21
|
-
*/
|
|
22
|
-
export const useOutsideClick = (options: UseOutsideClickOptions) => {
|
|
23
|
-
/**
|
|
24
|
-
* Document click handle that closes then tooltip when clicked outside.
|
|
25
|
-
* Should only be called when trigger is "click".
|
|
26
|
-
*/
|
|
27
|
-
const handleDocumentClick = (event: MouseEvent) => {
|
|
28
|
-
const component = options.queryComponent();
|
|
29
|
-
if (!component || !(event.target instanceof Node)) return;
|
|
30
|
-
|
|
31
|
-
const isOutsideClick = !component.contains(event.target);
|
|
32
|
-
if (isOutsideClick) options.onOutsideClick();
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// add global document event listeners only on/before mounted to also work in server side rendering
|
|
36
|
-
onBeforeMount(() => {
|
|
37
|
-
watchEffect(() => {
|
|
38
|
-
if (options.disabled?.value) {
|
|
39
|
-
document.removeEventListener("click", handleDocumentClick);
|
|
40
|
-
} else {
|
|
41
|
-
document.addEventListener("click", handleDocumentClick);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Clean up global event listeners to prevent dangling events.
|
|
48
|
-
*/
|
|
49
|
-
onBeforeUnmount(() => {
|
|
50
|
-
document.removeEventListener("click", handleDocumentClick);
|
|
51
|
-
});
|
|
52
|
-
};
|
package/src/utils/id.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns a unique global id string
|
|
3
|
-
*/
|
|
4
|
-
// ⚠️ we make use of an IIFE to encapsulate the globalCounter so it can never accidentally be used somewhere else.
|
|
5
|
-
const nextId = (() => {
|
|
6
|
-
let globalCounter = 1;
|
|
7
|
-
return () => globalCounter++;
|
|
8
|
-
})();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates a globally unique string using a counter.
|
|
12
|
-
* The given name is the prefix.
|
|
13
|
-
*/
|
|
14
|
-
export const createId = (name: string) => `${name}-${nextId()}`;
|
|
File without changes
|
|
File without changes
|