vlite3 1.4.5 → 1.4.7
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/README.md +1 -0
- package/components/AppShell/AppShellLayoutStorefront.vue.d.ts +4 -17
- package/components/AppShell/AppShellLayoutStorefront.vue.js +116 -152
- package/components/AvatarUploader/AvatarUploader.vue.d.ts +1 -1
- package/components/AvatarUploader/AvatarUploader.vue.js +1 -1
- package/components/CategoryManager/CategoryManager.vue2.js +2 -2
- package/components/Chat/ChatInterface.vue.js +1 -1
- package/components/ColorPicker/ColorIro.vue3.js +2 -2
- package/components/ColorPicker/ColorPicker.vue.js +2 -2
- package/components/CommandPalette/CommandPaletteContent.vue2.js +1 -1
- package/components/CommandPalette/{CommandPaletteItem.vue.js → CommandPaletteItem.vue2.js} +1 -1
- package/components/Comment/CommentEditor.vue.js +1 -1
- package/components/FilePicker/FilePickerDropzone.vue.d.ts +27 -0
- package/components/FilePicker/FilePickerDropzone.vue.js +117 -0
- package/components/FilePicker/FilePickerDropzone.vue2.js +4 -0
- package/components/FilePicker/FilePickerInput.vue.d.ts +22 -0
- package/components/FilePicker/FilePickerInput.vue.js +147 -0
- package/components/FilePicker/FilePickerInput.vue2.js +4 -0
- package/components/FilePicker/FilePreview.vue.d.ts +21 -0
- package/components/FilePicker/FilePreview.vue.js +95 -0
- package/components/FilePicker/FilePreview.vue2.js +4 -0
- package/components/FilePicker/composables/useFileProcessing.d.ts +65 -0
- package/components/FilePicker/composables/useFileProcessing.js +141 -0
- package/components/FilePicker/index.d.ts +2 -2
- package/components/FilePicker/{FilePicker.vue.d.ts → index.vue.d.ts} +11 -18
- package/components/FilePicker/index.vue.js +131 -0
- package/components/FilePicker/index.vue2.js +4 -0
- package/components/FilePicker/types.d.ts +17 -0
- package/components/FilePicker/utils.d.ts +23 -0
- package/components/FilePicker/utils.js +37 -0
- package/components/Form/AccordionView.vue.d.ts +26 -0
- package/components/Form/AccordionView.vue.js +219 -0
- package/components/Form/AccordionView.vue2.js +4 -0
- package/components/Form/FormField.vue.js +2 -2
- package/components/Form/FormFields.vue.js +2 -2
- package/components/Form/TableRow.vue.d.ts +18 -0
- package/components/Form/TableRow.vue.js +88 -0
- package/components/Form/TableRow.vue2.js +4 -0
- package/components/Form/TableView.vue.d.ts +23 -0
- package/components/Form/TableView.vue.js +88 -0
- package/components/Form/TableView.vue2.js +4 -0
- package/components/Form/composables/useCustomFieldRows.d.ts +52 -0
- package/components/Form/composables/useCustomFieldRows.js +77 -0
- package/components/Form/index.d.ts +1 -1
- package/components/Form/{CustomFields.vue.d.ts → index.vue.d.ts} +5 -6
- package/components/Form/index.vue.js +7 -0
- package/components/Form/index.vue2.js +141 -0
- package/components/Form/rowHelpers.d.ts +45 -0
- package/components/Form/rowHelpers.js +40 -0
- package/components/IconPicker.vue.d.ts +2 -2
- package/components/ImageMagnifier.vue.d.ts +18 -0
- package/components/ImageMagnifier.vue.js +73 -0
- package/components/ImageMagnifier.vue2.js +4 -0
- package/components/NavbarCommandPalette.vue.js +1 -1
- package/components/Screen/ScreenFilter.vue.js +2 -2
- package/components/ThumbnailSelector/ThumbnailSelector.vue.js +1 -1
- package/components/ThumbnailSelector/ThumbnailSelector.vue2.js +1 -1
- package/components/index.d.ts +2 -1
- package/index.d.ts +1 -0
- package/index.js +107 -105
- package/package.json +1 -1
- package/style.css +1 -1
- package/components/FilePicker/FilePicker.vue.js +0 -437
- package/components/FilePicker/FilePicker.vue2.js +0 -4
- package/components/Form/CustomFields.vue.js +0 -7
- package/components/Form/CustomFields.vue2.js +0 -465
- /package/components/ColorPicker/{ColorIro.vue2.js → ColorIro.vue.js} +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { defineComponent as w, computed as o, openBlock as s, createElementBlock as r, createElementVNode as a, withKeys as y, withModifiers as c, normalizeClass as u, createVNode as m, unref as k, toDisplayString as g, createBlock as F, createCommentVNode as x } from "vue";
|
|
2
|
+
import d from "../Icon.vue.js";
|
|
3
|
+
import { formatFileSize as S, getFileIcon as p } from "./utils.js";
|
|
4
|
+
const z = { class: "relative" }, C = ["aria-disabled", "aria-label", "data-testid"], P = {
|
|
5
|
+
key: 0,
|
|
6
|
+
class: "flex min-w-0 flex-1 items-center gap-2.5"
|
|
7
|
+
}, T = ["src", "alt"], B = { class: "min-w-0 flex-1 flex flex-col contain-inline-size" }, V = { class: "truncate min-w-0 font-medium leading-tight text-foreground" }, D = { class: "truncate min-w-0 text-[11px] leading-tight text-muted-foreground" }, N = {
|
|
8
|
+
key: 1,
|
|
9
|
+
class: "flex min-w-0 flex-1 items-center gap-2 text-muted-foreground"
|
|
10
|
+
}, j = { class: "truncate min-w-0 flex-1" }, $ = {
|
|
11
|
+
key: 0,
|
|
12
|
+
class: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center justify-center gap-2 bg-background"
|
|
13
|
+
}, K = /* @__PURE__ */ w({
|
|
14
|
+
__name: "FilePickerInput",
|
|
15
|
+
props: {
|
|
16
|
+
displayFiles: {},
|
|
17
|
+
firstDisplayFile: {},
|
|
18
|
+
hasValue: { type: Boolean },
|
|
19
|
+
multiSelect: { type: Boolean },
|
|
20
|
+
loading: { type: Boolean },
|
|
21
|
+
isProcessing: { type: Boolean },
|
|
22
|
+
disabled: { type: Boolean },
|
|
23
|
+
displayPlaceholder: {},
|
|
24
|
+
acceptedFileSummary: {},
|
|
25
|
+
size: {},
|
|
26
|
+
rounded: {},
|
|
27
|
+
getPreviewSrc: { type: Function },
|
|
28
|
+
dataTestid: {},
|
|
29
|
+
triggerAriaLabel: {},
|
|
30
|
+
onTrigger: { type: Function },
|
|
31
|
+
onClear: { type: Function }
|
|
32
|
+
},
|
|
33
|
+
setup(e) {
|
|
34
|
+
const n = e, b = o(() => {
|
|
35
|
+
const i = "group flex w-full min-w-0 items-center bg-background text-sm ring-offset-background transition-all focus-visible:outline-none cursor-pointer", t = {
|
|
36
|
+
sm: "min-h-8 px-2 py-1 text-xs gap-2",
|
|
37
|
+
md: "min-h-10 px-3 py-1.5 text-sm gap-2.5",
|
|
38
|
+
lg: "min-h-12 px-3 py-2 text-base gap-3"
|
|
39
|
+
}, l = {
|
|
40
|
+
none: "rounded-none",
|
|
41
|
+
sm: "rounded-sm",
|
|
42
|
+
md: "rounded-md",
|
|
43
|
+
lg: "rounded-lg",
|
|
44
|
+
xl: "rounded-xl",
|
|
45
|
+
"2xl": "rounded-2xl",
|
|
46
|
+
full: "rounded-full"
|
|
47
|
+
};
|
|
48
|
+
return [
|
|
49
|
+
i,
|
|
50
|
+
"border border-input focus-visible:border-primary",
|
|
51
|
+
t[n.size],
|
|
52
|
+
l[n.rounded],
|
|
53
|
+
"pr-10 text-left",
|
|
54
|
+
n.disabled ? "cursor-not-allowed opacity-50" : ""
|
|
55
|
+
].join(" ");
|
|
56
|
+
}), f = o(() => ({
|
|
57
|
+
sm: "h-6 w-6 rounded",
|
|
58
|
+
md: "h-7 w-7 rounded-md",
|
|
59
|
+
lg: "h-8 w-8 rounded-md"
|
|
60
|
+
})[n.size]), h = o(() => {
|
|
61
|
+
if (!n.hasValue) return "";
|
|
62
|
+
const i = n.displayFiles, t = i[0]?.fileName || "";
|
|
63
|
+
return !n.multiSelect || i.length <= 1 ? t : `${t} +${i.length - 1}`;
|
|
64
|
+
}), v = o(() => {
|
|
65
|
+
const i = n.displayFiles;
|
|
66
|
+
if (!i.length) return n.acceptedFileSummary;
|
|
67
|
+
if (n.multiSelect && i.length > 1) return `${i.length} files selected`;
|
|
68
|
+
const t = i[0];
|
|
69
|
+
return S(t.fileSize) || t.fileType || "";
|
|
70
|
+
});
|
|
71
|
+
return (i, t) => (s(), r("div", z, [
|
|
72
|
+
a("div", {
|
|
73
|
+
role: "button",
|
|
74
|
+
tabindex: "0",
|
|
75
|
+
"aria-disabled": e.disabled || e.loading || e.isProcessing,
|
|
76
|
+
"aria-label": e.triggerAriaLabel,
|
|
77
|
+
class: u(b.value),
|
|
78
|
+
"data-testid": e.dataTestid,
|
|
79
|
+
onClick: t[0] || (t[0] = //@ts-ignore
|
|
80
|
+
(...l) => e.onTrigger && e.onTrigger(...l)),
|
|
81
|
+
onKeydown: [
|
|
82
|
+
t[1] || (t[1] = y(c(
|
|
83
|
+
//@ts-ignore
|
|
84
|
+
(...l) => e.onTrigger && e.onTrigger(...l),
|
|
85
|
+
["prevent"]
|
|
86
|
+
), ["enter"])),
|
|
87
|
+
t[2] || (t[2] = y(c(
|
|
88
|
+
//@ts-ignore
|
|
89
|
+
(...l) => e.onTrigger && e.onTrigger(...l),
|
|
90
|
+
["prevent"]
|
|
91
|
+
), ["space"]))
|
|
92
|
+
]
|
|
93
|
+
}, [
|
|
94
|
+
e.hasValue && e.firstDisplayFile ? (s(), r("div", P, [
|
|
95
|
+
e.getPreviewSrc(e.firstDisplayFile) ? (s(), r("img", {
|
|
96
|
+
key: 0,
|
|
97
|
+
src: e.getPreviewSrc(e.firstDisplayFile),
|
|
98
|
+
alt: e.firstDisplayFile.fileName,
|
|
99
|
+
class: u(["shrink-0 border border-border bg-muted object-cover", f.value])
|
|
100
|
+
}, null, 10, T)) : (s(), r("div", {
|
|
101
|
+
key: 1,
|
|
102
|
+
class: u(["shrink-0 border border-primary/15 bg-primary/10 text-primary flex items-center justify-center", f.value])
|
|
103
|
+
}, [
|
|
104
|
+
m(d, {
|
|
105
|
+
icon: k(p)(e.firstDisplayFile),
|
|
106
|
+
class: "h-4 w-4"
|
|
107
|
+
}, null, 8, ["icon"])
|
|
108
|
+
], 2)),
|
|
109
|
+
a("div", B, [
|
|
110
|
+
a("p", V, g(h.value), 1),
|
|
111
|
+
a("p", D, g(v.value), 1)
|
|
112
|
+
])
|
|
113
|
+
])) : (s(), r("div", N, [
|
|
114
|
+
m(d, {
|
|
115
|
+
icon: "lucide:upload",
|
|
116
|
+
class: "h-4 w-4 shrink-0"
|
|
117
|
+
}),
|
|
118
|
+
a("span", j, g(e.displayPlaceholder), 1)
|
|
119
|
+
]))
|
|
120
|
+
], 42, C),
|
|
121
|
+
e.loading || e.isProcessing || e.hasValue && !e.disabled ? (s(), r("div", $, [
|
|
122
|
+
e.loading || e.isProcessing ? (s(), F(d, {
|
|
123
|
+
key: 0,
|
|
124
|
+
icon: "lucide:loader-2",
|
|
125
|
+
class: "h-4 w-4 animate-spin text-muted-foreground"
|
|
126
|
+
})) : e.hasValue && !e.disabled ? (s(), r("button", {
|
|
127
|
+
key: 1,
|
|
128
|
+
type: "button",
|
|
129
|
+
class: "text-muted-foreground hover:text-foreground focus:outline-none",
|
|
130
|
+
onClick: t[3] || (t[3] = c(
|
|
131
|
+
//@ts-ignore
|
|
132
|
+
(...l) => e.onClear && e.onClear(...l),
|
|
133
|
+
["stop"]
|
|
134
|
+
))
|
|
135
|
+
}, [
|
|
136
|
+
m(d, {
|
|
137
|
+
icon: "lucide:x-circle",
|
|
138
|
+
class: "h-4 w-4"
|
|
139
|
+
})
|
|
140
|
+
])) : x("", !0)
|
|
141
|
+
])) : x("", !0)
|
|
142
|
+
]));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
export {
|
|
146
|
+
K as default
|
|
147
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FilePickerValue } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
file: FilePickerValue;
|
|
4
|
+
index: number;
|
|
5
|
+
allowRename: boolean;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
loading: boolean;
|
|
8
|
+
isProcessing: boolean;
|
|
9
|
+
multiSelect: boolean;
|
|
10
|
+
enterFileNameLabel: string;
|
|
11
|
+
renameFileLabel: string;
|
|
12
|
+
getPreviewSrc: (file: FilePickerValue) => string;
|
|
13
|
+
isImage: (file: FilePickerValue) => boolean;
|
|
14
|
+
onRemove: (index: number) => void;
|
|
15
|
+
onRename: (index: number, newName: string) => void;
|
|
16
|
+
onTrigger: () => void;
|
|
17
|
+
}
|
|
18
|
+
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
19
|
+
fileNameInput: HTMLInputElement;
|
|
20
|
+
}, HTMLDivElement>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { defineComponent as b, ref as h, computed as u, openBlock as n, createElementBlock as r, normalizeClass as p, createElementVNode as t, createVNode as f, toDisplayString as s, withModifiers as a, unref as w, createBlock as g, createCommentVNode as y } from "vue";
|
|
2
|
+
import d from "../Icon.vue.js";
|
|
3
|
+
import k from "../Button.vue.js";
|
|
4
|
+
import { formatFileSize as F } from "./utils.js";
|
|
5
|
+
const N = { class: "shrink-0 mr-3" }, B = { class: "p-2 bg-primary-light rounded text-primary-fg-light" }, C = { class: "flex-1 min-w-0 flex flex-col items-start overflow-hidden contain-inline-size" }, R = {
|
|
6
|
+
key: 0,
|
|
7
|
+
class: "inline-flex items-center group/rename w-fit max-w-full mb-0.5"
|
|
8
|
+
}, P = { class: "grid relative w-fit max-w-full items-center -ml-1" }, S = { class: "invisible whitespace-pre col-start-1 row-start-1 text-sm font-medium px-1 py-0.5 max-w-full overflow-hidden text-ellipsis" }, z = ["value", "placeholder"], L = ["title"], I = {
|
|
9
|
+
key: 1,
|
|
10
|
+
class: "text-sm font-medium text-foreground truncate w-full min-w-0"
|
|
11
|
+
}, $ = { class: "text-xs text-muted-foreground -mt-1" }, V = { class: "shrink-0 ml-3 flex gap-2 items-center" }, q = /* @__PURE__ */ b({
|
|
12
|
+
__name: "FilePreview",
|
|
13
|
+
props: {
|
|
14
|
+
file: {},
|
|
15
|
+
index: {},
|
|
16
|
+
allowRename: { type: Boolean },
|
|
17
|
+
disabled: { type: Boolean },
|
|
18
|
+
loading: { type: Boolean },
|
|
19
|
+
isProcessing: { type: Boolean },
|
|
20
|
+
multiSelect: { type: Boolean },
|
|
21
|
+
enterFileNameLabel: {},
|
|
22
|
+
renameFileLabel: {},
|
|
23
|
+
getPreviewSrc: { type: Function },
|
|
24
|
+
isImage: { type: Function },
|
|
25
|
+
onRemove: { type: Function },
|
|
26
|
+
onRename: { type: Function },
|
|
27
|
+
onTrigger: { type: Function }
|
|
28
|
+
},
|
|
29
|
+
setup(e) {
|
|
30
|
+
const o = e, c = h(null), x = u(() => o.file.fileName || o.enterFileNameLabel), m = u(
|
|
31
|
+
() => !o.multiSelect && !o.disabled && !o.loading && !o.isProcessing
|
|
32
|
+
), v = () => {
|
|
33
|
+
c.value?.focus();
|
|
34
|
+
};
|
|
35
|
+
return (E, i) => (n(), r("div", {
|
|
36
|
+
class: p(["relative flex items-center min-w-0 p-3 border border-border rounded-lg bg-body transition-colors group", m.value ? "cursor-pointer hover:border-primary/50" : ""]),
|
|
37
|
+
onClick: i[3] || (i[3] = (l) => m.value ? e.onTrigger() : null)
|
|
38
|
+
}, [
|
|
39
|
+
t("div", N, [
|
|
40
|
+
t("div", B, [
|
|
41
|
+
f(d, {
|
|
42
|
+
icon: "lucide:file-text",
|
|
43
|
+
class: "w-5 h-5"
|
|
44
|
+
})
|
|
45
|
+
])
|
|
46
|
+
]),
|
|
47
|
+
t("div", C, [
|
|
48
|
+
e.allowRename && !e.disabled && !e.loading && !e.isProcessing ? (n(), r("div", R, [
|
|
49
|
+
t("div", P, [
|
|
50
|
+
t("span", S, s(x.value), 1),
|
|
51
|
+
t("input", {
|
|
52
|
+
ref_key: "fileNameInput",
|
|
53
|
+
ref: c,
|
|
54
|
+
value: e.file.fileName,
|
|
55
|
+
onClick: i[0] || (i[0] = a(() => {
|
|
56
|
+
}, ["stop"])),
|
|
57
|
+
onInput: i[1] || (i[1] = (l) => e.onRename(e.index, l.target.value)),
|
|
58
|
+
class: "col-start-1 row-start-1 w-auto min-w-[2ch] text-sm font-medium text-foreground bg-transparent border-b border-transparent hover:border-border focus:border-primary outline-none transition-colors py-0.5 px-1",
|
|
59
|
+
placeholder: e.enterFileNameLabel
|
|
60
|
+
}, null, 40, z)
|
|
61
|
+
]),
|
|
62
|
+
t("div", {
|
|
63
|
+
class: "shrink-0 ml-1 text-muted-foreground/50 group-hover/rename:text-foreground transition-colors cursor-text",
|
|
64
|
+
title: e.renameFileLabel,
|
|
65
|
+
onClick: a(v, ["stop"])
|
|
66
|
+
}, [
|
|
67
|
+
f(d, {
|
|
68
|
+
icon: "lucide:pencil",
|
|
69
|
+
class: "w-3 h-3"
|
|
70
|
+
})
|
|
71
|
+
], 8, L)
|
|
72
|
+
])) : (n(), r("p", I, s(e.file.fileName), 1)),
|
|
73
|
+
t("p", $, s(w(F)(e.file.fileSize)), 1)
|
|
74
|
+
]),
|
|
75
|
+
t("div", V, [
|
|
76
|
+
e.loading || e.isProcessing ? (n(), g(d, {
|
|
77
|
+
key: 0,
|
|
78
|
+
icon: "lucide:loader-2",
|
|
79
|
+
class: "w-4 h-4 animate-spin text-primary"
|
|
80
|
+
})) : e.disabled ? y("", !0) : (n(), g(k, {
|
|
81
|
+
key: 1,
|
|
82
|
+
size: "xs",
|
|
83
|
+
variant: "ghost",
|
|
84
|
+
icon: "lucide:x",
|
|
85
|
+
rounded: "full",
|
|
86
|
+
class: "text-muted-foreground hover:text-destructive hover:bg-destructive/10",
|
|
87
|
+
onClick: i[2] || (i[2] = a((l) => e.onRemove(e.index), ["stop"]))
|
|
88
|
+
}))
|
|
89
|
+
])
|
|
90
|
+
], 2));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
export {
|
|
94
|
+
q as default
|
|
95
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Ref, ComputedRef } from 'vue';
|
|
2
|
+
import { FilePickerValue, FilePickerModelValue } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Getter functions for each reactive prop. Using getters (rather than a
|
|
5
|
+
* snapshot object) is what keeps the composable reactive: the parent
|
|
6
|
+
* passes `() => props.modelValue`, and the composable's `computed`s call
|
|
7
|
+
* the getter to read the current value at evaluation time, so Vue's
|
|
8
|
+
* dependency tracking sees the actual reactive read.
|
|
9
|
+
*
|
|
10
|
+
* If we had captured the values directly (e.g. `modelValue: props.modelValue`),
|
|
11
|
+
* the composable's computeds would read a stale snapshot — props.modelValue
|
|
12
|
+
* never changes inside the composable because it's a plain value, not a
|
|
13
|
+
* reactive proxy. That was the bug in the previous revision: a file was
|
|
14
|
+
* picked, the emit went out, but the composable's `displayFiles` never
|
|
15
|
+
* updated because it was reading a frozen snapshot of the original null
|
|
16
|
+
* value.
|
|
17
|
+
*/
|
|
18
|
+
export interface UseFileProcessingGetters {
|
|
19
|
+
modelValue: () => FilePickerModelValue;
|
|
20
|
+
multiSelect: () => boolean;
|
|
21
|
+
fileTypes: () => string[];
|
|
22
|
+
maxSize: () => number | undefined;
|
|
23
|
+
maxFiles: () => number | undefined;
|
|
24
|
+
returnFormat: () => 'file' | 'base64';
|
|
25
|
+
disabled: () => boolean;
|
|
26
|
+
loading: () => boolean;
|
|
27
|
+
unknownFileFallback: () => string;
|
|
28
|
+
}
|
|
29
|
+
export interface UseFileProcessingReturn {
|
|
30
|
+
/** Normalized list of files for rendering. */
|
|
31
|
+
displayFiles: ComputedRef<FilePickerValue[]>;
|
|
32
|
+
/** True if a value is present. */
|
|
33
|
+
hasValue: ComputedRef<boolean>;
|
|
34
|
+
/** The first file (used by the input variant for the trigger button). */
|
|
35
|
+
firstDisplayFile: ComputedRef<FilePickerValue | undefined>;
|
|
36
|
+
/** Whether async processing is in-flight. */
|
|
37
|
+
isProcessing: Ref<boolean>;
|
|
38
|
+
/** Imperative API */
|
|
39
|
+
triggerInput: (fileInput: HTMLInputElement | null) => void;
|
|
40
|
+
processFiles: (fileList: FileList) => Promise<void>;
|
|
41
|
+
removeFile: (index: number) => void;
|
|
42
|
+
clearAll: () => void;
|
|
43
|
+
handleFileNameChange: (index: number, newName: string) => void;
|
|
44
|
+
/** Comma-separated file-types for the `accept` attribute on the hidden <input>. */
|
|
45
|
+
acceptAttribute: ComputedRef<string>;
|
|
46
|
+
/** Summary for the trigger meta line, e.g. ".png, .jpg" or "1.2 MB". */
|
|
47
|
+
acceptedFileSummary: ComputedRef<string>;
|
|
48
|
+
/** Preview src for image files; empty string for non-images. */
|
|
49
|
+
getPreviewSrc: (file: FilePickerValue) => string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Owns the FilePicker's reactive state and the imperative operations that
|
|
53
|
+
* mutate the model value (via emitted events). The orchestrator component
|
|
54
|
+
* passes the result of this composable down to FilePickerInput and
|
|
55
|
+
* FilePickerDropzone so neither subcomponent has to know about processing
|
|
56
|
+
* logic — they just render props and call back into the API.
|
|
57
|
+
*
|
|
58
|
+
* Emits are passed in as a callback so this composable stays UI-framework
|
|
59
|
+
* agnostic (the same pattern can be unit-tested without mounting a component).
|
|
60
|
+
*/
|
|
61
|
+
export declare const useFileProcessing: (get: UseFileProcessingGetters, emit: {
|
|
62
|
+
(e: "update:modelValue", value: FilePickerModelValue): void;
|
|
63
|
+
(e: "change", value: FilePickerModelValue): void;
|
|
64
|
+
(e: "error", error: string): void;
|
|
65
|
+
}) => UseFileProcessingReturn;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ref as x, onBeforeUnmount as T, computed as f } from "vue";
|
|
2
|
+
import { normalizeForDisplay as U, isImageFile as j, readFileAsBase64 as z } from "../utils.js";
|
|
3
|
+
const N = (e, o) => {
|
|
4
|
+
const c = x(!1), p = /* @__PURE__ */ new WeakMap(), m = /* @__PURE__ */ new Set();
|
|
5
|
+
T(() => {
|
|
6
|
+
m.forEach((l) => URL.revokeObjectURL(l)), m.clear();
|
|
7
|
+
});
|
|
8
|
+
const v = f(() => e.fileTypes().join(",")), b = f(() => {
|
|
9
|
+
const l = e.modelValue();
|
|
10
|
+
return Array.isArray(l) ? l.length > 0 : l !== null;
|
|
11
|
+
}), y = f(() => {
|
|
12
|
+
const l = e.modelValue(), s = [];
|
|
13
|
+
return Array.isArray(l) ? s.push(...l) : l && s.push(l), s.map((r) => U(r, e.unknownFileFallback()));
|
|
14
|
+
}), S = f(() => y.value[0]), V = f(() => e.fileTypes().join(", "));
|
|
15
|
+
return {
|
|
16
|
+
displayFiles: y,
|
|
17
|
+
hasValue: b,
|
|
18
|
+
firstDisplayFile: S,
|
|
19
|
+
isProcessing: c,
|
|
20
|
+
triggerInput: (l) => {
|
|
21
|
+
if (!(e.disabled() || e.loading() || c.value)) {
|
|
22
|
+
if (e.multiSelect() && e.maxFiles()) {
|
|
23
|
+
const s = e.modelValue();
|
|
24
|
+
if ((Array.isArray(s) ? s.length : s ? 1 : 0) >= e.maxFiles()) {
|
|
25
|
+
o("error", `Maximum ${e.maxFiles()} files allowed`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
l?.click();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
processFiles: async (l) => {
|
|
33
|
+
if (l.length === 0) return;
|
|
34
|
+
c.value = !0;
|
|
35
|
+
const s = [], r = [], a = Array.from(l);
|
|
36
|
+
let d = e.multiSelect() ? a : [a[0]];
|
|
37
|
+
if (e.multiSelect() && e.maxFiles()) {
|
|
38
|
+
const i = e.modelValue(), n = Array.isArray(i) ? i.length : i ? 1 : 0, t = e.maxFiles() - n;
|
|
39
|
+
if (t <= 0) {
|
|
40
|
+
o("error", `Maximum ${e.maxFiles()} files allowed`), c.value = !1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
d.length > t && (r.push(`Only ${t} more file(s) allowed. Maximum ${e.maxFiles()} files.`), d = d.slice(0, t));
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
for (const i of d) {
|
|
47
|
+
const n = e.fileTypes();
|
|
48
|
+
if (n.length > 0 && !n.some((u) => {
|
|
49
|
+
if (u.startsWith(".")) return i.name.toLowerCase().endsWith(u.toLowerCase());
|
|
50
|
+
if (u.endsWith("/*")) {
|
|
51
|
+
const w = u.split("/")[0];
|
|
52
|
+
return i.type.startsWith(w);
|
|
53
|
+
}
|
|
54
|
+
return i.type === u;
|
|
55
|
+
})) {
|
|
56
|
+
r.push(`File type not allowed: ${i.name}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
let t = !1;
|
|
60
|
+
const h = e.maxSize();
|
|
61
|
+
h && i.size > h && (r.push(`File too large: ${i.name}`), t = !0);
|
|
62
|
+
let F = "";
|
|
63
|
+
if (e.returnFormat() === "base64" && !t)
|
|
64
|
+
try {
|
|
65
|
+
F = await z(i);
|
|
66
|
+
} catch (A) {
|
|
67
|
+
console.error("Base64 read failed", A);
|
|
68
|
+
}
|
|
69
|
+
s.push({
|
|
70
|
+
fileName: i.name,
|
|
71
|
+
fileType: i.type,
|
|
72
|
+
fileSize: i.size,
|
|
73
|
+
file: i,
|
|
74
|
+
base64: F
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (r.length > 0 && o("error", r.join("; ")), s.length > 0) {
|
|
78
|
+
let i;
|
|
79
|
+
if (e.multiSelect()) {
|
|
80
|
+
const n = e.modelValue();
|
|
81
|
+
i = [...Array.isArray(n) ? n : n ? [n] : [], ...s];
|
|
82
|
+
} else
|
|
83
|
+
i = s[0];
|
|
84
|
+
o("update:modelValue", i), o("change", i);
|
|
85
|
+
}
|
|
86
|
+
} catch (i) {
|
|
87
|
+
console.error("Error processing files:", i), o("error", "Failed to process files");
|
|
88
|
+
} finally {
|
|
89
|
+
c.value = !1;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
removeFile: (l) => {
|
|
93
|
+
if (e.disabled() || e.loading()) return;
|
|
94
|
+
const s = e.modelValue();
|
|
95
|
+
if (e.multiSelect() && Array.isArray(s)) {
|
|
96
|
+
const r = [...s];
|
|
97
|
+
r.splice(l, 1), o("update:modelValue", r), o("change", r);
|
|
98
|
+
} else
|
|
99
|
+
o("update:modelValue", null), o("change", null);
|
|
100
|
+
},
|
|
101
|
+
clearAll: () => {
|
|
102
|
+
e.disabled() || e.loading() || (o("update:modelValue", null), o("change", null));
|
|
103
|
+
},
|
|
104
|
+
handleFileNameChange: (l, s) => {
|
|
105
|
+
if (e.disabled() || e.loading() || c.value) return;
|
|
106
|
+
const r = e.modelValue();
|
|
107
|
+
let a;
|
|
108
|
+
e.multiSelect() && Array.isArray(r) ? (a = [...r], typeof a[l] == "object" && a[l] !== null ? a[l] = { ...a[l], fileName: s } : typeof a[l] == "string" && (a[l] = {
|
|
109
|
+
fileName: s,
|
|
110
|
+
fileType: "unknown",
|
|
111
|
+
fileSize: 0,
|
|
112
|
+
file: null,
|
|
113
|
+
base64: a[l],
|
|
114
|
+
isUrl: !0
|
|
115
|
+
})) : r && typeof r == "object" && !Array.isArray(r) ? a = { ...r, fileName: s } : typeof r == "string" && (a = {
|
|
116
|
+
fileName: s,
|
|
117
|
+
fileType: "unknown",
|
|
118
|
+
fileSize: 0,
|
|
119
|
+
file: null,
|
|
120
|
+
base64: r,
|
|
121
|
+
isUrl: !0
|
|
122
|
+
}), a !== void 0 && (o("update:modelValue", a), o("change", a));
|
|
123
|
+
},
|
|
124
|
+
acceptAttribute: v,
|
|
125
|
+
acceptedFileSummary: V,
|
|
126
|
+
getPreviewSrc: (l) => {
|
|
127
|
+
if (!j(l)) return "";
|
|
128
|
+
if (l.base64?.startsWith("data:image/") || l.isUrl) return l.base64;
|
|
129
|
+
if (typeof File < "u" && l.file instanceof File) {
|
|
130
|
+
const s = p.get(l.file);
|
|
131
|
+
if (s) return s;
|
|
132
|
+
const r = URL.createObjectURL(l.file);
|
|
133
|
+
return p.set(l.file, r), m.add(r), r;
|
|
134
|
+
}
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
export {
|
|
140
|
+
N as useFileProcessing
|
|
141
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as FilePicker } from './
|
|
2
|
-
export type { FilePickerValue } from './
|
|
1
|
+
export { default as FilePicker } from './index.vue';
|
|
2
|
+
export type { FilePickerValue, FilePickerModelValue } from './types';
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
+
import { FilePickerValue, FilePickerModelValue } from './types';
|
|
1
2
|
import { InputSize, InputRounded } from '../../types';
|
|
2
|
-
export interface FilePickerValue {
|
|
3
|
-
fileName: string;
|
|
4
|
-
fileType: string;
|
|
5
|
-
fileSize: number;
|
|
6
|
-
file: File | null;
|
|
7
|
-
base64: string;
|
|
8
|
-
isUrl?: boolean;
|
|
9
|
-
}
|
|
10
3
|
interface Props {
|
|
11
|
-
modelValue?:
|
|
4
|
+
modelValue?: FilePickerModelValue;
|
|
12
5
|
multiSelect?: boolean;
|
|
13
6
|
fileTypes?: string[];
|
|
14
7
|
returnFormat?: 'file' | 'base64';
|
|
@@ -31,23 +24,23 @@ declare function __VLS_template(): {
|
|
|
31
24
|
trigger: () => void;
|
|
32
25
|
isDragging: boolean;
|
|
33
26
|
isLoading: boolean;
|
|
34
|
-
files:
|
|
27
|
+
files: FilePickerValue[];
|
|
35
28
|
}): any;
|
|
36
29
|
};
|
|
37
30
|
refs: {
|
|
38
31
|
fileInput: HTMLInputElement;
|
|
39
32
|
};
|
|
40
|
-
rootEl:
|
|
33
|
+
rootEl: any;
|
|
41
34
|
};
|
|
42
35
|
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
|
|
43
36
|
declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
44
37
|
error: (error: string) => any;
|
|
45
|
-
change: (value:
|
|
46
|
-
"update:modelValue": (value:
|
|
38
|
+
change: (value: FilePickerModelValue) => any;
|
|
39
|
+
"update:modelValue": (value: FilePickerModelValue) => any;
|
|
47
40
|
}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
|
|
48
41
|
onError?: (error: string) => any;
|
|
49
|
-
onChange?: (value:
|
|
50
|
-
"onUpdate:modelValue"?: (value:
|
|
42
|
+
onChange?: (value: FilePickerModelValue) => any;
|
|
43
|
+
"onUpdate:modelValue"?: (value: FilePickerModelValue) => any;
|
|
51
44
|
}>, {
|
|
52
45
|
variant: "dropzone" | "input";
|
|
53
46
|
size: InputSize;
|
|
@@ -55,13 +48,13 @@ declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {},
|
|
|
55
48
|
loading: boolean;
|
|
56
49
|
disabled: boolean;
|
|
57
50
|
multiSelect: boolean;
|
|
58
|
-
modelValue:
|
|
51
|
+
modelValue: FilePickerModelValue;
|
|
52
|
+
allowRename: boolean;
|
|
59
53
|
fileTypes: string[];
|
|
60
54
|
returnFormat: "file" | "base64";
|
|
61
|
-
allowRename: boolean;
|
|
62
55
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
|
|
63
56
|
fileInput: HTMLInputElement;
|
|
64
|
-
},
|
|
57
|
+
}, any>;
|
|
65
58
|
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
66
59
|
export default _default;
|
|
67
60
|
type __VLS_WithTemplateSlots<T, S> = T & {
|