vueless 1.3.7-beta.9 → 1.3.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugin-vite.js +3 -6
- package/ui.container-card/config.ts +6 -1
- package/ui.container-card/storybook/stories.ts +11 -0
- package/ui.container-card/tests/UCard.test.ts +33 -0
- package/ui.container-card/types.ts +5 -0
- package/ui.container-drawer/UDrawer.vue +14 -2
- package/ui.container-drawer/config.ts +7 -7
- package/ui.container-drawer/storybook/stories.ts +13 -4
- package/ui.container-drawer/tests/UDrawer.test.ts +23 -0
- package/ui.container-empty/UEmpty.vue +14 -3
- package/ui.container-empty/config.ts +7 -24
- package/ui.container-empty/storybook/stories.ts +1 -3
- package/ui.container-empty/tests/UEmpty.test.ts +2 -2
- package/ui.container-modal/UModal.vue +14 -2
- package/ui.container-modal/config.ts +1 -1
- package/ui.container-modal/tests/UModal.test.ts +26 -3
- package/ui.image-icon/UIcon.vue +5 -7
- package/ui.navigation-pagination/UPagination.vue +7 -1
- package/ui.navigation-pagination/tests/UPagination.test.ts +23 -0
- package/ui.text-key/storybook/docs.mdx +16 -0
package/package.json
CHANGED
package/plugin-vite.js
CHANGED
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
JAVASCRIPT_EXT,
|
|
34
34
|
TYPESCRIPT_EXT,
|
|
35
35
|
INTERNAL_ENV,
|
|
36
|
-
STORYBOOK_ENV,
|
|
37
36
|
NUXT_MODULE_ENV,
|
|
38
37
|
VUELESS_LOCAL_DIR,
|
|
39
38
|
VUELESS_PACKAGE_DIR,
|
|
@@ -67,7 +66,6 @@ export const Vueless = function (options = {}) {
|
|
|
67
66
|
const { debug, env, include, basePath } = options;
|
|
68
67
|
|
|
69
68
|
const isInternalEnv = env === INTERNAL_ENV;
|
|
70
|
-
const isStorybookEnv = env === STORYBOOK_ENV;
|
|
71
69
|
const isNuxtModuleEnv = env === NUXT_MODULE_ENV;
|
|
72
70
|
|
|
73
71
|
const vuelessSrcDir = isInternalEnv ? VUELESS_LOCAL_DIR : VUELESS_PACKAGE_DIR;
|
|
@@ -83,10 +81,9 @@ export const Vueless = function (options = {}) {
|
|
|
83
81
|
|
|
84
82
|
/* if server stopped by developer (Ctrl+C) */
|
|
85
83
|
process.on("SIGINT", async () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
84
|
+
/* remove `.cache` folder in components and restore changes */
|
|
85
|
+
await removeCustomPropTypes(vuelessSrcDir);
|
|
86
|
+
await restoreComponents(vuelessSrcDir);
|
|
90
87
|
|
|
91
88
|
/* remove cached icons */
|
|
92
89
|
await removeIconsCache(basePath);
|
|
@@ -17,7 +17,7 @@ export default /*tw*/ {
|
|
|
17
17
|
title: "{UHeader}",
|
|
18
18
|
description: "mt-0.5 font-normal text-lifted",
|
|
19
19
|
footer: {
|
|
20
|
-
base: "flex justify-between w-full border-
|
|
20
|
+
base: "flex justify-between w-full border-muted mt-6 pt-4 md:pt-6",
|
|
21
21
|
variants: {
|
|
22
22
|
variant: {
|
|
23
23
|
solid: "border-muted",
|
|
@@ -26,11 +26,16 @@ export default /*tw*/ {
|
|
|
26
26
|
soft: "border-default/50",
|
|
27
27
|
inverted: "border-default/50",
|
|
28
28
|
},
|
|
29
|
+
divided: {
|
|
30
|
+
true: "border-t",
|
|
31
|
+
false: "mt-0",
|
|
32
|
+
},
|
|
29
33
|
},
|
|
30
34
|
},
|
|
31
35
|
footerLeft: "",
|
|
32
36
|
footerRight: "",
|
|
33
37
|
defaults: {
|
|
34
38
|
variant: "outlined",
|
|
39
|
+
divided: true,
|
|
35
40
|
},
|
|
36
41
|
};
|
|
@@ -128,6 +128,17 @@ Description.args = {
|
|
|
128
128
|
export const Variants = EnumTemplate.bind({});
|
|
129
129
|
Variants.args = { enum: "variant", title: "{enumValue}" };
|
|
130
130
|
|
|
131
|
+
export const NoDivided = DefaultTemplate.bind({});
|
|
132
|
+
NoDivided.args = {
|
|
133
|
+
divided: false,
|
|
134
|
+
slotTemplate: `
|
|
135
|
+
<template #footer-left>
|
|
136
|
+
<UButton size="sm" label="Save" variant="subtle" />
|
|
137
|
+
</template>
|
|
138
|
+
${defaultTemplate}
|
|
139
|
+
`,
|
|
140
|
+
};
|
|
141
|
+
|
|
131
142
|
export const BeforeTitleSlot = DefaultTemplate.bind({});
|
|
132
143
|
BeforeTitleSlot.args = {
|
|
133
144
|
slotTemplate: `
|
|
@@ -62,6 +62,39 @@ describe("UCard", () => {
|
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it("Divided – shows divider between content and footer when divided is true", () => {
|
|
66
|
+
const component = mount(UCard, {
|
|
67
|
+
props: {
|
|
68
|
+
divided: true,
|
|
69
|
+
},
|
|
70
|
+
slots: {
|
|
71
|
+
"footer-left": "<div>Footer Content</div>",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const footer = component.find("[vl-key='footer']");
|
|
76
|
+
|
|
77
|
+
expect(footer.exists()).toBe(true);
|
|
78
|
+
expect(footer.attributes("class")).toContain("border-t");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("Divided – hides divider between content and footer when divided is false", () => {
|
|
82
|
+
const component = mount(UCard, {
|
|
83
|
+
props: {
|
|
84
|
+
divided: false,
|
|
85
|
+
},
|
|
86
|
+
slots: {
|
|
87
|
+
"footer-left": "<div>Footer Content</div>",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const footer = component.find("[vl-key='footer']");
|
|
92
|
+
|
|
93
|
+
expect(footer.exists()).toBe(true);
|
|
94
|
+
expect(footer.attributes("class")).not.toContain("border-t");
|
|
95
|
+
expect(footer.attributes("class")).toContain("mt-0");
|
|
96
|
+
});
|
|
97
|
+
|
|
65
98
|
it("Data Test – applies data-test attribute", () => {
|
|
66
99
|
const dataTest = "card-test";
|
|
67
100
|
|
|
@@ -47,6 +47,7 @@ const isDraggingFromHandle = ref(false);
|
|
|
47
47
|
const dragStartPosition = ref({ x: 0, y: 0 });
|
|
48
48
|
const dragCurrentPosition = ref({ x: 0, y: 0 });
|
|
49
49
|
const minDragDistance = 10;
|
|
50
|
+
const isMouseDownOnOverlay = ref(false);
|
|
50
51
|
|
|
51
52
|
const isShownDrawer = computed({
|
|
52
53
|
get: () => props.modelValue,
|
|
@@ -180,8 +181,15 @@ function toggleEventListeners() {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
function onMouseDownOverlay() {
|
|
185
|
+
isMouseDownOnOverlay.value = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
183
188
|
function onClickOutside() {
|
|
184
|
-
props.closeOnOverlay
|
|
189
|
+
if (!props.closeOnOverlay || !isMouseDownOnOverlay.value) return;
|
|
190
|
+
|
|
191
|
+
closeDrawer();
|
|
192
|
+
isMouseDownOnOverlay.value = false;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
function onKeydownEsc(e: KeyboardEvent) {
|
|
@@ -312,7 +320,11 @@ const {
|
|
|
312
320
|
:data-test="getDataTest()"
|
|
313
321
|
@keydown.self.esc="onKeydownEsc"
|
|
314
322
|
>
|
|
315
|
-
<div
|
|
323
|
+
<div
|
|
324
|
+
v-bind="innerWrapperAttrs"
|
|
325
|
+
@mousedown.self="onMouseDownOverlay"
|
|
326
|
+
@click.self="onClickOutside"
|
|
327
|
+
>
|
|
316
328
|
<div
|
|
317
329
|
ref="drawer"
|
|
318
330
|
:style="{ transform: dragTransform }"
|
|
@@ -106,23 +106,23 @@ export default /*tw*/ {
|
|
|
106
106
|
base: "flex items-center justify-center bg-inherit cursor-grab active:cursor-grabbing select-none",
|
|
107
107
|
variants: {
|
|
108
108
|
position: {
|
|
109
|
-
top: "w-full h-
|
|
110
|
-
bottom: "w-full h-
|
|
111
|
-
left: "w-
|
|
112
|
-
right: "w-
|
|
109
|
+
top: "w-full h-6",
|
|
110
|
+
bottom: "w-full h-6",
|
|
111
|
+
left: "w-6 h-auto",
|
|
112
|
+
right: "w-6 h-auto",
|
|
113
113
|
},
|
|
114
114
|
},
|
|
115
115
|
},
|
|
116
116
|
handle: {
|
|
117
117
|
base: "rounded-large cursor-grab active:cursor-grabbing bg-lifted hover:bg-accented transition",
|
|
118
118
|
compoundVariants: [
|
|
119
|
-
{ position: ["top", "bottom"], class: "w-
|
|
120
|
-
{ position: ["left", "right"], class: "w-1
|
|
119
|
+
{ position: ["top", "bottom"], class: "w-10 h-1" },
|
|
120
|
+
{ position: ["left", "right"], class: "w-1 h-10" },
|
|
121
121
|
],
|
|
122
122
|
},
|
|
123
123
|
defaults: {
|
|
124
124
|
variant: "solid",
|
|
125
|
-
position: "
|
|
125
|
+
position: "right",
|
|
126
126
|
inset: false,
|
|
127
127
|
handle: true,
|
|
128
128
|
closeOnEsc: true,
|
|
@@ -137,27 +137,29 @@ const EnumTemplate: StoryFn<UDrawerArgs> = (args: UDrawerArgs, { argTypes }) =>
|
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
export const Default = DefaultTemplate.bind({});
|
|
140
|
-
Default.args = {};
|
|
140
|
+
Default.args = { modelValue: true };
|
|
141
141
|
|
|
142
142
|
export const Description = DefaultTemplate.bind({});
|
|
143
143
|
Description.args = {
|
|
144
|
+
modelValue: true,
|
|
144
145
|
description: "Enter your email below to get started and create your account.",
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
export const NoHandle = DefaultTemplate.bind({});
|
|
148
|
-
NoHandle.args = { handle: false };
|
|
149
|
+
NoHandle.args = { modelValue: true, handle: false };
|
|
149
150
|
|
|
150
151
|
export const Inset = DefaultTemplate.bind({});
|
|
151
|
-
Inset.args = { inset: true };
|
|
152
|
+
Inset.args = { modelValue: true, inset: true };
|
|
152
153
|
|
|
153
154
|
export const NoCloseOnEscAndOverlay = DefaultTemplate.bind({});
|
|
154
155
|
NoCloseOnEscAndOverlay.args = {
|
|
156
|
+
modelValue: true,
|
|
155
157
|
closeOnEsc: false,
|
|
156
158
|
closeOnOverlay: false,
|
|
157
159
|
};
|
|
158
160
|
|
|
159
161
|
export const NoCloseOnCross = DefaultTemplate.bind({});
|
|
160
|
-
NoCloseOnCross.args = { closeOnCross: false };
|
|
162
|
+
NoCloseOnCross.args = { modelValue: true, closeOnCross: false };
|
|
161
163
|
|
|
162
164
|
export const Position = EnumTemplate.bind({});
|
|
163
165
|
Position.args = { enum: "position", modelValues: {} };
|
|
@@ -167,6 +169,7 @@ Variants.args = { enum: "variant", modelValues: {} };
|
|
|
167
169
|
|
|
168
170
|
export const BeforeTitleSlot = DefaultTemplate.bind({});
|
|
169
171
|
BeforeTitleSlot.args = {
|
|
172
|
+
modelValue: true,
|
|
170
173
|
slotTemplate: `
|
|
171
174
|
<template #before-title>
|
|
172
175
|
<UIcon name="account_circle" size="sm" color="primary" />
|
|
@@ -179,6 +182,7 @@ BeforeTitleSlot.args = {
|
|
|
179
182
|
|
|
180
183
|
export const TitleSlot = DefaultTemplate.bind({});
|
|
181
184
|
TitleSlot.args = {
|
|
185
|
+
modelValue: true,
|
|
182
186
|
slotTemplate: `
|
|
183
187
|
<template #title="{ title }">
|
|
184
188
|
<UHeader :label="title" color="primary" />
|
|
@@ -191,6 +195,7 @@ TitleSlot.args = {
|
|
|
191
195
|
|
|
192
196
|
export const AfterTitleSlot = DefaultTemplate.bind({});
|
|
193
197
|
AfterTitleSlot.args = {
|
|
198
|
+
modelValue: true,
|
|
194
199
|
slotTemplate: `
|
|
195
200
|
<template #after-title>
|
|
196
201
|
<UIcon name="verified" size="sm" color="primary" />
|
|
@@ -203,6 +208,7 @@ AfterTitleSlot.args = {
|
|
|
203
208
|
|
|
204
209
|
export const ActionsSlot = DefaultTemplate.bind({});
|
|
205
210
|
ActionsSlot.args = {
|
|
211
|
+
modelValue: true,
|
|
206
212
|
slotTemplate: `
|
|
207
213
|
<template #actions="{ close }">
|
|
208
214
|
<UButton
|
|
@@ -221,6 +227,7 @@ ActionsSlot.args = {
|
|
|
221
227
|
|
|
222
228
|
export const HandleSlot = DefaultTemplate.bind({});
|
|
223
229
|
HandleSlot.args = {
|
|
230
|
+
modelValue: true,
|
|
224
231
|
slotTemplate: `
|
|
225
232
|
<template #default>
|
|
226
233
|
${defaultTemplate}
|
|
@@ -233,6 +240,7 @@ HandleSlot.args = {
|
|
|
233
240
|
|
|
234
241
|
export const FooterLeftSlot = DefaultTemplate.bind({});
|
|
235
242
|
FooterLeftSlot.args = {
|
|
243
|
+
modelValue: true,
|
|
236
244
|
slotTemplate: `
|
|
237
245
|
<template #default>
|
|
238
246
|
${defaultTemplate}
|
|
@@ -246,6 +254,7 @@ FooterLeftSlot.args = {
|
|
|
246
254
|
|
|
247
255
|
export const FooterRightSlot = DefaultTemplate.bind({});
|
|
248
256
|
FooterRightSlot.args = {
|
|
257
|
+
modelValue: true,
|
|
249
258
|
slotTemplate: `
|
|
250
259
|
<template #default>
|
|
251
260
|
${defaultTemplate}
|
|
@@ -165,6 +165,7 @@ describe("UDrawer", () => {
|
|
|
165
165
|
|
|
166
166
|
expect(innerWrapper.exists()).toBe(true);
|
|
167
167
|
|
|
168
|
+
await innerWrapper.trigger("mousedown");
|
|
168
169
|
await innerWrapper.trigger("click");
|
|
169
170
|
await sleep(500);
|
|
170
171
|
|
|
@@ -537,6 +538,7 @@ describe("UDrawer", () => {
|
|
|
537
538
|
|
|
538
539
|
const innerWrapper = component.find("[vl-key='innerWrapper']");
|
|
539
540
|
|
|
541
|
+
await innerWrapper.trigger("mousedown");
|
|
540
542
|
await innerWrapper.trigger("click");
|
|
541
543
|
|
|
542
544
|
if (value) {
|
|
@@ -550,6 +552,27 @@ describe("UDrawer", () => {
|
|
|
550
552
|
});
|
|
551
553
|
});
|
|
552
554
|
|
|
555
|
+
it("CloseOnOverlay – does not close when mousedown on drawer and mouseup on overlay", async () => {
|
|
556
|
+
const component = mount(UDrawer, {
|
|
557
|
+
props: {
|
|
558
|
+
modelValue: true,
|
|
559
|
+
closeOnOverlay: true,
|
|
560
|
+
},
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const drawer = component.find("[vl-key='drawer']");
|
|
564
|
+
const innerWrapper = component.find("[vl-key='innerWrapper']");
|
|
565
|
+
|
|
566
|
+
// Mousedown on drawer content
|
|
567
|
+
await drawer.trigger("mousedown");
|
|
568
|
+
// Click (mouseup) on overlay
|
|
569
|
+
await innerWrapper.trigger("click");
|
|
570
|
+
|
|
571
|
+
// Drawer should NOT close
|
|
572
|
+
expect(component.emitted("update:modelValue")).toBeFalsy();
|
|
573
|
+
expect(component.emitted("close")).toBeFalsy();
|
|
574
|
+
});
|
|
575
|
+
|
|
553
576
|
it("CloseOnEsc – emits events when escape key is pressed based on prop", () => {
|
|
554
577
|
const closeOnEsc = [true, false];
|
|
555
578
|
|
|
@@ -11,6 +11,8 @@ import { COMPONENT_NAME } from "./constants";
|
|
|
11
11
|
import defaultConfig from "./config";
|
|
12
12
|
|
|
13
13
|
import type { Props, Config } from "./types";
|
|
14
|
+
import { hasSlotContent } from "../utils/helper";
|
|
15
|
+
import UText from "../ui.text-block/UText.vue";
|
|
14
16
|
|
|
15
17
|
defineOptions({ inheritAttrs: false });
|
|
16
18
|
|
|
@@ -47,6 +49,7 @@ const {
|
|
|
47
49
|
descriptionAttrs,
|
|
48
50
|
wrapperAttrs,
|
|
49
51
|
headerAttrs,
|
|
52
|
+
contentAttrs,
|
|
50
53
|
footerAttrs,
|
|
51
54
|
emptyIconWrapperAttrs,
|
|
52
55
|
emptyIconAttrs,
|
|
@@ -73,11 +76,19 @@ const {
|
|
|
73
76
|
@binding {string} description
|
|
74
77
|
-->
|
|
75
78
|
<slot :title="title" :description="description">
|
|
76
|
-
<
|
|
77
|
-
|
|
79
|
+
<div v-bind="contentAttrs">
|
|
80
|
+
<UHeader v-if="title" :label="title" v-bind="titleAttrs" />
|
|
81
|
+
<UText
|
|
82
|
+
v-if="description"
|
|
83
|
+
align="center"
|
|
84
|
+
:size="size"
|
|
85
|
+
:label="description"
|
|
86
|
+
v-bind="descriptionAttrs"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
78
89
|
</slot>
|
|
79
90
|
|
|
80
|
-
<div v-bind="footerAttrs">
|
|
91
|
+
<div v-if="hasSlotContent($slots['footer'])" v-bind="footerAttrs">
|
|
81
92
|
<!-- @slot Use it to add something to the footer. -->
|
|
82
93
|
<slot name="footer" />
|
|
83
94
|
</div>
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
export default /*tw*/ {
|
|
2
|
-
wrapper: "flex flex-col items-center justify-center size-full",
|
|
3
|
-
header:
|
|
4
|
-
base: "flex justify-center",
|
|
5
|
-
variants: {
|
|
6
|
-
size: {
|
|
7
|
-
sm: "mb-4",
|
|
8
|
-
md: "mb-5",
|
|
9
|
-
lg: "mb-6",
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
},
|
|
2
|
+
wrapper: "flex flex-col items-center justify-center gap-4 size-full",
|
|
3
|
+
header: "flex justify-center",
|
|
13
4
|
emptyIconWrapper: {
|
|
14
5
|
base: "rounded-full bg-inverted/5",
|
|
15
6
|
variants: {
|
|
@@ -24,12 +15,13 @@ export default /*tw*/ {
|
|
|
24
15
|
base: "{UIcon}",
|
|
25
16
|
defaults: {
|
|
26
17
|
size: {
|
|
27
|
-
sm: "
|
|
18
|
+
sm: "xl",
|
|
28
19
|
md: "3xl",
|
|
29
|
-
lg: "
|
|
20
|
+
lg: "5xl",
|
|
30
21
|
},
|
|
31
22
|
},
|
|
32
23
|
},
|
|
24
|
+
content: "flex flex-col items-center justify-center gap-1.5",
|
|
33
25
|
title: {
|
|
34
26
|
base: "{UHeader}",
|
|
35
27
|
defaults: {
|
|
@@ -40,17 +32,8 @@ export default /*tw*/ {
|
|
|
40
32
|
},
|
|
41
33
|
},
|
|
42
34
|
},
|
|
43
|
-
description: {
|
|
44
|
-
|
|
45
|
-
variants: {
|
|
46
|
-
size: {
|
|
47
|
-
sm: "text-small",
|
|
48
|
-
md: "text-medium",
|
|
49
|
-
lg: "text-large",
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
footer: "mt-4 flex justify-center",
|
|
35
|
+
description: "{UText}",
|
|
36
|
+
footer: "flex justify-center",
|
|
54
37
|
defaults: {
|
|
55
38
|
size: "md",
|
|
56
39
|
/* icons */
|
|
@@ -30,6 +30,7 @@ export default {
|
|
|
30
30
|
component: UEmpty,
|
|
31
31
|
args: {
|
|
32
32
|
title: "No contacts",
|
|
33
|
+
description: "There are no contacts in the list.",
|
|
33
34
|
},
|
|
34
35
|
argTypes: {
|
|
35
36
|
...getArgTypes(UEmpty.__name),
|
|
@@ -68,9 +69,6 @@ const EnumTemplate: StoryFn<UEmptyArgs> = (args: UEmptyArgs, { argTypes }) => ({
|
|
|
68
69
|
export const Default = DefaultTemplate.bind({});
|
|
69
70
|
Default.args = {};
|
|
70
71
|
|
|
71
|
-
export const Description = DefaultTemplate.bind({});
|
|
72
|
-
Description.args = { description: "There are no contacts in the list." };
|
|
73
|
-
|
|
74
72
|
export const Sizes = EnumTemplate.bind({});
|
|
75
73
|
Sizes.args = { enum: "size" };
|
|
76
74
|
|
|
@@ -11,9 +11,9 @@ describe("UEmpty.vue", () => {
|
|
|
11
11
|
describe("Props", () => {
|
|
12
12
|
it("Size – applies the correct size class", async () => {
|
|
13
13
|
const size = {
|
|
14
|
-
sm: "
|
|
14
|
+
sm: "xl",
|
|
15
15
|
md: "3xl",
|
|
16
|
-
lg: "
|
|
16
|
+
lg: "5xl",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
Object.entries(size).forEach(([size, value]) => {
|
|
@@ -49,6 +49,7 @@ const slots = useSlots();
|
|
|
49
49
|
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
50
50
|
|
|
51
51
|
const isWrapperTransitionComplete = ref(false);
|
|
52
|
+
const isMouseDownOnOverlay = ref(false);
|
|
52
53
|
|
|
53
54
|
const isShownModal = computed({
|
|
54
55
|
get: () => props.modelValue,
|
|
@@ -153,8 +154,15 @@ function onClickBackLink() {
|
|
|
153
154
|
emit("back");
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
function onMouseDownOverlay() {
|
|
158
|
+
isMouseDownOnOverlay.value = true;
|
|
159
|
+
}
|
|
160
|
+
|
|
156
161
|
function onClickOutside() {
|
|
157
|
-
props.closeOnOverlay
|
|
162
|
+
if (!props.closeOnOverlay || !isMouseDownOnOverlay.value) return;
|
|
163
|
+
|
|
164
|
+
closeModal();
|
|
165
|
+
isMouseDownOnOverlay.value = false;
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
function onKeydownEsc(e: KeyboardEvent) {
|
|
@@ -229,7 +237,11 @@ const {
|
|
|
229
237
|
:data-test="getDataTest()"
|
|
230
238
|
@keydown.self.esc="onKeydownEsc"
|
|
231
239
|
>
|
|
232
|
-
<div
|
|
240
|
+
<div
|
|
241
|
+
v-bind="innerWrapperAttrs"
|
|
242
|
+
@mousedown.self="onMouseDownOverlay"
|
|
243
|
+
@click.self="onClickOutside"
|
|
244
|
+
>
|
|
233
245
|
<div v-bind="modalAttrs">
|
|
234
246
|
<div v-if="isExistHeader" v-bind="headerAttrs">
|
|
235
247
|
<div v-bind="beforeTitleAttrs">
|
|
@@ -18,7 +18,7 @@ export default /*tw*/ {
|
|
|
18
18
|
leaveToClass: "opacity-0",
|
|
19
19
|
},
|
|
20
20
|
innerWrapper: {
|
|
21
|
-
base: "py-12 w-full h-
|
|
21
|
+
base: "py-12 w-full h-max scroll-container [scrollbar-gutter:stable]",
|
|
22
22
|
variants: {
|
|
23
23
|
wrapperTransitionCompleted: {
|
|
24
24
|
true: "overflow-y-auto",
|
|
@@ -200,11 +200,12 @@ describe("UModal", () => {
|
|
|
200
200
|
},
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
const
|
|
203
|
+
const innerWrapper = component.find('[vl-key="innerWrapper"]');
|
|
204
204
|
|
|
205
|
-
expect(
|
|
205
|
+
expect(innerWrapper.exists()).toBe(true);
|
|
206
206
|
|
|
207
|
-
await
|
|
207
|
+
await innerWrapper.trigger("mousedown");
|
|
208
|
+
await innerWrapper.trigger("click");
|
|
208
209
|
await sleep(500);
|
|
209
210
|
|
|
210
211
|
const modal = component.find('[vl-key="modal"]');
|
|
@@ -590,6 +591,7 @@ describe("UModal", () => {
|
|
|
590
591
|
|
|
591
592
|
const innerWrapper = component.find("[vl-key='innerWrapper']");
|
|
592
593
|
|
|
594
|
+
await innerWrapper.trigger("mousedown");
|
|
593
595
|
await innerWrapper.trigger("click");
|
|
594
596
|
|
|
595
597
|
if (value) {
|
|
@@ -603,6 +605,27 @@ describe("UModal", () => {
|
|
|
603
605
|
});
|
|
604
606
|
});
|
|
605
607
|
|
|
608
|
+
it("does not close when mousedown on modal and mouseup on overlay", async () => {
|
|
609
|
+
const component = mount(UModal, {
|
|
610
|
+
props: {
|
|
611
|
+
modelValue: true,
|
|
612
|
+
closeOnOverlay: true,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const modal = component.find("[vl-key='modal']");
|
|
617
|
+
const innerWrapper = component.find("[vl-key='innerWrapper']");
|
|
618
|
+
|
|
619
|
+
// Mousedown on modal content
|
|
620
|
+
await modal.trigger("mousedown");
|
|
621
|
+
// Click (mouseup) on overlay
|
|
622
|
+
await innerWrapper.trigger("click");
|
|
623
|
+
|
|
624
|
+
// Modal should NOT close
|
|
625
|
+
expect(component.emitted("update:modelValue")).toBeFalsy();
|
|
626
|
+
expect(component.emitted("close")).toBeFalsy();
|
|
627
|
+
});
|
|
628
|
+
|
|
606
629
|
// CloseOnEsc events
|
|
607
630
|
it("emits events when escape key is pressed based on closeOnEsc prop", () => {
|
|
608
631
|
const closeOnEsc = [true, false];
|
package/ui.image-icon/UIcon.vue
CHANGED
|
@@ -4,7 +4,7 @@ import { cachedIcons } from "virtual:vueless/icons";
|
|
|
4
4
|
|
|
5
5
|
import { useUI } from "../composables/useUI";
|
|
6
6
|
import { getDefaults } from "../utils/ui";
|
|
7
|
-
import {
|
|
7
|
+
import { INTERNAL_ICONS_LIBRARY, STORYBOOK_ICONS_LIBRARY } from "../constants";
|
|
8
8
|
|
|
9
9
|
import { COMPONENT_NAME } from "./constants";
|
|
10
10
|
import defaultConfig from "./config";
|
|
@@ -33,15 +33,15 @@ const dynamicComponent = computed(() => {
|
|
|
33
33
|
let userLibrary = config.value.defaults.library;
|
|
34
34
|
|
|
35
35
|
const isInternalIconExists = cachedIcons.find(([path]: [string]) =>
|
|
36
|
-
path.includes(`${
|
|
36
|
+
path.includes(`${INTERNAL_ICONS_LIBRARY}/${props.name}.svg`),
|
|
37
37
|
);
|
|
38
38
|
|
|
39
39
|
const isStorybookIconExists = cachedIcons.find(([path]: [string]) =>
|
|
40
|
-
path.includes(`${
|
|
40
|
+
path.includes(`${STORYBOOK_ICONS_LIBRARY}/${props.name}.svg`),
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
const isExternalIconExists = cachedIcons.find(([path]: [string]) =>
|
|
44
|
-
path.includes(`${
|
|
44
|
+
path.includes(`${userLibrary}/${props.name}.svg`),
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
if (isInternalIconExists && !isExternalIconExists) {
|
|
@@ -67,9 +67,7 @@ const dynamicComponent = computed(() => {
|
|
|
67
67
|
if (!name) return "";
|
|
68
68
|
|
|
69
69
|
const [, component] =
|
|
70
|
-
cachedIcons.find(([path]: [string]) =>
|
|
71
|
-
path.includes(`${ICONS_CACHED_DIR}/${userLibrary}/${props.name}.svg`),
|
|
72
|
-
) || [];
|
|
70
|
+
cachedIcons.find(([path]: [string]) => path.includes(`${userLibrary}/${props.name}.svg`)) || [];
|
|
73
71
|
|
|
74
72
|
if (!component) return "";
|
|
75
73
|
|
|
@@ -27,10 +27,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
27
27
|
|
|
28
28
|
const emit = defineEmits([
|
|
29
29
|
/**
|
|
30
|
-
* Triggers when current page changes.
|
|
30
|
+
* Triggers when the current page changes.
|
|
31
31
|
* @property {number} value
|
|
32
32
|
*/
|
|
33
33
|
"update:modelValue",
|
|
34
|
+
/**
|
|
35
|
+
* Triggers when pagination is changed.
|
|
36
|
+
* @property {number} value
|
|
37
|
+
*/
|
|
38
|
+
"change",
|
|
34
39
|
]);
|
|
35
40
|
|
|
36
41
|
const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>(
|
|
@@ -45,6 +50,7 @@ const currentPage = computed({
|
|
|
45
50
|
get: () => props.modelValue,
|
|
46
51
|
set: (value) => {
|
|
47
52
|
emit("update:modelValue", value);
|
|
53
|
+
emit("change", value);
|
|
48
54
|
},
|
|
49
55
|
});
|
|
50
56
|
|
|
@@ -378,6 +378,29 @@ describe("UPagination.vue", () => {
|
|
|
378
378
|
await buttons[buttons.length - 1].trigger("click");
|
|
379
379
|
expect(component.emitted("update:modelValue")?.[3]).toEqual([10]);
|
|
380
380
|
});
|
|
381
|
+
|
|
382
|
+
it("Change – emits change event when page is changed", async () => {
|
|
383
|
+
const component = mount(UPagination, {
|
|
384
|
+
props: {
|
|
385
|
+
modelValue: 1,
|
|
386
|
+
total: 100,
|
|
387
|
+
perPage: 10,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Find the second page button and click it
|
|
392
|
+
const pageButtons = component.findAllComponents(UButton).filter((button) => {
|
|
393
|
+
const text = button.text();
|
|
394
|
+
|
|
395
|
+
return text && !isNaN(Number(text));
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Second button
|
|
399
|
+
await pageButtons[1].trigger("click");
|
|
400
|
+
|
|
401
|
+
expect(component.emitted("change")).toBeTruthy();
|
|
402
|
+
expect(component.emitted("change")?.[0]).toEqual([2]); // Second button value
|
|
403
|
+
});
|
|
381
404
|
});
|
|
382
405
|
|
|
383
406
|
describe("Exposed refs", () => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
|
|
2
|
+
import { getSource } from "../../utils/storybook";
|
|
3
|
+
|
|
4
|
+
import * as stories from "./stories";
|
|
5
|
+
import defaultConfig from "../config?raw"
|
|
6
|
+
|
|
7
|
+
<Meta of={stories} />
|
|
8
|
+
<Title of={stories} />
|
|
9
|
+
<Subtitle of={stories} />
|
|
10
|
+
<Description of={stories} />
|
|
11
|
+
<Primary of={stories} />
|
|
12
|
+
<Controls of={stories.Default} />
|
|
13
|
+
<Stories of={stories} />
|
|
14
|
+
|
|
15
|
+
## Default config
|
|
16
|
+
<Source code={getSource(defaultConfig)} language="jsx" dark />
|