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
JavaScript
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
};