vueless 1.2.10-beta.0 → 1.2.10-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/composables/useRequestQueue.ts +47 -0
- package/constants.d.ts +1 -0
- package/constants.js +1 -0
- package/icons/storybook/arrow_forward_ios.svg +1 -0
- package/index.d.ts +4 -1
- package/index.ts +4 -1
- package/package.json +2 -2
- package/types.ts +2 -0
- package/ui.button/UButton.vue +2 -6
- package/ui.button/config.ts +5 -0
- package/ui.button/tests/UButton.test.ts +1 -1
- package/ui.container-drawer/UDrawer.vue +365 -0
- package/ui.container-drawer/config.ts +128 -0
- package/ui.container-drawer/constants.ts +5 -0
- package/ui.container-drawer/storybook/docs.mdx +16 -0
- package/ui.container-drawer/storybook/stories.ts +255 -0
- package/ui.container-drawer/tests/UDrawer.test.ts +680 -0
- package/ui.container-drawer/types.ts +67 -0
- package/ui.container-modal/storybook/stories.ts +4 -1
- package/ui.container-modal-confirm/storybook/stories.ts +29 -24
- package/ui.form-calendar/UCalendarMonthView.vue +4 -4
- package/ui.form-calendar/tests/UCalendar.test.ts +8 -8
- package/ui.form-calendar/tests/UCalendarMonthView.test.ts +1 -1
- package/ui.form-calendar/utilCalendar.ts +2 -2
- package/ui.form-date-picker-range/UDatePickerRangeInputs.vue +20 -19
- package/ui.loader-overlay/useLoaderOverlay.ts +4 -4
- package/ui.loader-progress/ULoaderProgress.vue +41 -46
- package/ui.loader-progress/storybook/docs.mdx +24 -13
- package/ui.loader-progress/storybook/stories.ts +0 -9
- package/ui.loader-progress/types.ts +2 -2
- package/ui.loader-progress/useLoaderProgress.ts +36 -26
- package/ui.loader-progress/utilLoaderProgress.ts +12 -18
- package/ui.navigation-tabs/UTabs.vue +0 -1
- package/utils/requestQueue.ts +21 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readonly, ref } from "vue";
|
|
2
|
+
|
|
3
|
+
import type { Ref } from "vue";
|
|
4
|
+
|
|
5
|
+
import { getRequestWithoutQuery } from "../utils/requestQueue";
|
|
6
|
+
|
|
7
|
+
const requestQueue = ref<string[]>([]);
|
|
8
|
+
|
|
9
|
+
function addToRequestQueue(url: string | string[]): void {
|
|
10
|
+
Array.isArray(url)
|
|
11
|
+
? requestQueue.value.push(...url.map(getRequestWithoutQuery))
|
|
12
|
+
: requestQueue.value.push(getRequestWithoutQuery(url));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function removeFromRequestQueue(url: string | string[]): void {
|
|
16
|
+
if (Array.isArray(url)) {
|
|
17
|
+
url.map(getRequestWithoutQuery).forEach(removeFromRequestQueue);
|
|
18
|
+
} else {
|
|
19
|
+
requestQueue.value = requestQueue.value.filter((item) => item !== getRequestWithoutQuery(url));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isLoading(request: string | string[]): boolean {
|
|
24
|
+
return Array.isArray(request)
|
|
25
|
+
? request.some((url) => requestQueue.value.some((item: string) => item === url))
|
|
26
|
+
: requestQueue.value.some((item: string) => item === request);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function onAddToRequestQueue(event: CustomEvent<{ request: string }>) {
|
|
30
|
+
addToRequestQueue(event.detail.request);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function onRemoveFromRequestQueue(event: CustomEvent<{ request: string }>) {
|
|
34
|
+
removeFromRequestQueue(event.detail.request);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
window.addEventListener("addToRequestQueue", onAddToRequestQueue as EventListener);
|
|
38
|
+
window.addEventListener("removeFromRequestQueue", onRemoveFromRequestQueue as EventListener);
|
|
39
|
+
|
|
40
|
+
export function useRequestQueue() {
|
|
41
|
+
return {
|
|
42
|
+
isLoading,
|
|
43
|
+
addToRequestQueue,
|
|
44
|
+
removeFromRequestQueue,
|
|
45
|
+
requestQueue: readonly(requestQueue) as Readonly<Ref<readonly string[]>>,
|
|
46
|
+
};
|
|
47
|
+
}
|
package/constants.d.ts
CHANGED
package/constants.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M304-76.26 242.26-139l343-343-343-343L304-887.74 709.74-482 304-76.26Z"/></svg>
|
package/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export {
|
|
|
15
15
|
createDebounce,
|
|
16
16
|
hasSlotContent,
|
|
17
17
|
} from "./utils/helper";
|
|
18
|
+
export { addToRequestQueue, removeFromRequestQueue } from "./utils/requestQueue";
|
|
18
19
|
export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
|
|
19
20
|
export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
|
|
20
21
|
export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
|
|
@@ -27,13 +28,14 @@ export { createVueI18nAdapter } from "./adapter.locale/vue-i18n";
|
|
|
27
28
|
export { useLocale } from "./composables/useLocale";
|
|
28
29
|
export { default as useUI } from "./composables/useUI";
|
|
29
30
|
export { useDarkMode } from "./composables/useDarkMode";
|
|
31
|
+
export { useRequestQueue } from "./composables/useRequestQueue";
|
|
32
|
+
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
|
|
30
33
|
export { useLoaderProgress } from "./ui.loader-progress/useLoaderProgress";
|
|
31
34
|
export { useMutationObserver } from "./composables/useMutationObserver";
|
|
32
35
|
export { Direction, useAutoPosition } from "./composables/useAutoPosition";
|
|
33
36
|
export { useComponentLocaleMessages } from "./composables/useComponentLocaleMassages";
|
|
34
37
|
/* loaders */
|
|
35
38
|
export { loaderProgressOn, loaderProgressOff } from "./ui.loader-progress/utilLoaderProgress";
|
|
36
|
-
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
|
|
37
39
|
export { loaderOverlayOn, loaderOverlayOff } from "./ui.loader-overlay/utilLoaderOverlay";
|
|
38
40
|
/* notifications */
|
|
39
41
|
export {
|
|
@@ -101,6 +103,7 @@ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccord
|
|
|
101
103
|
export { default as UCard } from "./ui.container-card/UCard.vue";
|
|
102
104
|
export { default as UModal } from "./ui.container-modal/UModal.vue";
|
|
103
105
|
export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
|
|
106
|
+
export { default as UDrawer } from "./ui.container-drawer/UDrawer.vue";
|
|
104
107
|
export { default as UPage } from "./ui.container-page/UPage.vue";
|
|
105
108
|
/* Images and Icons */
|
|
106
109
|
export { default as UIcon } from "./ui.image-icon/UIcon.vue";
|
package/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
createDebounce,
|
|
22
22
|
hasSlotContent,
|
|
23
23
|
} from "./utils/helper";
|
|
24
|
+
export { addToRequestQueue, removeFromRequestQueue } from "./utils/requestQueue";
|
|
24
25
|
export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
|
|
25
26
|
export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
|
|
26
27
|
export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
|
|
@@ -33,13 +34,14 @@ export { createVueI18nAdapter } from "./adapter.locale/vue-i18n";
|
|
|
33
34
|
export { useLocale } from "./composables/useLocale";
|
|
34
35
|
export { default as useUI } from "./composables/useUI";
|
|
35
36
|
export { useDarkMode } from "./composables/useDarkMode";
|
|
37
|
+
export { useRequestQueue } from "./composables/useRequestQueue";
|
|
38
|
+
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
|
|
36
39
|
export { useLoaderProgress } from "./ui.loader-progress/useLoaderProgress";
|
|
37
40
|
export { useMutationObserver } from "./composables/useMutationObserver";
|
|
38
41
|
export { Direction, useAutoPosition } from "./composables/useAutoPosition";
|
|
39
42
|
export { useComponentLocaleMessages } from "./composables/useComponentLocaleMassages";
|
|
40
43
|
/* loaders */
|
|
41
44
|
export { loaderProgressOn, loaderProgressOff } from "./ui.loader-progress/utilLoaderProgress";
|
|
42
|
-
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
|
|
43
45
|
export { loaderOverlayOn, loaderOverlayOff } from "./ui.loader-overlay/utilLoaderOverlay";
|
|
44
46
|
/* notifications */
|
|
45
47
|
export {
|
|
@@ -107,6 +109,7 @@ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccord
|
|
|
107
109
|
export { default as UCard } from "./ui.container-card/UCard.vue";
|
|
108
110
|
export { default as UModal } from "./ui.container-modal/UModal.vue";
|
|
109
111
|
export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
|
|
112
|
+
export { default as UDrawer } from "./ui.container-drawer/UDrawer.vue";
|
|
110
113
|
export { default as UPage } from "./ui.container-page/UPage.vue";
|
|
111
114
|
/* Images and Icons */
|
|
112
115
|
export { default as UIcon } from "./ui.image-icon/UIcon.vue";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vueless",
|
|
3
|
-
"version": "1.2.10-beta.
|
|
3
|
+
"version": "1.2.10-beta.10",
|
|
4
4
|
"description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
|
|
5
5
|
"author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
|
|
6
6
|
"homepage": "https://vueless.com",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"prettier": "^3.6.2",
|
|
67
67
|
"release-it": "^19.0.4",
|
|
68
68
|
"typescript": "^5.8.3",
|
|
69
|
-
"vite": "^7.1.
|
|
69
|
+
"vite": "^7.1.9",
|
|
70
70
|
"vitest": "^3.2.4",
|
|
71
71
|
"vue": "^3.5.18",
|
|
72
72
|
"vue-router": "^4.5.1",
|
package/types.ts
CHANGED
|
@@ -27,6 +27,7 @@ import UDividerConfig from "./ui.container-divider/config";
|
|
|
27
27
|
import UGroupConfig from "./ui.container-group/config";
|
|
28
28
|
import UModalConfig from "./ui.container-modal/config";
|
|
29
29
|
import UModalConfirmConfig from "./ui.container-modal-confirm/config";
|
|
30
|
+
import UDrawerConfig from "./ui.container-drawer/config";
|
|
30
31
|
import UPageConfig from "./ui.container-page/config";
|
|
31
32
|
import URowConfig from "./ui.container-row/config";
|
|
32
33
|
import ULoaderConfig from "./ui.loader/config";
|
|
@@ -272,6 +273,7 @@ export interface Components {
|
|
|
272
273
|
UModal: Partial<typeof UModalConfig>;
|
|
273
274
|
UModalConfirm: Partial<typeof UModalConfirmConfig>;
|
|
274
275
|
UPage: Partial<typeof UPageConfig>;
|
|
276
|
+
UDrawer: Partial<typeof UDrawerConfig>;
|
|
275
277
|
URow: Partial<typeof URowConfig>;
|
|
276
278
|
ULoader: Partial<typeof ULoaderConfig>;
|
|
277
279
|
ULoaderOverlay: Partial<typeof ULoaderOverlayConfig>;
|
package/ui.button/UButton.vue
CHANGED
|
@@ -62,6 +62,7 @@ defineExpose({
|
|
|
62
62
|
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
63
63
|
*/
|
|
64
64
|
const mutatedProps = computed(() => ({
|
|
65
|
+
icon: Boolean(props.icon) || hasSlotContent(slots["default"]),
|
|
65
66
|
leftIcon: Boolean(props.leftIcon) || hasSlotContent(slots["left"]),
|
|
66
67
|
rightIcon: Boolean(props.rightIcon) || hasSlotContent(slots["right"]),
|
|
67
68
|
label: Boolean(props.label),
|
|
@@ -129,11 +130,6 @@ const {
|
|
|
129
130
|
</template>
|
|
130
131
|
|
|
131
132
|
<!-- This is needed to prevent changing button height -->
|
|
132
|
-
<div
|
|
133
|
-
v-if="(!label && !hasSlotContent(slots['default']) && !icon) || loading"
|
|
134
|
-
tabindex="-1"
|
|
135
|
-
v-bind="invisibleAttrs"
|
|
136
|
-
v-text="'invisible'"
|
|
137
|
-
/>
|
|
133
|
+
<div v-if="icon || loading" tabindex="-1" v-bind="invisibleAttrs" v-text="'invisible'" />
|
|
138
134
|
</component>
|
|
139
135
|
</template>
|
package/ui.button/config.ts
CHANGED
|
@@ -54,6 +54,9 @@ export default /*tw*/ {
|
|
|
54
54
|
loading: {
|
|
55
55
|
true: "gap-0 pointer-events-none",
|
|
56
56
|
},
|
|
57
|
+
icon: {
|
|
58
|
+
true: "gap-0",
|
|
59
|
+
},
|
|
57
60
|
label: {
|
|
58
61
|
false: "gap-0",
|
|
59
62
|
},
|
|
@@ -62,6 +65,8 @@ export default /*tw*/ {
|
|
|
62
65
|
},
|
|
63
66
|
},
|
|
64
67
|
compoundVariants: [
|
|
68
|
+
{ label: false, leftIcon: true, class: "gap-0" },
|
|
69
|
+
{ label: false, rightIcon: true, class: "gap-0" },
|
|
65
70
|
{ square: true, size: "2xs", class: "p-1" },
|
|
66
71
|
{ square: true, size: "xs", class: "p-1.5" },
|
|
67
72
|
{ square: true, size: "sm", class: "p-2" },
|
|
@@ -115,7 +115,7 @@ describe("UButton.vue", () => {
|
|
|
115
115
|
|
|
116
116
|
const nestedUIconComponents = component.findAllComponents(UIcon);
|
|
117
117
|
|
|
118
|
-
expect(component.text()).toBe(
|
|
118
|
+
expect(component.text()).not.toBe(label);
|
|
119
119
|
expect(nestedUIconComponents.length).toBe(1);
|
|
120
120
|
expect(nestedUIconComponents[0].props("name")).toBe(icon);
|
|
121
121
|
});
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useSlots, watch, useId, useTemplateRef, nextTick, ref } from "vue";
|
|
3
|
+
|
|
4
|
+
import useUI from "../composables/useUI";
|
|
5
|
+
import { getDefaults } from "../utils/ui";
|
|
6
|
+
import { hasSlotContent } from "../utils/helper";
|
|
7
|
+
|
|
8
|
+
import UHeader from "../ui.text-header/UHeader.vue";
|
|
9
|
+
|
|
10
|
+
import defaultConfig from "./config";
|
|
11
|
+
import { COMPONENT_NAME } from "./constants";
|
|
12
|
+
|
|
13
|
+
import type { Props, Config } from "./types";
|
|
14
|
+
|
|
15
|
+
defineOptions({ inheritAttrs: false });
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
|
|
19
|
+
modelValue: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits([
|
|
23
|
+
/**
|
|
24
|
+
* Triggers when the drawer is toggled.
|
|
25
|
+
* @property {Boolean} value
|
|
26
|
+
*/
|
|
27
|
+
"update:modelValue",
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Triggers when the drawer is closed.
|
|
31
|
+
*/
|
|
32
|
+
"close",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const elementId = props.id || useId();
|
|
36
|
+
|
|
37
|
+
const slots = useSlots();
|
|
38
|
+
|
|
39
|
+
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
40
|
+
const drawerRef = useTemplateRef<HTMLDivElement>("drawer");
|
|
41
|
+
|
|
42
|
+
const isDragging = ref(false);
|
|
43
|
+
const dragStartPosition = ref({ x: 0, y: 0 });
|
|
44
|
+
const dragCurrentPosition = ref({ x: 0, y: 0 });
|
|
45
|
+
const minDragDistance = 10;
|
|
46
|
+
|
|
47
|
+
const isShownDrawer = computed({
|
|
48
|
+
get: () => props.modelValue,
|
|
49
|
+
set: (value) => emit("update:modelValue", value),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const isExistHeader = computed(() => {
|
|
53
|
+
return (
|
|
54
|
+
props.title ||
|
|
55
|
+
hasSlotContent(slots["before-title"]) ||
|
|
56
|
+
hasSlotContent(slots["title"]) ||
|
|
57
|
+
hasSlotContent(slots["after-title"]) ||
|
|
58
|
+
hasSlotContent(slots["actions"])
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const isExistFooter = computed(() => {
|
|
63
|
+
return hasSlotContent(slots["footer-left"]) || hasSlotContent(slots["footer-right"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const dragThreshold = computed(() => {
|
|
67
|
+
if (!drawerRef.value) return 0;
|
|
68
|
+
|
|
69
|
+
const rect = drawerRef.value.getBoundingClientRect();
|
|
70
|
+
|
|
71
|
+
const dragThresholdMap = {
|
|
72
|
+
top: rect.height / 2,
|
|
73
|
+
bottom: rect.height / 2,
|
|
74
|
+
left: rect.width / 2,
|
|
75
|
+
right: rect.width / 2,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return dragThresholdMap[props.position] || 0;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const dragDistance = computed(() => {
|
|
82
|
+
if (!isDragging.value) return 0;
|
|
83
|
+
|
|
84
|
+
const deltaX = dragCurrentPosition.value.x - dragStartPosition.value.x;
|
|
85
|
+
const deltaY = dragCurrentPosition.value.y - dragStartPosition.value.y;
|
|
86
|
+
|
|
87
|
+
const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
88
|
+
|
|
89
|
+
if (totalDistance < minDragDistance) return 0;
|
|
90
|
+
|
|
91
|
+
const dragDistanceMap = {
|
|
92
|
+
top: Math.min(0, deltaY),
|
|
93
|
+
bottom: Math.max(0, deltaY),
|
|
94
|
+
left: Math.min(0, deltaX),
|
|
95
|
+
right: Math.max(0, deltaX),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return dragDistanceMap[props.position] || 0;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const shouldCloseDrawer = computed(() => {
|
|
102
|
+
return Math.abs(dragDistance.value) > dragThreshold.value;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const dragTransform = computed(() => {
|
|
106
|
+
if (!isDragging.value) return "";
|
|
107
|
+
|
|
108
|
+
const distance = dragDistance.value;
|
|
109
|
+
|
|
110
|
+
const dragTransformMap = {
|
|
111
|
+
top: `translateY(${distance}px)`,
|
|
112
|
+
bottom: `translateY(${distance}px)`,
|
|
113
|
+
left: `translateX(${distance}px)`,
|
|
114
|
+
right: `translateX(${distance}px)`,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return dragTransformMap[props.position] || "";
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
function getFocusableElements() {
|
|
121
|
+
if (!wrapperRef.value) return [];
|
|
122
|
+
|
|
123
|
+
return Array.from(
|
|
124
|
+
wrapperRef.value.querySelectorAll(
|
|
125
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function trapFocus(e: KeyboardEvent) {
|
|
131
|
+
if (e.key !== "Tab") return;
|
|
132
|
+
|
|
133
|
+
const focusableElements = getFocusableElements();
|
|
134
|
+
|
|
135
|
+
if (!focusableElements.length) return;
|
|
136
|
+
|
|
137
|
+
const firstElement = focusableElements.at(0) as HTMLElement;
|
|
138
|
+
const lastElement = focusableElements.at(-1) as HTMLElement;
|
|
139
|
+
|
|
140
|
+
// Shift+Tab - if focused on first element, move to last
|
|
141
|
+
if (e.shiftKey && document.activeElement === firstElement) {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
lastElement.focus();
|
|
144
|
+
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Tab - if focused on last element, move to first
|
|
149
|
+
if (!e.shiftKey && document.activeElement === lastElement) {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
firstElement.focus();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
watch(isShownDrawer, (newValue) => {
|
|
156
|
+
onChangeShownDrawer(newValue);
|
|
157
|
+
|
|
158
|
+
if (!newValue) emit("close");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
function onChangeShownDrawer(newValue: boolean) {
|
|
162
|
+
toggleEventListeners();
|
|
163
|
+
|
|
164
|
+
if (newValue) {
|
|
165
|
+
nextTick(() => wrapperRef.value?.focus());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function toggleEventListeners() {
|
|
170
|
+
if (isShownDrawer.value) {
|
|
171
|
+
document.addEventListener("keydown", trapFocus);
|
|
172
|
+
document.addEventListener("keydown", onKeydownEsc);
|
|
173
|
+
} else {
|
|
174
|
+
document.removeEventListener("keydown", trapFocus);
|
|
175
|
+
document.removeEventListener("keydown", onKeydownEsc);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function onClickOutside() {
|
|
180
|
+
props.closeOnOverlay && closeDrawer();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function onKeydownEsc(e: KeyboardEvent) {
|
|
184
|
+
if (e.key !== "Escape" || !props.closeOnEsc) return;
|
|
185
|
+
|
|
186
|
+
closeDrawer();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function closeDrawer() {
|
|
190
|
+
isShownDrawer.value = false;
|
|
191
|
+
|
|
192
|
+
emit("close");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function onDragStart(e: MouseEvent | TouchEvent) {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
|
|
198
|
+
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
|
|
199
|
+
const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
|
|
200
|
+
|
|
201
|
+
dragStartPosition.value = { x: clientX, y: clientY };
|
|
202
|
+
dragCurrentPosition.value = { x: clientX, y: clientY };
|
|
203
|
+
|
|
204
|
+
document.addEventListener("mousemove", onDragMove);
|
|
205
|
+
document.addEventListener("mouseup", onDragEnd);
|
|
206
|
+
document.addEventListener("touchmove", onDragMove, { passive: false });
|
|
207
|
+
document.addEventListener("touchend", onDragEnd);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function onDragMove(e: MouseEvent | TouchEvent) {
|
|
211
|
+
e.preventDefault();
|
|
212
|
+
|
|
213
|
+
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
|
|
214
|
+
const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
|
|
215
|
+
|
|
216
|
+
dragCurrentPosition.value = { x: clientX, y: clientY };
|
|
217
|
+
|
|
218
|
+
const deltaX = clientX - dragStartPosition.value.x;
|
|
219
|
+
const deltaY = clientY - dragStartPosition.value.y;
|
|
220
|
+
const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
221
|
+
|
|
222
|
+
if (totalDistance >= minDragDistance) {
|
|
223
|
+
isDragging.value = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function onDragEnd() {
|
|
228
|
+
document.removeEventListener("mousemove", onDragMove);
|
|
229
|
+
document.removeEventListener("mouseup", onDragEnd);
|
|
230
|
+
document.removeEventListener("touchmove", onDragMove);
|
|
231
|
+
document.removeEventListener("touchend", onDragEnd);
|
|
232
|
+
|
|
233
|
+
if (isDragging.value && shouldCloseDrawer.value) {
|
|
234
|
+
closeDrawer();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
isDragging.value = false;
|
|
238
|
+
dragStartPosition.value = { x: 0, y: 0 };
|
|
239
|
+
dragCurrentPosition.value = { x: 0, y: 0 };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
defineExpose({
|
|
243
|
+
/**
|
|
244
|
+
* A reference to the component's wrapper element for direct DOM manipulation.
|
|
245
|
+
* @property {HTMLDivElement}
|
|
246
|
+
*/
|
|
247
|
+
wrapperRef,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const wrapperTransition = computed(() => {
|
|
251
|
+
const transitionMap = {
|
|
252
|
+
top: config.value.wrapperTransitionTop,
|
|
253
|
+
bottom: config.value.wrapperTransitionBottom,
|
|
254
|
+
left: config.value.wrapperTransitionLeft,
|
|
255
|
+
right: config.value.wrapperTransitionRight,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return transitionMap[props.position];
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get element / nested component attributes for each config token ✨
|
|
263
|
+
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
const {
|
|
267
|
+
getDataTest,
|
|
268
|
+
config,
|
|
269
|
+
handleAttrs,
|
|
270
|
+
handleWrapperAttrs,
|
|
271
|
+
drawerAttrs,
|
|
272
|
+
drawerWrapperAttrs,
|
|
273
|
+
titleAttrs,
|
|
274
|
+
overlayAttrs,
|
|
275
|
+
wrapperAttrs,
|
|
276
|
+
innerWrapperAttrs,
|
|
277
|
+
headerAttrs,
|
|
278
|
+
descriptionAttrs,
|
|
279
|
+
bodyAttrs,
|
|
280
|
+
footerLeftAttrs,
|
|
281
|
+
footerAttrs,
|
|
282
|
+
footerRightAttrs,
|
|
283
|
+
beforeTitleAttrs,
|
|
284
|
+
titleFallbackAttrs,
|
|
285
|
+
} = useUI<Config>(defaultConfig);
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<template>
|
|
289
|
+
<Transition v-bind="config.overlayTransition">
|
|
290
|
+
<div v-if="isShownDrawer" v-bind="overlayAttrs" />
|
|
291
|
+
</Transition>
|
|
292
|
+
|
|
293
|
+
<Transition v-bind="wrapperTransition">
|
|
294
|
+
<div
|
|
295
|
+
v-if="isShownDrawer"
|
|
296
|
+
:id="elementId"
|
|
297
|
+
ref="wrapper"
|
|
298
|
+
tabindex="0"
|
|
299
|
+
v-bind="wrapperAttrs"
|
|
300
|
+
:data-test="getDataTest()"
|
|
301
|
+
@keydown.self.esc="onKeydownEsc"
|
|
302
|
+
>
|
|
303
|
+
<div v-bind="innerWrapperAttrs" @click.self="onClickOutside">
|
|
304
|
+
<div
|
|
305
|
+
ref="drawer"
|
|
306
|
+
:style="{ transform: dragTransform }"
|
|
307
|
+
v-bind="drawerWrapperAttrs"
|
|
308
|
+
@mousedown="onDragStart"
|
|
309
|
+
@touchstart="onDragStart"
|
|
310
|
+
>
|
|
311
|
+
<div v-bind="drawerAttrs">
|
|
312
|
+
<div v-if="isExistHeader" v-bind="headerAttrs">
|
|
313
|
+
<div v-bind="beforeTitleAttrs">
|
|
314
|
+
<!-- @slot Use it to add something before the header title. -->
|
|
315
|
+
<slot name="before-title" />
|
|
316
|
+
<!--
|
|
317
|
+
@slot Use it to add something to the left side of the header.
|
|
318
|
+
@binding {string} title
|
|
319
|
+
-->
|
|
320
|
+
<slot name="title" :title="title">
|
|
321
|
+
<div v-bind="titleFallbackAttrs">
|
|
322
|
+
<UHeader :label="title" size="sm" v-bind="titleAttrs" />
|
|
323
|
+
<div v-if="description" v-bind="descriptionAttrs" v-text="description" />
|
|
324
|
+
</div>
|
|
325
|
+
</slot>
|
|
326
|
+
<!-- @slot Use it to add something after the header title. -->
|
|
327
|
+
<slot name="after-title" />
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<!--
|
|
331
|
+
@slot Use it to add something instead of the close button.
|
|
332
|
+
@binding {function} close
|
|
333
|
+
-->
|
|
334
|
+
<slot name="actions" :close="closeDrawer" />
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div v-bind="bodyAttrs">
|
|
338
|
+
<!-- @slot Use it to add something into the drawer body. -->
|
|
339
|
+
<slot />
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div v-if="isExistFooter" v-bind="footerAttrs">
|
|
343
|
+
<div v-if="hasSlotContent($slots['footer-left'])" v-bind="footerLeftAttrs">
|
|
344
|
+
<!-- @slot Use it to add something to the left side of the footer. -->
|
|
345
|
+
<slot name="footer-left" />
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
<div v-if="hasSlotContent($slots['footer-right'])" v-bind="footerRightAttrs">
|
|
349
|
+
<!-- @slot Use it to add something to the right side of the footer. -->
|
|
350
|
+
<slot name="footer-right" />
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<div v-if="handle" v-bind="handleWrapperAttrs">
|
|
356
|
+
<!-- @slot Use it to add something instead of the default handle. -->
|
|
357
|
+
<slot name="handle">
|
|
358
|
+
<div v-bind="handleAttrs" />
|
|
359
|
+
</slot>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</Transition>
|
|
365
|
+
</template>
|