@autorender/sdk-core 0.1.4

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/index.cjs ADDED
@@ -0,0 +1,2799 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ createUploader: () => createUploader,
24
+ registerAutorenderUploaderElement: () => registerAutorenderUploaderElement
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+
28
+ // src/constants/defaults.ts
29
+ var DEFAULT_BASE_URL = "https://autorenderv3.vercel.app/api/public";
30
+ var DEFAULT_LABELS = {
31
+ title: "Upload files",
32
+ description: "Drag & drop files or click to browse.",
33
+ selectButton: "Upload files",
34
+ selectFolderButton: "Upload folder",
35
+ uploadButton: "Upload",
36
+ uploading: "Uploading...",
37
+ uploadComplete: "All files uploaded successfully.",
38
+ uploadFailed: "Unable to upload files. Please try again.",
39
+ retry: "Retry",
40
+ remove: "Remove",
41
+ addMore: "Add more",
42
+ done: "Done",
43
+ clear: "Clear",
44
+ cancel: "Cancel",
45
+ dropzoneTitle: "Drop files here",
46
+ duplicateFileExists: "The following file already exists in this folder:",
47
+ duplicateFilesExist: "The following files already exist in this folder:",
48
+ duplicateFileError: "File already exists in this folder.",
49
+ invalidApiKey: "Invalid API key. Please verify your credentials and try again."
50
+ };
51
+ var DEFAULT_ICONS = {
52
+ upload: `
53
+ <svg viewBox="0 0 24 24" aria-hidden="true">
54
+ <path d="M12 16a1 1 0 0 1-1-1V7.41l-2.3 2.3a1 1 0 1 1-1.4-1.42l4-4a1 1 0 0 1 1.4 0l4 4a1 1 0 1 1-1.4 1.42L13 7.41V15a1 1 0 0 1-1 1Z" />
55
+ <path d="M5 20a3 3 0 0 1-3-3v-1a1 1 0 0 1 2 0v1a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-1a1 1 0 1 1 2 0v1a3 3 0 0 1-3 3Z" />
56
+ </svg>
57
+ `,
58
+ success: `
59
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" aria-hidden="true">
60
+ <path d="M9.5 17a1 1 0 0 1-.7-.29l-3.5-3.5a1 1 0 1 1 1.4-1.42l2.8 2.79 7.1-7.09a1 1 0 0 1 1.4 1.42l-7.8 7.79a1 1 0 0 1-.7.3Z" fill="currentColor" />
61
+ </svg>
62
+ `,
63
+ error: `
64
+ <svg viewBox="0 0 24 24" aria-hidden="true" >
65
+ <path d="m12 13.41 3.29 3.3a1 1 0 0 0 1.42-1.42L13.41 12l3.3-3.29a1 1 0 0 0-1.42-1.42L12 10.59l-3.29-3.3a1 1 0 1 0-1.42 1.42L10.59 12l-3.3 3.29a1 1 0 0 0 1.42 1.42L12 13.41Z" />
66
+ </svg>
67
+ `,
68
+ remove: `
69
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true">
70
+ <path
71
+ d="M9 5.25C9 4.55964 9.55964 4 10.25 4H13.75C14.4404 4 15 4.55964 15 5.25V7H9V5.25Z"
72
+ stroke="currentColor"
73
+ stroke-width="1.5"
74
+ />
75
+ <path
76
+ d="M5 7H19"
77
+ stroke="currentColor"
78
+ stroke-width="1.5"
79
+ stroke-linecap="round"
80
+ />
81
+ <rect
82
+ x="6.5"
83
+ y="7"
84
+ width="11"
85
+ height="12.5"
86
+ rx="2"
87
+ stroke="currentColor"
88
+ stroke-width="1.5"
89
+ />
90
+ <path
91
+ d="M10 11.5V16"
92
+ stroke="currentColor"
93
+ stroke-width="1.5"
94
+ stroke-linecap="round"
95
+ />
96
+ <path
97
+ d="M14 11.5V16"
98
+ stroke="currentColor"
99
+ stroke-width="1.5"
100
+ stroke-linecap="round"
101
+ />
102
+ </svg>
103
+ `,
104
+ close: `
105
+ <svg viewBox="0 0 24 24" aria-hidden="true">
106
+ <path d="M13.41 12 18 7.41 16.59 6 12 10.59 7.41 6 6 7.41 10.59 12 6 16.59 7.41 18 12 13.41 16.59 18 18 16.59 13.41 12Z" />
107
+ </svg>
108
+ `
109
+ };
110
+ var DEFAULT_SOURCES = [
111
+ {
112
+ id: "device",
113
+ label: "From device",
114
+ kind: "device",
115
+ icon: `
116
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true">
117
+ <rect x="4" y="5" width="16" height="14" rx="2" stroke="currentColor" stroke-width="1.5" />
118
+ <path d="M9.5 4h5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
119
+ </svg>
120
+ `
121
+ },
122
+ {
123
+ id: "camera",
124
+ label: "Camera",
125
+ kind: "camera",
126
+ icon: `
127
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true">
128
+ <path d="M5 7h2l1-2h8l1 2h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
129
+ <circle cx="12" cy="13" r="3.25" stroke="currentColor" stroke-width="1.5" />
130
+ </svg>
131
+ `
132
+ },
133
+ {
134
+ id: "facebook",
135
+ label: "Facebook",
136
+ kind: "facebook",
137
+ icon: `
138
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true">
139
+ <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.5" />
140
+ <path d="M13 8h2V6h-2a3 3 0 0 0-3 3v1H8v2h2v6h2v-6h2v-2h-2V9a1 1 0 0 1 1-1Z" fill="currentColor" />
141
+ </svg>
142
+ `
143
+ },
144
+ {
145
+ id: "google-drive",
146
+ label: "Google Drive",
147
+ kind: "google-drive",
148
+ icon: `
149
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true">
150
+ <path d="M6.5 18.5h11l3-5-5-8h-6l-5 8 2 3" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
151
+ <path d="M8 13.5h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
152
+ </svg>
153
+ `
154
+ }
155
+ ];
156
+ var DEFAULT_THEME = {
157
+ appearance: "light",
158
+ accentColor: "#0C55F9",
159
+ borderRadius: 8,
160
+ dropzoneBackground: "var(--ar-color-muted)",
161
+ dropzoneBorder: "var(--ar-color-border)"
162
+ };
163
+
164
+ // src/client/api-client.ts
165
+ var AutorenderApiClient = class {
166
+ constructor(opts) {
167
+ this.authValidated = false;
168
+ if (!opts.apiKey) {
169
+ throw new Error("apiKey is required");
170
+ }
171
+ this.apiKey = opts.apiKey;
172
+ this.baseUrl = DEFAULT_BASE_URL.replace(/\/+$/, "");
173
+ }
174
+ async request(path, { method = "POST", body, signal } = {}) {
175
+ const url = `${this.baseUrl}${path}`;
176
+ const response = await fetch(url, {
177
+ method,
178
+ headers: {
179
+ "Content-Type": "application/json",
180
+ "x-api-key": this.apiKey
181
+ },
182
+ body: body ? JSON.stringify(body) : void 0,
183
+ signal
184
+ });
185
+ if (!response.ok) {
186
+ let errorMessage = `Request failed with status ${response.status}`;
187
+ let errorPayload;
188
+ try {
189
+ const data = await response.json();
190
+ errorPayload = data;
191
+ if (data?.error) {
192
+ errorMessage = data.error;
193
+ } else if (data?.message) {
194
+ errorMessage = data.message;
195
+ }
196
+ } catch {
197
+ }
198
+ const error = new Error(errorMessage);
199
+ error.status = response.status;
200
+ error.payload = errorPayload;
201
+ throw error;
202
+ }
203
+ if (response.status === 204) {
204
+ return void 0;
205
+ }
206
+ return await response.json();
207
+ }
208
+ async validateApiKey(force = false, signal) {
209
+ if (this.authValidated && !force) return;
210
+ await this.request("/auth/validate", { method: "GET", signal });
211
+ this.authValidated = true;
212
+ }
213
+ // Legacy method - kept for backward compatibility but not used by new upload flow
214
+ completeUpload(payload, signal) {
215
+ return this.request("/assets/complete", {
216
+ method: "POST",
217
+ body: payload,
218
+ signal
219
+ });
220
+ }
221
+ createFolder(payload, signal) {
222
+ return this.request(
223
+ "/assets/folder",
224
+ {
225
+ method: "POST",
226
+ body: payload,
227
+ signal
228
+ }
229
+ );
230
+ }
231
+ // New upload flow methods
232
+ uploadInit(payload, signal) {
233
+ return this.request("/assets/upload", {
234
+ method: "POST",
235
+ body: payload,
236
+ signal
237
+ });
238
+ }
239
+ /**
240
+ * Upload file to App Runner /ingest endpoint
241
+ * This is called directly to App Runner, not through our API
242
+ */
243
+ async ingest(file, uploadToken, uploadUrl, onProgress, signal) {
244
+ return new Promise((resolve, reject) => {
245
+ const xhr = new XMLHttpRequest();
246
+ if (signal) {
247
+ signal.addEventListener("abort", () => {
248
+ xhr.abort();
249
+ reject(new DOMException("Upload aborted", "AbortError"));
250
+ });
251
+ }
252
+ xhr.upload.onprogress = (event) => {
253
+ if (event.lengthComputable && onProgress) {
254
+ const progress = Math.round(event.loaded / event.total * 100);
255
+ onProgress(progress);
256
+ }
257
+ };
258
+ xhr.onerror = () => {
259
+ reject(new Error(`Upload failed: ${xhr.statusText}`));
260
+ };
261
+ xhr.onload = () => {
262
+ if (xhr.status >= 200 && xhr.status < 300) {
263
+ try {
264
+ const response = JSON.parse(xhr.responseText);
265
+ resolve(response);
266
+ } catch (error) {
267
+ reject(new Error("Failed to parse response"));
268
+ }
269
+ } else {
270
+ try {
271
+ const error = JSON.parse(xhr.responseText);
272
+ reject(new Error(error.message || `Upload failed: ${xhr.statusText}`));
273
+ } catch {
274
+ reject(new Error(`Upload failed: ${xhr.statusText}`));
275
+ }
276
+ }
277
+ };
278
+ xhr.open("POST", uploadUrl);
279
+ xhr.setRequestHeader("Authorization", `Bearer ${uploadToken}`);
280
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
281
+ xhr.send(file);
282
+ });
283
+ }
284
+ completeUploadNew(payload, signal) {
285
+ return this.request("/assets/complete", {
286
+ method: "POST",
287
+ body: payload,
288
+ signal
289
+ });
290
+ }
291
+ };
292
+
293
+ // src/utils/helpers.ts
294
+ function generateId() {
295
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
296
+ return crypto.randomUUID();
297
+ }
298
+ return "id-" + Math.random().toString(36).slice(2, 11);
299
+ }
300
+ function resolveIcon(renderer) {
301
+ if (typeof renderer === "function") {
302
+ return resolveIcon(renderer());
303
+ }
304
+ if (typeof renderer === "string") {
305
+ const template = document.createElement("template");
306
+ template.innerHTML = renderer.trim();
307
+ return template.content.firstElementChild;
308
+ }
309
+ return renderer ? renderer.cloneNode(true) : null;
310
+ }
311
+ function toCssVars(theme) {
312
+ const variables = {};
313
+ if (theme.accentColor) {
314
+ variables["--ar-accent"] = theme.accentColor;
315
+ }
316
+ if (theme.borderRadius !== void 0) {
317
+ variables["--ar-radius"] = `${theme.borderRadius}px`;
318
+ }
319
+ if (theme.fontFamily) {
320
+ variables["--ar-font-family"] = theme.fontFamily;
321
+ }
322
+ if (theme.dropzoneBackground) {
323
+ variables["--ar-dropzone-bg"] = theme.dropzoneBackground;
324
+ }
325
+ if (theme.dropzoneBorder) {
326
+ variables["--ar-dropzone-border"] = theme.dropzoneBorder;
327
+ }
328
+ return variables;
329
+ }
330
+ function applyCssVariables(element, variables) {
331
+ Object.entries(variables).forEach(([key, value]) => {
332
+ element.style.setProperty(key, value);
333
+ });
334
+ }
335
+ function getRelativeFolderPath(basePath, relativePath) {
336
+ if (!relativePath) return void 0;
337
+ const parts = relativePath.split("/").map((segment) => segment.trim()).filter((segment) => segment && segment !== ".");
338
+ if (parts.length <= 1) {
339
+ return void 0;
340
+ }
341
+ const folderSegments = parts.slice(0, -1);
342
+ if (basePath) {
343
+ const baseSegments = basePath.split("/").map((segment) => segment.trim()).filter((segment) => segment && segment !== ".");
344
+ let offset = 0;
345
+ while (offset < baseSegments.length && offset < folderSegments.length && folderSegments[offset] === baseSegments[offset]) {
346
+ offset += 1;
347
+ }
348
+ return offset < folderSegments.length ? folderSegments.slice(offset).join("/") : void 0;
349
+ }
350
+ return folderSegments.length ? folderSegments.join("/") : void 0;
351
+ }
352
+ function calculateBatchProgress(items) {
353
+ if (items.length === 0) return 0;
354
+ const total = items.reduce((acc, item) => acc + item.progress, 0);
355
+ return Math.round(total / items.length);
356
+ }
357
+
358
+ // src/core/uploader-controller.ts
359
+ var DEFAULT_PARALLEL_UPLOADS = 4;
360
+ var UploaderController = class extends EventTarget {
361
+ constructor(client, target, options) {
362
+ super();
363
+ this.items = [];
364
+ this.abortController = null;
365
+ this.isUploading = false;
366
+ this.duplicateConflicts = /* @__PURE__ */ new Set();
367
+ this.client = client;
368
+ this.targetElement = target;
369
+ this.options = options;
370
+ this.applyTheme();
371
+ }
372
+ isAbortError(error) {
373
+ if (!error) return false;
374
+ if (error instanceof DOMException && error.name === "AbortError") return true;
375
+ if (error instanceof Error && error.name === "AbortError") return true;
376
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
377
+ return typeof message === "string" && message.toLowerCase().includes("abort");
378
+ }
379
+ setOptions(options) {
380
+ this.options = {
381
+ ...this.options,
382
+ ...options
383
+ };
384
+ this.applyTheme();
385
+ this.dispatch("statechange");
386
+ }
387
+ getOptions() {
388
+ return this.options;
389
+ }
390
+ getState() {
391
+ return {
392
+ items: this.items,
393
+ isUploading: this.isUploading,
394
+ progress: calculateBatchProgress(this.items),
395
+ duplicates: Array.from(this.duplicateConflicts)
396
+ };
397
+ }
398
+ addFiles(files) {
399
+ const existingKeys = /* @__PURE__ */ new Map();
400
+ const basePathKey = this.normalizePath(this.options.folderPath) ?? "";
401
+ const buildKey = (file, folderPath, relativePath) => `${basePathKey}::${folderPath ?? ""}::${relativePath ?? ""}::${file.name}::${file.size}`;
402
+ this.items.forEach((item) => {
403
+ existingKeys.set(buildKey(item.file, item.folderPath, item.relativePath), item);
404
+ });
405
+ const newItems = files.map(({ file, relativePath }) => {
406
+ const folderPath = getRelativeFolderPath(
407
+ this.options.folderPath,
408
+ relativePath
409
+ );
410
+ const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
411
+ const baseItem = {
412
+ id: generateId(),
413
+ file,
414
+ relativePath,
415
+ folderPath,
416
+ status: "pending",
417
+ progress: 0,
418
+ previewUrl
419
+ };
420
+ const key = buildKey(file, folderPath, relativePath);
421
+ if (existingKeys.has(key)) {
422
+ baseItem.status = "completed";
423
+ baseItem.progress = 100;
424
+ baseItem.error = void 0;
425
+ existingKeys.set(key, baseItem);
426
+ return baseItem;
427
+ }
428
+ existingKeys.set(key, baseItem);
429
+ return baseItem;
430
+ });
431
+ this.items = [...this.items, ...newItems];
432
+ this.dispatch("filesadded", { detail: { items: newItems } });
433
+ const duplicates = newItems.filter((item) => item.status === "completed");
434
+ if (duplicates.length) {
435
+ this.markDuplicatesCompleted(duplicates);
436
+ }
437
+ this.dispatch("statechange");
438
+ }
439
+ removeFile(id) {
440
+ const item = this.items.find((entry) => entry.id === id);
441
+ if (item?.previewUrl) {
442
+ URL.revokeObjectURL(item.previewUrl);
443
+ }
444
+ this.items = this.items.filter((item2) => item2.id !== id);
445
+ if (item) {
446
+ this.duplicateConflicts.delete(item.file.name);
447
+ }
448
+ this.dispatch("statechange");
449
+ }
450
+ reset() {
451
+ this.abortController?.abort();
452
+ this.abortController = null;
453
+ this.isUploading = false;
454
+ this.cleanupPreviews();
455
+ this.items = [];
456
+ this.duplicateConflicts.clear();
457
+ this.dispatch("statechange");
458
+ }
459
+ async uploadAll() {
460
+ if (this.isUploading) return;
461
+ if (this.items.length === 0) return;
462
+ this.duplicateConflicts.clear();
463
+ this.isUploading = true;
464
+ this.abortController = new AbortController();
465
+ this.dispatch("statechange");
466
+ try {
467
+ await this.client.validateApiKey(false, this.abortController?.signal ?? void 0);
468
+ await this.processUploads();
469
+ this.dispatch("complete", {
470
+ detail: {
471
+ items: this.items,
472
+ files: this.items.filter((item) => item.status === "completed").map((item) => item.response).filter(Boolean)
473
+ }
474
+ });
475
+ } catch (error) {
476
+ if (this.isAbortError(error)) {
477
+ this.dispatch("error", { detail: error });
478
+ return;
479
+ }
480
+ this.annotateItemsWithError(error);
481
+ this.dispatch("error", { detail: error });
482
+ throw error;
483
+ } finally {
484
+ this.isUploading = false;
485
+ this.abortController = null;
486
+ this.dispatch("statechange");
487
+ }
488
+ }
489
+ cancel() {
490
+ this.abortController?.abort();
491
+ this.abortController = null;
492
+ }
493
+ isDuplicateError(error) {
494
+ if (!error) return false;
495
+ if (typeof error === "object" && error !== null) {
496
+ const status = error.status ?? error.statusCode;
497
+ if (status === 409) {
498
+ return true;
499
+ }
500
+ const code = error.code;
501
+ if (typeof code === "string" && code.toLowerCase() === "conflict") {
502
+ return true;
503
+ }
504
+ }
505
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
506
+ const normalized = message.toLowerCase();
507
+ return normalized.includes("duplicate") || normalized.includes("already exists") || normalized.includes("status 409") || normalized.includes("conflict 409") || normalized.includes("409");
508
+ }
509
+ markDuplicatesCompleted(items, duplicateNames, options = {}) {
510
+ const normalizedNames = duplicateNames?.map((name) => name.trim().toLowerCase()).filter(Boolean) ?? [];
511
+ const targetSet = normalizedNames.length > 0 ? new Set(normalizedNames) : null;
512
+ const targetItems = targetSet !== null ? items.filter(
513
+ (item) => targetSet.has(item.file.name.trim().toLowerCase())
514
+ ) : items;
515
+ const duplicates = targetSet !== null && targetItems.length === 0 ? items : targetItems;
516
+ const { annotateError = true } = options;
517
+ duplicates.forEach((item) => {
518
+ if (!item) return;
519
+ const name = item.file.name;
520
+ if (!this.duplicateConflicts.has(name)) {
521
+ this.duplicateConflicts.add(name);
522
+ }
523
+ item.status = "completed";
524
+ item.progress = 100;
525
+ item.error = annotateError ? "duplicate" : void 0;
526
+ this.dispatch("fileprogress", { detail: { item } });
527
+ });
528
+ if (duplicates.length > 0) {
529
+ this.dispatch("progress", {
530
+ detail: {
531
+ progress: calculateBatchProgress(this.items),
532
+ items: this.items
533
+ }
534
+ });
535
+ this.dispatch("statechange");
536
+ }
537
+ return duplicates;
538
+ }
539
+ isAuthError(error) {
540
+ if (!error || typeof error !== "object") {
541
+ const message2 = error instanceof Error ? error.message : typeof error === "string" ? error : "";
542
+ return typeof message2 === "string" && message2.toLowerCase().includes("unauthorized");
543
+ }
544
+ const status = error.status;
545
+ if (typeof status === "number" && (status === 401 || status === 403)) {
546
+ return true;
547
+ }
548
+ const message = error instanceof Error ? error.message : typeof error?.message === "string" ? error.message : "";
549
+ const normalized = String(message).toLowerCase();
550
+ return normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("invalid api key");
551
+ }
552
+ annotateItemsWithError(error) {
553
+ const isAuth = this.isAuthError(error);
554
+ const fallbackMessage = error instanceof Error ? error.message : typeof error?.message === "string" ? error.message : "Upload failed";
555
+ let mutated = false;
556
+ this.items.forEach((item) => {
557
+ if (item.status === "completed") return;
558
+ mutated = true;
559
+ item.status = "error";
560
+ item.progress = item.progress ?? 0;
561
+ item.error = isAuth ? "invalid-api-key" : fallbackMessage;
562
+ this.dispatch("fileprogress", { detail: { item } });
563
+ });
564
+ if (mutated) {
565
+ this.dispatch("progress", {
566
+ detail: {
567
+ progress: calculateBatchProgress(this.items),
568
+ items: this.items
569
+ }
570
+ });
571
+ this.dispatch("statechange");
572
+ }
573
+ }
574
+ async processUploads() {
575
+ const allItems = this.items.filter((item) => item.status !== "completed");
576
+ if (allItems.length === 0) {
577
+ return;
578
+ }
579
+ const parallel = Math.max(1, this.options.parallelUploads ?? DEFAULT_PARALLEL_UPLOADS);
580
+ const uploadSettings = this.options.uploadSettings;
581
+ const tasks = allItems.map((item) => async () => {
582
+ if (this.abortController?.signal.aborted) {
583
+ throw new Error("Upload cancelled");
584
+ }
585
+ try {
586
+ await this.uploadSingleFile(item, uploadSettings);
587
+ } catch (error) {
588
+ if (this.isAbortError(error)) {
589
+ return;
590
+ }
591
+ if (this.isDuplicateError(error)) {
592
+ this.markDuplicatesCompleted([item], [item.file.name], {
593
+ annotateError: true
594
+ });
595
+ } else {
596
+ const err = error instanceof Error ? error : new Error(String(error));
597
+ item.status = "error";
598
+ item.error = err.message;
599
+ this.dispatch("fileprogress", { detail: { item } });
600
+ this.dispatch("statechange");
601
+ }
602
+ }
603
+ });
604
+ await this.runWithConcurrency(tasks, parallel);
605
+ }
606
+ async uploadSingleFile(item, uploadSettings) {
607
+ item.status = "uploading";
608
+ item.progress = 0;
609
+ this.dispatch("statechange");
610
+ const folderPath = item.folderPath ? this.combinePaths(this.options.folderPath, item.folderPath) : this.normalizePath(this.options.folderPath);
611
+ const initResponse = await this.client.uploadInit(
612
+ {
613
+ fileName: item.file.name,
614
+ fileSize: item.file.size,
615
+ mimeType: item.file.type || "application/octet-stream",
616
+ path: folderPath,
617
+ settings: uploadSettings ? {
618
+ pretransformations: uploadSettings.pretransformations,
619
+ tags: uploadSettings.tags,
620
+ is_unique_suffix_name: uploadSettings.is_unique_suffix_name ?? false
621
+ } : void 0
622
+ },
623
+ this.abortController?.signal
624
+ );
625
+ item.progress = 10;
626
+ this.dispatch("fileprogress", { detail: { item } });
627
+ this.dispatch("statechange");
628
+ const appRunnerResponse = await this.client.ingest(
629
+ item.file,
630
+ initResponse.uploadToken,
631
+ initResponse.uploadUrl,
632
+ (progress) => {
633
+ item.progress = 10 + Math.round(progress * 0.8);
634
+ this.dispatch("fileprogress", { detail: { item } });
635
+ this.dispatch("progress", {
636
+ detail: {
637
+ progress: calculateBatchProgress(this.items),
638
+ items: this.items
639
+ }
640
+ });
641
+ },
642
+ this.abortController?.signal
643
+ );
644
+ if (!appRunnerResponse.ok) {
645
+ throw new Error(appRunnerResponse.message || "Upload failed");
646
+ }
647
+ item.progress = 90;
648
+ this.dispatch("fileprogress", { detail: { item } });
649
+ this.dispatch("statechange");
650
+ const completeResponse = await this.client.completeUploadNew(
651
+ {
652
+ uploadId: initResponse.uploadId,
653
+ appRunnerResponse
654
+ },
655
+ this.abortController?.signal
656
+ );
657
+ item.status = "completed";
658
+ item.progress = 100;
659
+ item.response = {
660
+ file_no: completeResponse.file_no,
661
+ name: completeResponse.name,
662
+ url: completeResponse.url,
663
+ file_size: completeResponse.file_size,
664
+ folder_no: void 0,
665
+ // Not available in new response
666
+ path: completeResponse.path,
667
+ width: completeResponse.width,
668
+ height: completeResponse.height,
669
+ format: completeResponse.format
670
+ };
671
+ item.uploadedAt = new Date(completeResponse.created_at);
672
+ if (completeResponse.isDuplicate) {
673
+ item.error = "duplicate";
674
+ }
675
+ this.dispatch("fileprogress", { detail: { item } });
676
+ this.dispatch("progress", {
677
+ detail: {
678
+ progress: calculateBatchProgress(this.items),
679
+ items: this.items
680
+ }
681
+ });
682
+ this.dispatch("statechange");
683
+ }
684
+ applyTheme() {
685
+ if (!this.options.theme) return;
686
+ const vars = toCssVars(this.options.theme);
687
+ applyCssVariables(this.targetElement, vars);
688
+ if (this.options.theme.appearance && this.options.theme.appearance !== "system") {
689
+ this.targetElement.dataset.theme = this.options.theme.appearance;
690
+ } else {
691
+ delete this.targetElement.dataset.theme;
692
+ }
693
+ }
694
+ dispatch(event, detail) {
695
+ const customEvent = new CustomEvent(event, { detail });
696
+ this.dispatchEvent(customEvent);
697
+ switch (event) {
698
+ case "progress":
699
+ this.options.onProgress?.(
700
+ detail?.progress ?? calculateBatchProgress(this.items),
701
+ this.items
702
+ );
703
+ break;
704
+ case "fileprogress":
705
+ this.options.onFileProgress?.(detail.item);
706
+ break;
707
+ case "error":
708
+ this.options.onError?.(
709
+ detail instanceof Error ? detail : new Error(String(detail))
710
+ );
711
+ break;
712
+ case "complete":
713
+ this.options.onSuccess?.({
714
+ files: detail?.files ?? [],
715
+ items: this.items
716
+ });
717
+ break;
718
+ default:
719
+ break;
720
+ }
721
+ }
722
+ async runWithConcurrency(tasks, limit) {
723
+ const executing = /* @__PURE__ */ new Set();
724
+ for (const task of tasks) {
725
+ if (this.abortController?.signal.aborted) {
726
+ throw new Error("Upload cancelled");
727
+ }
728
+ const promise = task().finally(() => {
729
+ executing.delete(promise);
730
+ });
731
+ executing.add(promise);
732
+ if (executing.size >= limit) {
733
+ await Promise.race(executing);
734
+ }
735
+ }
736
+ await Promise.all(executing);
737
+ }
738
+ cleanupPreviews() {
739
+ this.items.forEach((item) => {
740
+ if (item.previewUrl) {
741
+ URL.revokeObjectURL(item.previewUrl);
742
+ item.previewUrl = void 0;
743
+ }
744
+ });
745
+ }
746
+ normalizePath(path) {
747
+ if (!path) {
748
+ return void 0;
749
+ }
750
+ const normalized = path.split("/").map((segment) => segment.trim()).filter(Boolean).join("/");
751
+ return normalized.length > 0 ? normalized : void 0;
752
+ }
753
+ combinePaths(basePath, relativePath) {
754
+ const base = this.normalizePath(basePath);
755
+ const relative = this.normalizePath(relativePath);
756
+ if (base && relative) {
757
+ return `${base}/${relative}`;
758
+ }
759
+ return base || relative || void 0;
760
+ }
761
+ };
762
+
763
+ // src/widget/styles.ts
764
+ var widgetStyles = (
765
+ /* css */
766
+ `
767
+ :host {
768
+ --ar-font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
769
+ --ar-color-bg: hsl(0 0% 100%);
770
+ --ar-color-bg-muted: hsl(210 40% 96%);
771
+ --ar-color-border: hsl(214 32% 91%);
772
+ --ar-color-text: hsl(222 47% 11%);
773
+ --ar-color-muted-foreground: hsl(215 16% 47%);
774
+ --ar-color-accent: #0C55F9;
775
+ --ar-radius: 8px;
776
+ display: block;
777
+ }
778
+
779
+
780
+ :host([data-trigger='inline']) .ar-uploader-root,
781
+ :host([data-trigger='inline']) .ar-inline-upload,
782
+ :host([data-trigger='inline']) .ar-inline-panel {
783
+ width: 100%;
784
+ min-width: 0;
785
+ }
786
+
787
+ :host([data-theme='dark']) {
788
+ --ar-color-bg: hsl(222 41% 6%);
789
+ --ar-color-bg-muted: hsl(217 33% 17%);
790
+ --ar-color-border: hsl(217 19% 27%);
791
+ --ar-color-text: hsl(213 31% 91%);
792
+ --ar-color-muted-foreground: hsl(215 20% 65%);
793
+ --ar-color-accent: #4C8DFF;
794
+ }
795
+
796
+ .ar-uploader-root {
797
+ display: flex;
798
+ flex-direction: column;
799
+ gap: 1.5rem;
800
+ font-family: var(--ar-font-family);
801
+ background-color: var(--ar-color-bg);
802
+ color: var(--ar-color-text);
803
+ border: 1px solid var(--ar-color-border);
804
+ border-radius: var(--ar-radius);
805
+ padding: 0.5rem 1rem 1rem 1rem;
806
+ }
807
+
808
+ /* Reduce root gap when sources are empty in inline mode */
809
+ :host([data-trigger='inline']) .ar-uploader-root:has(.ar-inline-upload--no-sources) {
810
+ gap: 0.75rem;
811
+ }
812
+
813
+
814
+ :host([data-theme='dark']) .ar-uploader-root {
815
+ background-color: hsl(222 41% 6%);
816
+ }
817
+
818
+ .ar-uploader-root[data-trigger='button'] {
819
+ border: none;
820
+ padding: 0;
821
+ box-shadow: none;
822
+ background: transparent;
823
+ }
824
+
825
+ .ar-header {
826
+ display: none;
827
+ }
828
+
829
+ .ar-uploader-root.has-items .ar-header {
830
+ display: flex;
831
+ }
832
+
833
+ .ar-title {
834
+ margin: 0;
835
+ font-size: 1.25rem;
836
+ font-weight: 600;
837
+ }
838
+
839
+ .ar-description {
840
+ margin: 0;
841
+ color: var(--ar-color-muted-foreground);
842
+ font-size: 0.95rem;
843
+ }
844
+
845
+ .ar-subtext {
846
+ margin: 0;
847
+ font-size: 0.8rem;
848
+ color: var(--ar-color-muted-foreground);
849
+ }
850
+
851
+ .ar-button-trigger {
852
+ display: inline-flex;
853
+ align-items: center;
854
+ justify-content: center;
855
+ gap: 0.45rem;
856
+ padding: 0.55rem 1.25rem;
857
+ font-weight: 600;
858
+ border-radius: var(--ar-radius);
859
+ border: 1px solid rgba(15, 23, 42, 0.08);
860
+ background: rgba(248, 250, 252, 0.9);
861
+ color: var(--ar-color-text);
862
+ cursor: pointer;
863
+ transition: all 0.2s ease;
864
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
865
+ }
866
+
867
+ :host([data-theme='dark']) .ar-button-trigger {
868
+ border: 1px solid rgba(148, 163, 184, 0.25);
869
+ background: rgba(30, 41, 59, 0.75);
870
+ color: var(--ar-color-text);
871
+ }
872
+
873
+ .ar-button-trigger:hover {
874
+ transform: translateY(-1px);
875
+ }
876
+
877
+ .ar-button-trigger:active {
878
+ transform: translateY(0);
879
+ }
880
+
881
+ .ar-button-icon {
882
+ display: inline-flex;
883
+ align-items: center;
884
+ justify-content: center;
885
+ }
886
+
887
+ .ar-button-icon svg {
888
+ width: 1rem;
889
+ height: 1rem;
890
+ fill: currentColor;
891
+ }
892
+
893
+ .ar-inline-upload {
894
+ display: flex;
895
+ flex-direction: column;
896
+ gap: 1.1rem;
897
+ }
898
+
899
+ /* Reduce gap when sources are empty in inline mode */
900
+ :host([data-trigger='inline']) .ar-inline-upload--no-sources {
901
+ gap: 0.5rem;
902
+ }
903
+
904
+ .ar-inline-panel {
905
+ display: flex;
906
+ flex-direction: column;
907
+ }
908
+
909
+ .ar-dropzone {
910
+ position: relative;
911
+ border: 1.5px dashed var(--ar-dropzone-border, var(--ar-color-border));
912
+ border-radius: var(--ar-radius);
913
+ background: var(--ar-dropzone-bg, var(--ar-color-bg));
914
+ padding: 3rem 2rem;
915
+ display: flex;
916
+ flex-direction: column;
917
+ align-items: center;
918
+ text-align: center;
919
+ gap: 1rem;
920
+ cursor: pointer;
921
+ transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
922
+ margin: 0.6rem 0rem 1rem 0rem;
923
+ }
924
+
925
+ /* Reduce bottom margin when sources are empty in inline mode */
926
+ :host([data-trigger='inline']) .ar-modal-body--no-sources .ar-dropzone {
927
+ margin-bottom: 0.25rem;
928
+ }
929
+
930
+ .ar-dropzone:hover {
931
+ border-color: var(--ar-color-accent);
932
+ background-color: rgba(12, 85, 249, 0.07);
933
+ transform: translateY(-1px);
934
+ }
935
+
936
+ .ar-dropzone.is-dragover {
937
+ border-color: var(--ar-color-accent);
938
+ background-color: rgba(12, 85, 249, 0.1);
939
+ }
940
+
941
+ .ar-dropzone.is-dragging {
942
+ border-color: var(--ar-color-accent);
943
+ background: rgba(12, 85, 249, 0.08);
944
+ color: var(--ar-color-accent);
945
+ }
946
+
947
+ .ar-dropzone-icon {
948
+ width: 3rem;
949
+ height: 3rem;
950
+ border-radius: 999px;
951
+ background: rgba(12, 85, 249, 0.08);
952
+ color: var(--ar-color-accent);
953
+ display: flex;
954
+ align-items: center;
955
+ justify-content: center;
956
+ }
957
+
958
+ .ar-dropzone svg {
959
+ width: 1.75rem;
960
+ height: 1.75rem;
961
+ fill: currentColor;
962
+ }
963
+
964
+ .ar-dropzone-actions {
965
+ display: none;
966
+ }
967
+
968
+ .ar-file-list {
969
+ list-style: none;
970
+ margin: 0;
971
+ padding: 0;
972
+ display: flex;
973
+ flex-direction: column;
974
+ gap: 0.75rem;
975
+ margin-bottom: 1rem;
976
+ }
977
+
978
+ .ar-file-list.is-grid {
979
+ display: grid;
980
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
981
+ gap: 1rem;
982
+ }
983
+
984
+ .ar-file-item {
985
+ border: none;
986
+ border-radius: var(--ar-radius);
987
+ background: rgba(148, 163, 184, 0.15);
988
+ padding: 0.5rem;
989
+ display: flex;
990
+ flex-direction: column;
991
+ gap: 0.45rem;
992
+ position: relative;
993
+ box-sizing: border-box;
994
+ height: 50px;
995
+ }
996
+
997
+ .ar-file-item.is-grid {
998
+ height: auto;
999
+ padding: 0px;
1000
+ background: none;
1001
+ }
1002
+
1003
+ :host([data-theme='dark']) .ar-file-item {
1004
+ background: hsl(222 41% 10%);
1005
+ }
1006
+
1007
+ .ar-file-header {
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: 0.6rem;
1011
+ }
1012
+
1013
+ .ar-file-header.is-grid {
1014
+ flex-direction: column;
1015
+ align-items: stretch;
1016
+ }
1017
+
1018
+ .ar-file-thumb {
1019
+ width: 32px;
1020
+ height: 32px;
1021
+ border-radius: calc(var(--ar-radius) * 0.5);
1022
+ background: rgba(12, 85, 249, 0.1);
1023
+ color: var(--ar-color-accent);
1024
+ display: flex;
1025
+ align-items: center;
1026
+ justify-content: center;
1027
+ font-size: 0.8rem;
1028
+ font-weight: 600;
1029
+ overflow: hidden;
1030
+ position: relative;
1031
+ }
1032
+
1033
+ .ar-file-thumb.is-grid {
1034
+ width: 100%;
1035
+ height: 140px;
1036
+ border-radius: calc(var(--ar-radius) * 0.75);
1037
+ font-size: 1rem;
1038
+ }
1039
+
1040
+ .ar-file-item.is-completed .ar-file-thumb {
1041
+ color: #fff;
1042
+ }
1043
+
1044
+ .ar-thumb-status {
1045
+ position: absolute;
1046
+ bottom: -2px;
1047
+ right: -2px;
1048
+ width: 18px;
1049
+ height: 18px;
1050
+ border-radius: 999px;
1051
+ background: #1e9f6d;
1052
+ color: #fff;
1053
+ border: 2px solid var(--ar-color-bg);
1054
+ display: flex;
1055
+ align-items: center;
1056
+ justify-content: center;
1057
+ box-shadow: 0 2px 6px rgba(15, 23, 42, 0.25);
1058
+ }
1059
+
1060
+ .ar-thumb-status svg {
1061
+ width: 10px;
1062
+ height: 10px;
1063
+ color: currentColor;
1064
+ }
1065
+
1066
+ .ar-thumb-status.is-error {
1067
+ background: rgba(225, 29, 72, 0.85);
1068
+ }
1069
+
1070
+ .ar-file-name {
1071
+ font-size: 0.8rem;
1072
+ margin: 0;
1073
+ color: var(--ar-color-text);
1074
+ }
1075
+
1076
+ .ar-file-meta {
1077
+ display: flex;
1078
+ gap: 0.75rem;
1079
+ font-size: 0.8rem;
1080
+ color: var(--ar-color-muted-foreground);
1081
+ }
1082
+
1083
+ .ar-file-item.is-grid .ar-file-meta {
1084
+ justify-content: center;
1085
+ }
1086
+
1087
+ .ar-remove-button {
1088
+ margin-left: auto;
1089
+ border: none;
1090
+ background: transparent;
1091
+ color: var(--ar-color-muted-foreground);
1092
+ cursor: pointer;
1093
+ padding: 0.25rem;
1094
+ border-radius: var(--ar-radius);
1095
+ display: inline-flex;
1096
+ align-items: center;
1097
+ justify-content: center;
1098
+ transition: background 0.2s ease, color 0.2s ease;
1099
+ }
1100
+
1101
+ .ar-remove-button.is-grid {
1102
+ position: absolute;
1103
+ top: 0.6rem;
1104
+ right: 0.6rem;
1105
+ background: rgba(255, 255, 255, 0.95);
1106
+ color: var(--ar-color-text);
1107
+ padding: 0.35rem;
1108
+ border-radius: var(--ar-radius);
1109
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.25);
1110
+ opacity: 0;
1111
+ pointer-events: none;
1112
+ transition: opacity 0.15s ease, background 0.2s ease, color 0.2s ease;
1113
+ }
1114
+
1115
+ .ar-file-item.is-grid:hover .ar-remove-button.is-grid {
1116
+ opacity: 1;
1117
+ pointer-events: auto;
1118
+ }
1119
+
1120
+ :host([data-theme='dark']) .ar-remove-button.is-grid {
1121
+ background: rgba(15, 23, 42, 0.92);
1122
+ color: var(--ar-color-text);
1123
+ box-shadow: 0 4px 18px rgba(8, 47, 73, 0.45);
1124
+ }
1125
+
1126
+ .ar-remove-button svg {
1127
+ width: 20px;
1128
+ height: 20px;
1129
+ display: block;
1130
+ }
1131
+
1132
+ .ar-remove-button svg path,
1133
+ .ar-remove-button svg line,
1134
+ .ar-remove-button svg rect,
1135
+ .ar-remove-button svg circle,
1136
+ .ar-remove-button svg polyline {
1137
+ stroke: currentColor;
1138
+ fill: none;
1139
+ }
1140
+
1141
+ .ar-remove-button:hover:not([disabled]) {
1142
+ color: var(--ar-color-text);
1143
+ }
1144
+
1145
+ .ar-progress-track {
1146
+ width: 100%;
1147
+ height: 4px;
1148
+ border-radius: var(--ar-radius);
1149
+ background-color: rgba(148, 163, 184, 0.35);
1150
+ overflow: hidden;
1151
+ }
1152
+
1153
+ .ar-file-item.is-uploading {
1154
+ border-color: var(--ar-color-accent);
1155
+ }
1156
+
1157
+ .ar-file-item.is-error {
1158
+ border: 1px solid rgba(225, 29, 72, 0.4);
1159
+ background: rgba(248, 113, 113, 0.12);
1160
+ }
1161
+
1162
+ .ar-progress-border {
1163
+ opacity: 0;
1164
+ height: 2px;
1165
+ width: 96%;
1166
+ border-radius: 999px;
1167
+ background: rgba(59, 130, 246, 0.18);
1168
+ overflow: hidden;
1169
+ margin-top: 0.35rem;
1170
+ position: absolute;
1171
+ bottom: 0;
1172
+ left: 6px;
1173
+ right: 6px;
1174
+ }
1175
+
1176
+ .ar-progress-border.is-active,
1177
+ .ar-progress-border.is-complete {
1178
+ opacity: 1;
1179
+ }
1180
+
1181
+ .ar-progress-border.is-train {
1182
+ background: rgba(59, 130, 246, 0.18);
1183
+ }
1184
+
1185
+ .ar-progress-fill {
1186
+ height: 100%;
1187
+ width: 0%;
1188
+ border-radius: inherit;
1189
+ background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);
1190
+ transition: width 0.25s ease;
1191
+ transform-origin: left center;
1192
+ animation: none;
1193
+ }
1194
+
1195
+ .ar-progress-fill.is-train {
1196
+ background-image: linear-gradient(
1197
+ 90deg,
1198
+ rgba(245, 250, 241, 0.93) 0%,
1199
+ rgba(240, 242, 247, 0.85) 80%,
1200
+ rgb(30, 101, 212) 100%
1201
+ );
1202
+ background-size: 220% 100%;
1203
+ animation: ar-progress-train 2.4s linear infinite;
1204
+ }
1205
+
1206
+ .ar-progress-border.is-complete {
1207
+ background: rgba(34, 197, 94, 0.25);
1208
+ }
1209
+
1210
+ .ar-progress-border.is-complete .ar-progress-fill {
1211
+ background-image: linear-gradient(
1212
+ 90deg,
1213
+ rgba(52, 211, 153, 0.3) 0%,
1214
+ rgba(16, 185, 129, 0.85) 50%,
1215
+ rgba(52, 211, 153, 0.3) 100%
1216
+ );
1217
+ animation: none;
1218
+ }
1219
+
1220
+ @keyframes ar-progress-train {
1221
+ 0% {
1222
+ background-position: 240% 0;
1223
+ }
1224
+ 100% {
1225
+ background-position: -240% 0;
1226
+ }
1227
+ }
1228
+
1229
+ .ar-file-item.is-completed {
1230
+ border-color: rgba(30, 159, 109, 0.6);
1231
+ }
1232
+
1233
+ .ar-file-item.is-grid .ar-file-name {
1234
+ text-align: left;
1235
+ font-size: 0.78rem;
1236
+ color: var(--ar-color-muted-foreground);
1237
+ white-space: nowrap;
1238
+ overflow: hidden;
1239
+ text-overflow: ellipsis;
1240
+ }
1241
+
1242
+ .ar-status {
1243
+ display: inline-flex;
1244
+ align-items: center;
1245
+ gap: 0.35rem;
1246
+ font-size: 0.8rem;
1247
+ font-weight: 500;
1248
+ color: var(--ar-color-muted-foreground);
1249
+ }
1250
+
1251
+ .ar-status svg {
1252
+ width: 1rem;
1253
+ height: 1rem;
1254
+ }
1255
+
1256
+ .ar-status.is-success {
1257
+ color: #1e9f6d;
1258
+ }
1259
+
1260
+ .ar-status.is-error {
1261
+ color: #e11d48;
1262
+ }
1263
+
1264
+ .ar-status.is-warning {
1265
+ color: #d97706;
1266
+ }
1267
+
1268
+ .ar-file-error {
1269
+ font-size: 0.78rem;
1270
+ color: #e11d48;
1271
+ }
1272
+
1273
+ .ar-error-banner {
1274
+ display: none;
1275
+ margin: 0.5rem 0;
1276
+ color: #b91c1c;
1277
+ font-size: 0.82rem;
1278
+ font-weight: 600;
1279
+ line-height: 1.4;
1280
+ }
1281
+
1282
+ .ar-error-banner:not([hidden]) {
1283
+ display: block;
1284
+ }
1285
+
1286
+ :host([data-theme='dark']) .ar-error-banner {
1287
+ color: #fecaca;
1288
+ }
1289
+
1290
+ .ar-actions {
1291
+ display: flex;
1292
+ justify-content: flex-end;
1293
+ }
1294
+
1295
+ .ar-upload-button {
1296
+ border-radius: 10px;
1297
+ padding: 0.65rem 1.5rem;
1298
+ font-weight: 600;
1299
+ border: none;
1300
+ background: var(--ar-color-accent);
1301
+ color: white;
1302
+ cursor: pointer;
1303
+ transition: filter 0.2s ease, transform 0.2s ease;
1304
+ min-width: 140px;
1305
+ }
1306
+
1307
+ .ar-upload-button:hover:not([disabled]) {
1308
+ filter: brightness(1.05);
1309
+ transform: translateY(-1px);
1310
+ }
1311
+
1312
+ .ar-upload-button[disabled] {
1313
+ opacity: 0.55;
1314
+ cursor: not-allowed;
1315
+ }
1316
+
1317
+ .ar-footer {
1318
+ font-size: 0.78rem;
1319
+ color: var(--ar-color-muted-foreground);
1320
+ text-align: right;
1321
+ }
1322
+
1323
+ .ar-modal {
1324
+ position: fixed;
1325
+ inset: 0;
1326
+ display: none;
1327
+ align-items: center;
1328
+ justify-content: center;
1329
+ z-index: 1000;
1330
+ }
1331
+
1332
+ .ar-modal.is-open {
1333
+ display: flex;
1334
+ }
1335
+
1336
+ .ar-modal-backdrop {
1337
+ position: absolute;
1338
+ inset: 0;
1339
+ background: rgba(15, 23, 42, 0.45);
1340
+ }
1341
+
1342
+ .ar-modal-dialog {
1343
+ position: relative;
1344
+ background: var(--ar-color-bg);
1345
+ border-radius: 16px;
1346
+ border: 1px solid var(--ar-color-border);
1347
+ box-shadow: 0 32px 80px rgba(15, 23, 42, 0.25);
1348
+ width: min(460px, 92vw);
1349
+ max-height: 90vh;
1350
+ display: flex;
1351
+ flex-direction: column;
1352
+ padding: 0.5rem 1rem 1rem 1rem;
1353
+ }
1354
+
1355
+ .ar-modal-header {
1356
+ display: flex;
1357
+ align-items: center;
1358
+ justify-content: space-between;
1359
+ gap: 1rem;
1360
+ margin-bottom: 1rem;
1361
+ }
1362
+
1363
+ .ar-modal-header.is-inline {
1364
+ justify-content: flex-start;
1365
+ align-items: center;
1366
+ gap: 0.5rem;
1367
+ padding-top:0.5rem;
1368
+ }
1369
+
1370
+ .ar-modal-title {
1371
+ margin: 0;
1372
+ font-size: 1rem;
1373
+ font-weight: 500;
1374
+ }
1375
+
1376
+ .ar-modal-close {
1377
+ border: none;
1378
+ background: transparent;
1379
+ color: var(--ar-color-muted-foreground);
1380
+ cursor: pointer;
1381
+ border-radius: 999px;
1382
+ padding: 0.3rem;
1383
+ transition: color 0.2s ease, background 0.2s ease;
1384
+ }
1385
+
1386
+ .ar-modal-close:hover {
1387
+ background: rgba(148, 163, 184, 0.15);
1388
+ color: var(--ar-color-accent);
1389
+ }
1390
+
1391
+ .ar-modal-close-icon svg {
1392
+ width: 1.2rem;
1393
+ height: 1.2rem;
1394
+ }
1395
+
1396
+ .ar-modal-body {
1397
+ display: flex;
1398
+ flex-direction: column;
1399
+ gap: 1rem;
1400
+ overflow-y: auto;
1401
+ padding-right: 0.25rem;
1402
+ }
1403
+
1404
+ /* Reduce gap when source list is hidden in inline mode */
1405
+ :host([data-trigger='inline']) .ar-modal-body--no-sources {
1406
+ gap: 0.5rem;
1407
+ }
1408
+
1409
+ .ar-modal-footer {
1410
+ display: flex;
1411
+ align-items: center;
1412
+ justify-content: space-between;
1413
+ gap: 1rem;
1414
+ }
1415
+
1416
+ .ar-modal-footer.is-inline {
1417
+ padding: 0;
1418
+ border-top: none;
1419
+ }
1420
+
1421
+ .ar-modal-footer.is-inline .ar-modal-footer-actions {
1422
+ margin-left: auto;
1423
+ }
1424
+
1425
+ .ar-modal-footer-actions {
1426
+ display: flex;
1427
+ align-items: center;
1428
+ gap: 0.5rem;
1429
+ }
1430
+
1431
+ .ar-modal-add-more,
1432
+ .ar-modal-clear {
1433
+ border-radius: var(--ar-radius);
1434
+ padding: 0.55rem 1.25rem;
1435
+ font-weight: 600;
1436
+ border: 1px solid var(--ar-color-border);
1437
+ background: var(--ar-color-bg);
1438
+ color: var(--ar-color-text);
1439
+ cursor: pointer;
1440
+ transition: all 0.2s ease;
1441
+ }
1442
+
1443
+ .ar-modal-add-more:hover {
1444
+ border-color: var(--ar-color-accent);
1445
+ color: var(--ar-color-accent);
1446
+ }
1447
+
1448
+ .ar-modal-clear {
1449
+ background: rgba(148, 163, 184, 0.12);
1450
+ }
1451
+
1452
+ .ar-modal-clear[disabled] {
1453
+ opacity: 0.45;
1454
+ cursor: not-allowed;
1455
+ pointer-events: none;
1456
+ }
1457
+
1458
+ .ar-modal-clear:hover {
1459
+ border-color: var(--ar-color-accent);
1460
+ color: var(--ar-color-accent);
1461
+ }
1462
+
1463
+ .ar-modal-upload-button,
1464
+ .ar-modal-done {
1465
+ border-radius: var(--ar-radius);
1466
+ padding: 0.55rem 1.25rem;
1467
+ font-weight: 600;
1468
+ border: none;
1469
+ background: var(--ar-color-accent);
1470
+ color: white;
1471
+ cursor: pointer;
1472
+ transition: filter 0.2s ease;
1473
+ }
1474
+
1475
+ .ar-modal-upload-button:hover,
1476
+ .ar-modal-done:hover {
1477
+ filter: brightness(1.05);
1478
+ }
1479
+
1480
+ .ar-uploaded-summary {
1481
+ display: flex;
1482
+ flex-direction: column;
1483
+ gap: 0.85rem;
1484
+ border-top: 1px solid var(--ar-color-border);
1485
+ padding-top: 1rem;
1486
+ }
1487
+
1488
+ .ar-uploader-root[data-trigger='button'] .ar-uploaded-summary {
1489
+ border: 1px solid var(--ar-color-border);
1490
+ border-radius: var(--ar-radius);
1491
+ padding: 1rem;
1492
+ background: var(--ar-color-bg-muted);
1493
+ }
1494
+
1495
+ .ar-summary-title {
1496
+ margin: 0;
1497
+ font-size: 0.95rem;
1498
+ font-weight: 600;
1499
+ }
1500
+
1501
+ .ar-summary-list {
1502
+ list-style: none;
1503
+ margin: 0;
1504
+ padding: 0;
1505
+ display: flex;
1506
+ flex-direction: column;
1507
+ gap: 0.75rem;
1508
+ }
1509
+
1510
+ .ar-summary-item {
1511
+ display: flex;
1512
+ align-items: center;
1513
+ gap: 0.75rem;
1514
+ }
1515
+
1516
+ .ar-summary-icon {
1517
+ width: 38px;
1518
+ height: 38px;
1519
+ border-radius: 12px;
1520
+ background: rgba(12, 85, 249, 0.12);
1521
+ color: var(--ar-color-accent);
1522
+ display: flex;
1523
+ align-items: center;
1524
+ justify-content: center;
1525
+ font-weight: 600;
1526
+ }
1527
+
1528
+ .ar-summary-info {
1529
+ display: flex;
1530
+ flex-direction: column;
1531
+ gap: 0.2rem;
1532
+ }
1533
+
1534
+ .ar-summary-name {
1535
+ font-weight: 600;
1536
+ font-size: 0.95rem;
1537
+ }
1538
+
1539
+ .ar-summary-meta {
1540
+ font-size: 0.8rem;
1541
+ color: var(--ar-color-muted-foreground);
1542
+ }
1543
+
1544
+ .ar-uploader-root.is-uploading .ar-dropzone {
1545
+ display: none;
1546
+ }
1547
+
1548
+ .ar-uploader-root.has-items .ar-file-list {
1549
+ display: flex;
1550
+ }
1551
+
1552
+ .ar-file-list:empty {
1553
+ display: none;
1554
+ }
1555
+
1556
+ .ar-modal-upload {
1557
+ display: flex;
1558
+ flex-direction: column;
1559
+ }
1560
+
1561
+ .ar-modal-upload.is-inline {
1562
+ gap: 1rem;
1563
+ }
1564
+
1565
+ .ar-modal-body .ar-file-list {
1566
+ display: flex;
1567
+ flex-direction: column;
1568
+ gap: 0.5rem;
1569
+ max-height: 220px;
1570
+ overflow-y: auto;
1571
+ border: none;
1572
+ background: transparent;
1573
+ padding: 0;
1574
+ }
1575
+
1576
+ button,
1577
+ .ar-button-trigger,
1578
+ .ar-dropzone-button,
1579
+ .ar-upload-button,
1580
+ .ar-modal-footer button,
1581
+ .ar-actions button,
1582
+ .ar-remove-button,
1583
+ .ar-modal-header button {
1584
+ font-size: 1rem;
1585
+ font-weight: 400;
1586
+ }
1587
+
1588
+ @media (max-width: 640px) {
1589
+ .ar-uploader-root {
1590
+ padding: 1rem;
1591
+ }
1592
+
1593
+ .ar-dropzone {
1594
+ padding: 2.25rem 1.25rem;
1595
+ }
1596
+
1597
+ .ar-modal-dialog {
1598
+ width: min(90vw, 420px);
1599
+ padding: 1.25rem;
1600
+ }
1601
+ }
1602
+
1603
+ .ar-actions-sources,
1604
+ .ar-modal-sources {
1605
+ display: none;
1606
+ }
1607
+
1608
+ .ar-actions-sources.is-visible,
1609
+ .ar-modal-sources.is-visible {
1610
+ display: flex;
1611
+ gap: 0.5rem;
1612
+ align-items: center;
1613
+ }
1614
+
1615
+ .ar-source-list {
1616
+ display: none;
1617
+ flex-direction: column;
1618
+ margin: 1rem 0rem;
1619
+ }
1620
+
1621
+ /* Remove margin when source list is hidden in inline mode */
1622
+ :host([data-trigger='inline']) .ar-source-list:not(.ar-source-list--choices) {
1623
+ margin: 0;
1624
+ }
1625
+
1626
+ .ar-source-list--choices {
1627
+ display: flex;
1628
+ }
1629
+
1630
+ .ar-source-option {
1631
+ display: flex;
1632
+ align-items: center;
1633
+ gap: 0.65rem;
1634
+ width: 100%;
1635
+ border: none;
1636
+ border-radius: var(--ar-radius);
1637
+ background: transparent;
1638
+ padding: 0.55rem 0.75rem;
1639
+ text-align: left;
1640
+ color: var(--ar-color-text);
1641
+ cursor: pointer;
1642
+ transition: background-color 0.2s ease, color 0.2s ease;
1643
+ }
1644
+
1645
+ .ar-source-option.is-primary {
1646
+ background: rgba(12, 85, 249, 0.12);
1647
+ color: var(--ar-color-accent);
1648
+ }
1649
+
1650
+ .ar-source-option:hover:not([disabled]),
1651
+ .ar-source-option:focus-visible {
1652
+ background: rgba(12, 85, 249, 0.08);
1653
+ color: var(--ar-color-accent);
1654
+ }
1655
+
1656
+ .ar-source-option[disabled] {
1657
+ cursor: not-allowed;
1658
+ opacity: 0.6;
1659
+ }
1660
+
1661
+ .ar-source-option-icon {
1662
+ display: inline-flex;
1663
+ align-items: center;
1664
+ justify-content: center;
1665
+ width: 20px;
1666
+ height: 20px;
1667
+ color: var(--ar-color-muted-foreground);
1668
+ }
1669
+
1670
+ .ar-source-option-label {
1671
+ flex: 1;
1672
+ font-size: 0.95rem;
1673
+ }
1674
+
1675
+ .ar-source-inline {
1676
+ display: flex;
1677
+ gap: 0.5rem;
1678
+ align-items: center;
1679
+ }
1680
+
1681
+ .ar-source-inline-button {
1682
+ width: 36px;
1683
+ height: 36px;
1684
+ border-radius: 999px;
1685
+ border: 1px solid var(--ar-color-border);
1686
+ background: transparent;
1687
+ display: inline-flex;
1688
+ align-items: center;
1689
+ justify-content: center;
1690
+ color: var(--ar-color-text);
1691
+ transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
1692
+ }
1693
+
1694
+ .ar-source-inline-button:hover:not([disabled]) {
1695
+ background: rgba(12, 85, 249, 0.12);
1696
+ color: var(--ar-color-accent);
1697
+ border-color: rgba(12, 85, 249, 0.2);
1698
+ }
1699
+
1700
+ .ar-source-inline-button svg {
1701
+ width: 18px;
1702
+ height: 18px;
1703
+ display: block;
1704
+ }
1705
+
1706
+ .ar-modal-cancel {
1707
+ background: rgba(148, 163, 184, 0.15);
1708
+ border: none;
1709
+ color: var(--ar-color-text);
1710
+ padding:0.5rem 1rem;
1711
+ border-radius: var(--ar-radius);
1712
+ }
1713
+
1714
+ .ar-modal-cancel:hover:not([disabled]) {
1715
+ background: rgba(148, 163, 184, 0.25);
1716
+ }
1717
+
1718
+ .ar-modal-cancel.is-fullwidth {
1719
+ width: 100%;
1720
+ justify-content: center;
1721
+ border-radius: var(--ar-radius);
1722
+ cursor: pointer;
1723
+ }
1724
+
1725
+ .ar-modal-powered,
1726
+ .ar-inline-powered {
1727
+ margin-top: 12px;
1728
+ font-size: 0.75rem;
1729
+ color: var(--ar-color-muted-foreground);
1730
+ text-align: center;
1731
+ }
1732
+
1733
+ /* Reduce margin when sources are empty in inline mode */
1734
+ :host([data-trigger='inline']) .ar-inline-upload--no-sources .ar-inline-powered {
1735
+ margin-top: 0.5rem;
1736
+ }
1737
+
1738
+ .ar-duplicate-banner {
1739
+ display: none;
1740
+ margin: 0.75rem 0;
1741
+ padding: 0.75rem;
1742
+ border-radius: var(--ar-radius);
1743
+ background: rgba(248, 113, 113, 0.12);
1744
+ color: #9f1239;
1745
+ font-size: 0.85rem;
1746
+ line-height: 1.3;
1747
+ }
1748
+
1749
+ .ar-duplicate-banner[hidden] {
1750
+ display: none !important;
1751
+ }
1752
+
1753
+ `
1754
+ );
1755
+
1756
+ // src/widget/uploader-element.ts
1757
+ var AutorenderUploaderElement = class extends HTMLElement {
1758
+ constructor() {
1759
+ super();
1760
+ this.controller = null;
1761
+ this.labels = DEFAULT_LABELS;
1762
+ this.icons = DEFAULT_ICONS;
1763
+ this.classNames = {};
1764
+ this.sourceOptions = [];
1765
+ this.triggerType = "inline";
1766
+ this.isModalOpen = false;
1767
+ this.fileLayout = "list";
1768
+ this.showGridFileName = true;
1769
+ this.rootElement = null;
1770
+ this.dropzone = null;
1771
+ this.fileList = null;
1772
+ this.sourceLists = [];
1773
+ this.modalBody = null;
1774
+ this.inlineUpload = null;
1775
+ this.uploadButton = null;
1776
+ this.triggerButton = null;
1777
+ this.modalElement = null;
1778
+ this.modalCloseButton = null;
1779
+ this.modalDoneButton = null;
1780
+ this.modalClearButton = null;
1781
+ this.modalUploadButton = null;
1782
+ this.modalCancelButton = null;
1783
+ this.modalAddMoreButton = null;
1784
+ this.modalFooterActions = null;
1785
+ this.modalSourceContainer = null;
1786
+ this.actionsSourceContainer = null;
1787
+ this.modalTitleElement = null;
1788
+ this.modalHeaderElement = null;
1789
+ this.modalPoweredElement = null;
1790
+ this.inlinePoweredElement = null;
1791
+ this.duplicateBanners = [];
1792
+ this.errorBannerElement = null;
1793
+ this.globalErrorMessage = null;
1794
+ this.controllerListeners = /* @__PURE__ */ new Map();
1795
+ this.handleKeydown = (event) => {
1796
+ if (event.key === "Escape" && this.isModalOpen) {
1797
+ this.isModalOpen = false;
1798
+ this.render();
1799
+ }
1800
+ };
1801
+ this.shadow = this.attachShadow({ mode: "open" });
1802
+ }
1803
+ isAbortError(error) {
1804
+ if (!error) return false;
1805
+ if (error instanceof DOMException && error.name === "AbortError") return true;
1806
+ if (error instanceof Error && error.name === "AbortError") return true;
1807
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
1808
+ return typeof message === "string" && message.toLowerCase().includes("abort");
1809
+ }
1810
+ connectedCallback() {
1811
+ this.render();
1812
+ }
1813
+ disconnectedCallback() {
1814
+ this.detachController();
1815
+ window.removeEventListener("keydown", this.handleKeydown);
1816
+ }
1817
+ setController(controller, options) {
1818
+ this.detachController();
1819
+ this.controller = controller;
1820
+ this.globalErrorMessage = null;
1821
+ this.applyOptions(options);
1822
+ this.attachController();
1823
+ this.render();
1824
+ }
1825
+ updateOptions(options) {
1826
+ if (!this.controller) return;
1827
+ this.applyOptions({ ...this.options, ...options });
1828
+ this.controller.setOptions(this.options);
1829
+ this.render();
1830
+ }
1831
+ openFileDialog() {
1832
+ if (this.triggerType === "button" && !this.isModalOpen) {
1833
+ this.isModalOpen = true;
1834
+ this.render();
1835
+ requestAnimationFrame(() => this.fileInput?.click());
1836
+ return;
1837
+ }
1838
+ this.fileInput?.click();
1839
+ }
1840
+ applyOptions(options) {
1841
+ const previousOptions = this.options ?? {};
1842
+ const themeInput = options.theme ?? previousOptions.theme ?? DEFAULT_THEME;
1843
+ const resolvedTheme = {
1844
+ appearance: themeInput.appearance ?? DEFAULT_THEME.appearance,
1845
+ accentColor: themeInput.accentColor ?? DEFAULT_THEME.accentColor,
1846
+ borderRadius: themeInput.borderRadius ?? DEFAULT_THEME.borderRadius,
1847
+ dropzoneBackground: themeInput.dropzoneBackground ?? DEFAULT_THEME.dropzoneBackground,
1848
+ dropzoneBorder: themeInput.dropzoneBorder ?? DEFAULT_THEME.dropzoneBorder,
1849
+ fontFamily: themeInput.fontFamily ?? previousOptions.theme?.fontFamily
1850
+ };
1851
+ const resolvedSources = options.sources ?? previousOptions.sources ?? [];
1852
+ const previousSourceIds = this.sourceOptions?.map((s) => s.id).sort().join(",") ?? "";
1853
+ this.options = {
1854
+ ...previousOptions,
1855
+ ...options,
1856
+ theme: resolvedTheme,
1857
+ sources: resolvedSources
1858
+ };
1859
+ this.labels = {
1860
+ ...DEFAULT_LABELS,
1861
+ ...this.options.labels ?? {}
1862
+ };
1863
+ this.icons = DEFAULT_ICONS;
1864
+ const currentSourceIds = resolvedSources.map((s) => s.id).sort().join(",");
1865
+ const sourcesChanged = previousSourceIds !== currentSourceIds;
1866
+ this.sourceOptions = resolvedSources;
1867
+ this.options.onSourceSelect = options.onSourceSelect ?? previousOptions.onSourceSelect;
1868
+ const requestedType = this.options.type ?? "inline";
1869
+ this.triggerType = requestedType === "button" ? "button" : "inline";
1870
+ this.fileLayout = this.options.fileLayout ?? "list";
1871
+ this.showGridFileName = this.options.showGridFileName ?? true;
1872
+ this.classNames = this.options.classNames ?? {};
1873
+ if (this.options.autoUpload === void 0 && this.triggerType === "inline") {
1874
+ this.options.autoUpload = true;
1875
+ }
1876
+ if (this.rootElement && sourcesChanged) {
1877
+ const hasItems = this.controller?.getState().items.length > 0;
1878
+ this.renderSourceOptions(hasItems);
1879
+ }
1880
+ }
1881
+ attachController() {
1882
+ if (!this.controller) return;
1883
+ const stateListener = () => this.updateState();
1884
+ const progressListener = () => this.updateState();
1885
+ const filesAddedListener = (event) => {
1886
+ const detail = event.detail;
1887
+ if (this.options.autoUpload && detail?.items?.length) {
1888
+ void this.controller?.uploadAll();
1889
+ }
1890
+ };
1891
+ const completeListener = () => {
1892
+ this.clearGlobalError();
1893
+ this.render();
1894
+ };
1895
+ const errorListener = (event) => {
1896
+ const detail = event.detail;
1897
+ this.handleControllerError(detail);
1898
+ };
1899
+ this.controller.addEventListener("statechange", stateListener);
1900
+ this.controller.addEventListener("progress", progressListener);
1901
+ this.controller.addEventListener("fileprogress", progressListener);
1902
+ this.controller.addEventListener("filesadded", filesAddedListener);
1903
+ this.controller.addEventListener("complete", completeListener);
1904
+ this.controller.addEventListener("error", errorListener);
1905
+ this.controllerListeners.set("statechange", stateListener);
1906
+ this.controllerListeners.set("progress", progressListener);
1907
+ this.controllerListeners.set("fileprogress", progressListener);
1908
+ this.controllerListeners.set("filesadded", filesAddedListener);
1909
+ this.controllerListeners.set("complete", completeListener);
1910
+ this.controllerListeners.set("error", errorListener);
1911
+ }
1912
+ detachController() {
1913
+ if (!this.controller) return;
1914
+ for (const [event, handler] of this.controllerListeners.entries()) {
1915
+ this.controller.removeEventListener(event, handler);
1916
+ }
1917
+ this.controllerListeners.clear();
1918
+ this.controller = null;
1919
+ }
1920
+ resolveClassName(key, fallback) {
1921
+ const extra = this.classNames[key];
1922
+ return extra ? `${fallback} ${extra}` : fallback;
1923
+ }
1924
+ render() {
1925
+ if (!this.controller) return;
1926
+ this.setAttribute("data-trigger", this.triggerType);
1927
+ const duplicateBannerMarkup = `<div class="ar-duplicate-banner" hidden></div>`;
1928
+ const errorBannerMarkup = `<div class="ar-error-banner" role="alert" hidden></div>`;
1929
+ const dropzoneMarkup = `
1930
+ <div class="${this.resolveClassName("dropzone", "ar-dropzone")}" role="button" tabindex="0">
1931
+ <div class="ar-dropzone-icon"></div>
1932
+ <p class="ar-description">${this.labels.dropzoneTitle}</p>
1933
+ </div>
1934
+ `;
1935
+ const fileListMarkup = `<ul class="${this.resolveClassName("fileList", "ar-file-list")}"></ul>`;
1936
+ const sourceListMarkup = '<div class="ar-source-list"></div>';
1937
+ const modalHeaderMarkup = `
1938
+ <div class="ar-modal-header">
1939
+ <h3 class="ar-modal-title">${this.labels.title}</h3>
1940
+ <button type="button" class="ar-modal-close" aria-label="Close"><span class="ar-modal-close-icon"></span></button>
1941
+ </div>
1942
+ `;
1943
+ const inlineHeaderMarkup = `
1944
+ <div class="ar-modal-header is-inline">
1945
+ <h3 class="ar-modal-title">${this.labels.title}</h3>
1946
+ </div>
1947
+ `;
1948
+ const uploadPanelMarkup = `
1949
+ <div class="ar-modal-body">
1950
+ <div class="ar-modal-upload${this.triggerType === "inline" ? " is-inline" : ""}">
1951
+ ${dropzoneMarkup}
1952
+ ${errorBannerMarkup}
1953
+ ${duplicateBannerMarkup}
1954
+ ${sourceListMarkup}
1955
+ ${fileListMarkup}
1956
+ </div>
1957
+ </div>
1958
+ `;
1959
+ const footerMarkup = `
1960
+ <div class="ar-modal-footer${this.triggerType === "inline" ? " is-inline" : ""}">
1961
+ <button type="button" class="ar-modal-clear">${this.labels.clear}</button>
1962
+ <button type="button" class="ar-modal-cancel">${this.labels.cancel}</button>
1963
+ <div class="ar-modal-footer-actions">
1964
+ <div class="ar-modal-sources"></div>
1965
+ <button type="button" class="ar-modal-add-more">${this.labels.addMore}</button>
1966
+ <button type="button" class="ar-modal-upload-button">${this.labels.uploadButton}</button>
1967
+ <button type="button" class="ar-modal-done">${this.labels.done}</button>
1968
+ </div>
1969
+ </div>
1970
+ `;
1971
+ const modalMarkup = this.triggerType === "button" ? `
1972
+ <div class="${this.resolveClassName("modal", "ar-modal")} ${this.isModalOpen ? "is-open" : ""}">
1973
+ <div class="ar-modal-backdrop"></div>
1974
+ <div class="ar-modal-dialog">
1975
+ ${modalHeaderMarkup}
1976
+ ${uploadPanelMarkup}
1977
+ ${footerMarkup}
1978
+ <div class="ar-modal-powered">Powered by Autorender</div>
1979
+ </div>
1980
+ </div>
1981
+ ` : "";
1982
+ const inlineMarkup = this.triggerType === "inline" ? `
1983
+ <div class="ar-inline-upload">
1984
+ <div class="ar-inline-panel">
1985
+ ${inlineHeaderMarkup}
1986
+ ${uploadPanelMarkup}
1987
+ ${footerMarkup}
1988
+ </div>
1989
+ <div class="ar-inline-powered">Powered by Autorender</div>
1990
+ </div>
1991
+ ` : "";
1992
+ const template = document.createElement("template");
1993
+ template.innerHTML = `
1994
+ <style>${widgetStyles}</style>
1995
+ <div class="${this.resolveClassName("root", "ar-uploader-root")}" data-trigger="${this.triggerType}">
1996
+ <input type="file" class="ar-file-input" hidden />
1997
+ ${this.triggerType === "button" ? `<button type="button" class="${this.resolveClassName("triggerButton", "ar-button-trigger")}" ${this.isModalOpen ? 'style="display: none;"' : ""}><span class="ar-button-icon"></span><span>${this.labels.selectButton}</span></button>` : inlineMarkup}
1998
+ ${modalMarkup}
1999
+ </div>
2000
+ `;
2001
+ this.shadow.innerHTML = "";
2002
+ this.shadow.appendChild(template.content.cloneNode(true));
2003
+ this.captureElements();
2004
+ this.applyDimensions();
2005
+ this.configureInputs();
2006
+ this.bindEvents();
2007
+ this.applyThemeVariables();
2008
+ this.updateState();
2009
+ if (this.triggerType === "button") {
2010
+ if (this.isModalOpen) {
2011
+ window.addEventListener("keydown", this.handleKeydown);
2012
+ } else {
2013
+ window.removeEventListener("keydown", this.handleKeydown);
2014
+ }
2015
+ }
2016
+ }
2017
+ captureElements() {
2018
+ this.rootElement = this.shadow.querySelector(".ar-uploader-root");
2019
+ this.fileInput = this.shadow.querySelector(".ar-file-input");
2020
+ this.dropzone = this.shadow.querySelector(".ar-dropzone");
2021
+ this.fileList = this.shadow.querySelector(".ar-file-list");
2022
+ this.sourceLists = Array.from(this.shadow.querySelectorAll(".ar-source-list"));
2023
+ this.modalBody = this.shadow.querySelector(".ar-modal-body");
2024
+ this.inlineUpload = this.shadow.querySelector(".ar-inline-upload");
2025
+ this.uploadButton = this.shadow.querySelector(".ar-upload-button");
2026
+ this.triggerButton = this.shadow.querySelector(".ar-button-trigger");
2027
+ this.modalElement = this.shadow.querySelector(".ar-modal");
2028
+ this.modalCloseButton = this.shadow.querySelector(".ar-modal-close");
2029
+ this.modalDoneButton = this.shadow.querySelector(".ar-modal-done");
2030
+ this.modalClearButton = this.shadow.querySelector(".ar-modal-clear");
2031
+ this.modalUploadButton = this.shadow.querySelector(".ar-modal-upload-button");
2032
+ this.modalCancelButton = this.shadow.querySelector(".ar-modal-cancel");
2033
+ this.modalAddMoreButton = this.shadow.querySelector(".ar-modal-add-more");
2034
+ this.modalFooterActions = this.shadow.querySelector(".ar-modal-footer-actions");
2035
+ this.modalSourceContainer = this.shadow.querySelector(".ar-modal-sources");
2036
+ this.actionsSourceContainer = this.shadow.querySelector(".ar-actions-sources");
2037
+ this.modalTitleElement = this.shadow.querySelector(".ar-modal-title");
2038
+ this.modalHeaderElement = this.shadow.querySelector(".ar-modal-header");
2039
+ this.modalPoweredElement = this.shadow.querySelector(".ar-modal-powered");
2040
+ this.inlinePoweredElement = this.shadow.querySelector(".ar-inline-powered");
2041
+ this.duplicateBanners = Array.from(
2042
+ this.shadow.querySelectorAll(".ar-duplicate-banner")
2043
+ );
2044
+ this.errorBannerElement = this.shadow.querySelector(".ar-error-banner");
2045
+ this.updateErrorBanner();
2046
+ const icon = resolveIcon(this.icons.upload);
2047
+ const iconContainer = this.shadow.querySelector(".ar-dropzone-icon");
2048
+ if (icon && iconContainer) {
2049
+ iconContainer.innerHTML = "";
2050
+ iconContainer.appendChild(icon);
2051
+ }
2052
+ const buttonIcon = resolveIcon(this.icons.upload);
2053
+ const triggerIconContainer = this.shadow.querySelector(".ar-button-icon");
2054
+ if (buttonIcon && triggerIconContainer) {
2055
+ triggerIconContainer.innerHTML = "";
2056
+ triggerIconContainer.appendChild(buttonIcon);
2057
+ }
2058
+ const closeIcon = resolveIcon(this.icons.close);
2059
+ const closeIconContainer = this.shadow.querySelector(".ar-modal-close-icon");
2060
+ if (closeIcon && closeIconContainer) {
2061
+ closeIconContainer.innerHTML = "";
2062
+ closeIconContainer.appendChild(closeIcon);
2063
+ }
2064
+ }
2065
+ applyDimensions() {
2066
+ if (!this.rootElement) return;
2067
+ const requestedWidth = this.options.width;
2068
+ if (requestedWidth === void 0 || requestedWidth === null || requestedWidth === "") {
2069
+ if (this.triggerType === "inline") {
2070
+ this.rootElement.style.width = "100%";
2071
+ } else {
2072
+ this.rootElement.style.removeProperty("width");
2073
+ }
2074
+ return;
2075
+ }
2076
+ const normalizedWidth = typeof requestedWidth === "number" ? `${requestedWidth}px` : requestedWidth;
2077
+ this.rootElement.style.width = normalizedWidth;
2078
+ }
2079
+ configureInputs() {
2080
+ this.fileInput?.removeAttribute("webkitdirectory");
2081
+ this.fileInput?.removeAttribute("mozdirectory");
2082
+ this.fileInput?.removeAttribute("directory");
2083
+ if (this.options.allowMultiple === false) {
2084
+ this.fileInput?.removeAttribute("multiple");
2085
+ } else {
2086
+ this.fileInput?.setAttribute("multiple", "");
2087
+ }
2088
+ if (this.options.accept?.length) {
2089
+ this.fileInput.accept = this.options.accept.join(",");
2090
+ }
2091
+ }
2092
+ bindEvents() {
2093
+ this.triggerButton?.addEventListener("click", () => {
2094
+ this.isModalOpen = true;
2095
+ this.render();
2096
+ });
2097
+ this.modalCloseButton?.addEventListener("click", () => {
2098
+ this.isModalOpen = false;
2099
+ this.render();
2100
+ });
2101
+ this.modalElement?.addEventListener("click", (event) => {
2102
+ const target = event.target;
2103
+ if (!target) return;
2104
+ if (target === this.modalElement || target.classList.contains("ar-modal-backdrop")) {
2105
+ this.isModalOpen = false;
2106
+ this.render();
2107
+ }
2108
+ });
2109
+ this.modalUploadButton?.addEventListener("click", () => {
2110
+ if (!this.controller) return;
2111
+ if (this.controller.getState().isUploading) return;
2112
+ void this.controller.uploadAll();
2113
+ });
2114
+ this.modalDoneButton?.addEventListener("click", () => {
2115
+ if (!this.controller) return;
2116
+ const state = this.controller.getState();
2117
+ const allCompleted = state.items.length > 0 && state.items.every((item) => item.status === "completed");
2118
+ if (this.triggerType === "inline") {
2119
+ if (state.isUploading) {
2120
+ return;
2121
+ }
2122
+ if (allCompleted || state.items.length === 0) {
2123
+ this.controller.reset();
2124
+ this.updateState();
2125
+ }
2126
+ return;
2127
+ }
2128
+ if (state.isUploading) {
2129
+ this.closeModal();
2130
+ return;
2131
+ }
2132
+ if (allCompleted) {
2133
+ this.closeModal();
2134
+ return;
2135
+ }
2136
+ this.closeModal();
2137
+ });
2138
+ this.modalClearButton?.addEventListener("click", () => {
2139
+ if (!this.controller) return;
2140
+ const { isUploading } = this.controller.getState();
2141
+ if (isUploading) {
2142
+ return;
2143
+ }
2144
+ this.controller.reset();
2145
+ this.updateState();
2146
+ });
2147
+ this.modalCancelButton?.addEventListener("click", () => {
2148
+ if (!this.controller) return;
2149
+ const currentState = this.controller.getState();
2150
+ if (currentState.isUploading) return;
2151
+ this.controller.reset();
2152
+ if (this.triggerType === "inline") {
2153
+ this.updateState();
2154
+ return;
2155
+ }
2156
+ this.isModalOpen = false;
2157
+ this.render();
2158
+ });
2159
+ this.modalAddMoreButton?.addEventListener("click", () => {
2160
+ this.openFileDialog();
2161
+ });
2162
+ this.dropzone?.addEventListener("click", () => this.openFileDialog());
2163
+ this.dropzone?.addEventListener("dragenter", (event) => {
2164
+ event.preventDefault();
2165
+ this.dropzone?.classList.add("is-dragging");
2166
+ });
2167
+ this.dropzone?.addEventListener("dragover", (event) => {
2168
+ event.preventDefault();
2169
+ this.dropzone?.classList.add("is-dragging");
2170
+ if (event.dataTransfer) {
2171
+ event.dataTransfer.dropEffect = "copy";
2172
+ }
2173
+ });
2174
+ this.dropzone?.addEventListener("dragleave", (event) => {
2175
+ if (event.target === this.dropzone) {
2176
+ this.dropzone?.classList.remove("is-dragging");
2177
+ }
2178
+ });
2179
+ this.dropzone?.addEventListener("drop", async (event) => {
2180
+ event.preventDefault();
2181
+ this.dropzone?.classList.remove("is-dragging");
2182
+ const files = await this.extractFilesFromEvent(event);
2183
+ if (files.length) {
2184
+ this.controller?.addFiles(files);
2185
+ }
2186
+ if (this.fileInput) {
2187
+ this.fileInput.value = "";
2188
+ }
2189
+ });
2190
+ this.fileInput?.addEventListener("change", () => {
2191
+ const files = this.fileInput?.files ? Array.from(this.fileInput.files) : [];
2192
+ const mapped = files.map((file) => ({
2193
+ file,
2194
+ relativePath: file.webkitRelativePath
2195
+ }));
2196
+ this.controller?.addFiles(mapped);
2197
+ if (this.fileInput) this.fileInput.value = "";
2198
+ });
2199
+ this.uploadButton?.addEventListener("click", () => {
2200
+ if (this.controller?.getState().isUploading) return;
2201
+ void this.controller?.uploadAll();
2202
+ });
2203
+ }
2204
+ async extractFilesFromEvent(event) {
2205
+ const items = event.dataTransfer?.items;
2206
+ if (!items) {
2207
+ const files = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];
2208
+ return files.map((file) => ({
2209
+ file,
2210
+ relativePath: file.webkitRelativePath
2211
+ }));
2212
+ }
2213
+ const entries = Array.from(items).map((item) => item.webkitGetAsEntry ? item.webkitGetAsEntry() : null).filter(Boolean);
2214
+ const collected = [];
2215
+ for (const entry of entries) {
2216
+ const files = await this.readEntry(entry, "");
2217
+ collected.push(...files);
2218
+ }
2219
+ return collected;
2220
+ }
2221
+ readEntry(entry, path = "") {
2222
+ return new Promise((resolve) => {
2223
+ if (entry.isFile) {
2224
+ entry.file((file) => {
2225
+ resolve([{ file, relativePath: path ? `${path}/${file.name}` : file.name }]);
2226
+ });
2227
+ } else if (entry.isDirectory) {
2228
+ const reader = entry.createReader();
2229
+ reader.readEntries(async (entries) => {
2230
+ const results = await Promise.all(
2231
+ entries.map((child) => this.readEntry(child, path ? `${path}/${entry.name}` : entry.name))
2232
+ );
2233
+ resolve(results.flat());
2234
+ });
2235
+ } else {
2236
+ resolve([]);
2237
+ }
2238
+ });
2239
+ }
2240
+ closeModal() {
2241
+ this.isModalOpen = false;
2242
+ this.render();
2243
+ }
2244
+ setGlobalError(message) {
2245
+ this.globalErrorMessage = message;
2246
+ this.updateErrorBanner();
2247
+ }
2248
+ clearGlobalError() {
2249
+ this.setGlobalError(null);
2250
+ }
2251
+ updateErrorBanner() {
2252
+ if (!this.errorBannerElement) return;
2253
+ if (this.globalErrorMessage) {
2254
+ this.errorBannerElement.hidden = false;
2255
+ this.errorBannerElement.textContent = this.globalErrorMessage;
2256
+ } else {
2257
+ this.errorBannerElement.hidden = true;
2258
+ this.errorBannerElement.textContent = "";
2259
+ }
2260
+ }
2261
+ handleControllerError(error) {
2262
+ if (this.isAbortError(error)) {
2263
+ this.clearGlobalError();
2264
+ return;
2265
+ }
2266
+ const status = typeof error === "object" && error !== null && "status" in error ? error.status : void 0;
2267
+ const messageCandidate = error instanceof Error ? error.message : typeof error?.message === "string" ? error.message : typeof error === "string" ? error : "";
2268
+ const normalized = String(messageCandidate ?? "").toLowerCase();
2269
+ const isAuthError = typeof status === "number" && (status === 401 || status === 403) || normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("invalid api key");
2270
+ if (isAuthError) {
2271
+ this.setGlobalError(this.labels.invalidApiKey);
2272
+ } else if (messageCandidate) {
2273
+ this.setGlobalError(String(messageCandidate));
2274
+ } else {
2275
+ this.setGlobalError(this.labels.uploadFailed);
2276
+ }
2277
+ }
2278
+ updateState() {
2279
+ if (!this.controller) return;
2280
+ if (!this.fileList) return;
2281
+ const state = this.controller.getState();
2282
+ const { items, isUploading, duplicates } = state;
2283
+ const hasItems = items.length > 0;
2284
+ const hasAuthError = items.some((item) => item.error === "invalid-api-key");
2285
+ if (hasAuthError) {
2286
+ this.setGlobalError(this.labels.invalidApiKey);
2287
+ }
2288
+ if (this.rootElement) {
2289
+ this.rootElement.classList.toggle("is-uploading", isUploading);
2290
+ this.rootElement.classList.toggle("has-items", hasItems);
2291
+ }
2292
+ if (this.dropzone) {
2293
+ if (hasItems) {
2294
+ this.dropzone.style.display = "none";
2295
+ } else {
2296
+ this.dropzone.style.display = "flex";
2297
+ }
2298
+ }
2299
+ this.renderFileList(items);
2300
+ this.updateErrorBanner();
2301
+ this.renderSourceOptions(hasItems);
2302
+ this.updateModalTitle(items, isUploading);
2303
+ this.updateDuplicateBanners(duplicates);
2304
+ if (this.modalHeaderElement) {
2305
+ this.modalHeaderElement.style.display = hasItems ? "" : "none";
2306
+ }
2307
+ if (this.modalPoweredElement) {
2308
+ this.modalPoweredElement.style.display = hasItems ? "none" : "";
2309
+ }
2310
+ if (this.inlinePoweredElement) {
2311
+ this.inlinePoweredElement.style.display = hasItems ? "none" : "";
2312
+ }
2313
+ if (this.triggerType === "button" || this.triggerType === "inline") {
2314
+ const completedCount = items.filter((item) => item.status === "completed").length;
2315
+ const allCompleted = hasItems && completedCount === items.length && completedCount > 0;
2316
+ if (this.modalFooterActions) {
2317
+ this.modalFooterActions.style.display = hasItems ? "" : "none";
2318
+ }
2319
+ const uploadButton = this.modalUploadButton;
2320
+ const doneButton = this.modalDoneButton;
2321
+ const cancelButton = this.modalCancelButton;
2322
+ const clearButton = this.modalClearButton;
2323
+ const addMoreButton = this.modalAddMoreButton;
2324
+ const showUpload = hasItems && !isUploading && !allCompleted;
2325
+ const showAddMore = hasItems && !isUploading && !allCompleted;
2326
+ const showDone = hasItems && (isUploading || allCompleted);
2327
+ const showClear = hasItems && (isUploading || allCompleted);
2328
+ const showInlineCancel = this.triggerType === "inline" && hasItems && !isUploading && !allCompleted;
2329
+ const showButtonCancel = this.triggerType === "button" && !isUploading && (!allCompleted || !hasItems);
2330
+ const showCancel = showInlineCancel || showButtonCancel;
2331
+ if (uploadButton) {
2332
+ uploadButton.style.display = showUpload ? "" : "none";
2333
+ uploadButton.toggleAttribute("disabled", !showUpload);
2334
+ }
2335
+ if (doneButton) {
2336
+ doneButton.style.display = showDone ? "" : "none";
2337
+ doneButton.textContent = this.labels.done;
2338
+ doneButton.toggleAttribute("disabled", !showDone);
2339
+ }
2340
+ if (clearButton) {
2341
+ clearButton.style.display = showClear ? "" : "none";
2342
+ clearButton.toggleAttribute("disabled", isUploading);
2343
+ }
2344
+ if (cancelButton) {
2345
+ cancelButton.style.display = showCancel ? "" : "none";
2346
+ cancelButton.classList.toggle(
2347
+ "is-fullwidth",
2348
+ this.triggerType === "button" && !hasItems
2349
+ );
2350
+ }
2351
+ if (addMoreButton) {
2352
+ addMoreButton.style.display = showAddMore ? "" : "none";
2353
+ addMoreButton.toggleAttribute("disabled", !showAddMore);
2354
+ }
2355
+ }
2356
+ }
2357
+ renderFileList(items) {
2358
+ if (!this.fileList) return;
2359
+ this.fileList.innerHTML = "";
2360
+ if (items.length === 0) {
2361
+ this.fileList.style.display = "none";
2362
+ this.fileList.classList.remove("is-grid");
2363
+ return;
2364
+ }
2365
+ const isGridLayout = this.fileLayout === "grid";
2366
+ this.fileList.style.display = isGridLayout ? "grid" : "flex";
2367
+ this.fileList.classList.toggle("is-grid", isGridLayout);
2368
+ if (isGridLayout) {
2369
+ this.fileList.style.gridTemplateColumns = "repeat(auto-fill, minmax(140px, 1fr))";
2370
+ this.fileList.style.gap = "1rem";
2371
+ } else {
2372
+ this.fileList.style.removeProperty("grid-template-columns");
2373
+ this.fileList.style.removeProperty("gap");
2374
+ }
2375
+ const isUploading = this.controller?.getState().isUploading ?? false;
2376
+ items.forEach((item) => {
2377
+ const li = document.createElement("li");
2378
+ li.className = "ar-file-item";
2379
+ li.classList.toggle("is-grid", isGridLayout);
2380
+ const isInvalidApiKeyError = item.error === "invalid-api-key";
2381
+ if (item.status === "uploading" || item.status === "pending") li.classList.add("is-uploading");
2382
+ if (item.status === "completed") li.classList.add("is-completed");
2383
+ if (item.status === "error" && !isInvalidApiKeyError) li.classList.add("is-error");
2384
+ const header = document.createElement("div");
2385
+ header.className = "ar-file-header";
2386
+ header.classList.toggle("is-grid", isGridLayout);
2387
+ const thumb = document.createElement("div");
2388
+ thumb.className = "ar-file-thumb";
2389
+ thumb.classList.toggle("is-grid", isGridLayout);
2390
+ if (item.previewUrl) {
2391
+ const img = document.createElement("img");
2392
+ img.src = item.previewUrl;
2393
+ img.alt = item.file.name;
2394
+ thumb.appendChild(img);
2395
+ } else {
2396
+ thumb.textContent = item.file.name.charAt(0).toUpperCase();
2397
+ }
2398
+ if (item.status === "completed") {
2399
+ const statusOverlay = document.createElement("div");
2400
+ statusOverlay.className = "ar-thumb-status";
2401
+ const successIcon = resolveIcon(this.icons.success);
2402
+ if (successIcon) {
2403
+ statusOverlay.appendChild(successIcon);
2404
+ }
2405
+ thumb.appendChild(statusOverlay);
2406
+ } else if (item.status === "error" && !isInvalidApiKeyError) {
2407
+ const statusOverlay = document.createElement("div");
2408
+ statusOverlay.className = "ar-thumb-status is-error";
2409
+ const errorIcon = resolveIcon(this.icons.error);
2410
+ if (errorIcon) {
2411
+ statusOverlay.appendChild(errorIcon);
2412
+ }
2413
+ thumb.appendChild(statusOverlay);
2414
+ }
2415
+ const name = document.createElement("span");
2416
+ name.className = "ar-file-name";
2417
+ name.textContent = item.file.name;
2418
+ if (isGridLayout && !this.showGridFileName) {
2419
+ name.style.display = "none";
2420
+ } else {
2421
+ name.style.display = "";
2422
+ }
2423
+ const removeButton = document.createElement("button");
2424
+ removeButton.type = "button";
2425
+ removeButton.className = "ar-remove-button";
2426
+ const shouldShowRemove = (item.status === "pending" || item.status === "error" && isInvalidApiKeyError) && !isUploading;
2427
+ removeButton.disabled = !shouldShowRemove;
2428
+ removeButton.hidden = !shouldShowRemove;
2429
+ removeButton.style.display = shouldShowRemove ? "inline-flex" : "none";
2430
+ removeButton.classList.toggle("is-grid", isGridLayout);
2431
+ const removeIcon = resolveIcon(this.icons.remove);
2432
+ if (removeIcon) {
2433
+ removeButton.appendChild(removeIcon);
2434
+ }
2435
+ removeButton.addEventListener("click", () => {
2436
+ if (this.controller?.getState().isUploading) return;
2437
+ this.controller?.removeFile(item.id);
2438
+ });
2439
+ header.appendChild(thumb);
2440
+ header.appendChild(name);
2441
+ header.appendChild(removeButton);
2442
+ li.appendChild(header);
2443
+ const progressBorder = document.createElement("div");
2444
+ progressBorder.className = "ar-progress-border";
2445
+ const progressFill = document.createElement("div");
2446
+ progressFill.className = "ar-progress-fill";
2447
+ const isPendingStatus = item.status === "pending";
2448
+ const isUploadingStatus = item.status === "uploading";
2449
+ const isCompleteStatus = item.status === "completed";
2450
+ const rawProgress = Math.max(0, Math.min(100, item.progress ?? 0));
2451
+ const calculatedTrain = !isCompleteStatus && rawProgress < 20;
2452
+ const shouldAnimateTrain = (isUploadingStatus || isUploading && isPendingStatus) && calculatedTrain;
2453
+ const isActive = shouldAnimateTrain || rawProgress > 0 && !isCompleteStatus;
2454
+ const stateLabel = isCompleteStatus ? "complete" : shouldAnimateTrain ? "train" : rawProgress >= 20 ? "progress" : "idle";
2455
+ progressBorder.dataset.state = stateLabel;
2456
+ progressBorder.dataset.status = item.status;
2457
+ progressBorder.dataset.progress = rawProgress.toFixed(0);
2458
+ progressBorder.classList.toggle("is-active", isActive);
2459
+ progressBorder.classList.toggle("is-train", shouldAnimateTrain);
2460
+ progressBorder.classList.toggle("is-complete", isCompleteStatus);
2461
+ progressBorder.style.display = isCompleteStatus ? "none" : "";
2462
+ progressFill.classList.toggle("is-train", shouldAnimateTrain);
2463
+ progressFill.classList.toggle("is-animating", shouldAnimateTrain);
2464
+ if (isCompleteStatus) {
2465
+ progressFill.style.width = "100%";
2466
+ } else if (shouldAnimateTrain) {
2467
+ progressFill.style.width = "100%";
2468
+ } else {
2469
+ const computedWidth = Math.max(rawProgress, 3);
2470
+ progressFill.style.width = `${Math.min(computedWidth, 100)}%`;
2471
+ }
2472
+ progressBorder.appendChild(progressFill);
2473
+ const metaRow = document.createElement("div");
2474
+ metaRow.className = "ar-file-meta";
2475
+ if (item.status === "completed" && item.error === "duplicate") {
2476
+ const duplicateBadge = document.createElement("span");
2477
+ duplicateBadge.className = "ar-status is-warning";
2478
+ duplicateBadge.appendChild(document.createTextNode(this.labels.duplicateFileError));
2479
+ metaRow.appendChild(duplicateBadge);
2480
+ }
2481
+ li.appendChild(progressBorder);
2482
+ if (metaRow.childNodes.length > 0) {
2483
+ li.appendChild(metaRow);
2484
+ }
2485
+ this.fileList?.appendChild(li);
2486
+ });
2487
+ }
2488
+ renderSourceOptions(hasItems) {
2489
+ const enabledSources = this.sourceOptions.filter((option) => !option.disabled);
2490
+ const hasSourcesConfigured = enabledSources.length > 0;
2491
+ const renderChoiceList = (container) => {
2492
+ container.innerHTML = "";
2493
+ if (!hasSourcesConfigured) {
2494
+ container.style.display = "none";
2495
+ container.classList.remove("ar-source-list--choices");
2496
+ return;
2497
+ }
2498
+ container.style.display = hasItems ? "none" : "flex";
2499
+ container.classList.toggle("ar-source-list--choices", !hasItems);
2500
+ if (hasItems) return;
2501
+ enabledSources.forEach((option) => {
2502
+ const button = document.createElement("button");
2503
+ button.type = "button";
2504
+ button.className = "ar-source-option";
2505
+ const icon = resolveIcon(option.icon ?? this.icons.upload);
2506
+ if (icon) {
2507
+ const iconWrapper = document.createElement("span");
2508
+ iconWrapper.className = "ar-source-option-icon";
2509
+ iconWrapper.appendChild(icon);
2510
+ button.appendChild(iconWrapper);
2511
+ }
2512
+ const label = document.createElement("span");
2513
+ label.className = "ar-source-option-label";
2514
+ label.textContent = option.label;
2515
+ button.appendChild(label);
2516
+ button.addEventListener("click", () => this.handleSourceOption(option));
2517
+ container.appendChild(button);
2518
+ });
2519
+ };
2520
+ this.sourceLists.forEach((container) => {
2521
+ renderChoiceList(container);
2522
+ });
2523
+ if (this.modalSourceContainer) {
2524
+ this.modalSourceContainer.classList.toggle("is-visible", hasSourcesConfigured && !hasItems);
2525
+ this.modalSourceContainer.style.display = hasSourcesConfigured ? "" : "none";
2526
+ this.modalSourceContainer.innerHTML = "";
2527
+ }
2528
+ if (this.actionsSourceContainer) {
2529
+ this.actionsSourceContainer.classList.toggle("is-visible", hasSourcesConfigured && !hasItems);
2530
+ this.actionsSourceContainer.style.display = hasSourcesConfigured ? "" : "none";
2531
+ this.actionsSourceContainer.innerHTML = "";
2532
+ }
2533
+ if (this.triggerType === "inline") {
2534
+ if (this.modalBody) {
2535
+ this.modalBody.classList.toggle("ar-modal-body--no-sources", !hasSourcesConfigured);
2536
+ }
2537
+ if (this.inlineUpload) {
2538
+ this.inlineUpload.classList.toggle("ar-inline-upload--no-sources", !hasSourcesConfigured);
2539
+ }
2540
+ }
2541
+ }
2542
+ updateModalTitle(items, isUploading) {
2543
+ if (!this.modalTitleElement) return;
2544
+ if (items.length === 0) {
2545
+ this.modalTitleElement.textContent = isUploading ? this.labels.uploading : this.labels.title;
2546
+ this.modalTitleElement.classList.remove("is-dynamic", "is-success");
2547
+ return;
2548
+ }
2549
+ const total = items.length;
2550
+ const modalTitle = this.modalTitleElement;
2551
+ const suffix = total === 1 ? "file" : "files";
2552
+ const completedCount = items.filter((item) => item.status === "completed").length;
2553
+ modalTitle.classList.add("is-dynamic");
2554
+ modalTitle.classList.toggle("is-success", completedCount === total);
2555
+ if (isUploading) {
2556
+ modalTitle.textContent = `Uploading ${completedCount}/${total} ${suffix}`;
2557
+ return;
2558
+ }
2559
+ if (completedCount === total) {
2560
+ modalTitle.textContent = `${total} ${suffix} uploaded`;
2561
+ return;
2562
+ }
2563
+ modalTitle.textContent = `${total} ${suffix} ready`;
2564
+ }
2565
+ updateDuplicateBanners(duplicates) {
2566
+ const hasDuplicates = duplicates.length > 0;
2567
+ const prefix = duplicates.length === 1 ? this.labels.duplicateFileExists : this.labels.duplicateFilesExist;
2568
+ const message = hasDuplicates ? `${prefix} ${duplicates.join(", ")}` : "";
2569
+ this.duplicateBanners.forEach((banner) => {
2570
+ banner.hidden = !hasDuplicates;
2571
+ banner.textContent = message;
2572
+ });
2573
+ }
2574
+ handleSourceOption(option) {
2575
+ if (option.disabled) return;
2576
+ this.options.onSourceSelect?.(option);
2577
+ this.dispatchEvent(new CustomEvent("sourceselect", { detail: option }));
2578
+ if (option.kind === "device" && !option.action) {
2579
+ this.openFileDialog();
2580
+ return;
2581
+ }
2582
+ if (option.action) {
2583
+ try {
2584
+ option.action();
2585
+ } catch (error) {
2586
+ console.error("[AutorenderUploader] Source action failed", error);
2587
+ }
2588
+ return;
2589
+ }
2590
+ console.warn("[AutorenderUploader] No handler configured for source option", option);
2591
+ }
2592
+ applyThemeVariables() {
2593
+ const theme = this.options.theme ?? {};
2594
+ const borderRadius = theme.borderRadius;
2595
+ const accentColor = theme.accentColor ?? DEFAULT_THEME.accentColor;
2596
+ const dropzoneBackground = theme.dropzoneBackground ?? DEFAULT_THEME.dropzoneBackground;
2597
+ const dropzoneBorder = theme.dropzoneBorder ?? DEFAULT_THEME.dropzoneBorder;
2598
+ const fontFamily = theme.fontFamily;
2599
+ if (borderRadius !== void 0) {
2600
+ this.style.setProperty(
2601
+ "--ar-radius",
2602
+ typeof borderRadius === "number" ? `${borderRadius}px` : String(borderRadius)
2603
+ );
2604
+ } else {
2605
+ this.style.removeProperty("--ar-radius");
2606
+ }
2607
+ this.style.setProperty("--ar-color-accent", accentColor);
2608
+ if (dropzoneBackground) {
2609
+ this.style.setProperty("--ar-dropzone-bg", dropzoneBackground);
2610
+ }
2611
+ if (dropzoneBorder) {
2612
+ this.style.setProperty("--ar-dropzone-border", dropzoneBorder);
2613
+ }
2614
+ if (fontFamily) {
2615
+ this.style.setProperty("--ar-font-family", fontFamily);
2616
+ }
2617
+ const appearance = theme.appearance ?? DEFAULT_THEME.appearance;
2618
+ let resolvedAppearance = appearance;
2619
+ if (appearance === "system" && typeof window !== "undefined") {
2620
+ resolvedAppearance = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
2621
+ }
2622
+ this.setAttribute("data-theme", resolvedAppearance);
2623
+ }
2624
+ };
2625
+ AutorenderUploaderElement.tagName = "autorender-uploader";
2626
+
2627
+ // src/index.ts
2628
+ var SOURCE_ALIAS_MAP = {
2629
+ local: "device",
2630
+ device: "device",
2631
+ fromdevice: "device",
2632
+ camera: "camera",
2633
+ facebook: "facebook",
2634
+ fb: "facebook",
2635
+ insta: "facebook",
2636
+ instagram: "facebook",
2637
+ gdrive: "google-drive",
2638
+ googledrive: "google-drive",
2639
+ google: "google-drive",
2640
+ googledisk: "google-drive",
2641
+ "google-drive": "google-drive"
2642
+ };
2643
+ function ensureCustomElement() {
2644
+ if (!customElements.get(AutorenderUploaderElement.tagName)) {
2645
+ customElements.define(AutorenderUploaderElement.tagName, AutorenderUploaderElement);
2646
+ }
2647
+ }
2648
+ function resolveTarget(target) {
2649
+ if (typeof target === "string") {
2650
+ const element = document.querySelector(target);
2651
+ if (!element) {
2652
+ throw new Error(`Target element "${target}" not found`);
2653
+ }
2654
+ return element;
2655
+ }
2656
+ return target;
2657
+ }
2658
+ function resolveThemePreset(theme) {
2659
+ const base = {
2660
+ borderRadius: DEFAULT_THEME.borderRadius,
2661
+ dropzoneBackground: DEFAULT_THEME.dropzoneBackground,
2662
+ dropzoneBorder: DEFAULT_THEME.dropzoneBorder
2663
+ };
2664
+ switch (theme) {
2665
+ case "dark":
2666
+ return {
2667
+ appearance: "dark",
2668
+ accentColor: "#4C8DFF",
2669
+ ...base
2670
+ };
2671
+ case "system":
2672
+ return {
2673
+ appearance: "system",
2674
+ accentColor: DEFAULT_THEME.accentColor,
2675
+ ...base
2676
+ };
2677
+ default:
2678
+ return {
2679
+ appearance: "light",
2680
+ accentColor: DEFAULT_THEME.accentColor,
2681
+ ...base
2682
+ };
2683
+ }
2684
+ }
2685
+ function resolveSourcesConfig(input) {
2686
+ if (input === void 0 || input === null) {
2687
+ return [];
2688
+ }
2689
+ const provided = Array.isArray(input) ? input : input.split(",").map((entry) => entry.trim()).filter(Boolean);
2690
+ if (provided.length === 0) {
2691
+ return [];
2692
+ }
2693
+ const resolved = [];
2694
+ const seen = /* @__PURE__ */ new Set();
2695
+ for (const entry of provided) {
2696
+ const normalized = entry.toLowerCase().replace(/[\s_-]+/g, "");
2697
+ const mappedId = SOURCE_ALIAS_MAP[normalized] ?? normalized;
2698
+ if (seen.has(mappedId)) continue;
2699
+ const match = DEFAULT_SOURCES.find((option) => option.id === mappedId);
2700
+ if (match) {
2701
+ resolved.push(match);
2702
+ seen.add(mappedId);
2703
+ }
2704
+ }
2705
+ return resolved;
2706
+ }
2707
+ function normalizeOptions(options, applyDefaults = false) {
2708
+ const {
2709
+ type,
2710
+ theme,
2711
+ sources,
2712
+ classNames,
2713
+ labels,
2714
+ fileLayout,
2715
+ showGridFileName,
2716
+ apiKey: _apiKey,
2717
+ target: _target,
2718
+ ...rest
2719
+ } = options;
2720
+ const normalized = { ...rest };
2721
+ if (type !== void 0) {
2722
+ normalized.type = type === "button" ? "button" : "inline";
2723
+ } else if (applyDefaults) {
2724
+ normalized.type = "inline";
2725
+ }
2726
+ if (theme !== void 0) {
2727
+ normalized.theme = resolveThemePreset(theme);
2728
+ } else if (applyDefaults) {
2729
+ normalized.theme = resolveThemePreset("light");
2730
+ }
2731
+ if (sources !== void 0) {
2732
+ normalized.sources = resolveSourcesConfig(sources);
2733
+ } else if ("sources" in options) {
2734
+ normalized.sources = [];
2735
+ } else if (applyDefaults) {
2736
+ normalized.sources = [];
2737
+ }
2738
+ if (classNames !== void 0) {
2739
+ normalized.classNames = classNames;
2740
+ }
2741
+ if (labels !== void 0) {
2742
+ normalized.labels = labels;
2743
+ }
2744
+ if (fileLayout !== void 0) {
2745
+ normalized.fileLayout = fileLayout;
2746
+ } else if (applyDefaults) {
2747
+ normalized.fileLayout = "list";
2748
+ }
2749
+ if (showGridFileName !== void 0) {
2750
+ normalized.showGridFileName = showGridFileName;
2751
+ } else if (applyDefaults) {
2752
+ normalized.showGridFileName = true;
2753
+ }
2754
+ return normalized;
2755
+ }
2756
+ function createUploader(options) {
2757
+ const target = resolveTarget(options.target);
2758
+ ensureCustomElement();
2759
+ const widget = document.createElement(
2760
+ AutorenderUploaderElement.tagName
2761
+ );
2762
+ target.innerHTML = "";
2763
+ target.appendChild(widget);
2764
+ const client = new AutorenderApiClient({
2765
+ apiKey: options.apiKey
2766
+ });
2767
+ const controllerOptions = normalizeOptions(options, true);
2768
+ const controller = new UploaderController(
2769
+ client,
2770
+ widget,
2771
+ controllerOptions
2772
+ );
2773
+ widget.setController(controller, controllerOptions);
2774
+ return {
2775
+ openFileDialog: () => widget.openFileDialog(),
2776
+ startUpload: () => controller.uploadAll(),
2777
+ reset: () => controller.reset(),
2778
+ destroy: () => {
2779
+ controller.cancel();
2780
+ controller.reset();
2781
+ widget.remove();
2782
+ },
2783
+ updateOptions: (partialOptions) => {
2784
+ const normalized = normalizeOptions(partialOptions, false);
2785
+ controller.setOptions(normalized);
2786
+ widget.updateOptions(normalized);
2787
+ },
2788
+ getState: () => controller.getState()
2789
+ };
2790
+ }
2791
+ function registerAutorenderUploaderElement() {
2792
+ ensureCustomElement();
2793
+ }
2794
+ // Annotate the CommonJS export names for ESM import in node:
2795
+ 0 && (module.exports = {
2796
+ createUploader,
2797
+ registerAutorenderUploaderElement
2798
+ });
2799
+ //# sourceMappingURL=index.cjs.map