@masters-union/union-stack 0.1.2
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/cdn/loader.v1.global.js +81 -0
- package/dist/cdn/loader.v1.global.js.map +1 -0
- package/dist/chunk-5Q2UADFS.js +344 -0
- package/dist/chunk-5Q2UADFS.js.map +1 -0
- package/dist/chunk-QE2P5WH4.cjs +346 -0
- package/dist/chunk-QE2P5WH4.cjs.map +1 -0
- package/dist/client-xFfk4uu4.d.cts +188 -0
- package/dist/client-xFfk4uu4.d.ts +188 -0
- package/dist/index.cjs +18 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/picker.cjs +489 -0
- package/dist/picker.cjs.map +1 -0
- package/dist/picker.d.cts +39 -0
- package/dist/picker.d.ts +39 -0
- package/dist/picker.js +486 -0
- package/dist/picker.js.map +1 -0
- package/dist/react.cjs +158 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +72 -0
- package/dist/react.d.ts +72 -0
- package/dist/react.js +133 -0
- package/dist/react.js.map +1 -0
- package/package.json +84 -0
package/dist/picker.js
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
// src/picker/styles.ts
|
|
2
|
+
var STYLE_ID = "unionstack-picker-styles";
|
|
3
|
+
function ensureStyles() {
|
|
4
|
+
if (typeof document === "undefined") return;
|
|
5
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
6
|
+
const el2 = document.createElement("style");
|
|
7
|
+
el2.id = STYLE_ID;
|
|
8
|
+
el2.textContent = BASE_CSS;
|
|
9
|
+
document.head.appendChild(el2);
|
|
10
|
+
}
|
|
11
|
+
function themeToCssVars(theme) {
|
|
12
|
+
const mode = theme?.mode || "light";
|
|
13
|
+
const defaults = mode === "dark" ? DARK_DEFAULTS : LIGHT_DEFAULTS;
|
|
14
|
+
return {
|
|
15
|
+
"--us-primary": theme?.primary ?? defaults.primary,
|
|
16
|
+
"--us-bg": theme?.background ?? defaults.background,
|
|
17
|
+
"--us-fg": theme?.foreground ?? defaults.foreground,
|
|
18
|
+
"--us-muted": theme?.muted ?? defaults.muted,
|
|
19
|
+
"--us-border": theme?.border ?? defaults.border,
|
|
20
|
+
"--us-radius": theme?.radius ?? "12px"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
var LIGHT_DEFAULTS = {
|
|
24
|
+
primary: "#4f46e5",
|
|
25
|
+
background: "#ffffff",
|
|
26
|
+
foreground: "#0f172a",
|
|
27
|
+
muted: "#64748b",
|
|
28
|
+
border: "#e2e8f0"
|
|
29
|
+
};
|
|
30
|
+
var DARK_DEFAULTS = {
|
|
31
|
+
primary: "#6366f1",
|
|
32
|
+
background: "#0f172a",
|
|
33
|
+
foreground: "#f1f5f9",
|
|
34
|
+
muted: "#94a3b8",
|
|
35
|
+
border: "#1e293b"
|
|
36
|
+
};
|
|
37
|
+
var BASE_CSS = `
|
|
38
|
+
.us-picker-backdrop {
|
|
39
|
+
position: fixed; inset: 0; z-index: 2147483000;
|
|
40
|
+
background: rgba(2, 6, 23, 0.55);
|
|
41
|
+
display: flex; align-items: center; justify-content: center;
|
|
42
|
+
padding: 16px; font-family: ui-sans-serif, system-ui, sans-serif;
|
|
43
|
+
animation: us-fade 120ms ease-out;
|
|
44
|
+
}
|
|
45
|
+
@keyframes us-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
46
|
+
.us-picker {
|
|
47
|
+
background: var(--us-bg); color: var(--us-fg);
|
|
48
|
+
border-radius: var(--us-radius);
|
|
49
|
+
width: 100%; max-width: 480px; max-height: calc(100vh - 32px);
|
|
50
|
+
display: flex; flex-direction: column;
|
|
51
|
+
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
}
|
|
54
|
+
.us-picker * { box-sizing: border-box; }
|
|
55
|
+
.us-picker-header {
|
|
56
|
+
display: flex; align-items: center; gap: 12px;
|
|
57
|
+
padding: 16px 20px; border-bottom: 1px solid var(--us-border);
|
|
58
|
+
}
|
|
59
|
+
.us-picker-header img { height: 24px; }
|
|
60
|
+
.us-picker-title { font-weight: 600; font-size: 16px; flex: 1; }
|
|
61
|
+
.us-picker-close {
|
|
62
|
+
background: none; border: 0; cursor: pointer;
|
|
63
|
+
color: var(--us-muted); font-size: 22px; line-height: 1;
|
|
64
|
+
padding: 4px 8px; border-radius: 6px;
|
|
65
|
+
}
|
|
66
|
+
.us-picker-close:hover { background: var(--us-border); color: var(--us-fg); }
|
|
67
|
+
.us-picker-body { padding: 20px; overflow-y: auto; }
|
|
68
|
+
.us-dropzone {
|
|
69
|
+
border: 2px dashed var(--us-border); border-radius: var(--us-radius);
|
|
70
|
+
padding: 32px 20px; text-align: center; cursor: pointer;
|
|
71
|
+
transition: border-color 120ms, background 120ms;
|
|
72
|
+
}
|
|
73
|
+
.us-dropzone:hover, .us-dropzone[data-drag="over"] {
|
|
74
|
+
border-color: var(--us-primary);
|
|
75
|
+
background: color-mix(in srgb, var(--us-primary) 5%, transparent);
|
|
76
|
+
}
|
|
77
|
+
.us-dropzone-title { font-weight: 500; margin-bottom: 4px; }
|
|
78
|
+
.us-dropzone-hint { color: var(--us-muted); font-size: 13px; }
|
|
79
|
+
.us-file-list { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
|
|
80
|
+
.us-file {
|
|
81
|
+
display: flex; align-items: center; gap: 12px;
|
|
82
|
+
padding: 10px 12px; border: 1px solid var(--us-border);
|
|
83
|
+
border-radius: 10px; font-size: 14px;
|
|
84
|
+
}
|
|
85
|
+
.us-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
86
|
+
.us-file-meta { color: var(--us-muted); font-size: 12px; }
|
|
87
|
+
.us-file-progress {
|
|
88
|
+
height: 4px; background: var(--us-border); border-radius: 999px; overflow: hidden;
|
|
89
|
+
margin-top: 6px;
|
|
90
|
+
}
|
|
91
|
+
.us-file-progress-bar {
|
|
92
|
+
height: 100%; background: var(--us-primary);
|
|
93
|
+
width: 0%; transition: width 200ms;
|
|
94
|
+
}
|
|
95
|
+
.us-file[data-state="done"] .us-file-progress-bar { background: #16a34a; width: 100%; }
|
|
96
|
+
.us-file[data-state="failed"] .us-file-progress-bar { background: #dc2626; }
|
|
97
|
+
.us-actions {
|
|
98
|
+
display: flex; gap: 8px; justify-content: flex-end;
|
|
99
|
+
padding: 14px 20px; border-top: 1px solid var(--us-border);
|
|
100
|
+
}
|
|
101
|
+
.us-btn {
|
|
102
|
+
padding: 8px 14px; border-radius: 8px; border: 1px solid var(--us-border);
|
|
103
|
+
background: transparent; color: var(--us-fg); cursor: pointer; font-size: 14px;
|
|
104
|
+
font-weight: 500;
|
|
105
|
+
}
|
|
106
|
+
.us-btn:hover { background: var(--us-border); }
|
|
107
|
+
.us-btn-primary {
|
|
108
|
+
background: var(--us-primary); color: white; border-color: var(--us-primary);
|
|
109
|
+
}
|
|
110
|
+
.us-btn-primary:hover { filter: brightness(0.95); }
|
|
111
|
+
.us-btn[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
112
|
+
.us-footer {
|
|
113
|
+
padding: 8px 20px; font-size: 11px; color: var(--us-muted); text-align: center;
|
|
114
|
+
}
|
|
115
|
+
.us-footer a { color: var(--us-muted); }
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
// src/picker/picker.ts
|
|
119
|
+
function mergeConfig(server, runtime) {
|
|
120
|
+
const merged = { ...runtime };
|
|
121
|
+
const runtimeBranding = runtime.branding || {};
|
|
122
|
+
merged.branding = {
|
|
123
|
+
logoUrl: runtimeBranding.logoUrl ?? server.branding.logoUrl ?? void 0,
|
|
124
|
+
title: runtimeBranding.title ?? server.branding.title ?? void 0,
|
|
125
|
+
hideFooter: runtimeBranding.hideFooter ?? server.branding.hideFooter
|
|
126
|
+
// PickerBranding has no footerText today — keep server's value internal.
|
|
127
|
+
};
|
|
128
|
+
const runtimeTheme = runtime.theme || {};
|
|
129
|
+
merged.theme = {
|
|
130
|
+
primary: runtimeTheme.primary ?? server.theme.primary ?? void 0,
|
|
131
|
+
background: runtimeTheme.background ?? server.theme.background ?? void 0,
|
|
132
|
+
foreground: runtimeTheme.foreground ?? server.theme.foreground ?? void 0,
|
|
133
|
+
border: runtimeTheme.border ?? server.theme.border ?? void 0,
|
|
134
|
+
radius: runtimeTheme.radius ?? server.theme.radius ?? void 0,
|
|
135
|
+
mode: runtimeTheme.mode ?? server.theme.mode ?? void 0
|
|
136
|
+
};
|
|
137
|
+
merged.maxFileSize = runtime.maxFileSize ?? server.constraints.maxFileSizeBytes;
|
|
138
|
+
merged.maxFiles = runtime.maxFiles ?? server.constraints.maxFilesPerUpload;
|
|
139
|
+
if (!runtime.accept && server.constraints.allowedMimeTypes?.length) {
|
|
140
|
+
const types = server.constraints.allowedMimeTypes.filter((t) => t !== "*/*");
|
|
141
|
+
if (types.length > 0) merged.accept = types.join(",");
|
|
142
|
+
}
|
|
143
|
+
return merged;
|
|
144
|
+
}
|
|
145
|
+
var DEFAULT_TITLE = "Upload files";
|
|
146
|
+
var FOOTER_LINK = "https://unionstack.mastersunion.link";
|
|
147
|
+
var Picker = class {
|
|
148
|
+
constructor(client, opts) {
|
|
149
|
+
this.client = client;
|
|
150
|
+
this.opts = opts;
|
|
151
|
+
this.$backdrop = null;
|
|
152
|
+
this.$list = null;
|
|
153
|
+
this.$confirm = null;
|
|
154
|
+
this.$cancel = null;
|
|
155
|
+
this.$closeBtn = null;
|
|
156
|
+
this.$input = null;
|
|
157
|
+
this.items = [];
|
|
158
|
+
this.abortCtrl = new AbortController();
|
|
159
|
+
this.uploadStarted = false;
|
|
160
|
+
this.resolved = false;
|
|
161
|
+
this.donePromise = new Promise((res) => {
|
|
162
|
+
this.resolvePromise = res;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// ---- public api ---------------------------------------------------------
|
|
166
|
+
async open() {
|
|
167
|
+
if (typeof document === "undefined") {
|
|
168
|
+
throw new Error("[union-stack] Picker requires a browser environment.");
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const serverConfig = await this.client.pickerConfigPromise;
|
|
172
|
+
if (serverConfig) this.opts = mergeConfig(serverConfig, this.opts);
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
ensureStyles();
|
|
176
|
+
this.mount();
|
|
177
|
+
this.opts.onOpen?.();
|
|
178
|
+
return this.donePromise;
|
|
179
|
+
}
|
|
180
|
+
close() {
|
|
181
|
+
this.unmount();
|
|
182
|
+
if (!this.resolved) this.resolveResult();
|
|
183
|
+
}
|
|
184
|
+
cancel() {
|
|
185
|
+
this.abortCtrl.abort();
|
|
186
|
+
this.opts.onCancel?.();
|
|
187
|
+
this.close();
|
|
188
|
+
}
|
|
189
|
+
// ---- mount / dom --------------------------------------------------------
|
|
190
|
+
mount() {
|
|
191
|
+
const root = document.createElement("div");
|
|
192
|
+
root.className = "us-picker-backdrop";
|
|
193
|
+
Object.entries(themeToCssVars(this.opts.theme)).forEach(([k, v]) => {
|
|
194
|
+
root.style.setProperty(k, v);
|
|
195
|
+
});
|
|
196
|
+
root.addEventListener("click", (e) => {
|
|
197
|
+
if (e.target === root && !this.uploadStarted) this.cancel();
|
|
198
|
+
});
|
|
199
|
+
const panel = el("div", "us-picker");
|
|
200
|
+
const header = el("div", "us-picker-header");
|
|
201
|
+
if (this.opts.branding?.logoUrl) {
|
|
202
|
+
const logo = document.createElement("img");
|
|
203
|
+
logo.src = this.opts.branding.logoUrl;
|
|
204
|
+
logo.alt = "logo";
|
|
205
|
+
header.appendChild(logo);
|
|
206
|
+
}
|
|
207
|
+
const title = el("div", "us-picker-title", this.opts.branding?.title ?? DEFAULT_TITLE);
|
|
208
|
+
header.appendChild(title);
|
|
209
|
+
this.$closeBtn = document.createElement("button");
|
|
210
|
+
this.$closeBtn.type = "button";
|
|
211
|
+
this.$closeBtn.className = "us-picker-close";
|
|
212
|
+
this.$closeBtn.setAttribute("aria-label", "Close");
|
|
213
|
+
this.$closeBtn.textContent = "\xD7";
|
|
214
|
+
this.$closeBtn.onclick = () => this.cancel();
|
|
215
|
+
header.appendChild(this.$closeBtn);
|
|
216
|
+
panel.appendChild(header);
|
|
217
|
+
const body = el("div", "us-picker-body");
|
|
218
|
+
body.appendChild(this.renderDropzone());
|
|
219
|
+
this.$list = el("div", "us-file-list");
|
|
220
|
+
body.appendChild(this.$list);
|
|
221
|
+
panel.appendChild(body);
|
|
222
|
+
const actions = el("div", "us-actions");
|
|
223
|
+
this.$cancel = document.createElement("button");
|
|
224
|
+
this.$cancel.type = "button";
|
|
225
|
+
this.$cancel.className = "us-btn";
|
|
226
|
+
this.$cancel.textContent = "Cancel";
|
|
227
|
+
this.$cancel.onclick = () => this.cancel();
|
|
228
|
+
this.$confirm = document.createElement("button");
|
|
229
|
+
this.$confirm.type = "button";
|
|
230
|
+
this.$confirm.className = "us-btn us-btn-primary";
|
|
231
|
+
this.$confirm.textContent = "Upload";
|
|
232
|
+
this.$confirm.disabled = true;
|
|
233
|
+
this.$confirm.onclick = () => this.startUpload();
|
|
234
|
+
actions.appendChild(this.$cancel);
|
|
235
|
+
actions.appendChild(this.$confirm);
|
|
236
|
+
panel.appendChild(actions);
|
|
237
|
+
if (!this.opts.branding?.hideFooter) {
|
|
238
|
+
const footer = el("div", "us-footer");
|
|
239
|
+
footer.innerHTML = `Powered by <a href="${FOOTER_LINK}" target="_blank" rel="noopener">UnionStack</a>`;
|
|
240
|
+
panel.appendChild(footer);
|
|
241
|
+
}
|
|
242
|
+
root.appendChild(panel);
|
|
243
|
+
(this.opts.container ?? document.body).appendChild(root);
|
|
244
|
+
this.$backdrop = root;
|
|
245
|
+
}
|
|
246
|
+
renderDropzone() {
|
|
247
|
+
const dz = el("div", "us-dropzone");
|
|
248
|
+
dz.setAttribute("role", "button");
|
|
249
|
+
dz.setAttribute("tabindex", "0");
|
|
250
|
+
dz.appendChild(el("div", "us-dropzone-title", "Drag files here"));
|
|
251
|
+
dz.appendChild(el("div", "us-dropzone-hint", "or click to browse"));
|
|
252
|
+
const input = document.createElement("input");
|
|
253
|
+
input.type = "file";
|
|
254
|
+
input.multiple = (this.opts.maxFiles ?? 10) > 1;
|
|
255
|
+
if (this.opts.accept) input.accept = this.opts.accept;
|
|
256
|
+
input.style.display = "none";
|
|
257
|
+
input.onchange = () => {
|
|
258
|
+
if (input.files) this.addFiles(Array.from(input.files));
|
|
259
|
+
input.value = "";
|
|
260
|
+
};
|
|
261
|
+
this.$input = input;
|
|
262
|
+
dz.appendChild(input);
|
|
263
|
+
dz.onclick = () => input.click();
|
|
264
|
+
dz.onkeydown = (e) => {
|
|
265
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
input.click();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
dz.addEventListener("dragover", (e) => {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
dz.setAttribute("data-drag", "over");
|
|
273
|
+
});
|
|
274
|
+
dz.addEventListener("dragleave", () => dz.removeAttribute("data-drag"));
|
|
275
|
+
dz.addEventListener("drop", (e) => {
|
|
276
|
+
e.preventDefault();
|
|
277
|
+
dz.removeAttribute("data-drag");
|
|
278
|
+
const dropped = e.dataTransfer?.files;
|
|
279
|
+
if (dropped) this.addFiles(Array.from(dropped));
|
|
280
|
+
});
|
|
281
|
+
return dz;
|
|
282
|
+
}
|
|
283
|
+
unmount() {
|
|
284
|
+
if (this.$backdrop?.parentNode) this.$backdrop.parentNode.removeChild(this.$backdrop);
|
|
285
|
+
this.$backdrop = null;
|
|
286
|
+
this.opts.onClose?.();
|
|
287
|
+
}
|
|
288
|
+
// ---- file selection -----------------------------------------------------
|
|
289
|
+
addFiles(files) {
|
|
290
|
+
const cap = this.opts.maxFiles ?? Infinity;
|
|
291
|
+
const remaining = cap - this.items.length;
|
|
292
|
+
if (remaining <= 0) return;
|
|
293
|
+
const chosen = files.slice(0, remaining);
|
|
294
|
+
for (const file of chosen) {
|
|
295
|
+
if (this.opts.maxFileSize && file.size > this.opts.maxFileSize) {
|
|
296
|
+
const item2 = {
|
|
297
|
+
uploadId: cryptoId(),
|
|
298
|
+
file,
|
|
299
|
+
state: "failed",
|
|
300
|
+
progress: 0,
|
|
301
|
+
error: `File exceeds ${formatBytes(this.opts.maxFileSize)} limit`
|
|
302
|
+
};
|
|
303
|
+
this.items.push(item2);
|
|
304
|
+
this.renderItem(item2);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const item = {
|
|
308
|
+
uploadId: cryptoId(),
|
|
309
|
+
file,
|
|
310
|
+
state: "queued",
|
|
311
|
+
progress: 0
|
|
312
|
+
};
|
|
313
|
+
this.items.push(item);
|
|
314
|
+
this.renderItem(item);
|
|
315
|
+
}
|
|
316
|
+
this.refreshConfirm();
|
|
317
|
+
}
|
|
318
|
+
renderItem(item) {
|
|
319
|
+
if (!this.$list) return;
|
|
320
|
+
const row = el("div", "us-file");
|
|
321
|
+
row.dataset.state = item.state;
|
|
322
|
+
row.dataset.uploadId = item.uploadId;
|
|
323
|
+
const main = el("div", "", "");
|
|
324
|
+
main.style.flex = "1";
|
|
325
|
+
main.style.minWidth = "0";
|
|
326
|
+
main.appendChild(el("div", "us-file-name", item.file.name));
|
|
327
|
+
const meta = el("div", "us-file-meta", formatBytes(item.file.size));
|
|
328
|
+
main.appendChild(meta);
|
|
329
|
+
const progress = el("div", "us-file-progress");
|
|
330
|
+
const bar = el("div", "us-file-progress-bar");
|
|
331
|
+
progress.appendChild(bar);
|
|
332
|
+
main.appendChild(progress);
|
|
333
|
+
row.appendChild(main);
|
|
334
|
+
const status = el("div", "us-file-meta");
|
|
335
|
+
status.style.minWidth = "60px";
|
|
336
|
+
status.style.textAlign = "right";
|
|
337
|
+
status.textContent = item.state === "failed" ? item.error || "failed" : item.state === "done" ? "done" : "0%";
|
|
338
|
+
row.appendChild(status);
|
|
339
|
+
item.$row = row;
|
|
340
|
+
item.$bar = bar;
|
|
341
|
+
item.$status = status;
|
|
342
|
+
this.$list.appendChild(row);
|
|
343
|
+
}
|
|
344
|
+
setItemState(item, state, progress) {
|
|
345
|
+
item.state = state;
|
|
346
|
+
if (progress !== void 0) item.progress = progress;
|
|
347
|
+
if (item.$row) item.$row.dataset.state = state;
|
|
348
|
+
if (item.$bar) item.$bar.style.width = `${item.progress}%`;
|
|
349
|
+
if (item.$status) {
|
|
350
|
+
if (state === "failed") item.$status.textContent = item.error || "failed";
|
|
351
|
+
else if (state === "done") item.$status.textContent = "done";
|
|
352
|
+
else item.$status.textContent = `${Math.round(item.progress)}%`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
refreshConfirm() {
|
|
356
|
+
if (!this.$confirm) return;
|
|
357
|
+
const queued = this.items.filter((i) => i.state === "queued").length;
|
|
358
|
+
this.$confirm.disabled = queued === 0 || this.uploadStarted;
|
|
359
|
+
}
|
|
360
|
+
// ---- upload -------------------------------------------------------------
|
|
361
|
+
async startUpload() {
|
|
362
|
+
if (this.uploadStarted) return;
|
|
363
|
+
const queued = this.items.filter((i) => i.state === "queued");
|
|
364
|
+
if (queued.length === 0) return;
|
|
365
|
+
this.uploadStarted = true;
|
|
366
|
+
if (this.$confirm) {
|
|
367
|
+
this.$confirm.disabled = true;
|
|
368
|
+
this.$confirm.textContent = "Uploading\u2026";
|
|
369
|
+
}
|
|
370
|
+
if (this.$cancel) this.$cancel.textContent = "Stop";
|
|
371
|
+
if (this.$closeBtn) this.$closeBtn.disabled = true;
|
|
372
|
+
if (this.$input) this.$input.disabled = true;
|
|
373
|
+
const itemByUploadId = /* @__PURE__ */ new Map();
|
|
374
|
+
const filesToUpload = queued.map((i) => i.file);
|
|
375
|
+
try {
|
|
376
|
+
await this.client.uploadMany(filesToUpload, {
|
|
377
|
+
...this.opts,
|
|
378
|
+
signal: this.abortCtrl.signal,
|
|
379
|
+
onUploadStarted: (pickedFiles) => {
|
|
380
|
+
pickedFiles.forEach((p, idx) => {
|
|
381
|
+
const item = queued[idx];
|
|
382
|
+
if (item) {
|
|
383
|
+
item.uploadId = p.uploadId;
|
|
384
|
+
if (item.$row) item.$row.dataset.uploadId = p.uploadId;
|
|
385
|
+
itemByUploadId.set(p.uploadId, item);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
this.opts.onUploadStarted?.(pickedFiles);
|
|
389
|
+
},
|
|
390
|
+
onFileUploadStarted: (p) => {
|
|
391
|
+
const item = itemByUploadId.get(p.uploadId);
|
|
392
|
+
if (item) this.setItemState(item, "uploading", 0);
|
|
393
|
+
this.opts.onFileUploadStarted?.(p);
|
|
394
|
+
},
|
|
395
|
+
onFileUploadProgress: (p, ev) => {
|
|
396
|
+
const item = itemByUploadId.get(p.uploadId);
|
|
397
|
+
if (item) this.setItemState(item, "uploading", ev.totalPercent);
|
|
398
|
+
this.opts.onFileUploadProgress?.(p, ev);
|
|
399
|
+
},
|
|
400
|
+
onFileUploadFinished: (f) => {
|
|
401
|
+
const item = itemByUploadId.get(f.uploadId);
|
|
402
|
+
if (item) {
|
|
403
|
+
item.uploaded = f;
|
|
404
|
+
this.setItemState(item, "done", 100);
|
|
405
|
+
}
|
|
406
|
+
this.opts.onFileUploadFinished?.(f);
|
|
407
|
+
},
|
|
408
|
+
onFileUploadFailed: (p, err) => {
|
|
409
|
+
const item = itemByUploadId.get(p.uploadId);
|
|
410
|
+
if (item) {
|
|
411
|
+
item.error = err.message;
|
|
412
|
+
this.setItemState(item, "failed");
|
|
413
|
+
}
|
|
414
|
+
this.opts.onFileUploadFailed?.(p, err);
|
|
415
|
+
},
|
|
416
|
+
onUploadDone: (r) => {
|
|
417
|
+
this.opts.onUploadDone?.(r);
|
|
418
|
+
this.resolveResult(r);
|
|
419
|
+
this.unmount();
|
|
420
|
+
},
|
|
421
|
+
onError: (err) => {
|
|
422
|
+
this.opts.onError?.(err);
|
|
423
|
+
this.resolveResult();
|
|
424
|
+
this.unmount();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
} catch {
|
|
428
|
+
if (!this.resolved) {
|
|
429
|
+
this.resolveResult();
|
|
430
|
+
this.unmount();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
resolveResult(result) {
|
|
435
|
+
if (this.resolved) return;
|
|
436
|
+
this.resolved = true;
|
|
437
|
+
const fallback = result ?? {
|
|
438
|
+
filesUploaded: this.items.filter((i) => i.uploaded).map((i) => i.uploaded),
|
|
439
|
+
filesFailed: this.items.filter((i) => i.state === "failed").map((i) => ({
|
|
440
|
+
file: {
|
|
441
|
+
uploadId: i.uploadId,
|
|
442
|
+
filename: i.file.name,
|
|
443
|
+
mimetype: i.file.type || "application/octet-stream",
|
|
444
|
+
size: i.file.size,
|
|
445
|
+
source: "local"
|
|
446
|
+
},
|
|
447
|
+
error: {
|
|
448
|
+
code: "VALIDATION",
|
|
449
|
+
message: i.error || "failed",
|
|
450
|
+
retryable: false
|
|
451
|
+
}
|
|
452
|
+
}))
|
|
453
|
+
};
|
|
454
|
+
this.resolvePromise(fallback);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
function el(tag, className, text) {
|
|
458
|
+
const node = document.createElement(tag);
|
|
459
|
+
if (className) node.className = className;
|
|
460
|
+
if (text !== void 0) node.textContent = text;
|
|
461
|
+
return node;
|
|
462
|
+
}
|
|
463
|
+
function formatBytes(n) {
|
|
464
|
+
if (n < 1024) return `${n} B`;
|
|
465
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
466
|
+
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
467
|
+
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
468
|
+
}
|
|
469
|
+
function cryptoId() {
|
|
470
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
471
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
472
|
+
}
|
|
473
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
474
|
+
}
|
|
475
|
+
function openPicker(client, opts) {
|
|
476
|
+
const picker = new Picker(client, opts);
|
|
477
|
+
return {
|
|
478
|
+
open: () => picker.open(),
|
|
479
|
+
close: () => picker.close(),
|
|
480
|
+
cancel: () => picker.cancel()
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export { Picker, openPicker };
|
|
485
|
+
//# sourceMappingURL=picker.js.map
|
|
486
|
+
//# sourceMappingURL=picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/picker/styles.ts","../src/picker/picker.ts"],"names":["el","item"],"mappings":"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","file":"picker.js","sourcesContent":["import type { PickerTheme } from './types.js';\n\nconst STYLE_ID = 'unionstack-picker-styles';\n\n/** Inject a single <style> tag once. Idempotent — safe to call repeatedly. */\nexport function ensureStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement('style');\n el.id = STYLE_ID;\n el.textContent = BASE_CSS;\n document.head.appendChild(el);\n}\n\n/** Resolve a partial theme into CSS variables on the modal root. */\nexport function themeToCssVars(theme: PickerTheme | undefined): Record<string, string> {\n const mode = theme?.mode || 'light';\n const defaults = mode === 'dark' ? DARK_DEFAULTS : LIGHT_DEFAULTS;\n return {\n '--us-primary': theme?.primary ?? defaults.primary,\n '--us-bg': theme?.background ?? defaults.background,\n '--us-fg': theme?.foreground ?? defaults.foreground,\n '--us-muted': theme?.muted ?? defaults.muted,\n '--us-border': theme?.border ?? defaults.border,\n '--us-radius': theme?.radius ?? '12px',\n };\n}\n\nconst LIGHT_DEFAULTS = {\n primary: '#4f46e5',\n background: '#ffffff',\n foreground: '#0f172a',\n muted: '#64748b',\n border: '#e2e8f0',\n};\nconst DARK_DEFAULTS = {\n primary: '#6366f1',\n background: '#0f172a',\n foreground: '#f1f5f9',\n muted: '#94a3b8',\n border: '#1e293b',\n};\n\n// All selectors namespaced under .us-picker to avoid bleed into host page.\nconst BASE_CSS = `\n.us-picker-backdrop {\n position: fixed; inset: 0; z-index: 2147483000;\n background: rgba(2, 6, 23, 0.55);\n display: flex; align-items: center; justify-content: center;\n padding: 16px; font-family: ui-sans-serif, system-ui, sans-serif;\n animation: us-fade 120ms ease-out;\n}\n@keyframes us-fade { from { opacity: 0; } to { opacity: 1; } }\n.us-picker {\n background: var(--us-bg); color: var(--us-fg);\n border-radius: var(--us-radius);\n width: 100%; max-width: 480px; max-height: calc(100vh - 32px);\n display: flex; flex-direction: column;\n box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4);\n overflow: hidden;\n}\n.us-picker * { box-sizing: border-box; }\n.us-picker-header {\n display: flex; align-items: center; gap: 12px;\n padding: 16px 20px; border-bottom: 1px solid var(--us-border);\n}\n.us-picker-header img { height: 24px; }\n.us-picker-title { font-weight: 600; font-size: 16px; flex: 1; }\n.us-picker-close {\n background: none; border: 0; cursor: pointer;\n color: var(--us-muted); font-size: 22px; line-height: 1;\n padding: 4px 8px; border-radius: 6px;\n}\n.us-picker-close:hover { background: var(--us-border); color: var(--us-fg); }\n.us-picker-body { padding: 20px; overflow-y: auto; }\n.us-dropzone {\n border: 2px dashed var(--us-border); border-radius: var(--us-radius);\n padding: 32px 20px; text-align: center; cursor: pointer;\n transition: border-color 120ms, background 120ms;\n}\n.us-dropzone:hover, .us-dropzone[data-drag=\"over\"] {\n border-color: var(--us-primary);\n background: color-mix(in srgb, var(--us-primary) 5%, transparent);\n}\n.us-dropzone-title { font-weight: 500; margin-bottom: 4px; }\n.us-dropzone-hint { color: var(--us-muted); font-size: 13px; }\n.us-file-list { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }\n.us-file {\n display: flex; align-items: center; gap: 12px;\n padding: 10px 12px; border: 1px solid var(--us-border);\n border-radius: 10px; font-size: 14px;\n}\n.us-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.us-file-meta { color: var(--us-muted); font-size: 12px; }\n.us-file-progress {\n height: 4px; background: var(--us-border); border-radius: 999px; overflow: hidden;\n margin-top: 6px;\n}\n.us-file-progress-bar {\n height: 100%; background: var(--us-primary);\n width: 0%; transition: width 200ms;\n}\n.us-file[data-state=\"done\"] .us-file-progress-bar { background: #16a34a; width: 100%; }\n.us-file[data-state=\"failed\"] .us-file-progress-bar { background: #dc2626; }\n.us-actions {\n display: flex; gap: 8px; justify-content: flex-end;\n padding: 14px 20px; border-top: 1px solid var(--us-border);\n}\n.us-btn {\n padding: 8px 14px; border-radius: 8px; border: 1px solid var(--us-border);\n background: transparent; color: var(--us-fg); cursor: pointer; font-size: 14px;\n font-weight: 500;\n}\n.us-btn:hover { background: var(--us-border); }\n.us-btn-primary {\n background: var(--us-primary); color: white; border-color: var(--us-primary);\n}\n.us-btn-primary:hover { filter: brightness(0.95); }\n.us-btn[disabled] { opacity: 0.5; cursor: not-allowed; }\n.us-footer {\n padding: 8px 20px; font-size: 11px; color: var(--us-muted); text-align: center;\n}\n.us-footer a { color: var(--us-muted); }\n`;\n","import type { UnionStackClient } from '../client.js';\nimport type { PickerConfig, UploadError } from '../types.js';\nimport type {\n PickResponse,\n PickedFile,\n PickerHandle,\n PickerOptions,\n UploadedFile,\n} from './types.js';\nimport { ensureStyles, themeToCssVars } from './styles.js';\n\n// Merge server-managed picker config with runtime options. Runtime options\n// always win — they're the explicit dev escape hatch.\nfunction mergeConfig(server: PickerConfig, runtime: PickerOptions): PickerOptions {\n const merged: PickerOptions = { ...runtime };\n\n // Branding: shallow merge, runtime wins per-field.\n const runtimeBranding = runtime.branding || {};\n merged.branding = {\n logoUrl: runtimeBranding.logoUrl ?? server.branding.logoUrl ?? undefined,\n title: runtimeBranding.title ?? server.branding.title ?? undefined,\n hideFooter: runtimeBranding.hideFooter ?? server.branding.hideFooter,\n // PickerBranding has no footerText today — keep server's value internal.\n };\n\n // Theme: shallow merge.\n const runtimeTheme = runtime.theme || {};\n merged.theme = {\n primary: runtimeTheme.primary ?? server.theme.primary ?? undefined,\n background: runtimeTheme.background ?? server.theme.background ?? undefined,\n foreground: runtimeTheme.foreground ?? server.theme.foreground ?? undefined,\n border: runtimeTheme.border ?? server.theme.border ?? undefined,\n radius: runtimeTheme.radius ?? server.theme.radius ?? undefined,\n mode: runtimeTheme.mode ?? server.theme.mode ?? undefined,\n };\n\n // Constraints inform the dropzone (accept attr, size cap).\n merged.maxFileSize = runtime.maxFileSize ?? server.constraints.maxFileSizeBytes;\n merged.maxFiles = runtime.maxFiles ?? server.constraints.maxFilesPerUpload;\n if (!runtime.accept && server.constraints.allowedMimeTypes?.length) {\n const types = server.constraints.allowedMimeTypes.filter(t => t !== '*/*');\n if (types.length > 0) merged.accept = types.join(',');\n }\n return merged;\n}\n\ntype FileItemState = 'queued' | 'uploading' | 'done' | 'failed' | 'cancelled';\n\ninterface FileItem {\n uploadId: string; // matches PickedFile.uploadId once described\n file: File;\n state: FileItemState;\n progress: number; // 0-100\n error?: string;\n uploaded?: UploadedFile;\n // DOM refs we mutate on progress.\n $row?: HTMLElement;\n $bar?: HTMLElement;\n $status?: HTMLElement;\n}\n\nconst DEFAULT_TITLE = 'Upload files';\nconst FOOTER_LINK = 'https://unionstack.mastersunion.link';\n\n/**\n * Single-shot file picker modal. Built imperatively (no framework) so it can\n * be lazy-loaded by either the vanilla SDK or the React wrapper.\n */\nexport class Picker {\n private $backdrop: HTMLElement | null = null;\n private $list: HTMLElement | null = null;\n private $confirm: HTMLButtonElement | null = null;\n private $cancel: HTMLButtonElement | null = null;\n private $closeBtn: HTMLButtonElement | null = null;\n private $input: HTMLInputElement | null = null;\n\n private items: FileItem[] = [];\n private abortCtrl = new AbortController();\n private uploadStarted = false;\n private resolved = false;\n private resolvePromise!: (r: PickResponse) => void;\n private donePromise: Promise<PickResponse>;\n\n constructor(\n private client: UnionStackClient,\n private opts: PickerOptions,\n ) {\n this.donePromise = new Promise<PickResponse>(res => { this.resolvePromise = res; });\n }\n\n // ---- public api ---------------------------------------------------------\n\n async open(): Promise<PickResponse> {\n if (typeof document === 'undefined') {\n throw new Error('[union-stack] Picker requires a browser environment.');\n }\n // Pull server-side branding/theme defaults before painting the modal.\n // Runtime opts win over server config — see mergeConfig below.\n try {\n const serverConfig = await this.client.pickerConfigPromise;\n if (serverConfig) this.opts = mergeConfig(serverConfig, this.opts);\n } catch { /* fall through with whatever opts the caller passed */ }\n\n ensureStyles();\n this.mount();\n this.opts.onOpen?.();\n return this.donePromise;\n }\n\n close(): void {\n this.unmount();\n // Resolve with whatever was collected so callers awaiting open() never hang.\n if (!this.resolved) this.resolveResult();\n }\n\n cancel(): void {\n this.abortCtrl.abort();\n this.opts.onCancel?.();\n this.close();\n }\n\n // ---- mount / dom --------------------------------------------------------\n\n private mount() {\n const root = document.createElement('div');\n root.className = 'us-picker-backdrop';\n Object.entries(themeToCssVars(this.opts.theme)).forEach(([k, v]) => {\n root.style.setProperty(k, v);\n });\n root.addEventListener('click', e => {\n if (e.target === root && !this.uploadStarted) this.cancel();\n });\n\n const panel = el('div', 'us-picker');\n\n // Header\n const header = el('div', 'us-picker-header');\n if (this.opts.branding?.logoUrl) {\n const logo = document.createElement('img');\n logo.src = this.opts.branding.logoUrl;\n logo.alt = 'logo';\n header.appendChild(logo);\n }\n const title = el('div', 'us-picker-title', this.opts.branding?.title ?? DEFAULT_TITLE);\n header.appendChild(title);\n this.$closeBtn = document.createElement('button');\n this.$closeBtn.type = 'button';\n this.$closeBtn.className = 'us-picker-close';\n this.$closeBtn.setAttribute('aria-label', 'Close');\n this.$closeBtn.textContent = '×';\n this.$closeBtn.onclick = () => this.cancel();\n header.appendChild(this.$closeBtn);\n panel.appendChild(header);\n\n // Body\n const body = el('div', 'us-picker-body');\n body.appendChild(this.renderDropzone());\n this.$list = el('div', 'us-file-list');\n body.appendChild(this.$list);\n panel.appendChild(body);\n\n // Actions\n const actions = el('div', 'us-actions');\n this.$cancel = document.createElement('button');\n this.$cancel.type = 'button';\n this.$cancel.className = 'us-btn';\n this.$cancel.textContent = 'Cancel';\n this.$cancel.onclick = () => this.cancel();\n this.$confirm = document.createElement('button');\n this.$confirm.type = 'button';\n this.$confirm.className = 'us-btn us-btn-primary';\n this.$confirm.textContent = 'Upload';\n this.$confirm.disabled = true;\n this.$confirm.onclick = () => this.startUpload();\n actions.appendChild(this.$cancel);\n actions.appendChild(this.$confirm);\n panel.appendChild(actions);\n\n if (!this.opts.branding?.hideFooter) {\n const footer = el('div', 'us-footer');\n footer.innerHTML = `Powered by <a href=\"${FOOTER_LINK}\" target=\"_blank\" rel=\"noopener\">UnionStack</a>`;\n panel.appendChild(footer);\n }\n\n root.appendChild(panel);\n (this.opts.container ?? document.body).appendChild(root);\n this.$backdrop = root;\n }\n\n private renderDropzone(): HTMLElement {\n const dz = el('div', 'us-dropzone');\n dz.setAttribute('role', 'button');\n dz.setAttribute('tabindex', '0');\n dz.appendChild(el('div', 'us-dropzone-title', 'Drag files here'));\n dz.appendChild(el('div', 'us-dropzone-hint', 'or click to browse'));\n\n const input = document.createElement('input');\n input.type = 'file';\n input.multiple = (this.opts.maxFiles ?? 10) > 1;\n if (this.opts.accept) input.accept = this.opts.accept;\n input.style.display = 'none';\n input.onchange = () => {\n if (input.files) this.addFiles(Array.from(input.files));\n input.value = '';\n };\n this.$input = input;\n dz.appendChild(input);\n\n dz.onclick = () => input.click();\n dz.onkeydown = e => {\n if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); input.click(); }\n };\n\n dz.addEventListener('dragover', e => {\n e.preventDefault();\n dz.setAttribute('data-drag', 'over');\n });\n dz.addEventListener('dragleave', () => dz.removeAttribute('data-drag'));\n dz.addEventListener('drop', e => {\n e.preventDefault();\n dz.removeAttribute('data-drag');\n const dropped = e.dataTransfer?.files;\n if (dropped) this.addFiles(Array.from(dropped));\n });\n\n return dz;\n }\n\n private unmount() {\n if (this.$backdrop?.parentNode) this.$backdrop.parentNode.removeChild(this.$backdrop);\n this.$backdrop = null;\n this.opts.onClose?.();\n }\n\n // ---- file selection -----------------------------------------------------\n\n private addFiles(files: File[]) {\n const cap = this.opts.maxFiles ?? Infinity;\n const remaining = cap - this.items.length;\n if (remaining <= 0) return;\n const chosen = files.slice(0, remaining);\n\n for (const file of chosen) {\n if (this.opts.maxFileSize && file.size > this.opts.maxFileSize) {\n // Skip the file but surface a row in the list so the user sees why.\n const item: FileItem = {\n uploadId: cryptoId(),\n file, state: 'failed', progress: 0,\n error: `File exceeds ${formatBytes(this.opts.maxFileSize)} limit`,\n };\n this.items.push(item);\n this.renderItem(item);\n continue;\n }\n\n const item: FileItem = {\n uploadId: cryptoId(),\n file, state: 'queued', progress: 0,\n };\n this.items.push(item);\n this.renderItem(item);\n }\n\n this.refreshConfirm();\n }\n\n private renderItem(item: FileItem) {\n if (!this.$list) return;\n const row = el('div', 'us-file');\n row.dataset.state = item.state;\n row.dataset.uploadId = item.uploadId;\n\n const main = el('div', '', '');\n main.style.flex = '1';\n main.style.minWidth = '0';\n main.appendChild(el('div', 'us-file-name', item.file.name));\n\n const meta = el('div', 'us-file-meta', formatBytes(item.file.size));\n main.appendChild(meta);\n\n const progress = el('div', 'us-file-progress');\n const bar = el('div', 'us-file-progress-bar');\n progress.appendChild(bar);\n main.appendChild(progress);\n row.appendChild(main);\n\n const status = el('div', 'us-file-meta');\n status.style.minWidth = '60px';\n status.style.textAlign = 'right';\n status.textContent = item.state === 'failed'\n ? (item.error || 'failed')\n : item.state === 'done' ? 'done' : '0%';\n row.appendChild(status);\n\n item.$row = row;\n item.$bar = bar;\n item.$status = status;\n\n this.$list.appendChild(row);\n }\n\n private setItemState(item: FileItem, state: FileItemState, progress?: number) {\n item.state = state;\n if (progress !== undefined) item.progress = progress;\n if (item.$row) item.$row.dataset.state = state;\n if (item.$bar) item.$bar.style.width = `${item.progress}%`;\n if (item.$status) {\n if (state === 'failed') item.$status.textContent = item.error || 'failed';\n else if (state === 'done') item.$status.textContent = 'done';\n else item.$status.textContent = `${Math.round(item.progress)}%`;\n }\n }\n\n private refreshConfirm() {\n if (!this.$confirm) return;\n const queued = this.items.filter(i => i.state === 'queued').length;\n this.$confirm.disabled = queued === 0 || this.uploadStarted;\n }\n\n // ---- upload -------------------------------------------------------------\n\n private async startUpload() {\n if (this.uploadStarted) return;\n const queued = this.items.filter(i => i.state === 'queued');\n if (queued.length === 0) return;\n\n this.uploadStarted = true;\n if (this.$confirm) {\n this.$confirm.disabled = true;\n this.$confirm.textContent = 'Uploading…';\n }\n if (this.$cancel) this.$cancel.textContent = 'Stop';\n if (this.$closeBtn) this.$closeBtn.disabled = true;\n if (this.$input) this.$input.disabled = true;\n\n // Map PickedFile.uploadId → FileItem so callbacks update the right row.\n const itemByUploadId = new Map<string, FileItem>();\n const filesToUpload = queued.map(i => i.file);\n\n try {\n await this.client.uploadMany(filesToUpload, {\n ...this.opts,\n signal: this.abortCtrl.signal,\n onUploadStarted: (pickedFiles: PickedFile[]) => {\n // Pair each PickedFile to the queued FileItem in order.\n pickedFiles.forEach((p, idx) => {\n const item = queued[idx];\n if (item) {\n item.uploadId = p.uploadId;\n if (item.$row) item.$row.dataset.uploadId = p.uploadId;\n itemByUploadId.set(p.uploadId, item);\n }\n });\n this.opts.onUploadStarted?.(pickedFiles);\n },\n onFileUploadStarted: p => {\n const item = itemByUploadId.get(p.uploadId);\n if (item) this.setItemState(item, 'uploading', 0);\n this.opts.onFileUploadStarted?.(p);\n },\n onFileUploadProgress: (p, ev) => {\n const item = itemByUploadId.get(p.uploadId);\n if (item) this.setItemState(item, 'uploading', ev.totalPercent);\n this.opts.onFileUploadProgress?.(p, ev);\n },\n onFileUploadFinished: f => {\n const item = itemByUploadId.get(f.uploadId);\n if (item) {\n item.uploaded = f;\n this.setItemState(item, 'done', 100);\n }\n this.opts.onFileUploadFinished?.(f);\n },\n onFileUploadFailed: (p, err) => {\n const item = itemByUploadId.get(p.uploadId);\n if (item) {\n item.error = err.message;\n this.setItemState(item, 'failed');\n }\n this.opts.onFileUploadFailed?.(p, err);\n },\n onUploadDone: r => {\n this.opts.onUploadDone?.(r);\n this.resolveResult(r);\n this.unmount();\n },\n onError: (err: UploadError) => {\n this.opts.onError?.(err);\n this.resolveResult();\n this.unmount();\n },\n });\n } catch {\n // Errors already surfaced via onError / onFileUploadFailed.\n if (!this.resolved) {\n this.resolveResult();\n this.unmount();\n }\n }\n }\n\n private resolveResult(result?: PickResponse) {\n if (this.resolved) return;\n this.resolved = true;\n const fallback: PickResponse = result ?? {\n filesUploaded: this.items.filter(i => i.uploaded).map(i => i.uploaded!),\n filesFailed: this.items\n .filter(i => i.state === 'failed')\n .map(i => ({\n file: {\n uploadId: i.uploadId,\n filename: i.file.name,\n mimetype: i.file.type || 'application/octet-stream',\n size: i.file.size,\n source: 'local',\n },\n error: {\n code: 'VALIDATION',\n message: i.error || 'failed',\n retryable: false,\n },\n })),\n };\n this.resolvePromise(fallback);\n }\n}\n\n// ---- helpers --------------------------------------------------------------\n\nfunction el(tag: string, className: string, text?: string): HTMLElement {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = text;\n return node;\n}\n\nfunction formatBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;\n return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;\n}\n\nfunction cryptoId(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return (crypto as { randomUUID: () => string }).randomUUID().replace(/-/g, '');\n }\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\n\nexport function openPicker(client: UnionStackClient, opts: PickerOptions): PickerHandle {\n const picker = new Picker(client, opts);\n return {\n open: () => picker.open(),\n close: () => picker.close(),\n cancel: () => picker.cancel(),\n };\n}\n"]}
|