vueless 1.2.10-beta.1 → 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.
Files changed (34) hide show
  1. package/composables/useRequestQueue.ts +47 -0
  2. package/constants.d.ts +1 -0
  3. package/constants.js +1 -0
  4. package/icons/storybook/arrow_forward_ios.svg +1 -0
  5. package/index.d.ts +4 -1
  6. package/index.ts +4 -1
  7. package/package.json +2 -2
  8. package/types.ts +2 -0
  9. package/ui.button/UButton.vue +2 -6
  10. package/ui.button/config.ts +5 -0
  11. package/ui.button/tests/UButton.test.ts +1 -1
  12. package/ui.container-drawer/UDrawer.vue +365 -0
  13. package/ui.container-drawer/config.ts +128 -0
  14. package/ui.container-drawer/constants.ts +5 -0
  15. package/ui.container-drawer/storybook/docs.mdx +16 -0
  16. package/ui.container-drawer/storybook/stories.ts +255 -0
  17. package/ui.container-drawer/tests/UDrawer.test.ts +680 -0
  18. package/ui.container-drawer/types.ts +67 -0
  19. package/ui.container-modal/storybook/stories.ts +4 -1
  20. package/ui.container-modal-confirm/storybook/stories.ts +29 -24
  21. package/ui.form-calendar/UCalendarMonthView.vue +4 -4
  22. package/ui.form-calendar/tests/UCalendar.test.ts +8 -8
  23. package/ui.form-calendar/tests/UCalendarMonthView.test.ts +1 -1
  24. package/ui.form-calendar/utilCalendar.ts +2 -2
  25. package/ui.form-date-picker-range/UDatePickerRangeInputs.vue +20 -19
  26. package/ui.loader-overlay/useLoaderOverlay.ts +4 -4
  27. package/ui.loader-progress/ULoaderProgress.vue +41 -46
  28. package/ui.loader-progress/storybook/docs.mdx +24 -13
  29. package/ui.loader-progress/storybook/stories.ts +0 -9
  30. package/ui.loader-progress/types.ts +2 -2
  31. package/ui.loader-progress/useLoaderProgress.ts +36 -26
  32. package/ui.loader-progress/utilLoaderProgress.ts +12 -18
  33. package/ui.navigation-tabs/UTabs.vue +0 -1
  34. 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
@@ -186,6 +186,7 @@ export namespace COMPONENTS {
186
186
  let UModal: string;
187
187
  let UModalConfirm: string;
188
188
  let UPage: string;
189
+ let UDrawer: string;
189
190
  let UIcon: string;
190
191
  let UAvatar: string;
191
192
  let UTable: string;
package/constants.js CHANGED
@@ -296,6 +296,7 @@ export const COMPONENTS = {
296
296
  UModal: "ui.container-modal",
297
297
  UModalConfirm: "ui.container-modal-confirm",
298
298
  UPage: "ui.container-page",
299
+ UDrawer: "ui.container-drawer",
299
300
 
300
301
  /* Images and Icons */
301
302
  UIcon: "ui.image-icon",
@@ -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.1",
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.5",
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>;
@@ -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>
@@ -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>