UNPKG

radfile

Version:

A modern, unstyled, and customizable file upload component library for Vue 3, built with [Radix Vue](https://www.radix-vue.com/) primitives.

486 lines (485 loc) 14.1 kB
import { inject as b, defineComponent as m, useTemplateRef as A, createBlock as v, openBlock as p, unref as i, mergeProps as w, withCtx as _, renderSlot as h, ref as z, computed as f, createElementVNode as L, createTextVNode as F, toDisplayString as C, createCommentVNode as P, normalizeProps as $, provide as M, toRefs as N, guardReactiveProps as E, onMounted as V, onBeforeUnmount as K, createElementBlock as j, Fragment as Z, watch as H, onUnmounted as W } from "vue"; import { Primitive as g } from "radix-vue"; import { useDropZone as G } from "@vueuse/core"; const T = Symbol(), D = () => b(T), R = Symbol(), B = () => b(R), x = Symbol(), I = () => b(x), ae = /* @__PURE__ */ m({ __name: "UploadDropZone", props: { asChild: { type: Boolean }, as: { default: "div" }, class: { default: "" } }, setup(s) { const o = s, e = D(), t = B(), l = A("zone"), { isOverDropZone: d } = G(l, { multiple: e.restrictions.allowMultiple, onDrop: (u) => { t.addFile(u ?? []); } }); return (u, r) => (p(), v(i(g), w(o, { ref_key: "zone", ref: l, "data-dropping": i(d) || void 0 }), { default: _(() => [ h(u.$slots, "default") ]), _: 3 }, 16, ["data-dropping"])); } }), q = ["multiple", "accept"], le = /* @__PURE__ */ m({ __name: "UploadFileButton", props: { asChild: { type: Boolean }, as: { default: "button" } }, setup(s) { const o = s, e = B(), t = D(), l = z(), d = f(() => { if (t.restrictions.allowedFileTypes) return Object.entries(t.restrictions.allowedFileTypes.reduce((a, n) => (Object.entries(n.accept).forEach(([c, y]) => { a[c] = y; }), a), {})).map(([a, n]) => [a, ...n]).flat().join(","); }); async function u() { var a, n; if ("showOpenFilePicker" in window && window.showOpenFilePicker) try { const c = await window.showOpenFilePicker({ multiple: t.restrictions.allowMultiple, types: t.restrictions.allowedFileTypes }), y = await Promise.all( c.map((U) => U.getFile()) ); e.addFile(t.restrictions.allowMultiple ? y : y[0]); } catch (c) { c instanceof Error && c.name !== "AbortError" && (console.error("Error selecting file:", c), (a = t.onPickError) == null || a.call(t, c)); } else (n = l.value) == null || n.click(); } function r(a) { var y; const n = a.target; if (!((y = n.files) != null && y.length)) return; const c = Array.from(n.files); e.addFile(t.restrictions.allowMultiple ? c : c[0]), n.value = ""; } return (a, n) => (p(), v(i(g), w(o, { onClick: u }), { default: _(() => [ h(a.$slots, "default", {}, () => [ F(C(i(t).restrictions.allowMultiple ? i(e).files.value.length > 0 ? "Add more files" : "Select files" : "Select file"), 1) ]), L("input", { ref_key: "fileInput", ref: l, type: "file", multiple: i(t).restrictions.allowMultiple, accept: d.value, onChange: r, style: { display: "none" } }, null, 40, q) ]), _: 3 }, 16)); } }), oe = /* @__PURE__ */ m({ __name: "UploadFiles", props: { asChild: { type: Boolean }, as: { default: "div" }, class: { default: "" } }, setup(s) { const o = s, { files: e } = B(); return (t, l) => i(e).length > 0 ? (p(), v(i(g), $(w({ key: 0 }, o)), { default: _(() => [ h(t.$slots, "default", { files: i(e) }) ]), _: 3 }, 16)) : P("", !0); } }), ne = /* @__PURE__ */ m({ __name: "UploadItem", props: { asChild: { type: Boolean }, as: { default: "div" }, item: {} }, setup(s) { const o = s; return M(x, N(o).item), (e, t) => (p(), v(i(g), w(o, { "data-status": e.item.status.status }), { default: _(() => [ h(e.$slots, "default", { status: e.item.status.status }) ]), _: 3 }, 16, ["data-status"])); } }), re = /* @__PURE__ */ m({ __name: "UploadItemErrorMessage", props: { class: { default: "" }, asChild: { type: Boolean }, as: { default: "div" } }, setup(s) { const o = s, e = I(), t = f(() => e.value.status.status === "error" ? e.value.status.error instanceof Error ? e.value.status.error.message : String(e.value.status.error) : null); return (l, d) => t.value ? (p(), v(i(g), $(w({ key: 0 }, o)), { default: _(() => [ h(l.$slots, "default", { error: t.value }, () => [ F(C(t.value), 1) ]) ]), _: 3 }, 16)) : P("", !0); } }), ue = /* @__PURE__ */ m({ __name: "UploadItemName", props: { asChild: { type: Boolean }, as: { default: "span" }, class: { default: "" } }, setup(s) { const o = s, e = I(); return (t, l) => (p(), v(i(g), $(E(o)), { default: _(() => [ F(C(i(e).file.name), 1) ]), _: 1 }, 16)); } }), J = ["src", "alt"], ie = /* @__PURE__ */ m({ __name: "UploadItemPreview", props: { class: { default: "" }, asChild: { type: Boolean }, as: { default: "div" } }, setup(s) { const o = s, e = I(), t = z(null); V(() => { e.value.file.type.startsWith("image/") && (t.value = URL.createObjectURL(e.value.file)); }), K(() => { t.value && URL.revokeObjectURL(t.value); }); const l = f(() => e.value.file.type.startsWith("image/") && t.value ? { type: "image", url: t.value, alt: e.value.file.name } : { type: "generic", fileType: e.value.file.type.split("/")[0] }); return (d, u) => (p(), v(i(g), $(E(o)), { default: _(() => [ h(d.$slots, "default", { preview: l.value }, () => [ l.value.type === "image" ? (p(), j("img", { key: 0, src: l.value.url, alt: l.value.alt }, null, 8, J)) : (p(), j(Z, { key: 1 }, [ F(C(l.value.fileType), 1) ], 64)) ]) ]), _: 3 }, 16)); } }), de = /* @__PURE__ */ m({ __name: "UploadItemProgress", props: { class: { default: "" }, asChild: { type: Boolean }, as: { default: "div" } }, setup(s) { const o = s, e = I(), t = f(() => e.value.status.status === "pending" ? e.value.status.uploaded / e.value.file.size : e.value.status.status === "success" ? 1 : 0); return (l, d) => (p(), v(i(g), w(o, { "data-pending": i(e).status.status === "pending" || void 0 }), { default: _(() => [ h(l.$slots, "default", { progress: t.value }, () => [ F(C(t.value.toLocaleString(void 0, { style: "percent" })), 1) ]) ]), _: 3 }, 16, ["data-pending"])); } }), ce = /* @__PURE__ */ m({ __name: "UploadItemRemove", props: { class: { default: "" }, asChild: { type: Boolean }, as: { default: "button" } }, setup(s) { const o = s, e = I(), t = B(); function l() { t.removeFile(e.value); } return (d, u) => (p(), v(i(g), w(o, { onClick: l }), { default: _(() => [ h(d.$slots, "default", {}, () => [ u[0] || (u[0] = F(" Remove ")) ]) ]), _: 3 }, 16)); } }), pe = /* @__PURE__ */ m({ __name: "UploadItemSize", props: { asChild: { type: Boolean }, as: { default: "span" } }, setup(s) { const o = s, e = I(); function t(u) { const r = ["B", "KB", "MB", "GB", "TB"]; let a = u, n = 0; for (; a >= 1024 && n < r.length - 1; ) a /= 1024, n++; return `${a.toFixed(1)} ${r[n]}`; } const l = t(e.value.file.size), d = f(() => e.value.file.size); return (u, r) => (p(), v(i(g), $(E(o)), { default: _(() => [ h(u.$slots, "default", { size: i(l), bytes: d.value }, () => [ F(C(i(l)), 1) ]) ]), _: 3 }, 16)); } }), fe = /* @__PURE__ */ m({ __name: "UploadEmptyState", props: { asChild: { type: Boolean }, as: { default: "div" }, class: { default: "" } }, setup(s) { const o = s, { files: e } = B(); return (t, l) => i(e).length === 0 ? (p(), v(i(g), $(w({ key: 0 }, o)), { default: _(() => [ h(t.$slots, "default") ]), _: 3 }, 16)) : P("", !0); } }), me = /* @__PURE__ */ m({ __name: "UploadProgress", props: { class: { default: "" }, asChild: { type: Boolean }, as: { default: "div" } }, setup(s) { const o = s, e = b(R), t = f(() => { const n = e.files.value.filter((U) => U.status.status === "pending"); if (n.length === 0) return null; const c = n.reduce((U, S) => U + S.status.uploaded, 0), y = n.reduce((U, S) => U + S.file.size, 0); return c / y; }), l = f(() => e.files.value.length), d = f( () => e.files.value.filter((a) => a.status.status === "pending").length ), u = f( () => e.files.value.filter((a) => a.status.status === "success").length ), r = f( () => e.files.value.filter((a) => a.status.status === "error").length ); return (a, n) => t.value !== null ? (p(), v(i(g), $(w({ key: 0 }, o)), { default: _(() => [ h(a.$slots, "default", { progress: t.value, totalFiles: l.value, pendingCount: d.value, successCount: u.value, errorCount: r.value }, () => [ F(C(t.value.toLocaleString(void 0, { style: "percent" })), 1) ]) ]), _: 3 }, 16)) : P("", !0); } }); function Q(s, o) { const e = z([]), t = () => { o(e.value.map((r) => r)); }, l = (r) => { if (!Array.isArray(r)) return l([r]); if (r.length !== 0) { if (s.restrictions.allowMultiple) { const a = r.map((n) => ({ file: n, id: Math.random().toString(36).slice(2), metadata: s.getMetadata(n), status: { status: "idle" } })); e.value = [...e.value, ...a], t(); return; } if (e.value.length > 1) throw new Error("Maximum number of files exceeded"); e.value = [{ file: r[0], id: Math.random().toString(36).slice(2), metadata: s.getMetadata(r[0]), status: { status: "idle" } }], t(); } }; return { files: e, addFile: l, removeFile: (r) => { e.value = e.value.filter((a) => a !== r), t(); }, setStatus: (r, a) => { e.value = e.value.map((n) => n === r ? { ...n, status: a } : n), t(); }, emitUpdatedModelValue: t }; } function X(s, o, e, t) { const l = s.createUploadHandler({ onProgress: (u, r) => { const a = o.value.find((n) => n.file === u); if (!a) throw new Error(`File ${u.name} not found`); if (a.status.status !== "pending") throw new Error(`File ${u.name} is not pending`); t(a, { status: "pending", uploaded: r }), e(); }, onDone: (u) => { var a; const r = o.value.find((n) => n.file === u); t(r, { status: "success" }), e(), (a = s.onSuccess) == null || a.call(s, r.metadata); }, onError: (u, r) => { var n; const a = o.value.find((c) => c.file === u); t(a, { status: "error", error: r }), e(), (n = s.onUploadError) == null || n.call(s, r); } }), d = () => { const u = o.value.filter( (r) => r.status.status === "idle" ); u.length !== 0 && (u.forEach((r) => { r.status = { status: "pending", uploaded: 0 }; }), l.onUpload(u), e()); }; return H(o, () => { s.autoUpload && d(); }), W(() => { l.onCleanup(); }), { upload: d, handler: l }; } function Y(s) { const o = f( () => s.value.some((l) => l.status.status === "idle") ), e = f(() => s.value.length > 0), t = f( () => s.value.some((l) => l.status.status === "pending") ); return { hasIdle: o, hasFiles: e, isUploading: t }; } const ve = /* @__PURE__ */ m({ __name: "UploadRoot", props: { asChild: { type: Boolean }, as: { default: "div" }, class: { default: "" }, options: {}, modelValue: {} }, emits: ["update:modelValue"], setup(s, { emit: o }) { const e = s, t = o, l = f(() => { const { options: k, ...O } = e; return O; }); M(T, e.options); const { files: d, addFile: u, removeFile: r, setStatus: a, emitUpdatedModelValue: n } = Q( e.options, (k) => t("update:modelValue", k) ), { upload: c } = X(e.options, d, n, a), { hasIdle: y, hasFiles: U, isUploading: S } = Y(d); return M(R, { files: d, addFile: u, hasIdle: y, hasFiles: U, isUploading: S, removeFile: r, upload: c }), (k, O) => (p(), v(i(g), $(E(l.value)), { default: _(() => [ h(k.$slots, "default", { hasIdle: i(y), hasFiles: i(U), isUploading: i(S) }) ]), _: 3 }, 16)); } }), _e = /* @__PURE__ */ m({ __name: "UploadStartUploadButton", props: { asChild: { type: Boolean }, as: { default: "button" }, class: { default: "" } }, setup(s) { const o = s, e = B(), t = f(() => ({ ...o, onClick: () => { e.upload(); } })); return (l, d) => (p(), v(i(g), w(t.value, { disabled: i(e).isUploading.value || !i(e).hasIdle.value }), { default: _(() => [ h(l.$slots, "default", {}, () => [ d[0] || (d[0] = F("Start Uploading")) ]) ]), _: 3 }, 16, ["disabled"])); } }); export { ae as UploadDropZone, fe as UploadEmptyState, le as UploadFileButton, oe as UploadFiles, ne as UploadItem, re as UploadItemErrorMessage, ue as UploadItemName, ie as UploadItemPreview, de as UploadItemProgress, ce as UploadItemRemove, pe as UploadItemSize, me as UploadProgress, ve as UploadRoot, _e as UploadStartUploadButton, B as injectState, I as injectUploadFile, D as injectUploadOptions, x as uploadFileKey, T as uploadOptionsKey, R as uploadStateKey, Q as useFileManagement, X as useUploadHandler, Y as useUploadState };