@indielayer/ui 1.9.2 → 1.10.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/docs/components/menu/DocsMenu.vue +1 -0
- package/docs/pages/component/form/usage.vue +2 -0
- package/docs/pages/component/menu/usage.vue +2 -0
- package/docs/pages/component/select/index.vue +7 -0
- package/docs/pages/component/select/multiple.vue +42 -0
- package/docs/pages/component/select/usage.vue +8 -12
- package/docs/pages/component/table/virtual.vue +19 -6
- package/docs/pages/component/toggle/index.vue +1 -1
- package/docs/pages/component/tooltip/index.vue +1 -1
- package/docs/pages/component/upload/index.vue +29 -0
- package/docs/pages/component/upload/usage.vue +115 -0
- package/docs/search/components.json +1 -1
- package/lib/common/icons.d.ts +2 -0
- package/lib/common/icons.js +17 -15
- package/lib/components/checkbox/Checkbox.vue2.js +9 -9
- package/lib/components/datepicker/Datepicker.vue.d.ts +4 -4
- package/lib/components/datepicker/Datepicker.vue.js +1 -1
- package/lib/components/drawer/Drawer.vue.js +1 -17
- package/lib/components/form/Form.vue.d.ts +4 -4
- package/lib/components/form/Form.vue.js +34 -34
- package/lib/components/formGroup/FormGroup.vue.d.ts +1 -1
- package/lib/components/formGroup/FormGroup.vue.js +39 -37
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +68 -66
- package/lib/components/label/theme/Label.base.theme.js +7 -7
- package/lib/components/menu/Menu.vue.d.ts +2 -0
- package/lib/components/menu/MenuItem.vue.d.ts +15 -3
- package/lib/components/menu/MenuItem.vue.js +1 -1
- package/lib/components/menu/MenuItem.vue2.js +43 -37
- package/lib/components/modal/Modal.vue.d.ts +4 -4
- package/lib/components/modal/Modal.vue.js +38 -34
- package/lib/components/notifications/Notifications.vue.d.ts +15 -0
- package/lib/components/notifications/Notifications.vue.js +149 -127
- package/lib/components/progress/Progress.vue.d.ts +4 -4
- package/lib/components/progress/Progress.vue.js +7 -7
- package/lib/components/scroll/Scroll.vue2.js +1 -1
- package/lib/components/select/Select.vue.d.ts +43 -1
- package/lib/components/select/Select.vue.js +358 -258
- package/lib/components/select/theme/Select.base.theme.js +1 -0
- package/lib/components/tab/Tab.vue.js +1 -1
- package/lib/components/tab/TabGroup.vue.js +2 -2
- package/lib/components/table/Table.vue.d.ts +4 -4
- package/lib/components/table/Table.vue.js +37 -37
- package/lib/components/table/TableCell.vue.d.ts +1 -1
- package/lib/components/tag/Tag.vue.js +23 -21
- package/lib/components/textarea/Textarea.vue.js +1 -1
- package/lib/components/upload/Upload.vue.d.ts +195 -0
- package/lib/components/upload/Upload.vue.js +264 -0
- package/lib/components/upload/Upload.vue2.js +4 -0
- package/lib/components/upload/__tests__/Upload.spec.d.ts +1 -0
- package/lib/components/upload/index.d.ts +2 -0
- package/lib/components/upload/theme/Upload.base.theme.d.ts +3 -0
- package/lib/components/upload/theme/Upload.base.theme.js +8 -0
- package/lib/components/upload/theme/Upload.carbon.theme.d.ts +3 -0
- package/lib/components/upload/theme/Upload.carbon.theme.js +5 -0
- package/lib/composables/useVirtualList.js +56 -53
- package/lib/index.js +43 -41
- package/lib/index.umd.js +4 -4
- package/lib/node_modules/.pnpm/@vueuse_core@11.1.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/core/index.js +501 -0
- package/lib/node_modules/.pnpm/@vueuse_shared@11.1.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/shared/index.js +96 -0
- package/lib/theme.d.ts +2 -1
- package/lib/themes/base/components.d.ts +1 -0
- package/lib/themes/base/components.js +23 -21
- package/lib/themes/carbon/components.d.ts +1 -0
- package/lib/themes/carbon/components.js +23 -21
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -3
- package/src/common/icons.ts +2 -0
- package/src/components/checkbox/Checkbox.vue +5 -5
- package/src/components/drawer/Drawer.vue +0 -16
- package/src/components/form/Form.vue +10 -4
- package/src/components/formGroup/FormGroup.vue +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/label/theme/Label.base.theme.ts +7 -5
- package/src/components/menu/Menu.vue +2 -0
- package/src/components/menu/MenuItem.vue +8 -6
- package/src/components/modal/Modal.vue +6 -1
- package/src/components/notifications/Notifications.vue +34 -4
- package/src/components/progress/Progress.vue +2 -2
- package/src/components/select/Select.vue +165 -67
- package/src/components/select/theme/Select.base.theme.ts +2 -0
- package/src/components/table/Table.vue +2 -3
- package/src/components/tag/Tag.vue +11 -9
- package/src/components/upload/Upload.vue +365 -0
- package/src/components/upload/__tests__/Upload.spec.ts +11 -0
- package/src/components/upload/index.ts +2 -0
- package/src/components/upload/theme/Upload.base.theme.ts +9 -0
- package/src/components/upload/theme/Upload.carbon.theme.ts +7 -0
- package/src/composables/useInputtable.ts +1 -1
- package/src/composables/useVirtualList.ts +8 -5
- package/src/theme.ts +2 -0
- package/src/themes/base/components.ts +1 -0
- package/src/themes/carbon/components.ts +1 -0
- package/src/version.ts +1 -1
- package/volar.d.ts +1 -0
- package/lib/node_modules/.pnpm/@vueuse_core@10.2.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/core/index.js +0 -412
- package/lib/node_modules/.pnpm/@vueuse_shared@10.2.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/shared/index.js +0 -90
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
const validators = {
|
|
3
|
+
...useCommon.validators(),
|
|
4
|
+
variant: ['box'] as const,
|
|
5
|
+
}
|
|
6
|
+
const uploadProps = {
|
|
7
|
+
...useInteractive.props(),
|
|
8
|
+
...useInputtable.props(),
|
|
9
|
+
placeholder: String,
|
|
10
|
+
accept: String,
|
|
11
|
+
multiple: Boolean,
|
|
12
|
+
maxFiles: [Number, String],
|
|
13
|
+
maxFileSize: [Number, String],
|
|
14
|
+
variant: {
|
|
15
|
+
type: String as PropType<UploadVariant>,
|
|
16
|
+
default: 'box',
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// URL to submit data to.
|
|
20
|
+
action: String,
|
|
21
|
+
// Additional HTTP headers
|
|
22
|
+
headers: Object as PropType<Record<string, string>>,
|
|
23
|
+
method: {
|
|
24
|
+
type: String as PropType<'POST' | 'PUT'>,
|
|
25
|
+
default: 'POST',
|
|
26
|
+
},
|
|
27
|
+
withCredentials: Boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type UploadFile = {
|
|
31
|
+
file: File;
|
|
32
|
+
completed: boolean;
|
|
33
|
+
response?: any;
|
|
34
|
+
progress: number;
|
|
35
|
+
error: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type UploadVariant = typeof validators.variant[number]
|
|
39
|
+
export type UploadProps = ExtractPublicPropTypes<typeof uploadProps>
|
|
40
|
+
|
|
41
|
+
type InternalClasses = 'wrapper' | 'input'
|
|
42
|
+
export interface UploadTheme extends ThemeComponent<UploadProps, InternalClasses> {}
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
name: 'XUpload',
|
|
46
|
+
validators,
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { computed, ref, watch, type ExtractPublicPropTypes, type PropType } from 'vue'
|
|
52
|
+
import { useDropZone } from '@vueuse/core'
|
|
53
|
+
import { useCommon } from '../../composables/useCommon'
|
|
54
|
+
import { useInteractive } from '../../composables/useInteractive'
|
|
55
|
+
import { useInputtable } from '../../composables/useInputtable'
|
|
56
|
+
import { useTheme, type ThemeComponent } from '../../composables/useTheme'
|
|
57
|
+
import { uploadIcon, closeIcon, fileIcon } from '../../common/icons'
|
|
58
|
+
|
|
59
|
+
import XLabel from '../label/Label.vue'
|
|
60
|
+
import XInputFooter from '../inputFooter/InputFooter.vue'
|
|
61
|
+
import XProgress from '../progress/Progress.vue'
|
|
62
|
+
import XIcon from '../icon/Icon.vue'
|
|
63
|
+
|
|
64
|
+
const props = defineProps(uploadProps)
|
|
65
|
+
|
|
66
|
+
const emit = defineEmits([...useInputtable.emits(), 'upload', 'remove'])
|
|
67
|
+
|
|
68
|
+
const elRef = ref<HTMLElement | null>(null)
|
|
69
|
+
|
|
70
|
+
const isUploadMode = computed(() => !!props.action)
|
|
71
|
+
|
|
72
|
+
// works as internal model, but exposed for form validations
|
|
73
|
+
const modelValue = ref<UploadFile[]>([])
|
|
74
|
+
|
|
75
|
+
watch(modelValue, (val) => {
|
|
76
|
+
emit('update:modelValue', val)
|
|
77
|
+
}, { deep: true, immediate: true })
|
|
78
|
+
|
|
79
|
+
const { focus, blur } = useInteractive(elRef)
|
|
80
|
+
|
|
81
|
+
function isInputValid(files: File[] | FileList | null) {
|
|
82
|
+
if (!files) return false
|
|
83
|
+
|
|
84
|
+
if ((!props.multiple && files.length > 1) || (props.maxFiles && files.length > Number(props.maxFiles))) {
|
|
85
|
+
errorInternal.value = 'Too many files'
|
|
86
|
+
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (props.maxFileSize) {
|
|
91
|
+
for (let i = 0; i < files.length; i++) {
|
|
92
|
+
if (files[i].size > Number(props.maxFileSize)) {
|
|
93
|
+
errorInternal.value = 'File size is too large'
|
|
94
|
+
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (props.accept) {
|
|
101
|
+
const accept = props.accept.split(',').map((type) => type.trim())
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < files.length; i++) {
|
|
104
|
+
const file = files[i]
|
|
105
|
+
|
|
106
|
+
if (!accept.some((type) => file.type === type || file.name.endsWith(type))) {
|
|
107
|
+
errorInternal.value = 'Invalid file type'
|
|
108
|
+
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function onChange(event: Event) {
|
|
118
|
+
const target = event.target as HTMLInputElement
|
|
119
|
+
const files = target.files
|
|
120
|
+
|
|
121
|
+
const isValid = isInputValid(files)
|
|
122
|
+
|
|
123
|
+
if (!isValid) return
|
|
124
|
+
|
|
125
|
+
emit('change', event)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onInput(event: Event) {
|
|
129
|
+
const target = event.target as HTMLInputElement
|
|
130
|
+
const files = target.files
|
|
131
|
+
|
|
132
|
+
const isValid = isInputValid(files)
|
|
133
|
+
|
|
134
|
+
if (!isValid) return
|
|
135
|
+
|
|
136
|
+
handleFiles(files)
|
|
137
|
+
|
|
138
|
+
if (props.validateOnInput && !isFirstValidation.value) validate(modelValue.value)
|
|
139
|
+
|
|
140
|
+
emit('input', event)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dropZoneRef = ref<HTMLElement | null>(null)
|
|
144
|
+
|
|
145
|
+
function onDrop(files: File[] | null) {
|
|
146
|
+
const isValid = isInputValid(files)
|
|
147
|
+
|
|
148
|
+
if (!isValid) return
|
|
149
|
+
|
|
150
|
+
handleFiles(files)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { isOverDropZone } = useDropZone(dropZoneRef, {
|
|
154
|
+
onDrop,
|
|
155
|
+
multiple: props.multiple,
|
|
156
|
+
preventDefaultForUnhandled: false,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
function handleFiles(files: File[] | FileList | null) {
|
|
160
|
+
modelValue.value = files ? Array.from(files).map((file) => ({
|
|
161
|
+
file,
|
|
162
|
+
completed: isUploadMode.value ? false : true,
|
|
163
|
+
progress: isUploadMode.value ? 0 : 100,
|
|
164
|
+
error: '',
|
|
165
|
+
})) : []
|
|
166
|
+
|
|
167
|
+
if (isUploadMode.value) upload()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function removeFile(file: File) {
|
|
171
|
+
const index = modelValue.value.findIndex((f) => f.file === file)
|
|
172
|
+
|
|
173
|
+
if (index === -1) return
|
|
174
|
+
|
|
175
|
+
emit('remove', modelValue.value[index])
|
|
176
|
+
|
|
177
|
+
modelValue.value.splice(index, 1)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isImage(file: File) {
|
|
181
|
+
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg']
|
|
182
|
+
|
|
183
|
+
return file.type.startsWith('image') || imageExtensions.some((ext) => file.name.endsWith(ext))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getImagePreview(file: File) {
|
|
187
|
+
return URL.createObjectURL(file)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function calculateFileSize(size: number) {
|
|
191
|
+
if (size < 1024) return `${size} B`
|
|
192
|
+
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`
|
|
193
|
+
if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(2)} MB`
|
|
194
|
+
|
|
195
|
+
return `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function uploadFileRequest(uploadFile: UploadFile) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
if (!props.action) reject(new Error('No action provided'))
|
|
201
|
+
else {
|
|
202
|
+
const xhr = new XMLHttpRequest()
|
|
203
|
+
const formData = new FormData()
|
|
204
|
+
|
|
205
|
+
formData.append('file', uploadFile.file)
|
|
206
|
+
|
|
207
|
+
xhr.open(props.method, props.action, true)
|
|
208
|
+
|
|
209
|
+
// Set headers
|
|
210
|
+
if (props.headers && typeof props.headers === 'object') {
|
|
211
|
+
Object.keys(props.headers).forEach((key) => {
|
|
212
|
+
xhr.setRequestHeader(key, props.headers![key])
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Progress event for tracking upload
|
|
217
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
218
|
+
if (event.lengthComputable) {
|
|
219
|
+
const percentComplete = (event.loaded / event.total) * 100
|
|
220
|
+
|
|
221
|
+
uploadFile.progress = percentComplete
|
|
222
|
+
|
|
223
|
+
console.log(`Upload progress: ${percentComplete}%`)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// Event listener when upload is complete
|
|
228
|
+
xhr.addEventListener('load', () => {
|
|
229
|
+
if (xhr.status === 200) {
|
|
230
|
+
uploadFile.completed = true
|
|
231
|
+
try {
|
|
232
|
+
uploadFile.response = JSON.parse(xhr.responseText)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
uploadFile.response = xhr.responseText
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
resolve(xhr.responseText)
|
|
238
|
+
} else {
|
|
239
|
+
const errorMsg = `Upload failed. Status: ${xhr.status}`
|
|
240
|
+
|
|
241
|
+
uploadFile.error = errorMsg
|
|
242
|
+
reject(new Error(errorMsg))
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
xhr.addEventListener('error', () => {
|
|
247
|
+
uploadFile.error = 'An error occurred during the upload.'
|
|
248
|
+
reject(new Error('An error occurred during the upload.'))
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
xhr.send(formData)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function upload() {
|
|
257
|
+
if (!props.action && !modelValue.value) return
|
|
258
|
+
|
|
259
|
+
const promises = []
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < modelValue.value.length; i++) {
|
|
262
|
+
promises.push(uploadFileRequest(modelValue.value[i]))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await Promise.all(promises)
|
|
266
|
+
|
|
267
|
+
validate(modelValue.value)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const {
|
|
271
|
+
errorInternal,
|
|
272
|
+
hideFooterInternal,
|
|
273
|
+
isInsideForm,
|
|
274
|
+
inputListeners,
|
|
275
|
+
isFirstValidation,
|
|
276
|
+
reset,
|
|
277
|
+
validate,
|
|
278
|
+
setError,
|
|
279
|
+
} = useInputtable(props, { focus, emit })
|
|
280
|
+
|
|
281
|
+
const { styles, classes, className } = useTheme('Upload', {}, props)
|
|
282
|
+
|
|
283
|
+
defineExpose({ focus, blur, reset, validate, setError })
|
|
284
|
+
</script>
|
|
285
|
+
|
|
286
|
+
<template>
|
|
287
|
+
<div
|
|
288
|
+
:class="[
|
|
289
|
+
className,
|
|
290
|
+
classes.wrapper,
|
|
291
|
+
]"
|
|
292
|
+
>
|
|
293
|
+
<x-label
|
|
294
|
+
:style="styles"
|
|
295
|
+
:disabled="disabled"
|
|
296
|
+
:required="required"
|
|
297
|
+
:is-inside-form="isInsideForm"
|
|
298
|
+
:label="label"
|
|
299
|
+
:tooltip="tooltip"
|
|
300
|
+
>
|
|
301
|
+
<input
|
|
302
|
+
:id="id"
|
|
303
|
+
ref="elRef"
|
|
304
|
+
type="file"
|
|
305
|
+
:class="[
|
|
306
|
+
'sr-only',
|
|
307
|
+
classes.input,
|
|
308
|
+
]"
|
|
309
|
+
:disabled="disabled"
|
|
310
|
+
:name="name"
|
|
311
|
+
:accept="accept"
|
|
312
|
+
:multiple="multiple"
|
|
313
|
+
:readonly="readonly"
|
|
314
|
+
v-on="{
|
|
315
|
+
...inputListeners,
|
|
316
|
+
input: onInput,
|
|
317
|
+
change: onChange,
|
|
318
|
+
}"
|
|
319
|
+
/>
|
|
320
|
+
<slot name="box" :is-over="isOverDropZone">
|
|
321
|
+
<div
|
|
322
|
+
ref="dropZoneRef"
|
|
323
|
+
class="border border-dashed rounded-md flex items-center justify-center gap-2 p-4 cursor-pointer transition-colors duration-500 hover:border-primary-500 dark:hover:border-primary-400"
|
|
324
|
+
:class="{
|
|
325
|
+
'border-primary-500 dark:border-primary-400': isOverDropZone,
|
|
326
|
+
}"
|
|
327
|
+
>
|
|
328
|
+
<x-icon :icon="uploadIcon" />
|
|
329
|
+
<span class="text-secondary-500 dark:text-secondary-400">{{ placeholder }}</span>
|
|
330
|
+
<span class="text-xs text-secondary-400 dark:text-secondary-500">or drag and drop</span>
|
|
331
|
+
</div>
|
|
332
|
+
</slot>
|
|
333
|
+
|
|
334
|
+
<x-input-footer v-if="!hideFooterInternal" :error="errorInternal" :helper="helper"/>
|
|
335
|
+
</x-label>
|
|
336
|
+
<slot name="files" :files="modelValue">
|
|
337
|
+
<div v-for="(uploadFile, i) in modelValue" :key="i" class="border rounded-md p-2 bg-secondary-100 my-2">
|
|
338
|
+
<div class="flex items-center">
|
|
339
|
+
<div
|
|
340
|
+
class="rounded-md w-9 h-9 flex items-center justify-center mr-2 border"
|
|
341
|
+
:class="{
|
|
342
|
+
'p-2 bg-white': !isImage(uploadFile.file),
|
|
343
|
+
}"
|
|
344
|
+
>
|
|
345
|
+
<img
|
|
346
|
+
v-if="isImage(uploadFile.file)"
|
|
347
|
+
:src="getImagePreview(uploadFile.file)"
|
|
348
|
+
class="w-full h-full object-cover rounded-md"
|
|
349
|
+
/>
|
|
350
|
+
<x-icon v-else :icon="fileIcon"/>
|
|
351
|
+
</div>
|
|
352
|
+
<div class="truncate flex-1 pr-2">
|
|
353
|
+
<div class="font-medium truncate">{{ uploadFile.file.name }}</div>
|
|
354
|
+
<div class="text-xs text-secondary-500">{{ calculateFileSize(uploadFile.file.size) }}</div>
|
|
355
|
+
</div>
|
|
356
|
+
<button type="button" class="shrink-0" @click="removeFile(uploadFile.file)">
|
|
357
|
+
<slot name="removeIcon"><x-icon :icon="closeIcon"/></slot>
|
|
358
|
+
</button>
|
|
359
|
+
</div>
|
|
360
|
+
<x-progress v-if="isUploadMode" :percentage="uploadFile.progress" class="mt-1" :color="uploadFile.error ? 'warning' : 'success'"/>
|
|
361
|
+
<x-input-footer v-if="uploadFile.error" :error="uploadFile.error"/>
|
|
362
|
+
</div>
|
|
363
|
+
</slot>
|
|
364
|
+
</div>
|
|
365
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import Upload from '../Upload.vue'
|
|
4
|
+
|
|
5
|
+
describe('Upload', () => {
|
|
6
|
+
it('renders without errors', () => {
|
|
7
|
+
const wrapper = mount(Upload)
|
|
8
|
+
|
|
9
|
+
expect(wrapper.vm).toBeTruthy()
|
|
10
|
+
})
|
|
11
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MaybeRef, PropType } from 'vue'
|
|
2
|
-
import {
|
|
2
|
+
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
3
3
|
import { injectFormGroupKey, injectFormKey } from './keys'
|
|
4
4
|
|
|
5
5
|
export interface XFormInputMethods {
|
|
@@ -196,6 +196,7 @@ function createCalculateRange<T>(type: 'horizontal' | 'vertical', overscan: numb
|
|
|
196
196
|
|
|
197
197
|
function createGetDistance<T>(itemSize: UseVirtualListItemSize, source: UseVirtualListResources<T>['source']) {
|
|
198
198
|
return (index: number) => {
|
|
199
|
+
|
|
199
200
|
if (typeof itemSize === 'number') {
|
|
200
201
|
const size = index * itemSize
|
|
201
202
|
|
|
@@ -210,10 +211,9 @@ function createGetDistance<T>(itemSize: UseVirtualListItemSize, source: UseVirtu
|
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
function useWatchForSizes<T>(size: UseVirtualElementSizes, list: MaybeRef<T[]>, containerRef: Ref<HTMLElement | null>, calculateRange: () => void
|
|
214
|
+
function useWatchForSizes<T>(size: UseVirtualElementSizes, list: MaybeRef<T[]>, containerRef: Ref<HTMLElement | null>, calculateRange: () => void) {
|
|
214
215
|
watch([size.width, size.height, list, containerRef], () => {
|
|
215
216
|
calculateRange()
|
|
216
|
-
scrollTo(0)
|
|
217
217
|
})
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -263,14 +263,17 @@ function useVerticalVirtualList<T>(options: UseVirtualListOptions, list: MaybeRe
|
|
|
263
263
|
|
|
264
264
|
const scrollTo = createScrollTo('vertical', calculateRange, getDistanceTop, containerRef)
|
|
265
265
|
|
|
266
|
-
useWatchForSizes(size,
|
|
266
|
+
useWatchForSizes(size, source, containerRef, calculateRange)
|
|
267
267
|
|
|
268
268
|
const wrapperProps = computed(() => {
|
|
269
|
+
const total = totalHeight.value + topOffset + bottomOffset
|
|
270
|
+
const offTop = offsetTop.value > total ? total : offsetTop.value
|
|
271
|
+
|
|
269
272
|
return {
|
|
270
273
|
style: {
|
|
271
274
|
width: '100%',
|
|
272
|
-
height: `${totalHeight.value -
|
|
273
|
-
marginTop: `${
|
|
275
|
+
height: `${totalHeight.value - offTop + topOffset + bottomOffset}px`,
|
|
276
|
+
marginTop: `${offTop}px`,
|
|
274
277
|
},
|
|
275
278
|
}
|
|
276
279
|
})
|
package/src/theme.ts
CHANGED
|
@@ -51,6 +51,7 @@ import type {
|
|
|
51
51
|
TagTheme,
|
|
52
52
|
TextareaTheme,
|
|
53
53
|
ToggleTheme,
|
|
54
|
+
UploadTheme,
|
|
54
55
|
} from './components'
|
|
55
56
|
|
|
56
57
|
export type ComponentThemes = {
|
|
@@ -105,6 +106,7 @@ export type ComponentThemes = {
|
|
|
105
106
|
Tag: TagTheme;
|
|
106
107
|
Textarea: TextareaTheme;
|
|
107
108
|
Toggle: ToggleTheme;
|
|
109
|
+
Upload: UploadTheme;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
export type UITheme = {
|
|
@@ -49,3 +49,4 @@ export { default as TableRow } from '../../components/table/theme/TableRow.base.
|
|
|
49
49
|
export { default as Tag } from '../../components/tag/theme/Tag.base.theme'
|
|
50
50
|
export { default as Textarea } from '../../components/textarea/theme/Textarea.base.theme'
|
|
51
51
|
export { default as Toggle } from '../../components/toggle/theme/Toggle.base.theme'
|
|
52
|
+
export { default as Upload } from '../../components/upload/theme/Upload.base.theme'
|
|
@@ -49,3 +49,4 @@ export { default as TableRow } from '../../components/table/theme/TableRow.carbo
|
|
|
49
49
|
export { default as Tag } from '../../components/tag/theme/Tag.carbon.theme'
|
|
50
50
|
export { default as Textarea } from '../../components/textarea/theme/Textarea.carbon.theme'
|
|
51
51
|
export { default as Toggle } from '../../components/toggle/theme/Toggle.carbon.theme'
|
|
52
|
+
export { default as Upload } from '../../components/upload/theme/Upload.carbon.theme'
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.
|
|
1
|
+
export default '1.10.0'
|
package/volar.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ declare module 'vue' {
|
|
|
57
57
|
XToggle: typeof import('@indielayer/ui')['XToggle']
|
|
58
58
|
XToggleTip: typeof import('@indielayer/ui')['XToggleTip']
|
|
59
59
|
XTooltip: typeof import('@indielayer/ui')['XTooltip']
|
|
60
|
+
XUpload: typeof import('@indielayer/ui')['XUpload']
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|