@destyler/file-upload 0.0.7-beta.7 → 0.0.7-beta.9

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/index.mjs DELETED
@@ -1,572 +0,0 @@
1
- import { createAnatomy } from '@zag-js/anatomy';
2
- import { createScope, getEventTarget, dataAttr, visuallyHiddenStyle, contains, isSelfTarget, raf, addDomEvent } from '@zag-js/dom-query';
3
- import { formatBytes } from '@zag-js/i18n-utils';
4
- import { isValidFileType, isValidFileSize, getAcceptAttrString, isFileEqual } from '@zag-js/file-utils';
5
- import { createMachine, ref } from '@zag-js/core';
6
- import { compact, callAll, createSplitProps } from '@zag-js/utils';
7
- import { createProps } from '@zag-js/types';
8
-
9
- const anatomy = createAnatomy("file-upload").parts(
10
- "root",
11
- "dropzone",
12
- "item",
13
- "itemDeleteTrigger",
14
- "itemGroup",
15
- "itemName",
16
- "itemPreview",
17
- "itemPreviewImage",
18
- "itemSizeText",
19
- "label",
20
- "trigger",
21
- "clearTrigger"
22
- );
23
- const parts = anatomy.build();
24
-
25
- const dom = createScope({
26
- getRootId: (ctx) => ctx.ids?.root ?? `file-upload:${ctx.id}`,
27
- getDropzoneId: (ctx) => ctx.ids?.dropzone ?? `file-upload:${ctx.id}:dropzone`,
28
- getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `file-upload:${ctx.id}:input`,
29
- getTriggerId: (ctx) => ctx.ids?.trigger ?? `file-upload:${ctx.id}:trigger`,
30
- getLabelId: (ctx) => ctx.ids?.label ?? `file-upload:${ctx.id}:label`,
31
- getItemId: (ctx, id) => ctx.ids?.item?.(id) ?? `file-upload:${ctx.id}:item:${id}`,
32
- getItemNameId: (ctx, id) => ctx.ids?.itemName?.(id) ?? `file-upload:${ctx.id}:item-name:${id}`,
33
- getItemSizeTextId: (ctx, id) => ctx.ids?.itemSizeText?.(id) ?? `file-upload:${ctx.id}:item-size:${id}`,
34
- getItemPreviewId: (ctx, id) => ctx.ids?.itemPreview?.(id) ?? `file-upload:${ctx.id}:item-preview:${id}`,
35
- getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
36
- getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
37
- getDropzoneEl: (ctx) => dom.getById(ctx, dom.getDropzoneId(ctx))
38
- });
39
-
40
- function isEventWithFiles(event) {
41
- const target = getEventTarget(event);
42
- if (!event.dataTransfer)
43
- return !!target && "files" in target;
44
- return event.dataTransfer.types.some((type) => {
45
- return type === "Files" || type === "application/x-moz-file";
46
- });
47
- }
48
- function isFilesWithinRange(ctx, incomingCount) {
49
- if (!ctx.multiple && incomingCount > 1)
50
- return false;
51
- if (!ctx.multiple && incomingCount + ctx.acceptedFiles.length === 2)
52
- return true;
53
- if (incomingCount + ctx.acceptedFiles.length > ctx.maxFiles)
54
- return false;
55
- return true;
56
- }
57
- function getFilesFromEvent(ctx, files) {
58
- const acceptedFiles = [];
59
- const rejectedFiles = [];
60
- files.forEach((file) => {
61
- const [accepted, acceptError] = isValidFileType(file, ctx.acceptAttr);
62
- const [sizeMatch, sizeError] = isValidFileSize(file, ctx.minFileSize, ctx.maxFileSize);
63
- const validateErrors = ctx.validate?.(file, {
64
- acceptedFiles: ctx.acceptedFiles,
65
- rejectedFiles: ctx.rejectedFiles
66
- });
67
- const valid = validateErrors ? validateErrors.length === 0 : true;
68
- if (accepted && sizeMatch && valid) {
69
- acceptedFiles.push(file);
70
- } else {
71
- const errors = [acceptError, sizeError];
72
- if (!valid)
73
- errors.push(...validateErrors ?? []);
74
- rejectedFiles.push({ file, errors: errors.filter(Boolean) });
75
- }
76
- });
77
- if (!isFilesWithinRange(ctx, acceptedFiles.length)) {
78
- acceptedFiles.forEach((file) => {
79
- rejectedFiles.push({ file, errors: ["TOO_MANY_FILES"] });
80
- });
81
- acceptedFiles.splice(0);
82
- }
83
- return {
84
- acceptedFiles,
85
- rejectedFiles
86
- };
87
- }
88
-
89
- function connect(state, send, normalize) {
90
- const disabled = state.context.disabled;
91
- const allowDrop = state.context.allowDrop;
92
- const translations = state.context.translations;
93
- const dragging = state.matches("dragging");
94
- const focused = state.matches("focused") && !disabled;
95
- return {
96
- dragging,
97
- focused,
98
- openFilePicker() {
99
- send("OPEN");
100
- },
101
- deleteFile(file) {
102
- send({ type: "FILE.DELETE", file });
103
- },
104
- acceptedFiles: state.context.acceptedFiles,
105
- rejectedFiles: state.context.rejectedFiles,
106
- setFiles(files) {
107
- const count = files.length;
108
- send({ type: "FILES.SET", files, count });
109
- },
110
- clearRejectedFiles() {
111
- send({ type: "REJECTED_FILES.CLEAR" });
112
- },
113
- clearFiles() {
114
- send({ type: "FILES.CLEAR" });
115
- },
116
- getFileSize(file) {
117
- return formatBytes(file.size, state.context.locale);
118
- },
119
- createFileUrl(file, cb) {
120
- const win = dom.getWin(state.context);
121
- const url = win.URL.createObjectURL(file);
122
- cb(url);
123
- return () => win.URL.revokeObjectURL(url);
124
- },
125
- setClipboardFiles(dt) {
126
- const items = Array.from(dt?.items ?? []);
127
- const files = items.reduce((acc, item) => {
128
- if (item.kind !== "file")
129
- return acc;
130
- const file = item.getAsFile();
131
- if (!file)
132
- return acc;
133
- return [...acc, file];
134
- }, []);
135
- if (!files.length)
136
- return false;
137
- send({ type: "FILES.SET", files });
138
- return true;
139
- },
140
- getRootProps() {
141
- return normalize.element({
142
- ...parts.root.attrs,
143
- "dir": state.context.dir,
144
- "id": dom.getRootId(state.context),
145
- "data-disabled": dataAttr(disabled),
146
- "data-dragging": dataAttr(dragging)
147
- });
148
- },
149
- getDropzoneProps() {
150
- return normalize.element({
151
- ...parts.dropzone.attrs,
152
- "dir": state.context.dir,
153
- "id": dom.getDropzoneId(state.context),
154
- "tabIndex": disabled ? void 0 : 0,
155
- "role": "button",
156
- "aria-label": translations.dropzone,
157
- "aria-disabled": disabled,
158
- "data-invalid": dataAttr(state.context.invalid),
159
- "data-disabled": dataAttr(disabled),
160
- "data-dragging": dataAttr(dragging),
161
- onKeyDown(event) {
162
- if (event.defaultPrevented)
163
- return;
164
- if (!isSelfTarget(event))
165
- return;
166
- if (event.key !== "Enter" && event.key !== " ")
167
- return;
168
- send({ type: "DROPZONE.CLICK", src: "keydown" });
169
- },
170
- onClick(event) {
171
- const isLabel = event.currentTarget.localName === "label";
172
- if (isLabel)
173
- event.preventDefault();
174
- send("DROPZONE.CLICK");
175
- },
176
- onDragOver(event) {
177
- if (!allowDrop)
178
- return;
179
- event.preventDefault();
180
- event.stopPropagation();
181
- try {
182
- event.dataTransfer.dropEffect = "copy";
183
- } catch {
184
- }
185
- const hasFiles = isEventWithFiles(event);
186
- if (!hasFiles)
187
- return;
188
- const count = event.dataTransfer.items.length;
189
- send({ type: "DROPZONE.DRAG_OVER", count });
190
- },
191
- onDragLeave(event) {
192
- if (!allowDrop || disabled)
193
- return;
194
- if (contains(event.currentTarget, event.relatedTarget))
195
- return;
196
- send({ type: "DROPZONE.DRAG_LEAVE" });
197
- },
198
- onDrop(event) {
199
- if (allowDrop) {
200
- event.preventDefault();
201
- event.stopPropagation();
202
- }
203
- const hasFiles = isEventWithFiles(event);
204
- if (disabled || !hasFiles)
205
- return;
206
- send({ type: "DROPZONE.DROP", files: Array.from(event.dataTransfer.files) });
207
- },
208
- onFocus() {
209
- send("DROPZONE.FOCUS");
210
- },
211
- onBlur() {
212
- send("DROPZONE.BLUR");
213
- }
214
- });
215
- },
216
- getTriggerProps() {
217
- return normalize.button({
218
- ...parts.trigger.attrs,
219
- "dir": state.context.dir,
220
- "id": dom.getTriggerId(state.context),
221
- disabled,
222
- "data-disabled": dataAttr(disabled),
223
- "data-invalid": dataAttr(state.context.invalid),
224
- "type": "button",
225
- onClick(event) {
226
- if (disabled)
227
- return;
228
- if (contains(dom.getDropzoneEl(state.context), event.currentTarget)) {
229
- event.stopPropagation();
230
- }
231
- send("OPEN");
232
- }
233
- });
234
- },
235
- getHiddenInputProps() {
236
- return normalize.input({
237
- id: dom.getHiddenInputId(state.context),
238
- tabIndex: -1,
239
- disabled,
240
- type: "file",
241
- required: state.context.required,
242
- capture: state.context.capture,
243
- name: state.context.name,
244
- accept: state.context.acceptAttr,
245
- webkitdirectory: state.context.directory ? "" : void 0,
246
- multiple: state.context.multiple || state.context.maxFiles > 1,
247
- onClick(event) {
248
- event.stopPropagation();
249
- event.currentTarget.value = "";
250
- },
251
- onChange(event) {
252
- if (disabled)
253
- return;
254
- const { files } = event.currentTarget;
255
- send({ type: "FILES.SET", files: files ? Array.from(files) : [] });
256
- },
257
- style: visuallyHiddenStyle
258
- });
259
- },
260
- getItemGroupProps() {
261
- return normalize.element({
262
- ...parts.itemGroup.attrs,
263
- "dir": state.context.dir,
264
- "data-disabled": dataAttr(disabled)
265
- });
266
- },
267
- getItemProps(props) {
268
- const { file } = props;
269
- return normalize.element({
270
- ...parts.item.attrs,
271
- "dir": state.context.dir,
272
- "id": dom.getItemId(state.context, file.name),
273
- "data-disabled": dataAttr(disabled)
274
- });
275
- },
276
- getItemNameProps(props) {
277
- const { file } = props;
278
- return normalize.element({
279
- ...parts.itemName.attrs,
280
- "dir": state.context.dir,
281
- "id": dom.getItemNameId(state.context, file.name),
282
- "data-disabled": dataAttr(disabled)
283
- });
284
- },
285
- getItemSizeTextProps(props) {
286
- const { file } = props;
287
- return normalize.element({
288
- ...parts.itemSizeText.attrs,
289
- "dir": state.context.dir,
290
- "id": dom.getItemSizeTextId(state.context, file.name),
291
- "data-disabled": dataAttr(disabled)
292
- });
293
- },
294
- getItemPreviewProps(props) {
295
- const { file } = props;
296
- return normalize.element({
297
- ...parts.itemPreview.attrs,
298
- "dir": state.context.dir,
299
- "id": dom.getItemPreviewId(state.context, file.name),
300
- "data-disabled": dataAttr(disabled)
301
- });
302
- },
303
- getItemPreviewImageProps(props) {
304
- const { file, url } = props;
305
- const isImage = file.type.startsWith("image/");
306
- if (!isImage) {
307
- throw new Error("Preview Image is only supported for image files");
308
- }
309
- return normalize.img({
310
- ...parts.itemPreviewImage.attrs,
311
- "alt": translations.itemPreview?.(file),
312
- "src": url,
313
- "data-disabled": dataAttr(disabled)
314
- });
315
- },
316
- getItemDeleteTriggerProps(props) {
317
- const { file } = props;
318
- return normalize.button({
319
- ...parts.itemDeleteTrigger.attrs,
320
- "dir": state.context.dir,
321
- "type": "button",
322
- disabled,
323
- "data-disabled": dataAttr(disabled),
324
- "aria-label": translations.deleteFile?.(file),
325
- onClick() {
326
- if (disabled)
327
- return;
328
- send({ type: "FILE.DELETE", file });
329
- }
330
- });
331
- },
332
- getLabelProps() {
333
- return normalize.label({
334
- ...parts.label.attrs,
335
- "dir": state.context.dir,
336
- "id": dom.getLabelId(state.context),
337
- "htmlFor": dom.getHiddenInputId(state.context),
338
- "data-disabled": dataAttr(disabled)
339
- });
340
- },
341
- getClearTriggerProps() {
342
- return normalize.button({
343
- ...parts.clearTrigger.attrs,
344
- "dir": state.context.dir,
345
- "type": "button",
346
- disabled,
347
- "hidden": state.context.acceptedFiles.length === 0,
348
- "data-disabled": dataAttr(disabled),
349
- onClick(event) {
350
- if (event.defaultPrevented)
351
- return;
352
- if (disabled)
353
- return;
354
- send({ type: "FILES.CLEAR" });
355
- }
356
- });
357
- }
358
- };
359
- }
360
-
361
- const invoke = {
362
- change: (ctx) => {
363
- ctx.onFileChange?.({
364
- acceptedFiles: ctx.acceptedFiles,
365
- rejectedFiles: ctx.rejectedFiles
366
- });
367
- },
368
- accept: (ctx) => {
369
- ctx.onFileAccept?.({ files: ctx.acceptedFiles });
370
- },
371
- reject: (ctx) => {
372
- ctx.onFileReject?.({ files: ctx.rejectedFiles });
373
- }
374
- };
375
- const set = {
376
- files: (ctx, acceptedFiles, rejectedFiles) => {
377
- ctx.acceptedFiles = ref(acceptedFiles);
378
- invoke.accept(ctx);
379
- if (rejectedFiles) {
380
- ctx.rejectedFiles = ref(rejectedFiles);
381
- invoke.reject(ctx);
382
- }
383
- invoke.change(ctx);
384
- }
385
- };
386
- function machine(userContext) {
387
- const ctx = compact(userContext);
388
- return createMachine(
389
- {
390
- id: "fileupload",
391
- initial: "idle",
392
- context: {
393
- minFileSize: 0,
394
- maxFileSize: Number.POSITIVE_INFINITY,
395
- maxFiles: 1,
396
- allowDrop: true,
397
- accept: ctx.accept,
398
- preventDocumentDrop: true,
399
- ...ctx,
400
- acceptedFiles: ref([]),
401
- rejectedFiles: ref([]),
402
- translations: {
403
- dropzone: "dropzone",
404
- itemPreview: (file) => `preview of ${file.name}`,
405
- deleteFile: (file) => `delete file ${file.name}`,
406
- ...ctx.translations
407
- }
408
- },
409
- computed: {
410
- acceptAttr: (ctx2) => getAcceptAttrString(ctx2.accept),
411
- multiple: (ctx2) => ctx2.maxFiles > 1
412
- },
413
- watch: {
414
- acceptedFiles: ["syncInputElement"]
415
- },
416
- on: {
417
- "FILES.SET": {
418
- actions: ["setFilesFromEvent"]
419
- },
420
- "FILE.DELETE": {
421
- actions: ["removeFile"]
422
- },
423
- "FILES.CLEAR": {
424
- actions: ["clearFiles"]
425
- },
426
- "REJECTED_FILES.CLEAR": {
427
- actions: ["clearRejectedFiles"]
428
- }
429
- },
430
- activities: ["preventDocumentDrop"],
431
- states: {
432
- idle: {
433
- on: {
434
- "OPEN": {
435
- actions: ["openFilePicker"]
436
- },
437
- "DROPZONE.CLICK": {
438
- actions: ["openFilePicker"]
439
- },
440
- "DROPZONE.FOCUS": "focused",
441
- "DROPZONE.DRAG_OVER": "dragging"
442
- }
443
- },
444
- focused: {
445
- on: {
446
- "DROPZONE.BLUR": "idle",
447
- "OPEN": {
448
- actions: ["openFilePicker"]
449
- },
450
- "DROPZONE.CLICK": {
451
- actions: ["openFilePicker"]
452
- },
453
- "DROPZONE.DRAG_OVER": "dragging"
454
- }
455
- },
456
- dragging: {
457
- on: {
458
- "DROPZONE.DROP": {
459
- target: "idle",
460
- actions: ["setFilesFromEvent"]
461
- },
462
- "DROPZONE.DRAG_LEAVE": "idle"
463
- }
464
- }
465
- }
466
- },
467
- {
468
- activities: {
469
- preventDocumentDrop(ctx2) {
470
- if (!ctx2.preventDocumentDrop)
471
- return;
472
- if (!ctx2.allowDrop)
473
- return;
474
- if (ctx2.disabled)
475
- return;
476
- const doc = dom.getDoc(ctx2);
477
- const onDragOver = (event) => {
478
- event?.preventDefault();
479
- };
480
- const onDrop = (event) => {
481
- if (contains(dom.getRootEl(ctx2), getEventTarget(event)))
482
- return;
483
- event.preventDefault();
484
- };
485
- return callAll(addDomEvent(doc, "dragover", onDragOver, false), addDomEvent(doc, "drop", onDrop, false));
486
- }
487
- },
488
- actions: {
489
- syncInputElement(ctx2) {
490
- queueMicrotask(() => {
491
- const inputEl = dom.getHiddenInputEl(ctx2);
492
- if (!inputEl)
493
- return;
494
- const win = dom.getWin(ctx2);
495
- const dataTransfer = new win.DataTransfer();
496
- ctx2.acceptedFiles.forEach((v) => {
497
- dataTransfer.items.add(v);
498
- });
499
- inputEl.files = dataTransfer.files;
500
- });
501
- },
502
- openFilePicker(ctx2) {
503
- raf(() => {
504
- dom.getHiddenInputEl(ctx2)?.click();
505
- });
506
- },
507
- setFilesFromEvent(ctx2, evt) {
508
- const result = getFilesFromEvent(ctx2, evt.files);
509
- const { acceptedFiles, rejectedFiles } = result;
510
- if (ctx2.multiple) {
511
- const files = ref([...ctx2.acceptedFiles, ...acceptedFiles]);
512
- set.files(ctx2, files, rejectedFiles);
513
- return;
514
- }
515
- if (acceptedFiles.length) {
516
- const files = ref([acceptedFiles[0]]);
517
- set.files(ctx2, files, rejectedFiles);
518
- } else if (rejectedFiles.length) {
519
- set.files(ctx2, ctx2.acceptedFiles, rejectedFiles);
520
- }
521
- },
522
- removeFile(ctx2, evt) {
523
- const nextFiles = ctx2.acceptedFiles.filter((file) => file !== evt.file);
524
- ctx2.acceptedFiles = ref(nextFiles);
525
- invoke.change(ctx2);
526
- },
527
- clearRejectedFiles(ctx2) {
528
- ctx2.rejectedFiles = ref([]);
529
- invoke.change(ctx2);
530
- },
531
- clearFiles(ctx2) {
532
- ctx2.acceptedFiles = ref([]);
533
- ctx2.rejectedFiles = ref([]);
534
- invoke.change(ctx2);
535
- }
536
- },
537
- compareFns: {
538
- acceptedFiles: (a, b) => a.length === b.length && a.every((file, i) => isFileEqual(file, b[i]))
539
- }
540
- }
541
- );
542
- }
543
-
544
- const props = createProps()([
545
- "accept",
546
- "allowDrop",
547
- "capture",
548
- "dir",
549
- "directory",
550
- "disabled",
551
- "getRootNode",
552
- "id",
553
- "ids",
554
- "locale",
555
- "maxFiles",
556
- "maxFileSize",
557
- "minFileSize",
558
- "name",
559
- "invalid",
560
- "onFileAccept",
561
- "onFileReject",
562
- "onFileChange",
563
- "preventDocumentDrop",
564
- "required",
565
- "translations",
566
- "validate"
567
- ]);
568
- const splitProps = createSplitProps(props);
569
- const itemProps = createProps()(["file"]);
570
- const splitItemProps = createSplitProps(itemProps);
571
-
572
- export { anatomy, connect, itemProps, machine, props, splitItemProps, splitProps };