@v1nt1248/3nclient-lib 0.1.47 → 0.1.49
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/dist/components/index.d.ts +3 -1
- package/dist/components/ui3n-input-file/types.d.ts +13 -0
- package/dist/components/ui3n-input-file/ui3n-input-file.vue.d.ts +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/style.css +1 -1
- package/dist/ui3n-components.js +5894 -5708
- package/dist/ui3n-utils.js +817 -809
- package/dist/utils/ui.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/index.ts +5 -0
- package/src/components/ui3n-input-file/types.ts +14 -0
- package/src/components/ui3n-input-file/ui3n-input-file.vue +167 -0
package/dist/utils/ui.d.ts
CHANGED
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ import Ui3nEmoji from './ui3n-emoji/ui3n-emoji.vue';
|
|
|
11
11
|
import type { Ui3nEmojiProps, Ui3nEmojiEmits } from './ui3n-emoji/types';
|
|
12
12
|
import Ui3nInput from './ui3n-input/ui3n-input.vue';
|
|
13
13
|
import type { Ui3nInputProps, Ui3nInputEmits } from './ui3n-input/types';
|
|
14
|
+
import Ui3nInputFile from './ui3n-input-file/ui3n-input-file.vue';
|
|
15
|
+
import type { Ui3nInputFileProps, Ui3nInputFileEmits } from './ui3n-input-file/types';
|
|
14
16
|
import Ui3nText from './ui3n-text/ui3n-text.vue';
|
|
15
17
|
import type { Ui3nTextProps, Ui3nTextEmits } from './ui3n-text/types';
|
|
16
18
|
import Ui3nCheckbox from './ui3n-checkbox/ui3n-checkbox.vue';
|
|
@@ -101,6 +103,9 @@ export {
|
|
|
101
103
|
Ui3nInput,
|
|
102
104
|
Ui3nInputProps,
|
|
103
105
|
Ui3nInputEmits,
|
|
106
|
+
Ui3nInputFile,
|
|
107
|
+
Ui3nInputFileProps,
|
|
108
|
+
Ui3nInputFileEmits,
|
|
104
109
|
Ui3nText,
|
|
105
110
|
Ui3nTextProps,
|
|
106
111
|
Ui3nTextEmits,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Ui3nInputFileProps {
|
|
2
|
+
modelValue?: File[] | FileList;
|
|
3
|
+
buttonText?: string;
|
|
4
|
+
multiple?: boolean;
|
|
5
|
+
maxNumberOfFiles?: number | string;
|
|
6
|
+
maxFileSize?: number;
|
|
7
|
+
allowedFileTypes?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Ui3nInputFileEmits {
|
|
12
|
+
(event: 'update:modelValue', value: File[] | FileList): void;
|
|
13
|
+
(event: 'error', value: string): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.ui3nInputFile">
|
|
3
|
+
<label
|
|
4
|
+
:for="id"
|
|
5
|
+
:class="[$style.label, disabled && $style.disabled]"
|
|
6
|
+
@click.stop.prevent="selectFiles"
|
|
7
|
+
>
|
|
8
|
+
{{ buttonText }}
|
|
9
|
+
</label>
|
|
10
|
+
|
|
11
|
+
<input
|
|
12
|
+
:id="id"
|
|
13
|
+
ref="fileInput"
|
|
14
|
+
type="file"
|
|
15
|
+
name="file"
|
|
16
|
+
hidden
|
|
17
|
+
:multiple="multiple"
|
|
18
|
+
:accept="allowedFileTypes"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
@change="onChange"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script lang="ts" setup>
|
|
26
|
+
import { ref } from 'vue';
|
|
27
|
+
import size from 'lodash/size';
|
|
28
|
+
import last from 'lodash/last';
|
|
29
|
+
import trim from 'lodash/trim';
|
|
30
|
+
import { getRandomId, formatFileSize } from '@/utils';
|
|
31
|
+
import type { Ui3nInputFileProps, Ui3nInputFileEmits } from './types';
|
|
32
|
+
import type { Nullable } from '@/components/types';
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(defineProps<Ui3nInputFileProps>(), {
|
|
35
|
+
modelValue: () => [],
|
|
36
|
+
buttonText: 'Select file',
|
|
37
|
+
allowedFileTypes: '*',
|
|
38
|
+
});
|
|
39
|
+
const emits = defineEmits<Ui3nInputFileEmits>();
|
|
40
|
+
|
|
41
|
+
const id = getRandomId(4);
|
|
42
|
+
const fileInput = ref<Nullable<HTMLInputElement>>(null);
|
|
43
|
+
|
|
44
|
+
function selectFiles(ev: MouseEvent) {
|
|
45
|
+
if (props.disabled) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ev.stopImmediatePropagation();
|
|
50
|
+
fileInput.value!.click();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateFileSize(file: File) {
|
|
54
|
+
if (!file) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!props.maxFileSize) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { name, size } = file;
|
|
63
|
+
if (size > props.maxFileSize) {
|
|
64
|
+
emits('error', `The [${name}] file is too big. Maximum file size ${formatFileSize(props.maxFileSize)}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateFileType(file: File) {
|
|
71
|
+
if (!file) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { name } = file;
|
|
76
|
+
const fileExt = `.${last(name.split('.'))}`.toLowerCase();
|
|
77
|
+
const allowedFileTypes = props.allowedFileTypes.split(',').map(type => trim(type).toLowerCase());
|
|
78
|
+
|
|
79
|
+
const isValid = allowedFileTypes.includes('*') || allowedFileTypes.includes(fileExt);
|
|
80
|
+
if (!isValid) {
|
|
81
|
+
emits('error', `The [${name}] file type is not valid.`);
|
|
82
|
+
}
|
|
83
|
+
return isValid;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateDuplicateFile(file: File) {
|
|
87
|
+
if (!file) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { name } = file;
|
|
92
|
+
const isFoundDuplicate = [...props.modelValue].find(file => file.name === name);
|
|
93
|
+
|
|
94
|
+
if (isFoundDuplicate) {
|
|
95
|
+
emits('error', `The [${name}] file has already been downloaded.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return !isFoundDuplicate;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateCount(files: File[] = []) {
|
|
102
|
+
if (!props.maxNumberOfFiles) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const total = files.reduce((acc, file) => {
|
|
107
|
+
const { size } = file;
|
|
108
|
+
if (size) {
|
|
109
|
+
acc += size;
|
|
110
|
+
}
|
|
111
|
+
return acc;
|
|
112
|
+
}, 0);
|
|
113
|
+
|
|
114
|
+
const isValid = total <= Number(props.maxNumberOfFiles);
|
|
115
|
+
if (!isValid) {
|
|
116
|
+
emits('error', `You cannot select more than ${props.maxNumberOfFiles} files.`);
|
|
117
|
+
}
|
|
118
|
+
return isValid;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function processFiles(files: File[]) {
|
|
122
|
+
if (size(files)) {
|
|
123
|
+
if (!validateCount(files)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const inputValue = files
|
|
128
|
+
.filter(f => validateDuplicateFile(f))
|
|
129
|
+
.filter(f => validateFileSize(f))
|
|
130
|
+
.filter(f => validateFileType(f));
|
|
131
|
+
|
|
132
|
+
if (size(inputValue) > 0) {
|
|
133
|
+
emits('update:modelValue', [...props.modelValue, ...inputValue]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onChange() {
|
|
139
|
+
processFiles([...fileInput.value!.files!]);
|
|
140
|
+
fileInput.value!.value = '';
|
|
141
|
+
}
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<style lang="scss" module>
|
|
145
|
+
.ui3nInputFile {
|
|
146
|
+
position: relative;
|
|
147
|
+
width: max-content;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.label {
|
|
151
|
+
font-size: var(--font-12);
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
line-height: var(--font-16);
|
|
154
|
+
color: var(--color-text-button-secondary-default);
|
|
155
|
+
padding: 0 var(--space-xs);
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
|
|
158
|
+
&:hover {
|
|
159
|
+
color: var(--color-text-button-secondary-hover);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
&.disabled {
|
|
163
|
+
cursor: default;
|
|
164
|
+
opacity: 0.5;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
</style>
|