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.
- 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,67 @@
|
|
|
1
|
+
import defaultConfig from "./config";
|
|
2
|
+
|
|
3
|
+
import type { ComponentConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export type Config = typeof defaultConfig;
|
|
6
|
+
|
|
7
|
+
export interface Props {
|
|
8
|
+
/**
|
|
9
|
+
* Drawer state (shown / hidden).
|
|
10
|
+
*/
|
|
11
|
+
modelValue?: boolean;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Drawer title.
|
|
15
|
+
*/
|
|
16
|
+
title?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Drawer description.
|
|
20
|
+
*/
|
|
21
|
+
description?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Drawer position.
|
|
25
|
+
*/
|
|
26
|
+
position?: "top" | "bottom" | "left" | "right";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Drawer variant.
|
|
30
|
+
*/
|
|
31
|
+
variant?: "solid" | "outlined" | "subtle" | "soft";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Control whether the Drawer has a handle or not.
|
|
35
|
+
*/
|
|
36
|
+
handle?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Inset the drawer from the edges.
|
|
40
|
+
*/
|
|
41
|
+
inset?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Allow closing drawer by clicking on overlay.
|
|
45
|
+
*/
|
|
46
|
+
closeOnOverlay?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Allow closing drawer by pressing escape (esc) on the keyboard.
|
|
50
|
+
*/
|
|
51
|
+
closeOnEsc?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Unique element id.
|
|
55
|
+
*/
|
|
56
|
+
id?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Component config object.
|
|
60
|
+
*/
|
|
61
|
+
config?: ComponentConfig<Config>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Data-test attribute for automated testing.
|
|
65
|
+
*/
|
|
66
|
+
dataTest?: string | null;
|
|
67
|
+
}
|
|
@@ -25,7 +25,7 @@ import type { UnknownObject } from "../../types";
|
|
|
25
25
|
|
|
26
26
|
interface UModalArgs extends Props {
|
|
27
27
|
slotTemplate?: string;
|
|
28
|
-
enum: "size";
|
|
28
|
+
enum: "size" | "variant";
|
|
29
29
|
modelValues?: UnknownObject;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -280,6 +280,9 @@ WithoutDivider.parameters = {
|
|
|
280
280
|
export const Sizes = EnumTemplate.bind({});
|
|
281
281
|
Sizes.args = { enum: "size", modelValues: {} };
|
|
282
282
|
|
|
283
|
+
export const Variant = EnumTemplate.bind({});
|
|
284
|
+
Variant.args = { enum: "variant", modelValues: {} };
|
|
285
|
+
|
|
283
286
|
export const BackLink: StoryFn<UModalArgs> = (args: UModalArgs) => ({
|
|
284
287
|
components: { UModal, UButton, UCheckbox, UCol, URow, UDivider, UInput, UInputPassword },
|
|
285
288
|
setup() {
|
|
@@ -23,7 +23,7 @@ import type { UnknownObject } from "../../types";
|
|
|
23
23
|
interface UModalConfirmArgs extends Props {
|
|
24
24
|
width: string;
|
|
25
25
|
slotTemplate?: string;
|
|
26
|
-
enum: "size" | "confirmColor";
|
|
26
|
+
enum: "size" | "variant" | "confirmColor";
|
|
27
27
|
modelValues?: UnknownObject;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -83,6 +83,30 @@ const DefaultTemplate: StoryFn<UModalConfirmArgs> = (args: UModalConfirmArgs) =>
|
|
|
83
83
|
`,
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
const EnumTemplate: StoryFn<UModalConfirmArgs> = (args: UModalConfirmArgs, { argTypes }) => ({
|
|
87
|
+
components: { UModalConfirm, UButton, URow },
|
|
88
|
+
setup: () => ({ args, argTypes, getArgs }),
|
|
89
|
+
template: `
|
|
90
|
+
<URow>
|
|
91
|
+
<UButton
|
|
92
|
+
v-for="option in argTypes?.[args.enum]?.options"
|
|
93
|
+
:key="option"
|
|
94
|
+
:label="option"
|
|
95
|
+
@click="args.modelValues[option] = !args.modelValues[option]"
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<UModalConfirm
|
|
99
|
+
v-for="option in argTypes?.[args.enum]?.options"
|
|
100
|
+
:key="option"
|
|
101
|
+
v-bind="getArgs(args, option)"
|
|
102
|
+
v-model="args.modelValues[option]"
|
|
103
|
+
>
|
|
104
|
+
${defaultTemplate}
|
|
105
|
+
</UModalConfirm>
|
|
106
|
+
</URow>
|
|
107
|
+
`,
|
|
108
|
+
});
|
|
109
|
+
|
|
86
110
|
export const Default = DefaultTemplate.bind({});
|
|
87
111
|
Default.args = {};
|
|
88
112
|
|
|
@@ -194,31 +218,12 @@ WithoutCancelButton.args = { cancelHidden: true };
|
|
|
194
218
|
export const DisableConfirmButton = DefaultTemplate.bind({});
|
|
195
219
|
DisableConfirmButton.args = { confirmDisabled: true };
|
|
196
220
|
|
|
197
|
-
export const Sizes
|
|
198
|
-
components: { UModalConfirm, UButton, URow },
|
|
199
|
-
setup: () => ({ args, argTypes, getArgs }),
|
|
200
|
-
template: `
|
|
201
|
-
<URow>
|
|
202
|
-
<UButton
|
|
203
|
-
v-for="option in argTypes?.[args.enum]?.options"
|
|
204
|
-
:key="option"
|
|
205
|
-
:label="option"
|
|
206
|
-
@click="args.modelValues[option] = !args.modelValues[option]"
|
|
207
|
-
/>
|
|
208
|
-
|
|
209
|
-
<UModalConfirm
|
|
210
|
-
v-for="option in argTypes?.[args.enum]?.options"
|
|
211
|
-
:key="option"
|
|
212
|
-
v-bind="getArgs(args, option)"
|
|
213
|
-
v-model="args.modelValues[option]"
|
|
214
|
-
>
|
|
215
|
-
${defaultTemplate}
|
|
216
|
-
</UModalConfirm>
|
|
217
|
-
</URow>
|
|
218
|
-
`,
|
|
219
|
-
});
|
|
221
|
+
export const Sizes = EnumTemplate.bind({});
|
|
220
222
|
Sizes.args = { enum: "size", modelValues: {} };
|
|
221
223
|
|
|
224
|
+
export const Variant = EnumTemplate.bind({});
|
|
225
|
+
Variant.args = { enum: "variant", modelValues: {} };
|
|
226
|
+
|
|
222
227
|
export const Colors: StoryFn<UModalConfirmArgs> = (args: UModalConfirmArgs, { argTypes }) => ({
|
|
223
228
|
components: { UModalConfirm, UButton, URow },
|
|
224
229
|
setup: () => ({ args, argTypes, getArgs }),
|
|
@@ -91,7 +91,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
91
91
|
size="md"
|
|
92
92
|
v-bind="selectedMonthAttrs"
|
|
93
93
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
94
|
-
:label="formatDate(month, '
|
|
94
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
95
95
|
@click="onClickMonth(month)"
|
|
96
96
|
@mousedown.prevent.capture
|
|
97
97
|
/>
|
|
@@ -102,7 +102,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
102
102
|
color="primary"
|
|
103
103
|
v-bind="currentMonthAttrs"
|
|
104
104
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
105
|
-
:label="formatDate(month, '
|
|
105
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
106
106
|
@click="onClickMonth(month)"
|
|
107
107
|
@mousedown.prevent.capture
|
|
108
108
|
/>
|
|
@@ -114,7 +114,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
114
114
|
size="md"
|
|
115
115
|
v-bind="activeMonthAttrs"
|
|
116
116
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
117
|
-
:label="formatDate(month, '
|
|
117
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
118
118
|
@click="onClickMonth(month)"
|
|
119
119
|
@mousedown.prevent.capture
|
|
120
120
|
/>
|
|
@@ -126,7 +126,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
126
126
|
size="md"
|
|
127
127
|
v-bind="monthAttrs"
|
|
128
128
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
129
|
-
:label="formatDate(month, '
|
|
129
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
130
130
|
@click="onClickMonth(month)"
|
|
131
131
|
@mousedown.prevent.capture
|
|
132
132
|
/>
|
|
@@ -375,7 +375,7 @@ describe("UCalendar.vue", () => {
|
|
|
375
375
|
},
|
|
376
376
|
});
|
|
377
377
|
|
|
378
|
-
const nextPrevButtons = component.findAll('[vl-key="nextPrevButton"]');
|
|
378
|
+
const nextPrevButtons = component.findAll('button[vl-key="nextPrevButton"]');
|
|
379
379
|
|
|
380
380
|
expect(nextPrevButtons.length).toBe(4);
|
|
381
381
|
});
|
|
@@ -391,7 +391,7 @@ describe("UCalendar.vue", () => {
|
|
|
391
391
|
},
|
|
392
392
|
});
|
|
393
393
|
|
|
394
|
-
const nextPrevButtons = component.findAll('[vl-key="nextPrevButton"]');
|
|
394
|
+
const nextPrevButtons = component.findAll('button[vl-key="nextPrevButton"]');
|
|
395
395
|
|
|
396
396
|
expect(nextPrevButtons.length).toBe(2);
|
|
397
397
|
});
|
|
@@ -422,7 +422,7 @@ describe("UCalendar.vue", () => {
|
|
|
422
422
|
|
|
423
423
|
const dayView = component.findComponent(DayView);
|
|
424
424
|
const initialDays = dayView.findAll('[vl-key="day"]').map((day) => day.text());
|
|
425
|
-
const navButtons = component.findAll('[vl-key="nextPrevButton"]');
|
|
425
|
+
const navButtons = component.findAll('button[vl-key="nextPrevButton"]');
|
|
426
426
|
|
|
427
427
|
expect(navButtons.length).toBe(4);
|
|
428
428
|
|
|
@@ -446,7 +446,7 @@ describe("UCalendar.vue", () => {
|
|
|
446
446
|
|
|
447
447
|
const dayView = component.findComponent(DayView);
|
|
448
448
|
const initialDays = dayView.findAll('[vl-key="day"]').map((day) => day.text());
|
|
449
|
-
const navButtons = component.findAll('[vl-key="nextPrevButton"]');
|
|
449
|
+
const navButtons = component.findAll('button[vl-key="nextPrevButton"]');
|
|
450
450
|
const nextButton = navButtons[2];
|
|
451
451
|
|
|
452
452
|
await nextButton.trigger("click");
|
|
@@ -585,10 +585,10 @@ describe("UCalendar.vue", () => {
|
|
|
585
585
|
});
|
|
586
586
|
|
|
587
587
|
it("Arrow Key Navigation – moves focus correctly in month view", async () => {
|
|
588
|
-
const expectedMonthAfterRight = "
|
|
589
|
-
const expectedMonthAfterLeft = "
|
|
590
|
-
const expectedMonthAfterDown = "
|
|
591
|
-
const expectedMonthAfterUp = "
|
|
588
|
+
const expectedMonthAfterRight = "Feb";
|
|
589
|
+
const expectedMonthAfterLeft = "Jan";
|
|
590
|
+
const expectedMonthAfterDown = "Apr";
|
|
591
|
+
const expectedMonthAfterUp = "Jan";
|
|
592
592
|
|
|
593
593
|
const component = mount(UCalendar, {
|
|
594
594
|
props: {
|
|
@@ -205,7 +205,7 @@ describe("UCalendarMonthView.vue", () => {
|
|
|
205
205
|
|
|
206
206
|
const allButtons = component.findAllComponents(UButton);
|
|
207
207
|
const mayButton = allButtons.find((button) => button.props("label") === "May");
|
|
208
|
-
const decemberButton = allButtons.find((button) => button.props("label") === "
|
|
208
|
+
const decemberButton = allButtons.find((button) => button.props("label") === "Dec");
|
|
209
209
|
|
|
210
210
|
expect(mayButton?.props("disabled")).toBe(true);
|
|
211
211
|
expect(decemberButton?.props("disabled")).toBe(true);
|
|
@@ -86,7 +86,7 @@ export function parseDate<TLocale extends DateLocale>(
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
if (!(parsedDate instanceof Date && !isNaN(parsedDate.getTime()))) {
|
|
89
|
-
throw new Error(`Invalid date provided: ${originalDate}`);
|
|
89
|
+
throw new Error(`[vueless] Invalid date provided: ${originalDate}`);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
if (timeless === true) {
|
|
@@ -158,7 +158,7 @@ export function dateIsOutOfRange<TLocale extends DateLocale>(
|
|
|
158
158
|
dateFormat: string | null = null,
|
|
159
159
|
) {
|
|
160
160
|
if ((!dateFormat && typeof min === "string") || (!dateFormat && typeof max === "string")) {
|
|
161
|
-
throw new Error("strings needs a date format");
|
|
161
|
+
throw new Error("[vueless] strings needs a date format.");
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
const minDate =
|
|
@@ -27,26 +27,15 @@ const inputRangeToError = defineModel<string>("inputRangeToError", { required: t
|
|
|
27
27
|
const rangeStart = defineModel<string>("rangeStart", { required: true });
|
|
28
28
|
const rangeEnd = defineModel<string>("rangeEnd", { required: true });
|
|
29
29
|
|
|
30
|
-
function
|
|
31
|
-
if (!
|
|
30
|
+
function isFromGreaterThanTo(fromValue: string, toValue: string) {
|
|
31
|
+
if (!fromValue || !toValue) return false;
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
const parsedTo = parseDate(
|
|
33
|
+
const parsedFrom = parseDate(fromValue, INPUT_RANGE_FORMAT, props.locale);
|
|
34
|
+
const parsedTo = parseDate(toValue, INPUT_RANGE_FORMAT, props.locale);
|
|
35
35
|
|
|
36
|
-
if (!
|
|
36
|
+
if (!parsedFrom || !parsedTo) return false;
|
|
37
37
|
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isSmallerThanFrom(value: string) {
|
|
42
|
-
if (!value) return false;
|
|
43
|
-
|
|
44
|
-
const parsedValue = parseDate(value, INPUT_RANGE_FORMAT, props.locale);
|
|
45
|
-
const parsedFrom = parseDate(localValue.value.from, props.dateFormat, props.locale);
|
|
46
|
-
|
|
47
|
-
if (!parsedValue || !parsedFrom) return false;
|
|
48
|
-
|
|
49
|
-
return parsedValue < parsedFrom && !isSameDay(parsedValue, parsedFrom);
|
|
38
|
+
return parsedFrom > parsedTo && !isSameDay(parsedFrom, parsedTo);
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
function validateInput(value: string, type: `${InputRangeType}`) {
|
|
@@ -60,18 +49,30 @@ function validateInput(value: string, type: `${InputRangeType}`) {
|
|
|
60
49
|
error = props.locale.notCorrectMonthNumber;
|
|
61
50
|
} else if (isWrongDayNumber(value) && value) {
|
|
62
51
|
error = props.locale.notCorrectDayNumber;
|
|
63
|
-
} else if (
|
|
52
|
+
} else if (type === InputRangeType.Start && isFromGreaterThanTo(value, rangeEnd.value)) {
|
|
64
53
|
error = props.locale.fromDateGraterThanSecond;
|
|
65
|
-
} else if (
|
|
54
|
+
} else if (type === InputRangeType.End && isFromGreaterThanTo(rangeStart.value, value)) {
|
|
66
55
|
error = props.locale.toDateSmallerThanFirst;
|
|
67
56
|
}
|
|
68
57
|
|
|
69
58
|
if (type === InputRangeType.Start) {
|
|
70
59
|
inputRangeFromError.value = error;
|
|
60
|
+
|
|
61
|
+
if (!error && rangeEnd.value) {
|
|
62
|
+
inputRangeToError.value = isFromGreaterThanTo(value, rangeEnd.value)
|
|
63
|
+
? props.locale.toDateSmallerThanFirst
|
|
64
|
+
: "";
|
|
65
|
+
}
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
if (type === InputRangeType.End) {
|
|
74
69
|
inputRangeToError.value = error;
|
|
70
|
+
|
|
71
|
+
if (!error && rangeStart.value) {
|
|
72
|
+
inputRangeFromError.value = isFromGreaterThanTo(rangeStart.value, value)
|
|
73
|
+
? props.locale.fromDateGraterThanSecond
|
|
74
|
+
: "";
|
|
75
|
+
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -2,15 +2,15 @@ import { inject, readonly, ref } from "vue";
|
|
|
2
2
|
|
|
3
3
|
import type { Ref, InjectionKey } from "vue";
|
|
4
4
|
|
|
5
|
-
export const LoaderOverlaySymbol: InjectionKey<LoaderOverlay> =
|
|
6
|
-
Symbol.for("vueless:loader-overlay");
|
|
7
|
-
|
|
8
5
|
interface LoaderOverlay {
|
|
9
6
|
isLoading: Readonly<Ref<boolean, boolean>>;
|
|
10
7
|
loaderOverlayOn: () => void;
|
|
11
8
|
loaderOverlayOff: () => void;
|
|
12
9
|
}
|
|
13
10
|
|
|
11
|
+
export const LoaderOverlaySymbol: InjectionKey<LoaderOverlay> =
|
|
12
|
+
Symbol.for("vueless:loader-overlay");
|
|
13
|
+
|
|
14
14
|
const isLoading = ref(true);
|
|
15
15
|
|
|
16
16
|
function loaderOverlayOn(): void {
|
|
@@ -34,7 +34,7 @@ export function useLoaderOverlay(): LoaderOverlay {
|
|
|
34
34
|
|
|
35
35
|
if (!loaderOverlay) {
|
|
36
36
|
throw new Error(
|
|
37
|
-
"LoaderOverlay not provided. Ensure you are using `provide` with `LoaderOverlaySymbol`.",
|
|
37
|
+
"[vueless] LoaderOverlay not provided. Ensure you are using `provide` with `LoaderOverlaySymbol`.",
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -3,9 +3,10 @@ import { computed, watch, ref, useTemplateRef, onBeforeMount, onBeforeUnmount }
|
|
|
3
3
|
|
|
4
4
|
import useUI from "../composables/useUI";
|
|
5
5
|
import { getDefaults } from "../utils/ui";
|
|
6
|
+
import { getRequestWithoutQuery } from "../utils/requestQueue";
|
|
6
7
|
|
|
7
|
-
import { clamp, queue, getRequestWithoutQuery } from "./utilLoaderProgress";
|
|
8
8
|
import { useLoaderProgress } from "./useLoaderProgress";
|
|
9
|
+
import { clamp, queue } from "./utilLoaderProgress";
|
|
9
10
|
|
|
10
11
|
import { COMPONENT_NAME, MAXIMUM, SPEED } from "./constants";
|
|
11
12
|
import defaultConfig from "./config";
|
|
@@ -16,7 +17,7 @@ defineOptions({ inheritAttrs: false });
|
|
|
16
17
|
|
|
17
18
|
const props = withDefaults(defineProps<Props>(), {
|
|
18
19
|
...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
|
|
19
|
-
resources:
|
|
20
|
+
resources: "any",
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
const error = ref(false);
|
|
@@ -25,46 +26,9 @@ const progress = ref(0);
|
|
|
25
26
|
const opacity = ref(1);
|
|
26
27
|
const status = ref<number | null>(null);
|
|
27
28
|
|
|
29
|
+
const { progressRequestQueue, loaderProgressOff, loaderProgressOn } = useLoaderProgress();
|
|
28
30
|
const progressRef = useTemplateRef<HTMLDivElement>("progress-bar");
|
|
29
31
|
|
|
30
|
-
const { requestQueue, loaderProgressOff, loaderProgressOn } = useLoaderProgress();
|
|
31
|
-
|
|
32
|
-
onBeforeMount(() => {
|
|
33
|
-
if (window.__VuelessProgressLoaderInstance === undefined) {
|
|
34
|
-
window.__VuelessProgressLoaderInstance = 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!window.__VuelessProgressLoaderInstance) {
|
|
38
|
-
window.addEventListener("loaderProgressOn", onLoaderProgressOn as EventListener);
|
|
39
|
-
window.addEventListener("loaderProgressOff", onLoaderProgressOff as EventListener);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
window.__VuelessProgressLoaderInstance += 1;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
onBeforeUnmount(() => {
|
|
46
|
-
if (window.__VuelessProgressLoaderInstance === undefined) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
window.__VuelessProgressLoaderInstance -= 1;
|
|
51
|
-
|
|
52
|
-
if (!window.__VuelessProgressLoaderInstance) {
|
|
53
|
-
delete window.__VuelessProgressLoaderInstance;
|
|
54
|
-
|
|
55
|
-
window.removeEventListener("loaderProgressOn", onLoaderProgressOn as EventListener);
|
|
56
|
-
window.removeEventListener("loaderProgressOff", onLoaderProgressOff as EventListener);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
function onLoaderProgressOn(event: CustomEvent<{ resource: string }>) {
|
|
61
|
-
loaderProgressOn(event.detail.resource);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function onLoaderProgressOff(event: CustomEvent<{ resource: string }>) {
|
|
65
|
-
loaderProgressOff(event.detail.resource);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
32
|
const isLoading = computed(() => {
|
|
69
33
|
return typeof status.value === "number";
|
|
70
34
|
});
|
|
@@ -85,15 +49,15 @@ const resourceSubscriptions = computed(() => {
|
|
|
85
49
|
});
|
|
86
50
|
|
|
87
51
|
const isActiveRequests = computed(() => {
|
|
88
|
-
const isAnyRequestActive = props.resources === "any" &&
|
|
52
|
+
const isAnyRequestActive = props.resources === "any" && progressRequestQueue.value.length;
|
|
89
53
|
const isSubscribedRequestsActive = resourceSubscriptions.value.some((resource) =>
|
|
90
|
-
|
|
54
|
+
progressRequestQueue.value.includes(resource),
|
|
91
55
|
);
|
|
92
56
|
|
|
93
57
|
return isAnyRequestActive || isSubscribedRequestsActive;
|
|
94
58
|
});
|
|
95
59
|
|
|
96
|
-
watch(() =>
|
|
60
|
+
watch(() => progressRequestQueue, onChangeRequestsQueue, { immediate: true, deep: true });
|
|
97
61
|
|
|
98
62
|
watch(
|
|
99
63
|
() => props.loading,
|
|
@@ -151,12 +115,10 @@ function start() {
|
|
|
151
115
|
}
|
|
152
116
|
|
|
153
117
|
function set(amount: number) {
|
|
154
|
-
let currentProgress;
|
|
118
|
+
let currentProgress = 0;
|
|
155
119
|
|
|
156
120
|
if (isLoading.value) {
|
|
157
121
|
currentProgress = amount < progress.value ? clamp(amount, 0, 100) : clamp(amount, 0.8, 100);
|
|
158
|
-
} else {
|
|
159
|
-
currentProgress = 0;
|
|
160
122
|
}
|
|
161
123
|
|
|
162
124
|
status.value = currentProgress === 100 ? null : currentProgress;
|
|
@@ -205,6 +167,39 @@ function stop() {
|
|
|
205
167
|
set(100);
|
|
206
168
|
}
|
|
207
169
|
|
|
170
|
+
function onLoaderProgressOn(event: CustomEvent<{ request: string }>) {
|
|
171
|
+
loaderProgressOn(event.detail.request);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function onLoaderProgressOff(event: CustomEvent<{ request: string }>) {
|
|
175
|
+
loaderProgressOff(event.detail.request);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
onBeforeMount(() => {
|
|
179
|
+
if (!window.__VuelessLoaderProgressInstanceCount) {
|
|
180
|
+
window.addEventListener("loaderProgressOn", onLoaderProgressOn as EventListener);
|
|
181
|
+
window.addEventListener("loaderProgressOff", onLoaderProgressOff as EventListener);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
window.__VuelessLoaderProgressInstanceCount = window.__VuelessLoaderProgressInstanceCount ?? 0;
|
|
185
|
+
window.__VuelessLoaderProgressInstanceCount += 1;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
onBeforeUnmount(() => {
|
|
189
|
+
if (window.__VuelessLoaderProgressInstanceCount === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
window.__VuelessLoaderProgressInstanceCount -= 1;
|
|
194
|
+
|
|
195
|
+
if (!window.__VuelessLoaderProgressInstanceCount) {
|
|
196
|
+
delete window.__VuelessLoaderProgressInstanceCount;
|
|
197
|
+
|
|
198
|
+
window.removeEventListener("loaderProgressOn", onLoaderProgressOn as EventListener);
|
|
199
|
+
window.removeEventListener("loaderProgressOff", onLoaderProgressOff as EventListener);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
208
203
|
defineExpose({
|
|
209
204
|
/**
|
|
210
205
|
* Start loading animation.
|
|
@@ -16,26 +16,33 @@ import defaultConfig from "../config?raw"
|
|
|
16
16
|
To control the loader state in Vue components use `useLoaderProgress` composable.
|
|
17
17
|
|
|
18
18
|
The loader uses queue of resources and will be shown until at least one item is present in a queue.
|
|
19
|
+
The queue approach is useful if you want to implement global loading handling (for example in axios interceptors)
|
|
19
20
|
|
|
20
21
|
<Source code={`
|
|
21
22
|
import { useLoaderProgress } from "vueless";
|
|
22
23
|
|
|
23
24
|
const {
|
|
25
|
+
isLoading,
|
|
24
26
|
loaderProgressOn,
|
|
25
27
|
loaderProgressOff,
|
|
26
|
-
|
|
28
|
+
progressRequestQueue,
|
|
27
29
|
} = useLoaderProgress();
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
loaderProgressOff(["/transactions", "/products"]);
|
|
31
|
+
/* get loader state */
|
|
32
|
+
console.log(isLoading.value);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/* show loader */
|
|
35
|
+
loaderProgressOn(); // simple
|
|
36
|
+
loaderProgressOn("/transactions"); // single resource
|
|
37
|
+
loaderProgressOff(["/transactions", "/products"]); // multiple resources
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
/* hide loader */
|
|
40
|
+
loaderProgressOff(); // simple
|
|
41
|
+
loaderProgressOff("/transactions"); // single resource
|
|
42
|
+
loaderProgressOff(["/transactions", "/products"]); // multiple resources
|
|
43
|
+
|
|
44
|
+
/* access loader progress resource queue */
|
|
45
|
+
console.log(progressRequestQueue.value);
|
|
39
46
|
`} language="jsx" dark />
|
|
40
47
|
|
|
41
48
|
## Using loader outside Vue components
|
|
@@ -44,11 +51,15 @@ To control the loader state outside Vue components, use the following methods.
|
|
|
44
51
|
<Source code={`
|
|
45
52
|
import { loaderProgressOn, loaderProgressOff } from "vueless";
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
loaderProgressOn(
|
|
54
|
+
/* show loader */
|
|
55
|
+
loaderProgressOn(); // simple
|
|
56
|
+
loaderProgressOn("/transactions"); // single resource
|
|
57
|
+
loaderProgressOff(["/transactions", "/products"]); // multiple resources
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
loaderProgressOff(
|
|
59
|
+
/* hide loader */
|
|
60
|
+
loaderProgressOff(); // simple
|
|
61
|
+
loaderProgressOff("/transactions"); // single resource
|
|
62
|
+
loaderProgressOff(["/transactions", "/products"]); // multiple resources
|
|
52
63
|
`} language="jsx" dark />
|
|
53
64
|
|
|
54
65
|
## Default config
|
|
@@ -6,7 +6,6 @@ import UButton from "../../ui.button/UButton.vue";
|
|
|
6
6
|
import URow from "../../ui.container-row/URow.vue";
|
|
7
7
|
import UCol from "../../ui.container-col/UCol.vue";
|
|
8
8
|
|
|
9
|
-
import { useLoaderProgress } from "../useLoaderProgress";
|
|
10
9
|
import { loaderProgressOff, loaderProgressOn } from "../utilLoaderProgress";
|
|
11
10
|
|
|
12
11
|
import type { Meta, StoryFn } from "@storybook/vue3-vite";
|
|
@@ -34,13 +33,6 @@ export default {
|
|
|
34
33
|
const DefaultTemplate: StoryFn<ULoaderProgressArgs> = (args: ULoaderProgressArgs) => ({
|
|
35
34
|
components: { ULoaderProgress, UButton, URow },
|
|
36
35
|
setup() {
|
|
37
|
-
const loaderProgress = useLoaderProgress();
|
|
38
|
-
|
|
39
|
-
if (!loaderProgress) {
|
|
40
|
-
throw new Error("LoaderProgress is not provided. Ensure it is properly injected.");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { loaderProgressOn, loaderProgressOff } = loaderProgress;
|
|
44
36
|
const slots = getSlotNames(ULoaderProgress.__name);
|
|
45
37
|
|
|
46
38
|
return { args, slots, loaderProgressOn, loaderProgressOff };
|
|
@@ -77,7 +69,6 @@ const EnumTemplate: StoryFn<ULoaderProgressArgs> = (args: ULoaderProgressArgs, {
|
|
|
77
69
|
v-for="option in argTypes?.[args.enum]?.options"
|
|
78
70
|
:key="option"
|
|
79
71
|
v-bind="getArgs(args, option)"
|
|
80
|
-
:resources="args.resources"
|
|
81
72
|
class="static"
|
|
82
73
|
/>
|
|
83
74
|
</UCol>
|
|
@@ -3,7 +3,7 @@ import type { ComponentConfig } from "../types";
|
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
5
|
interface Window {
|
|
6
|
-
|
|
6
|
+
__VuelessLoaderProgressInstanceCount?: number;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ export interface Props {
|
|
|
27
27
|
/**
|
|
28
28
|
* API resource names (endpoint URIs).
|
|
29
29
|
*/
|
|
30
|
-
resources?: string | string[] | "any"
|
|
30
|
+
resources?: string | string[] | "any";
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Loader progress size.
|