@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/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"]}