vueless 0.0.569 → 0.0.571
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/types.ts +24 -3
- package/ui.button/types.ts +2 -2
- package/ui.container-accordion/UAccordion.vue +2 -2
- package/ui.container-accordion/types.ts +1 -0
- package/ui.container-modal/UModal.vue +15 -8
- package/ui.container-page/UPage.vue +10 -6
- package/ui.form-checkbox/types.ts +7 -0
- package/ui.form-checkbox-group/UCheckboxGroup.vue +1 -1
- package/ui.form-checkbox-multi-state/UCheckboxMultiState.vue +4 -1
- package/ui.form-color-picker/UColorPicker.vue +67 -147
- package/ui.form-color-picker/storybook/Docs.mdx +2 -2
- package/ui.form-color-picker/storybook/{stories.js → stories.ts} +13 -5
- package/ui.form-color-picker/types.ts +64 -0
- package/ui.form-color-picker/useAttrs.ts +15 -0
- package/ui.form-input/UInput.vue +162 -321
- package/ui.form-input/{config.js → config.ts} +3 -0
- package/ui.form-input/storybook/Docs.mdx +2 -2
- package/ui.form-input/storybook/{stories.js → stories.ts} +14 -6
- package/ui.form-input/types.ts +103 -0
- package/ui.form-input/useAttrs.ts +31 -0
- package/ui.form-input-file/UInputFile.vue +188 -245
- package/ui.form-input-file/storybook/Docs.mdx +2 -2
- package/ui.form-input-file/storybook/{stories.js → stories.ts} +13 -5
- package/ui.form-input-file/types.ts +72 -0
- package/ui.form-input-file/useAttrs.ts +21 -0
- package/ui.form-input-file/{utilFileForm.js → utilFileForm.ts} +1 -1
- package/ui.form-input-money/UInputMoney.vue +76 -223
- package/ui.form-input-money/storybook/Docs.mdx +2 -2
- package/ui.form-input-money/storybook/{stories.js → stories.ts} +14 -6
- package/ui.form-input-money/types.ts +118 -0
- package/ui.form-input-money/useAttrs.ts +15 -0
- package/ui.form-input-money/{useFormatCurrency.js → useFormatCurrency.ts} +28 -17
- package/ui.form-input-money/utilFormat.ts +83 -0
- package/ui.form-input-number/UInputNumber.vue +69 -156
- package/ui.form-input-number/storybook/Docs.mdx +2 -2
- package/ui.form-input-number/storybook/{stories.js → stories.ts} +17 -9
- package/ui.form-input-number/types.ts +65 -0
- package/ui.form-input-number/useAttrs.ts +15 -0
- package/ui.form-input-rating/UInputRating.vue +70 -158
- package/ui.form-input-rating/storybook/Docs.mdx +2 -2
- package/ui.form-input-rating/storybook/{stories.js → stories.ts} +14 -6
- package/ui.form-input-rating/types.ts +67 -0
- package/ui.form-input-rating/useAttrs.ts +14 -0
- package/ui.form-input-search/UInputSearch.vue +97 -224
- package/ui.form-input-search/storybook/Docs.mdx +2 -2
- package/ui.form-input-search/storybook/{stories.js → stories.ts} +13 -5
- package/ui.form-input-search/types.ts +93 -0
- package/ui.form-input-search/useAttrs.ts +15 -0
- package/ui.form-radio/URadio.vue +1 -1
- package/ui.form-radio-group/URadioGroup.vue +1 -1
- package/ui.navigation-pagination/UPagination.vue +15 -15
- package/ui.navigation-pagination/types.ts +3 -0
- package/ui.navigation-progress/UProgress.vue +16 -2
- package/ui.navigation-progress/types.ts +2 -0
- package/ui.text-files/UFiles.vue +20 -16
- package/ui.text-files/types.ts +1 -1
- package/ui.text-notify/UNotify.vue +38 -48
- package/ui.text-notify/types.ts +24 -0
- package/web-types.json +227 -138
- package/ui.form-color-picker/useAttrs.js +0 -9
- package/ui.form-input/useAttrs.js +0 -15
- package/ui.form-input-file/useAttrs.js +0 -15
- package/ui.form-input-money/useAttrs.js +0 -9
- package/ui.form-input-money/utilFormat.js +0 -75
- package/ui.form-input-number/useAttrs.js +0 -9
- package/ui.form-input-rating/useAttrs.js +0 -8
- package/ui.form-input-search/useAttrs.js +0 -9
- /package/ui.form-color-picker/{config.js → config.ts} +0 -0
- /package/ui.form-color-picker/{constants.js → constants.ts} +0 -0
- /package/ui.form-input/{constants.js → constants.ts} +0 -0
- /package/ui.form-input-file/{config.js → config.ts} +0 -0
- /package/ui.form-input-file/{constants.js → constants.ts} +0 -0
- /package/ui.form-input-money/{config.js → config.ts} +0 -0
- /package/ui.form-input-money/{constants.js → constants.ts} +0 -0
- /package/ui.form-input-number/{config.js → config.ts} +0 -0
- /package/ui.form-input-number/{constants.js → constants.ts} +0 -0
- /package/ui.form-input-rating/{config.js → config.ts} +0 -0
- /package/ui.form-input-rating/{constants.js → constants.ts} +0 -0
- /package/ui.form-input-search/{config.js → config.ts} +0 -0
- /package/ui.form-input-search/{constants.js → constants.ts} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import defaultConfig from "./config.ts";
|
|
2
|
+
|
|
3
|
+
export type Config = Partial<typeof defaultConfig>;
|
|
4
|
+
|
|
5
|
+
export type IconSize = "xs" | "sm" | "md";
|
|
6
|
+
|
|
7
|
+
export interface UInputProps {
|
|
8
|
+
/**
|
|
9
|
+
* Input value.
|
|
10
|
+
*/
|
|
11
|
+
modelValue?: string | number;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Input label.
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Label placement.
|
|
20
|
+
*/
|
|
21
|
+
labelAlign?: "top" | "topInside" | "topWithDesc" | "left" | "right";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Input placeholder.
|
|
25
|
+
*/
|
|
26
|
+
placeholder?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Input description.
|
|
30
|
+
*/
|
|
31
|
+
description?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Error message.
|
|
35
|
+
*/
|
|
36
|
+
error?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Input size.
|
|
40
|
+
*/
|
|
41
|
+
size?: "sm" | "md" | "lg";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Left icon name.
|
|
45
|
+
*/
|
|
46
|
+
leftIcon?: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Right icon name.
|
|
50
|
+
*/
|
|
51
|
+
rightIcon?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maximum character length.
|
|
55
|
+
*/
|
|
56
|
+
maxLength?: string | number;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prevents some characters from input.
|
|
60
|
+
* You can use predefined values or own RegExp.
|
|
61
|
+
*/
|
|
62
|
+
validationRule?: "symbol" | "string" | "stringAndNumber" | "number" | "integer";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Input type.
|
|
66
|
+
*/
|
|
67
|
+
type?: "text" | "number" | "tel" | "email" | "url" | "search" | "password";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set specific keyboard for mobile devices.
|
|
71
|
+
*/
|
|
72
|
+
inputmode?: "text" | "decimal" | "numeric" | "tel" | "email" | "url" | "search" | "none";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Make input read-only.
|
|
76
|
+
*/
|
|
77
|
+
readonly?: boolean;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Disable the input.
|
|
81
|
+
*/
|
|
82
|
+
disabled?: boolean;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Disable browsers autocomplete.
|
|
86
|
+
*/
|
|
87
|
+
noAutocomplete?: boolean;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Unique element id.
|
|
91
|
+
*/
|
|
92
|
+
id?: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Component config object.
|
|
96
|
+
*/
|
|
97
|
+
config?: Config;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Data-test attribute for automated testing.
|
|
101
|
+
*/
|
|
102
|
+
dataTest?: string;
|
|
103
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import useUI from "../composables/useUI.ts";
|
|
3
|
+
|
|
4
|
+
import defaultConfig from "./config.ts";
|
|
5
|
+
|
|
6
|
+
import type { ComputedRef } from "vue";
|
|
7
|
+
import type { UseAttrs } from "../types.ts";
|
|
8
|
+
import type { UInputProps, Config } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
type ComponentState = {
|
|
11
|
+
applyPasswordClasses: ComputedRef<boolean>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function useAttrs(
|
|
15
|
+
props: UInputProps,
|
|
16
|
+
{ applyPasswordClasses }: ComponentState,
|
|
17
|
+
): UseAttrs<Config> {
|
|
18
|
+
const { config, getKeysAttrs } = useUI<Config>(defaultConfig, () => props.config);
|
|
19
|
+
|
|
20
|
+
const mutatedProps = computed(() => ({
|
|
21
|
+
error: Boolean(props.error),
|
|
22
|
+
label: Boolean(props.label),
|
|
23
|
+
/* component state, not a props */
|
|
24
|
+
typePassword: applyPasswordClasses.value,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
config,
|
|
29
|
+
...getKeysAttrs(mutatedProps),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,92 +1,4 @@
|
|
|
1
|
-
<
|
|
2
|
-
<ULabel
|
|
3
|
-
:for="elementId"
|
|
4
|
-
:size="size"
|
|
5
|
-
:label="label"
|
|
6
|
-
:error="error"
|
|
7
|
-
:align="labelAlign"
|
|
8
|
-
:disabled="disabled"
|
|
9
|
-
:description="description"
|
|
10
|
-
v-bind="inputLabelAttrs"
|
|
11
|
-
>
|
|
12
|
-
<div ref="dropZoneRef" :ondrop="onDrop" v-bind="dropzoneAttrs">
|
|
13
|
-
<UText v-if="hasSlotContent($slots['top'])" :size="size" v-bind="descriptionTopAttrs">
|
|
14
|
-
<!-- @slot Use it to add something above the component content. -->
|
|
15
|
-
<slot name="top" />
|
|
16
|
-
</UText>
|
|
17
|
-
|
|
18
|
-
<div v-bind="contentAttrs">
|
|
19
|
-
<!-- @slot Use it to add something before the placeholder. -->
|
|
20
|
-
<slot name="left" />
|
|
21
|
-
|
|
22
|
-
<span v-if="!isValue" v-bind="placeholderAttrs" v-text="currentLocale.noFile" />
|
|
23
|
-
|
|
24
|
-
<UFiles
|
|
25
|
-
:size="size"
|
|
26
|
-
v-bind="fileListAttrs"
|
|
27
|
-
:file-list="fileList"
|
|
28
|
-
:removable="multiple && !disabled"
|
|
29
|
-
@remove="onClickRemoveItem"
|
|
30
|
-
>
|
|
31
|
-
<template #right="{ file }">
|
|
32
|
-
<slot name="right" :file="file" />
|
|
33
|
-
</template>
|
|
34
|
-
</UFiles>
|
|
35
|
-
|
|
36
|
-
<div v-bind="buttonsAttrs">
|
|
37
|
-
<template v-if="Array.isArray(currentFiles) || !currentFiles">
|
|
38
|
-
<UButton
|
|
39
|
-
filled
|
|
40
|
-
no-ring
|
|
41
|
-
:for="elementId"
|
|
42
|
-
tag="label"
|
|
43
|
-
variant="thirdary"
|
|
44
|
-
:size="buttonSize"
|
|
45
|
-
:right-icon="config.defaults.chooseFileIcon"
|
|
46
|
-
:label="currentLocale.uploadFile"
|
|
47
|
-
:disabled="disabled"
|
|
48
|
-
v-bind="chooseFileButtonAttrs"
|
|
49
|
-
:data-test="`${dataTest}-upload`"
|
|
50
|
-
/>
|
|
51
|
-
|
|
52
|
-
<input
|
|
53
|
-
:id="elementId"
|
|
54
|
-
ref="fileInputRef"
|
|
55
|
-
type="file"
|
|
56
|
-
:disabled="disabled"
|
|
57
|
-
:accept="accept"
|
|
58
|
-
:multiple="multiple"
|
|
59
|
-
v-bind="inputAttrs"
|
|
60
|
-
@change="onChangeFile"
|
|
61
|
-
/>
|
|
62
|
-
</template>
|
|
63
|
-
|
|
64
|
-
<UButton
|
|
65
|
-
v-if="isValue && !disabled"
|
|
66
|
-
round
|
|
67
|
-
square
|
|
68
|
-
filled
|
|
69
|
-
no-ring
|
|
70
|
-
variant="thirdary"
|
|
71
|
-
:size="buttonSize"
|
|
72
|
-
:disabled="disabled"
|
|
73
|
-
:left-icon="config.defaults.clearIcon"
|
|
74
|
-
v-bind="clearButtonAttrs"
|
|
75
|
-
:data-test="`${dataTest}-clear`"
|
|
76
|
-
@click="onClickResetFiles"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<UText v-if="hasSlotContent($slots['bottom'])" :size="size" v-bind="descriptionBottomAttrs">
|
|
82
|
-
<!-- @slot Use it to add something below the component content. -->
|
|
83
|
-
<slot name="bottom" />
|
|
84
|
-
</UText>
|
|
85
|
-
</div>
|
|
86
|
-
</ULabel>
|
|
87
|
-
</template>
|
|
88
|
-
|
|
89
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
90
2
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, useId } from "vue";
|
|
91
3
|
import { merge } from "lodash-es";
|
|
92
4
|
|
|
@@ -97,120 +9,27 @@ import UFiles from "../ui.text-files/UFiles.vue";
|
|
|
97
9
|
|
|
98
10
|
import { getDefault } from "../utils/ui.ts";
|
|
99
11
|
import { hasSlotContent } from "../utils/helper.ts";
|
|
100
|
-
import {
|
|
101
|
-
|
|
102
|
-
import useAttrs from "./useAttrs.js";
|
|
103
|
-
import { getFileMbSize } from "./utilFileForm.js";
|
|
104
|
-
import { UInputFile } from "./constants.js";
|
|
105
|
-
import defaultConfig from "./config.js";
|
|
106
|
-
|
|
107
|
-
defineOptions({ inheritAttrs: false });
|
|
108
|
-
|
|
109
|
-
const props = defineProps({
|
|
110
|
-
/**
|
|
111
|
-
* Input value.
|
|
112
|
-
*/
|
|
113
|
-
modelValue: {
|
|
114
|
-
type: [Array, File],
|
|
115
|
-
default: null,
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Input label.
|
|
120
|
-
*/
|
|
121
|
-
label: {
|
|
122
|
-
type: String,
|
|
123
|
-
default: "",
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Input description.
|
|
128
|
-
*/
|
|
129
|
-
description: {
|
|
130
|
-
type: String,
|
|
131
|
-
default: "",
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Error message.
|
|
136
|
-
*/
|
|
137
|
-
error: {
|
|
138
|
-
type: String,
|
|
139
|
-
default: "",
|
|
140
|
-
},
|
|
12
|
+
import { getFileMbSize } from "./utilFileForm.ts";
|
|
141
13
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* @values top, topInside, topWithDesc
|
|
145
|
-
*/
|
|
146
|
-
labelAlign: {
|
|
147
|
-
type: String,
|
|
148
|
-
default: getDefault(defaultConfig, UInputFile).labelAlign,
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Input size.
|
|
153
|
-
* @values sm, md, lg
|
|
154
|
-
*/
|
|
155
|
-
size: {
|
|
156
|
-
type: String,
|
|
157
|
-
default: getDefault(defaultConfig, UInputFile).size,
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Max file size in megabytes.
|
|
162
|
-
*/
|
|
163
|
-
maxFileSize: {
|
|
164
|
-
type: Number,
|
|
165
|
-
default: getDefault(defaultConfig, UInputFile).maxFileSize,
|
|
166
|
-
},
|
|
14
|
+
import useAttrs from "./useAttrs.ts";
|
|
15
|
+
import { useLocale } from "../composables/useLocale.ts";
|
|
167
16
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*/
|
|
171
|
-
allowedFileTypes: {
|
|
172
|
-
type: Array,
|
|
173
|
-
default: () => getDefault(defaultConfig, UInputFile).allowedFileTypes,
|
|
174
|
-
},
|
|
17
|
+
import { UInputFile } from "./constants.ts";
|
|
18
|
+
import defaultConfig from "./config.ts";
|
|
175
19
|
|
|
176
|
-
|
|
177
|
-
* Allow selecting multiple files.
|
|
178
|
-
*/
|
|
179
|
-
multiple: {
|
|
180
|
-
type: Boolean,
|
|
181
|
-
default: getDefault(defaultConfig, UInputFile).multiple,
|
|
182
|
-
},
|
|
20
|
+
import type { UInputFileProps, ButtonSize } from "./types.ts";
|
|
183
21
|
|
|
184
|
-
|
|
185
|
-
* Disable the input.
|
|
186
|
-
*/
|
|
187
|
-
disabled: {
|
|
188
|
-
type: Boolean,
|
|
189
|
-
default: getDefault(defaultConfig, UInputFile).disabled,
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Unique element id.
|
|
194
|
-
*/
|
|
195
|
-
id: {
|
|
196
|
-
type: String,
|
|
197
|
-
default: "",
|
|
198
|
-
},
|
|
199
|
-
/**
|
|
200
|
-
* Component config object.
|
|
201
|
-
*/
|
|
202
|
-
config: {
|
|
203
|
-
type: Object,
|
|
204
|
-
default: () => ({}),
|
|
205
|
-
},
|
|
22
|
+
defineOptions({ inheritAttrs: false });
|
|
206
23
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
24
|
+
const props = withDefaults(defineProps<UInputFileProps>(), {
|
|
25
|
+
labelAlign: getDefault<UInputFileProps>(defaultConfig, UInputFile).labelAlign,
|
|
26
|
+
size: getDefault<UInputFileProps>(defaultConfig, UInputFile).size,
|
|
27
|
+
maxFileSize: getDefault<UInputFileProps>(defaultConfig, UInputFile).maxFileSize,
|
|
28
|
+
allowedFileTypes: getDefault<UInputFileProps>(defaultConfig, UInputFile).allowedFileTypes,
|
|
29
|
+
multiple: getDefault<UInputFileProps>(defaultConfig, UInputFile).multiple,
|
|
30
|
+
disabled: getDefault<UInputFileProps>(defaultConfig, UInputFile).disabled,
|
|
31
|
+
dataTest: "",
|
|
32
|
+
config: () => ({}),
|
|
214
33
|
});
|
|
215
34
|
|
|
216
35
|
const emit = defineEmits([
|
|
@@ -229,8 +48,8 @@ const emit = defineEmits([
|
|
|
229
48
|
|
|
230
49
|
const { tm } = useLocale();
|
|
231
50
|
|
|
232
|
-
const dropZoneRef = ref(null);
|
|
233
|
-
const fileInputRef = ref(null);
|
|
51
|
+
const dropZoneRef = ref<HTMLDivElement | null>(null);
|
|
52
|
+
const fileInputRef = ref<HTMLInputElement | null>(null);
|
|
234
53
|
|
|
235
54
|
const elementId = props.id || useId();
|
|
236
55
|
|
|
@@ -252,7 +71,7 @@ const {
|
|
|
252
71
|
const i18nGlobal = tm(UInputFile);
|
|
253
72
|
const currentLocale = computed(() => merge(defaultConfig.i18n, i18nGlobal, props.config.i18n));
|
|
254
73
|
|
|
255
|
-
const currentFiles = computed({
|
|
74
|
+
const currentFiles = computed<File | File[] | null>({
|
|
256
75
|
get: () => props.modelValue,
|
|
257
76
|
set: (newValue) => {
|
|
258
77
|
const fallbackValue = props.multiple ? [] : null;
|
|
@@ -297,44 +116,49 @@ const buttonSize = computed(() => {
|
|
|
297
116
|
lg: "md",
|
|
298
117
|
};
|
|
299
118
|
|
|
300
|
-
return sizes[props.size];
|
|
119
|
+
return sizes[props.size] as ButtonSize;
|
|
301
120
|
});
|
|
302
121
|
|
|
303
122
|
onMounted(() => {
|
|
304
|
-
dropZoneRef.value
|
|
305
|
-
|
|
123
|
+
if (dropZoneRef.value) {
|
|
124
|
+
dropZoneRef.value.addEventListener("dragover", onDragOver);
|
|
125
|
+
dropZoneRef.value.addEventListener("dragleave", onDragLeave);
|
|
126
|
+
}
|
|
306
127
|
});
|
|
307
128
|
|
|
308
129
|
onBeforeUnmount(() => {
|
|
309
|
-
dropZoneRef.value
|
|
310
|
-
|
|
130
|
+
if (dropZoneRef.value) {
|
|
131
|
+
dropZoneRef.value.removeEventListener("dragover", onDragOver);
|
|
132
|
+
dropZoneRef.value.removeEventListener("dragleave", onDragLeave);
|
|
133
|
+
}
|
|
311
134
|
});
|
|
312
135
|
|
|
313
|
-
watch(
|
|
314
|
-
() => props.multiple,
|
|
315
|
-
() => {
|
|
316
|
-
if (!props.multiple && Array.isArray(currentFiles.value)) {
|
|
317
|
-
currentFiles.value = currentFiles.value[0];
|
|
318
|
-
}
|
|
136
|
+
watch(() => props.multiple, normalizeFilesForMultipleMode);
|
|
319
137
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
138
|
+
function normalizeFilesForMultipleMode() {
|
|
139
|
+
if (!props.multiple) return;
|
|
140
|
+
|
|
141
|
+
if (!Array.isArray(currentFiles.value)) {
|
|
142
|
+
currentFiles.value = currentFiles.value ? [currentFiles.value] : [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
325
145
|
|
|
326
|
-
function removeDuplicates(files) {
|
|
327
|
-
return files.filter(
|
|
146
|
+
function removeDuplicates(files: File[]) {
|
|
147
|
+
return files.filter(
|
|
148
|
+
(file) =>
|
|
149
|
+
file instanceof File &&
|
|
150
|
+
!fileList.value.some((item) => item instanceof File && item.name === file.name),
|
|
151
|
+
);
|
|
328
152
|
}
|
|
329
153
|
|
|
330
|
-
function validate(file) {
|
|
154
|
+
function validate(file: File) {
|
|
331
155
|
const targetFileSize = getFileMbSize(file);
|
|
332
156
|
|
|
333
157
|
const isValidType = extensionNames.value.length
|
|
334
158
|
? extensionNames.value.some((item) => file.type.includes(item))
|
|
335
159
|
: true;
|
|
336
160
|
|
|
337
|
-
const isValidSize = targetFileSize <= props.maxFileSize;
|
|
161
|
+
const isValidSize = Number(targetFileSize) <= props.maxFileSize;
|
|
338
162
|
|
|
339
163
|
if (!isValidSize && props.maxFileSize) {
|
|
340
164
|
currentError.value = currentLocale.value.sizeError;
|
|
@@ -345,20 +169,37 @@ function validate(file) {
|
|
|
345
169
|
}
|
|
346
170
|
}
|
|
347
171
|
|
|
348
|
-
function onChangeFile(event) {
|
|
349
|
-
|
|
172
|
+
function onChangeFile(event: Event) {
|
|
173
|
+
const target = event.target as HTMLInputElement | null;
|
|
350
174
|
|
|
351
|
-
if (
|
|
352
|
-
|
|
175
|
+
if (target && target.files) {
|
|
176
|
+
const file = target.files[0];
|
|
353
177
|
|
|
354
|
-
|
|
355
|
-
}
|
|
178
|
+
validate(file);
|
|
356
179
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
: Array.from(event.target.files).at(0);
|
|
180
|
+
if (currentError.value) {
|
|
181
|
+
onClickResetFiles();
|
|
360
182
|
|
|
361
|
-
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (props.multiple) {
|
|
187
|
+
if (!Array.isArray(currentFiles.value)) {
|
|
188
|
+
currentFiles.value = currentFiles.value instanceof File ? [currentFiles.value] : [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
currentFiles.value = [
|
|
192
|
+
...currentFiles.value,
|
|
193
|
+
...removeDuplicates(
|
|
194
|
+
Array.from(target.files).filter((file): file is File => file instanceof File),
|
|
195
|
+
),
|
|
196
|
+
];
|
|
197
|
+
} else {
|
|
198
|
+
currentFiles.value = file instanceof File ? file : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (fileInputRef.value) fileInputRef.value.value = "";
|
|
202
|
+
}
|
|
362
203
|
}
|
|
363
204
|
|
|
364
205
|
function onClickResetFiles() {
|
|
@@ -367,32 +208,39 @@ function onClickResetFiles() {
|
|
|
367
208
|
if (fileInputRef.value) fileInputRef.value.value = "";
|
|
368
209
|
}
|
|
369
210
|
|
|
370
|
-
function onDragOver(event) {
|
|
211
|
+
function onDragOver(event: DragEvent) {
|
|
371
212
|
event.preventDefault();
|
|
372
213
|
|
|
373
|
-
dropZoneRef.value.
|
|
214
|
+
if (dropZoneRef.value && config.value && config.value.dropzoneHover) {
|
|
215
|
+
dropZoneRef.value.classList.add(...config.value.dropzoneHover.split(" "));
|
|
216
|
+
}
|
|
374
217
|
}
|
|
375
218
|
|
|
376
|
-
function onDragLeave(event) {
|
|
219
|
+
function onDragLeave(event: DragEvent) {
|
|
377
220
|
event.preventDefault();
|
|
378
221
|
|
|
379
|
-
dropZoneRef.value.
|
|
222
|
+
if (dropZoneRef.value && config.value && config.value.dropzoneHover) {
|
|
223
|
+
dropZoneRef.value.classList.remove(...config.value.dropzoneHover.split(" "));
|
|
224
|
+
}
|
|
380
225
|
}
|
|
381
226
|
|
|
382
|
-
function onDrop(event) {
|
|
227
|
+
function onDrop(event: DragEvent) {
|
|
383
228
|
event.preventDefault();
|
|
384
229
|
|
|
385
|
-
let targetFiles =
|
|
230
|
+
let targetFiles: (File | null)[] = [];
|
|
386
231
|
|
|
387
|
-
if (event.dataTransfer.items) {
|
|
232
|
+
if (event.dataTransfer && event.dataTransfer.items) {
|
|
388
233
|
targetFiles = [...event.dataTransfer.items]
|
|
389
234
|
.filter((item) => item.kind === "file")
|
|
390
|
-
.map((item) => item.getAsFile())
|
|
391
|
-
|
|
235
|
+
.map((item) => item.getAsFile())
|
|
236
|
+
.filter((file): file is File => !file);
|
|
237
|
+
} else if (event.dataTransfer && event.dataTransfer.files) {
|
|
392
238
|
targetFiles = [...event.dataTransfer.files];
|
|
393
239
|
}
|
|
394
240
|
|
|
395
|
-
if (targetFiles.length)
|
|
241
|
+
if (targetFiles.length) {
|
|
242
|
+
targetFiles.filter((file): file is File => !file).forEach(validate);
|
|
243
|
+
}
|
|
396
244
|
|
|
397
245
|
nextTick(() => {
|
|
398
246
|
if (currentError.value || !targetFiles.length) {
|
|
@@ -401,13 +249,108 @@ function onDrop(event) {
|
|
|
401
249
|
return;
|
|
402
250
|
}
|
|
403
251
|
|
|
252
|
+
const validFiles = targetFiles.filter((file): file is File => !file);
|
|
253
|
+
|
|
404
254
|
currentFiles.value = props.multiple
|
|
405
|
-
? [
|
|
406
|
-
|
|
255
|
+
? [
|
|
256
|
+
...(Array.isArray(currentFiles.value) ? currentFiles.value : []),
|
|
257
|
+
...removeDuplicates(validFiles),
|
|
258
|
+
]
|
|
259
|
+
: validFiles[0];
|
|
407
260
|
});
|
|
408
261
|
}
|
|
409
262
|
|
|
410
|
-
function onClickRemoveItem(id) {
|
|
411
|
-
|
|
263
|
+
function onClickRemoveItem(id: string | number) {
|
|
264
|
+
if (Array.isArray(currentFiles.value)) {
|
|
265
|
+
currentFiles.value = currentFiles.value.filter((file) => file.name !== id);
|
|
266
|
+
}
|
|
412
267
|
}
|
|
413
268
|
</script>
|
|
269
|
+
|
|
270
|
+
<template>
|
|
271
|
+
<ULabel
|
|
272
|
+
:for="elementId"
|
|
273
|
+
:size="size"
|
|
274
|
+
:label="label"
|
|
275
|
+
:error="error"
|
|
276
|
+
:align="labelAlign"
|
|
277
|
+
:disabled="disabled"
|
|
278
|
+
:description="description"
|
|
279
|
+
v-bind="inputLabelAttrs"
|
|
280
|
+
>
|
|
281
|
+
<div ref="dropZoneRef" :ondrop="onDrop" v-bind="dropzoneAttrs">
|
|
282
|
+
<UText v-if="hasSlotContent($slots['top'])" :size="size" v-bind="descriptionTopAttrs">
|
|
283
|
+
<!-- @slot Use it to add something above the component content. -->
|
|
284
|
+
<slot name="top" />
|
|
285
|
+
</UText>
|
|
286
|
+
|
|
287
|
+
<div v-bind="contentAttrs">
|
|
288
|
+
<!-- @slot Use it to add something before the placeholder. -->
|
|
289
|
+
<slot name="left" />
|
|
290
|
+
|
|
291
|
+
<span v-if="!isValue" v-bind="placeholderAttrs" v-text="currentLocale.noFile" />
|
|
292
|
+
|
|
293
|
+
<UFiles
|
|
294
|
+
:size="size"
|
|
295
|
+
v-bind="fileListAttrs"
|
|
296
|
+
:file-list="fileList"
|
|
297
|
+
:removable="multiple && !disabled"
|
|
298
|
+
@remove="onClickRemoveItem"
|
|
299
|
+
>
|
|
300
|
+
<template #right="{ file }">
|
|
301
|
+
<slot name="right" :file="file" />
|
|
302
|
+
</template>
|
|
303
|
+
</UFiles>
|
|
304
|
+
|
|
305
|
+
<div v-bind="buttonsAttrs">
|
|
306
|
+
<template v-if="Array.isArray(currentFiles) || !currentFiles">
|
|
307
|
+
<UButton
|
|
308
|
+
filled
|
|
309
|
+
no-ring
|
|
310
|
+
:for="elementId"
|
|
311
|
+
tag="label"
|
|
312
|
+
variant="thirdary"
|
|
313
|
+
:size="buttonSize"
|
|
314
|
+
:right-icon="config.defaults?.chooseFileIcon"
|
|
315
|
+
:label="currentLocale.uploadFile"
|
|
316
|
+
:disabled="disabled"
|
|
317
|
+
v-bind="chooseFileButtonAttrs"
|
|
318
|
+
:data-test="`${dataTest}-upload`"
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
<input
|
|
322
|
+
:id="elementId"
|
|
323
|
+
ref="fileInputRef"
|
|
324
|
+
type="file"
|
|
325
|
+
:disabled="disabled"
|
|
326
|
+
:accept="accept"
|
|
327
|
+
:multiple="multiple"
|
|
328
|
+
v-bind="inputAttrs"
|
|
329
|
+
@change="onChangeFile"
|
|
330
|
+
/>
|
|
331
|
+
</template>
|
|
332
|
+
|
|
333
|
+
<UButton
|
|
334
|
+
v-if="isValue && !disabled"
|
|
335
|
+
round
|
|
336
|
+
square
|
|
337
|
+
filled
|
|
338
|
+
no-ring
|
|
339
|
+
variant="thirdary"
|
|
340
|
+
:size="buttonSize"
|
|
341
|
+
:disabled="disabled"
|
|
342
|
+
:left-icon="config.defaults?.clearIcon"
|
|
343
|
+
v-bind="clearButtonAttrs"
|
|
344
|
+
:data-test="`${dataTest}-clear`"
|
|
345
|
+
@click="onClickResetFiles"
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<UText v-if="hasSlotContent($slots['bottom'])" :size="size" v-bind="descriptionBottomAttrs">
|
|
351
|
+
<!-- @slot Use it to add something below the component content. -->
|
|
352
|
+
<slot name="bottom" />
|
|
353
|
+
</UText>
|
|
354
|
+
</div>
|
|
355
|
+
</ULabel>
|
|
356
|
+
</template>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/blocks";
|
|
2
2
|
import { getSource } from "../../utils/storybook.ts";
|
|
3
3
|
|
|
4
|
-
import * as stories from "./stories.
|
|
5
|
-
import defaultConfig from "../config.
|
|
4
|
+
import * as stories from "./stories.ts";
|
|
5
|
+
import defaultConfig from "../config.ts?raw"
|
|
6
6
|
|
|
7
7
|
<Meta of={stories} />
|
|
8
8
|
<Title of={stories} />
|