UNPKG

@zag-js/file-upload

Version:

Core logic for the file-upload widget implemented as a state machine

267 lines (265 loc) • 9.08 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/file-upload.machine.ts var file_upload_machine_exports = {}; __export(file_upload_machine_exports, { machine: () => machine }); module.exports = __toCommonJS(file_upload_machine_exports); var import_core = require("@zag-js/core"); var import_dom_query = require("@zag-js/dom-query"); var import_file_utils = require("@zag-js/file-utils"); var import_utils = require("@zag-js/utils"); var dom = __toESM(require("./file-upload.dom.js")); var import_file_upload = require("./file-upload.utils.js"); var machine = (0, import_core.createMachine)({ props({ props }) { return { minFileSize: 0, maxFileSize: Number.POSITIVE_INFINITY, maxFiles: 1, allowDrop: true, preventDocumentDrop: true, defaultAcceptedFiles: [], ...props, translations: { dropzone: "dropzone", itemPreview: (file) => `preview of ${file.name}`, deleteFile: (file) => `delete file ${file.name}`, ...props.translations } }; }, initialState() { return "idle"; }, context({ prop, bindable, getContext }) { return { acceptedFiles: bindable(() => ({ defaultValue: prop("defaultAcceptedFiles"), value: prop("acceptedFiles"), isEqual: (a, b) => a.length === b?.length && a.every((file, i) => (0, import_file_utils.isFileEqual)(file, b[i])), hash(value) { return value.map((file) => `${file.name}-${file.size}`).join(","); }, onChange(value) { const ctx = getContext(); prop("onFileAccept")?.({ files: value }); prop("onFileChange")?.({ acceptedFiles: value, rejectedFiles: ctx.get("rejectedFiles") }); } })), rejectedFiles: bindable(() => ({ defaultValue: [], isEqual: (a, b) => a.length === b?.length && a.every((file, i) => (0, import_file_utils.isFileEqual)(file.file, b[i].file)), onChange(value) { const ctx = getContext(); prop("onFileReject")?.({ files: value }); prop("onFileChange")?.({ acceptedFiles: ctx.get("acceptedFiles"), rejectedFiles: value }); } })), transforming: bindable(() => ({ defaultValue: false })) }; }, computed: { acceptAttr: ({ prop }) => (0, import_file_utils.getAcceptAttrString)(prop("accept")), multiple: ({ prop }) => prop("maxFiles") > 1 }, watch({ track, context, action }) { track([() => context.hash("acceptedFiles")], () => { action(["syncInputElement"]); }); }, on: { "FILES.SET": { actions: ["setFiles"] }, "FILE.SELECT": { actions: ["setEventFiles"] }, "FILE.DELETE": { actions: ["removeFile"] }, "FILES.CLEAR": { actions: ["clearFiles"] }, "REJECTED_FILES.CLEAR": { actions: ["clearRejectedFiles"] } }, effects: ["preventDocumentDrop"], states: { idle: { on: { OPEN: { actions: ["openFilePicker"] }, "DROPZONE.CLICK": { actions: ["openFilePicker"] }, "DROPZONE.FOCUS": { target: "focused" }, "DROPZONE.DRAG_OVER": { target: "dragging" } } }, focused: { on: { "DROPZONE.BLUR": { target: "idle" }, OPEN: { actions: ["openFilePicker"] }, "DROPZONE.CLICK": { actions: ["openFilePicker"] }, "DROPZONE.DRAG_OVER": { target: "dragging" } } }, dragging: { on: { "DROPZONE.DROP": { target: "idle", actions: ["setEventFiles"] }, "DROPZONE.DRAG_LEAVE": { target: "idle" } } } }, implementations: { effects: { preventDocumentDrop({ prop, scope }) { if (!prop("preventDocumentDrop")) return; if (!prop("allowDrop")) return; if (prop("disabled")) return; const doc = scope.getDoc(); const onDragOver = (event) => { event?.preventDefault(); }; const onDrop = (event) => { if ((0, import_dom_query.contains)(dom.getRootEl(scope), (0, import_dom_query.getEventTarget)(event))) return; event.preventDefault(); }; return (0, import_utils.callAll)((0, import_dom_query.addDomEvent)(doc, "dragover", onDragOver, false), (0, import_dom_query.addDomEvent)(doc, "drop", onDrop, false)); } }, actions: { syncInputElement({ scope, context }) { queueMicrotask(() => { const inputEl = dom.getHiddenInputEl(scope); if (!inputEl) return; (0, import_file_upload.setInputFiles)(inputEl, context.get("acceptedFiles")); const win = scope.getWin(); inputEl.dispatchEvent(new win.Event("change", { bubbles: true })); }); }, openFilePicker({ scope }) { (0, import_dom_query.raf)(() => { dom.getHiddenInputEl(scope)?.click(); }); }, setFiles(params) { const { computed, context, event } = params; const { acceptedFiles, rejectedFiles } = (0, import_file_upload.getEventFiles)(params, event.files); context.set( "acceptedFiles", computed("multiple") ? acceptedFiles : acceptedFiles.length > 0 ? [acceptedFiles[0]] : [] ); context.set("rejectedFiles", rejectedFiles); }, setEventFiles(params) { const { computed, context, event, prop } = params; const currentAcceptedFiles = context.get("acceptedFiles"); const currentRejectedFiles = context.get("rejectedFiles"); const { acceptedFiles, rejectedFiles } = (0, import_file_upload.getEventFiles)( params, event.files, currentAcceptedFiles, currentRejectedFiles ); const set = (files) => { if (computed("multiple")) { context.set("acceptedFiles", (prev) => [...prev, ...files]); context.set("rejectedFiles", rejectedFiles); return; } if (files.length) { context.set("acceptedFiles", [files[0]]); context.set("rejectedFiles", rejectedFiles); return; } if (rejectedFiles.length) { context.set("acceptedFiles", context.get("acceptedFiles")); context.set("rejectedFiles", rejectedFiles); } }; const transform = prop("transformFiles"); if (transform) { context.set("transforming", true); transform(acceptedFiles).then(set).catch((err) => { (0, import_utils.warn)(`[zag-js/file-upload] error transforming files ${err}`); }).finally(() => { context.set("transforming", false); }); } else { set(acceptedFiles); } }, removeFile({ context, event }) { if (event.itemType === "rejected") { const rejectedFiles = context.get("rejectedFiles").filter((item) => !(0, import_file_utils.isFileEqual)(item.file, event.file)); context.set("rejectedFiles", rejectedFiles); } else { const files = context.get("acceptedFiles").filter((file) => !(0, import_file_utils.isFileEqual)(file, event.file)); context.set("acceptedFiles", files); } }, clearRejectedFiles({ context }) { context.set("rejectedFiles", []); }, clearFiles({ context }) { context.set("acceptedFiles", []); context.set("rejectedFiles", []); } } } }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { machine });