@uploadista/vue 0.0.3
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/.turbo/turbo-check.log +240 -0
- package/LICENSE +21 -0
- package/README.md +554 -0
- package/package.json +36 -0
- package/src/components/FlowUploadList.vue +342 -0
- package/src/components/FlowUploadZone.vue +305 -0
- package/src/components/UploadList.vue +303 -0
- package/src/components/UploadZone.vue +254 -0
- package/src/components/index.ts +11 -0
- package/src/composables/index.ts +44 -0
- package/src/composables/plugin.ts +76 -0
- package/src/composables/useDragDrop.ts +343 -0
- package/src/composables/useFlowUpload.ts +431 -0
- package/src/composables/useMultiFlowUpload.ts +322 -0
- package/src/composables/useMultiUpload.ts +546 -0
- package/src/composables/useUpload.ts +300 -0
- package/src/composables/useUploadMetrics.ts +502 -0
- package/src/composables/useUploadistaClient.ts +73 -0
- package/src/index.ts +28 -0
- package/src/providers/UploadistaProvider.vue +69 -0
- package/src/providers/index.ts +1 -0
- package/src/utils/index.ts +156 -0
- package/src/utils/is-browser-file.ts +2 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { computed, readonly, ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface DragDropOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Accept specific file types (MIME types or file extensions)
|
|
6
|
+
*/
|
|
7
|
+
accept?: string[];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Maximum number of files allowed
|
|
11
|
+
*/
|
|
12
|
+
maxFiles?: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maximum file size in bytes
|
|
16
|
+
*/
|
|
17
|
+
maxFileSize?: number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether to allow multiple files
|
|
21
|
+
*/
|
|
22
|
+
multiple?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Custom validation function for files
|
|
26
|
+
*/
|
|
27
|
+
validator?: (files: File[]) => string[] | null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Called when files are dropped or selected
|
|
31
|
+
*/
|
|
32
|
+
onFilesReceived?: (files: File[]) => void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Called when validation fails
|
|
36
|
+
*/
|
|
37
|
+
onValidationError?: (errors: string[]) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Called when drag state changes
|
|
41
|
+
*/
|
|
42
|
+
onDragStateChange?: (isDragging: boolean) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DragDropState {
|
|
46
|
+
/**
|
|
47
|
+
* Whether files are currently being dragged over the drop zone
|
|
48
|
+
*/
|
|
49
|
+
isDragging: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether the drag is currently over the drop zone
|
|
53
|
+
*/
|
|
54
|
+
isOver: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the dragged items are valid files
|
|
58
|
+
*/
|
|
59
|
+
isValid: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Current validation errors
|
|
63
|
+
*/
|
|
64
|
+
errors: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const initialState: DragDropState = {
|
|
68
|
+
isDragging: false,
|
|
69
|
+
isOver: false,
|
|
70
|
+
isValid: true,
|
|
71
|
+
errors: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Vue composable for handling drag and drop file uploads with validation.
|
|
76
|
+
* Provides drag state management, file validation, and file picker integration.
|
|
77
|
+
*
|
|
78
|
+
* @param options - Configuration and event handlers
|
|
79
|
+
* @returns Drag and drop state and handlers
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```vue
|
|
83
|
+
* <script setup lang="ts">
|
|
84
|
+
* import { useDragDrop } from '@uploadista/vue';
|
|
85
|
+
* import { ref } from 'vue';
|
|
86
|
+
*
|
|
87
|
+
* const inputRef = ref<HTMLInputElement>();
|
|
88
|
+
*
|
|
89
|
+
* const dragDrop = useDragDrop({
|
|
90
|
+
* accept: ['image/*', '.pdf'],
|
|
91
|
+
* maxFiles: 5,
|
|
92
|
+
* maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
93
|
+
* multiple: true,
|
|
94
|
+
* onFilesReceived: (files) => {
|
|
95
|
+
* console.log('Received files:', files);
|
|
96
|
+
* // Process files with upload composables
|
|
97
|
+
* },
|
|
98
|
+
* onValidationError: (errors) => {
|
|
99
|
+
* console.error('Validation errors:', errors);
|
|
100
|
+
* },
|
|
101
|
+
* });
|
|
102
|
+
*
|
|
103
|
+
* const openFilePicker = () => {
|
|
104
|
+
* inputRef.value?.click();
|
|
105
|
+
* };
|
|
106
|
+
* </script>
|
|
107
|
+
*
|
|
108
|
+
* <template>
|
|
109
|
+
* <div>
|
|
110
|
+
* <div
|
|
111
|
+
* @dragenter="dragDrop.onDragEnter"
|
|
112
|
+
* @dragover="dragDrop.onDragOver"
|
|
113
|
+
* @dragleave="dragDrop.onDragLeave"
|
|
114
|
+
* @drop="dragDrop.onDrop"
|
|
115
|
+
* @click="openFilePicker"
|
|
116
|
+
* :style="{
|
|
117
|
+
* border: dragDrop.state.isDragging ? '2px dashed #007bff' : '2px dashed #ccc',
|
|
118
|
+
* backgroundColor: dragDrop.state.isOver ? '#f8f9fa' : 'transparent',
|
|
119
|
+
* padding: '2rem',
|
|
120
|
+
* textAlign: 'center',
|
|
121
|
+
* cursor: 'pointer',
|
|
122
|
+
* }"
|
|
123
|
+
* >
|
|
124
|
+
* <p v-if="dragDrop.state.isDragging">Drop files here...</p>
|
|
125
|
+
* <p v-else>Drag files here or click to select</p>
|
|
126
|
+
*
|
|
127
|
+
* <div v-if="dragDrop.state.errors.length > 0" style="color: red; margin-top: 1rem">
|
|
128
|
+
* <p v-for="(error, index) in dragDrop.state.errors" :key="index">{{ error }}</p>
|
|
129
|
+
* </div>
|
|
130
|
+
* </div>
|
|
131
|
+
*
|
|
132
|
+
* <input
|
|
133
|
+
* ref="inputRef"
|
|
134
|
+
* type="file"
|
|
135
|
+
* :multiple="dragDrop.inputProps.multiple"
|
|
136
|
+
* :accept="dragDrop.inputProps.accept"
|
|
137
|
+
* @change="dragDrop.onInputChange"
|
|
138
|
+
* style="display: none"
|
|
139
|
+
* />
|
|
140
|
+
* </div>
|
|
141
|
+
* </template>
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function useDragDrop(options: DragDropOptions = {}) {
|
|
145
|
+
const {
|
|
146
|
+
accept,
|
|
147
|
+
maxFiles,
|
|
148
|
+
maxFileSize,
|
|
149
|
+
multiple = true,
|
|
150
|
+
validator,
|
|
151
|
+
onFilesReceived,
|
|
152
|
+
onValidationError,
|
|
153
|
+
onDragStateChange,
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
const state = ref<DragDropState>({ ...initialState });
|
|
157
|
+
const dragCounter = ref(0);
|
|
158
|
+
|
|
159
|
+
const updateState = (update: Partial<DragDropState>) => {
|
|
160
|
+
state.value = { ...state.value, ...update };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const validateFiles = (files: File[]): string[] => {
|
|
164
|
+
const errors: string[] = [];
|
|
165
|
+
|
|
166
|
+
// Check file count
|
|
167
|
+
if (maxFiles && files.length > maxFiles) {
|
|
168
|
+
errors.push(
|
|
169
|
+
`Maximum ${maxFiles} files allowed. You selected ${files.length} files.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check individual files
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
// Check file size
|
|
176
|
+
if (maxFileSize && file.size > maxFileSize) {
|
|
177
|
+
const maxSizeMB = (maxFileSize / (1024 * 1024)).toFixed(1);
|
|
178
|
+
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
|
|
179
|
+
errors.push(
|
|
180
|
+
`File "${file.name}" (${fileSizeMB}MB) exceeds maximum size of ${maxSizeMB}MB.`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check file type
|
|
185
|
+
if (accept && accept.length > 0) {
|
|
186
|
+
const isAccepted = accept.some((acceptType) => {
|
|
187
|
+
if (acceptType.startsWith(".")) {
|
|
188
|
+
// File extension check
|
|
189
|
+
return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
|
|
190
|
+
} else {
|
|
191
|
+
// MIME type check (supports wildcards like image/*)
|
|
192
|
+
if (acceptType.endsWith("/*")) {
|
|
193
|
+
const baseType = acceptType.slice(0, -2);
|
|
194
|
+
return file.type.startsWith(baseType);
|
|
195
|
+
} else {
|
|
196
|
+
return file.type === acceptType;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!isAccepted) {
|
|
202
|
+
errors.push(
|
|
203
|
+
`File "${file.name}" type "${file.type}" is not accepted. Accepted types: ${accept.join(", ")}.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Run custom validator
|
|
210
|
+
if (validator) {
|
|
211
|
+
const customErrors = validator(files);
|
|
212
|
+
if (customErrors) {
|
|
213
|
+
errors.push(...customErrors);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return errors;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const processFiles = (files: File[]) => {
|
|
221
|
+
const fileArray = Array.from(files);
|
|
222
|
+
const errors = validateFiles(fileArray);
|
|
223
|
+
|
|
224
|
+
if (errors.length > 0) {
|
|
225
|
+
updateState({ errors, isValid: false });
|
|
226
|
+
onValidationError?.(errors);
|
|
227
|
+
} else {
|
|
228
|
+
updateState({ errors: [], isValid: true });
|
|
229
|
+
onFilesReceived?.(fileArray);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getFilesFromDataTransfer = (dataTransfer: DataTransfer): File[] => {
|
|
234
|
+
const files: File[] = [];
|
|
235
|
+
|
|
236
|
+
if (dataTransfer.items) {
|
|
237
|
+
// Use DataTransferItemList interface
|
|
238
|
+
for (let i = 0; i < dataTransfer.items.length; i++) {
|
|
239
|
+
const item = dataTransfer.items[i];
|
|
240
|
+
if (item && item.kind === "file") {
|
|
241
|
+
const file = item.getAsFile();
|
|
242
|
+
if (file) {
|
|
243
|
+
files.push(file);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Fallback to DataTransfer.files
|
|
249
|
+
for (let i = 0; i < dataTransfer.files.length; i++) {
|
|
250
|
+
const file = dataTransfer.files[i];
|
|
251
|
+
if (file) {
|
|
252
|
+
files.push(file);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return files;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const onDragEnter = (event: DragEvent) => {
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
event.stopPropagation();
|
|
263
|
+
|
|
264
|
+
dragCounter.value++;
|
|
265
|
+
|
|
266
|
+
if (dragCounter.value === 1) {
|
|
267
|
+
updateState({ isDragging: true, isOver: true });
|
|
268
|
+
onDragStateChange?.(true);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const onDragOver = (event: DragEvent) => {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
|
|
276
|
+
// Set dropEffect to indicate what operation is allowed
|
|
277
|
+
if (event.dataTransfer) {
|
|
278
|
+
event.dataTransfer.dropEffect = "copy";
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const onDragLeave = (event: DragEvent) => {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
event.stopPropagation();
|
|
285
|
+
|
|
286
|
+
dragCounter.value--;
|
|
287
|
+
|
|
288
|
+
if (dragCounter.value === 0) {
|
|
289
|
+
updateState({ isDragging: false, isOver: false, errors: [] });
|
|
290
|
+
onDragStateChange?.(false);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const onDrop = (event: DragEvent) => {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
event.stopPropagation();
|
|
297
|
+
|
|
298
|
+
dragCounter.value = 0;
|
|
299
|
+
updateState({ isDragging: false, isOver: false });
|
|
300
|
+
onDragStateChange?.(false);
|
|
301
|
+
|
|
302
|
+
if (event.dataTransfer) {
|
|
303
|
+
const files = getFilesFromDataTransfer(event.dataTransfer);
|
|
304
|
+
if (files.length > 0) {
|
|
305
|
+
processFiles(files);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const onInputChange = (event: Event) => {
|
|
311
|
+
const input = event.target as HTMLInputElement;
|
|
312
|
+
if (input.files && input.files.length > 0) {
|
|
313
|
+
const files = Array.from(input.files);
|
|
314
|
+
processFiles(files);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Reset input value to allow selecting the same files again
|
|
318
|
+
input.value = "";
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const reset = () => {
|
|
322
|
+
state.value = { ...initialState };
|
|
323
|
+
dragCounter.value = 0;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const inputProps = computed(() => ({
|
|
327
|
+
type: "file" as const,
|
|
328
|
+
multiple,
|
|
329
|
+
accept: accept?.join(", "),
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
state: readonly(state),
|
|
334
|
+
onDragEnter,
|
|
335
|
+
onDragOver,
|
|
336
|
+
onDragLeave,
|
|
337
|
+
onDrop,
|
|
338
|
+
onInputChange,
|
|
339
|
+
inputProps,
|
|
340
|
+
processFiles,
|
|
341
|
+
reset,
|
|
342
|
+
};
|
|
343
|
+
}
|