@unofficialbox/box-open-elements 0.5.0 → 0.6.0

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.
Files changed (219) hide show
  1. package/README.md +3 -3
  2. package/dist/components/collections/table.d.ts +57 -3
  3. package/dist/components/collections/table.js +339 -20
  4. package/dist/components/feedback/due-badge.d.ts +38 -0
  5. package/dist/components/feedback/due-badge.js +168 -0
  6. package/dist/components/feedback/due-types.d.ts +32 -0
  7. package/dist/components/feedback/due-types.js +91 -0
  8. package/dist/components/feedback/progress-steps.d.ts +51 -5
  9. package/dist/components/feedback/progress-steps.js +200 -22
  10. package/dist/components/feedback/stage-path.d.ts +44 -0
  11. package/dist/components/feedback/stage-path.js +235 -0
  12. package/dist/components/forms/select.d.ts +21 -0
  13. package/dist/components/forms/select.js +100 -3
  14. package/dist/components/forms/text-field.d.ts +21 -0
  15. package/dist/components/forms/text-field.js +100 -1
  16. package/dist/components/layout/nav-sidebar.d.ts +14 -0
  17. package/dist/components/layout/nav-sidebar.js +49 -0
  18. package/dist/components/layout/split-view.d.ts +14 -0
  19. package/dist/components/layout/split-view.js +80 -1
  20. package/dist/components/overlays/command-palette.d.ts +62 -0
  21. package/dist/components/overlays/command-palette.js +537 -0
  22. package/dist/components/overlays/command-types.d.ts +90 -0
  23. package/dist/components/overlays/command-types.js +259 -0
  24. package/dist/components/overlays/drawer.d.ts +18 -0
  25. package/dist/components/overlays/drawer.js +135 -9
  26. package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
  27. package/dist/components/overlays/shortcuts-overlay.js +385 -0
  28. package/dist/components/overlays/tooltip.js +1 -1
  29. package/dist/element-maps.d.ts +292 -0
  30. package/dist/element-maps.js +1 -0
  31. package/dist/entries/activity-density.d.ts +1 -0
  32. package/dist/entries/activity-density.js +1 -0
  33. package/dist/entries/agent-chat.d.ts +1 -0
  34. package/dist/entries/agent-chat.js +1 -0
  35. package/dist/entries/audit-log.d.ts +1 -0
  36. package/dist/entries/audit-log.js +1 -0
  37. package/dist/entries/command-palette.d.ts +1 -0
  38. package/dist/entries/command-palette.js +1 -0
  39. package/dist/entries/compare-view.d.ts +1 -0
  40. package/dist/entries/compare-view.js +1 -0
  41. package/dist/entries/diff-viewer.d.ts +1 -0
  42. package/dist/entries/diff-viewer.js +1 -0
  43. package/dist/entries/due-badge.d.ts +1 -0
  44. package/dist/entries/due-badge.js +1 -0
  45. package/dist/entries/form-wizard.d.ts +1 -0
  46. package/dist/entries/form-wizard.js +1 -0
  47. package/dist/entries/lineage-graph.d.ts +1 -0
  48. package/dist/entries/lineage-graph.js +1 -0
  49. package/dist/entries/notification-bell.d.ts +1 -0
  50. package/dist/entries/notification-bell.js +1 -0
  51. package/dist/entries/notification-inbox.d.ts +1 -0
  52. package/dist/entries/notification-inbox.js +1 -0
  53. package/dist/entries/provenance-strip.d.ts +1 -0
  54. package/dist/entries/provenance-strip.js +1 -0
  55. package/dist/entries/run-trace.d.ts +1 -0
  56. package/dist/entries/run-trace.js +1 -0
  57. package/dist/entries/shortcuts-overlay.d.ts +1 -0
  58. package/dist/entries/shortcuts-overlay.js +1 -0
  59. package/dist/entries/signature-ceremony.d.ts +1 -0
  60. package/dist/entries/signature-ceremony.js +1 -0
  61. package/dist/entries/stage-path.d.ts +1 -0
  62. package/dist/entries/stage-path.js +1 -0
  63. package/dist/entries/timeline.d.ts +1 -0
  64. package/dist/entries/timeline.js +1 -0
  65. package/dist/entries/version-graph.d.ts +1 -0
  66. package/dist/entries/version-graph.js +1 -0
  67. package/dist/entries/version-list.d.ts +1 -0
  68. package/dist/entries/version-list.js +1 -0
  69. package/dist/entries/wizard-summary.d.ts +1 -0
  70. package/dist/entries/wizard-summary.js +1 -0
  71. package/dist/entries/work-queue.d.ts +1 -0
  72. package/dist/entries/work-queue.js +1 -0
  73. package/dist/entries/workload-board.d.ts +1 -0
  74. package/dist/entries/workload-board.js +1 -0
  75. package/dist/index.d.ts +21 -0
  76. package/dist/index.js +21 -0
  77. package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
  78. package/dist/patterns/agent-chat/agent-chat.js +820 -0
  79. package/dist/patterns/agent-chat/controller.d.ts +27 -0
  80. package/dist/patterns/agent-chat/controller.js +182 -0
  81. package/dist/patterns/agent-chat/index.d.ts +3 -0
  82. package/dist/patterns/agent-chat/index.js +6 -0
  83. package/dist/patterns/agent-chat/types.d.ts +109 -0
  84. package/dist/patterns/agent-chat/types.js +1 -0
  85. package/dist/patterns/audit/activity-density.d.ts +68 -0
  86. package/dist/patterns/audit/activity-density.js +428 -0
  87. package/dist/patterns/audit/audit-log.d.ts +81 -0
  88. package/dist/patterns/audit/audit-log.js +794 -0
  89. package/dist/patterns/audit/index.d.ts +3 -0
  90. package/dist/patterns/audit/index.js +7 -0
  91. package/dist/patterns/audit/shared.d.ts +10 -0
  92. package/dist/patterns/audit/shared.js +50 -0
  93. package/dist/patterns/audit/types.d.ts +143 -0
  94. package/dist/patterns/audit/types.js +327 -0
  95. package/dist/patterns/content-explorer/actions/controller.js +12 -2
  96. package/dist/patterns/content-explorer/box-transport.js +117 -25
  97. package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
  98. package/dist/patterns/content-explorer/content-explorer.js +36 -3
  99. package/dist/patterns/content-explorer/contracts.d.ts +24 -1
  100. package/dist/patterns/content-explorer/contracts.js +68 -11
  101. package/dist/patterns/content-explorer/controller.d.ts +16 -1
  102. package/dist/patterns/content-explorer/controller.js +130 -4
  103. package/dist/patterns/content-explorer/navigation/controller.js +11 -9
  104. package/dist/patterns/content-explorer/types.d.ts +58 -1
  105. package/dist/patterns/content-picker/content-picker.d.ts +71 -0
  106. package/dist/patterns/content-picker/content-picker.js +747 -0
  107. package/dist/patterns/content-picker/controller.d.ts +38 -0
  108. package/dist/patterns/content-picker/controller.js +141 -0
  109. package/dist/patterns/content-picker/index.d.ts +3 -0
  110. package/dist/patterns/content-picker/index.js +6 -0
  111. package/dist/patterns/content-picker/types.d.ts +52 -0
  112. package/dist/patterns/content-picker/types.js +26 -0
  113. package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
  114. package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
  115. package/dist/patterns/content-sidebar/index.d.ts +2 -0
  116. package/dist/patterns/content-sidebar/index.js +4 -0
  117. package/dist/patterns/content-sidebar/types.d.ts +18 -0
  118. package/dist/patterns/content-sidebar/types.js +20 -0
  119. package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
  120. package/dist/patterns/content-uploader/box-transport.js +54 -0
  121. package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
  122. package/dist/patterns/content-uploader/content-uploader.js +559 -0
  123. package/dist/patterns/content-uploader/controller.d.ts +39 -0
  124. package/dist/patterns/content-uploader/controller.js +227 -0
  125. package/dist/patterns/content-uploader/index.d.ts +4 -0
  126. package/dist/patterns/content-uploader/index.js +8 -0
  127. package/dist/patterns/content-uploader/types.d.ts +120 -0
  128. package/dist/patterns/content-uploader/types.js +38 -0
  129. package/dist/patterns/diff/compare-view.d.ts +47 -0
  130. package/dist/patterns/diff/compare-view.js +276 -0
  131. package/dist/patterns/diff/diff-viewer.d.ts +42 -0
  132. package/dist/patterns/diff/diff-viewer.js +434 -0
  133. package/dist/patterns/diff/engine.d.ts +22 -0
  134. package/dist/patterns/diff/engine.js +276 -0
  135. package/dist/patterns/diff/index.d.ts +4 -0
  136. package/dist/patterns/diff/index.js +8 -0
  137. package/dist/patterns/diff/types.d.ts +69 -0
  138. package/dist/patterns/diff/types.js +24 -0
  139. package/dist/patterns/form-wizard/controller.d.ts +39 -0
  140. package/dist/patterns/form-wizard/controller.js +157 -0
  141. package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
  142. package/dist/patterns/form-wizard/form-wizard.js +468 -0
  143. package/dist/patterns/form-wizard/index.d.ts +4 -0
  144. package/dist/patterns/form-wizard/index.js +8 -0
  145. package/dist/patterns/form-wizard/types.d.ts +109 -0
  146. package/dist/patterns/form-wizard/types.js +83 -0
  147. package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
  148. package/dist/patterns/form-wizard/wizard-summary.js +324 -0
  149. package/dist/patterns/internal/safe-href.d.ts +17 -0
  150. package/dist/patterns/internal/safe-href.js +17 -0
  151. package/dist/patterns/lineage/index.d.ts +3 -0
  152. package/dist/patterns/lineage/index.js +6 -0
  153. package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
  154. package/dist/patterns/lineage/lineage-graph.js +434 -0
  155. package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
  156. package/dist/patterns/lineage/provenance-strip.js +194 -0
  157. package/dist/patterns/lineage/types.d.ts +53 -0
  158. package/dist/patterns/lineage/types.js +49 -0
  159. package/dist/patterns/notifications/index.d.ts +3 -0
  160. package/dist/patterns/notifications/index.js +6 -0
  161. package/dist/patterns/notifications/notification-bell.d.ts +38 -0
  162. package/dist/patterns/notifications/notification-bell.js +198 -0
  163. package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
  164. package/dist/patterns/notifications/notification-inbox.js +537 -0
  165. package/dist/patterns/notifications/types.d.ts +61 -0
  166. package/dist/patterns/notifications/types.js +108 -0
  167. package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
  168. package/dist/patterns/preview/annotation-inspector.js +239 -94
  169. package/dist/patterns/preview/annotation-thread.d.ts +25 -5
  170. package/dist/patterns/preview/annotation-thread.js +233 -82
  171. package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
  172. package/dist/patterns/preview/annotation-toolbar.js +162 -96
  173. package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
  174. package/dist/patterns/preview/content-preview-adapter.js +26 -1
  175. package/dist/patterns/preview/preview-element.d.ts +15 -2
  176. package/dist/patterns/preview/preview-element.js +223 -52
  177. package/dist/patterns/preview/provider-adapter.d.ts +47 -0
  178. package/dist/patterns/preview/provider-adapter.js +17 -0
  179. package/dist/patterns/run/index.d.ts +2 -0
  180. package/dist/patterns/run/index.js +4 -0
  181. package/dist/patterns/run/run-trace.d.ts +40 -0
  182. package/dist/patterns/run/run-trace.js +441 -0
  183. package/dist/patterns/run/types.d.ts +66 -0
  184. package/dist/patterns/run/types.js +152 -0
  185. package/dist/patterns/signature/index.d.ts +2 -0
  186. package/dist/patterns/signature/index.js +4 -0
  187. package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
  188. package/dist/patterns/signature/signature-ceremony.js +317 -0
  189. package/dist/patterns/signature/types.d.ts +68 -0
  190. package/dist/patterns/signature/types.js +89 -0
  191. package/dist/patterns/timeline/index.d.ts +2 -0
  192. package/dist/patterns/timeline/index.js +4 -0
  193. package/dist/patterns/timeline/timeline.d.ts +27 -0
  194. package/dist/patterns/timeline/timeline.js +430 -0
  195. package/dist/patterns/timeline/types.d.ts +36 -0
  196. package/dist/patterns/timeline/types.js +27 -0
  197. package/dist/patterns/versions/graph-layout.d.ts +43 -0
  198. package/dist/patterns/versions/graph-layout.js +159 -0
  199. package/dist/patterns/versions/index.d.ts +4 -0
  200. package/dist/patterns/versions/index.js +8 -0
  201. package/dist/patterns/versions/shared.d.ts +4 -0
  202. package/dist/patterns/versions/shared.js +13 -0
  203. package/dist/patterns/versions/types.d.ts +37 -0
  204. package/dist/patterns/versions/types.js +39 -0
  205. package/dist/patterns/versions/version-graph.d.ts +36 -0
  206. package/dist/patterns/versions/version-graph.js +438 -0
  207. package/dist/patterns/versions/version-list.d.ts +39 -0
  208. package/dist/patterns/versions/version-list.js +407 -0
  209. package/dist/patterns/work-queue/controller.d.ts +26 -0
  210. package/dist/patterns/work-queue/controller.js +150 -0
  211. package/dist/patterns/work-queue/index.d.ts +4 -0
  212. package/dist/patterns/work-queue/index.js +7 -0
  213. package/dist/patterns/work-queue/types.d.ts +128 -0
  214. package/dist/patterns/work-queue/types.js +45 -0
  215. package/dist/patterns/work-queue/work-queue.d.ts +48 -0
  216. package/dist/patterns/work-queue/work-queue.js +534 -0
  217. package/dist/patterns/work-queue/workload-board.d.ts +53 -0
  218. package/dist/patterns/work-queue/workload-board.js +608 -0
  219. package/package.json +89 -5
@@ -0,0 +1,54 @@
1
+ const DEFAULT_UPLOAD_BASE_URL = "https://upload.box.com/api/2.0";
2
+ const resolveFetch = (providedFetch) => {
3
+ if (providedFetch) {
4
+ return providedFetch;
5
+ }
6
+ if (typeof globalThis.fetch !== "function") {
7
+ throw new Error("A fetch implementation is required to use the Box upload transport");
8
+ }
9
+ return globalThis.fetch.bind(globalThis);
10
+ };
11
+ /**
12
+ * Multipart upload against `POST {uploadBase}/files/content` (the Box
13
+ * pre-flight-free path). fetch cannot observe upload progress, so
14
+ * `onProgress` fires once at completion; a chunked upload-session transport
15
+ * can implement the same `UploadTransport` contract with real fractions.
16
+ */
17
+ export const createBoxUploadTransport = (options = {}) => {
18
+ const uploadBaseUrl = (options.uploadBaseUrl ?? DEFAULT_UPLOAD_BASE_URL).replace(/\/$/, "");
19
+ const fetchImpl = resolveFetch(options.fetch);
20
+ return {
21
+ async uploadFile(request) {
22
+ const attributes = JSON.stringify({
23
+ name: request.fileName,
24
+ parent: { id: request.folderId },
25
+ });
26
+ const body = new FormData();
27
+ body.append("attributes", attributes);
28
+ body.append("file", request.file, request.fileName);
29
+ const response = await fetchImpl(`${uploadBaseUrl}/files/content`, {
30
+ method: "POST",
31
+ headers: {
32
+ Authorization: `Bearer ${request.token}`,
33
+ "Accept-Language": request.language ?? "en-US",
34
+ },
35
+ body,
36
+ ...(request.signal ? { signal: request.signal } : {}),
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error(`Box upload failed (${response.status})`);
40
+ }
41
+ const payload = (await response.json());
42
+ const entry = payload.entries?.[0];
43
+ if (entry?.id == null) {
44
+ throw new Error("Box upload response contained no file entry");
45
+ }
46
+ request.onProgress?.(1);
47
+ return {
48
+ fileId: String(entry.id),
49
+ ...(entry.name != null ? { name: entry.name } : {}),
50
+ ...(entry.size != null ? { size: entry.size } : {}),
51
+ };
52
+ },
53
+ };
54
+ };
@@ -0,0 +1,62 @@
1
+ import { ContentUploaderController } from "./controller.js";
2
+ import type { UploadFileLike, UploadQueueItem, UploadTransport, UploaderState } from "./types.js";
3
+ import { BaseElement } from "../../core/index.js";
4
+ export declare class ContentUploader extends BaseElement {
5
+ static readonly tagName: string;
6
+ static get observedAttributes(): string[];
7
+ private controller;
8
+ private pendingStart;
9
+ private unsubscribeFns;
10
+ private transportValue;
11
+ private dropZoneEl;
12
+ private queueEl;
13
+ private footerEl;
14
+ private summaryEl;
15
+ private queueSignature;
16
+ get folderId(): string | null;
17
+ set folderId(value: string | null);
18
+ get token(): string | null;
19
+ set token(value: string | null);
20
+ get language(): string | null;
21
+ set language(value: string | null);
22
+ get concurrency(): number | undefined;
23
+ set concurrency(value: number | undefined);
24
+ /** Uploads start as files are added unless `auto-start="false"`. */
25
+ get autoStart(): boolean;
26
+ set autoStart(value: boolean);
27
+ /** Comma-separated in markup, e.g. `extensions="pdf,docx"`. */
28
+ get extensions(): string[];
29
+ set extensions(value: string[]);
30
+ /** Maximum accepted file size in bytes. */
31
+ get maxFileSize(): number | undefined;
32
+ set maxFileSize(value: number | undefined);
33
+ get dropLabel(): string;
34
+ set dropLabel(value: string);
35
+ get dropMessage(): string;
36
+ set dropMessage(value: string);
37
+ get transport(): UploadTransport | null;
38
+ set transport(value: UploadTransport | null);
39
+ get state(): Readonly<UploaderState> | null;
40
+ /** The live queue controller. Null until configured. */
41
+ get uploaderController(): ContentUploaderController | null;
42
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
43
+ connectedCallback(): void;
44
+ disconnectedCallback(): void;
45
+ addFiles(files: UploadFileLike[]): UploadQueueItem[];
46
+ start(): void;
47
+ cancelItem(itemId: string): void;
48
+ retryItem(itemId: string): void;
49
+ removeItem(itemId: string): void;
50
+ clearCompleted(): void;
51
+ private updateStringAttribute;
52
+ private scheduleStart;
53
+ private startController;
54
+ private readConfig;
55
+ private subscribeToController;
56
+ private teardownController;
57
+ protected renderTemplate(): void;
58
+ protected setupListeners(): void;
59
+ private rebuildQueue;
60
+ private patchQueueProgress;
61
+ protected update(): void;
62
+ }
@@ -0,0 +1,559 @@
1
+ import { ContentUploaderController } from "./controller.js";
2
+ import { summarizeUploadQueue } from "./types.js";
3
+ import { formatItemSize } from "../content-explorer/adapters/item-summary.js";
4
+ import { DropZone } from "../../components/files/drop-zone.js";
5
+ import { ProgressBar } from "../../components/feedback/progress-bar.js";
6
+ import { BaseElement } from "../../core/index.js";
7
+ import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
8
+ import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
9
+ const DEFAULT_TAG_NAME = "box-content-uploader";
10
+ const escapeHtml = (value) => value
11
+ .replaceAll("&", "&amp;")
12
+ .replaceAll("<", "&lt;")
13
+ .replaceAll(">", "&gt;")
14
+ .replaceAll('"', "&quot;")
15
+ .replaceAll("'", "&#39;");
16
+ const readPositiveNumber = (value) => {
17
+ if (!value) {
18
+ return undefined;
19
+ }
20
+ const parsed = Number.parseInt(value, 10);
21
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
22
+ };
23
+ const readTokenList = (value) => value
24
+ ? value
25
+ .split(",")
26
+ .map(entry => entry.trim())
27
+ .filter(Boolean)
28
+ : [];
29
+ const STATUS_LABELS = {
30
+ queued: "Queued",
31
+ uploading: "Uploading",
32
+ succeeded: "Uploaded",
33
+ failed: "Failed",
34
+ cancelled: "Cancelled",
35
+ };
36
+ /** Actions rendered for a row, by status. */
37
+ const rowActions = (status) => {
38
+ switch (status) {
39
+ case "queued":
40
+ case "uploading":
41
+ return [{ action: "cancel", label: "Cancel" }];
42
+ case "failed":
43
+ case "cancelled":
44
+ return [
45
+ { action: "retry", label: "Retry" },
46
+ { action: "remove", label: "Remove" },
47
+ ];
48
+ case "succeeded":
49
+ return [{ action: "remove", label: "Remove" }];
50
+ default: {
51
+ const _exhaustive = status;
52
+ return _exhaustive;
53
+ }
54
+ }
55
+ };
56
+ const elementStyles = `
57
+ :host {
58
+ display: block;
59
+ color: inherit;
60
+ font: inherit;
61
+ }
62
+
63
+ [part="panel"] {
64
+ display: grid;
65
+ gap: ${boePanel.gap};
66
+ padding: ${boePanel.padding};
67
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
68
+ border-radius: ${boePanel.radius};
69
+ background: var(--boe-token-surface-surface, #ffffff);
70
+ }
71
+
72
+ [part="queue"] {
73
+ list-style: none;
74
+ margin: 0;
75
+ padding: 0;
76
+ display: grid;
77
+ gap: 0.45rem;
78
+ }
79
+
80
+ [part="queue"]:empty {
81
+ display: none;
82
+ }
83
+
84
+ [part="row"] {
85
+ display: grid;
86
+ gap: 0.35rem;
87
+ padding: 0.55rem 0.6rem;
88
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 52%, transparent);
89
+ border-radius: ${boeRadius.large};
90
+ background: color-mix(in srgb, var(--boe-token-surface-surface, #ffffff) 82%, transparent);
91
+ transition: border-color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
92
+ }
93
+
94
+ [part="row"][data-status="failed"] {
95
+ border-color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 40%, transparent);
96
+ }
97
+
98
+ [part="row-header"] {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 0.5rem;
102
+ }
103
+
104
+ [part="row-name"] {
105
+ flex: 1;
106
+ overflow: hidden;
107
+ text-overflow: ellipsis;
108
+ white-space: nowrap;
109
+ font-weight: 600;
110
+ }
111
+
112
+ [part="row-size"] {
113
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
114
+ font-size: 0.8rem;
115
+ }
116
+
117
+ [part="row-status"] {
118
+ display: inline-flex;
119
+ padding: 0.18rem 0.5rem;
120
+ border-radius: 999px;
121
+ background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
122
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
123
+ font-size: 0.75rem;
124
+ font-weight: 600;
125
+ }
126
+
127
+ [part="row"][data-status="succeeded"] [part="row-status"] {
128
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 14%, transparent);
129
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 78%, black 22%);
130
+ }
131
+
132
+ [part="row"][data-status="failed"] [part="row-status"] {
133
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 12%, transparent);
134
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 74%, black 26%);
135
+ }
136
+
137
+ [part="row-error"] {
138
+ margin: 0;
139
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 74%, black 26%);
140
+ font-size: 0.8rem;
141
+ }
142
+
143
+ [part="row-actions"] {
144
+ display: flex;
145
+ gap: 0.4rem;
146
+ }
147
+
148
+ [part="row-action"] {
149
+ appearance: none;
150
+ font: inherit;
151
+ font-size: 0.8rem;
152
+ font-weight: 600;
153
+ padding: 0.3rem 0.65rem;
154
+ border-radius: 999px;
155
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 60%, transparent);
156
+ background: var(--boe-token-surface-surface, #ffffff);
157
+ color: inherit;
158
+ cursor: pointer;
159
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, border-color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
160
+ }
161
+
162
+ [part="row-action"]:hover {
163
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
164
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
165
+ }
166
+
167
+ [part="row-action"]:focus-visible {
168
+ outline: none;
169
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
170
+ }
171
+
172
+ [part="footer"] {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: ${boePanel.gap};
176
+ }
177
+
178
+ [part="footer"][hidden] {
179
+ display: none;
180
+ }
181
+
182
+ [part="summary"] {
183
+ flex: 1;
184
+ font-size: 0.85rem;
185
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
186
+ }
187
+
188
+ [part="clear-completed"] {
189
+ appearance: none;
190
+ font: inherit;
191
+ font-size: 0.875rem;
192
+ font-weight: 600;
193
+ padding: 0.4rem 0.7rem;
194
+ border-radius: ${boeRadius.control};
195
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
196
+ background: var(--boe-token-surface-surface, #ffffff);
197
+ color: var(--boe-token-text-text, #222222);
198
+ cursor: pointer;
199
+ }
200
+
201
+ [part="clear-completed"]:hover {
202
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
203
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
204
+ }
205
+
206
+ [part="clear-completed"]:focus-visible {
207
+ outline: none;
208
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
209
+ }
210
+ `;
211
+ export class ContentUploader extends BaseElement {
212
+ static tagName = DEFAULT_TAG_NAME;
213
+ static get observedAttributes() {
214
+ return [
215
+ "auto-start",
216
+ "concurrency",
217
+ "drop-label",
218
+ "drop-message",
219
+ "extensions",
220
+ "folder-id",
221
+ "language",
222
+ "max-file-size",
223
+ "token",
224
+ ];
225
+ }
226
+ controller = null;
227
+ pendingStart = false;
228
+ unsubscribeFns = [];
229
+ transportValue = null;
230
+ dropZoneEl;
231
+ queueEl;
232
+ footerEl;
233
+ summaryEl;
234
+ queueSignature = "";
235
+ get folderId() {
236
+ return this.getAttribute("folder-id");
237
+ }
238
+ set folderId(value) {
239
+ this.updateStringAttribute("folder-id", value);
240
+ }
241
+ get token() {
242
+ return this.getAttribute("token");
243
+ }
244
+ set token(value) {
245
+ this.updateStringAttribute("token", value);
246
+ }
247
+ get language() {
248
+ return this.getAttribute("language");
249
+ }
250
+ set language(value) {
251
+ this.updateStringAttribute("language", value);
252
+ }
253
+ get concurrency() {
254
+ return readPositiveNumber(this.getAttribute("concurrency"));
255
+ }
256
+ set concurrency(value) {
257
+ if (typeof value === "number" && value > 0) {
258
+ this.setAttribute("concurrency", String(value));
259
+ return;
260
+ }
261
+ this.removeAttribute("concurrency");
262
+ }
263
+ /** Uploads start as files are added unless `auto-start="false"`. */
264
+ get autoStart() {
265
+ return this.getAttribute("auto-start") !== "false";
266
+ }
267
+ set autoStart(value) {
268
+ if (value) {
269
+ this.removeAttribute("auto-start");
270
+ return;
271
+ }
272
+ this.setAttribute("auto-start", "false");
273
+ }
274
+ /** Comma-separated in markup, e.g. `extensions="pdf,docx"`. */
275
+ get extensions() {
276
+ return readTokenList(this.getAttribute("extensions"));
277
+ }
278
+ set extensions(value) {
279
+ if (value.length) {
280
+ this.setAttribute("extensions", value.join(","));
281
+ return;
282
+ }
283
+ this.removeAttribute("extensions");
284
+ }
285
+ /** Maximum accepted file size in bytes. */
286
+ get maxFileSize() {
287
+ return readPositiveNumber(this.getAttribute("max-file-size"));
288
+ }
289
+ set maxFileSize(value) {
290
+ if (typeof value === "number" && value > 0) {
291
+ this.setAttribute("max-file-size", String(value));
292
+ return;
293
+ }
294
+ this.removeAttribute("max-file-size");
295
+ }
296
+ get dropLabel() {
297
+ return this.getAttribute("drop-label") ?? "Upload files";
298
+ }
299
+ set dropLabel(value) {
300
+ this.setAttribute("drop-label", value);
301
+ }
302
+ get dropMessage() {
303
+ return this.getAttribute("drop-message") ?? "Drag files here or click to browse.";
304
+ }
305
+ set dropMessage(value) {
306
+ this.setAttribute("drop-message", value);
307
+ }
308
+ get transport() {
309
+ return this.transportValue;
310
+ }
311
+ set transport(value) {
312
+ this.transportValue = value;
313
+ this.scheduleStart();
314
+ }
315
+ get state() {
316
+ return this.controller?.getState() ?? null;
317
+ }
318
+ /** The live queue controller. Null until configured. */
319
+ get uploaderController() {
320
+ return this.controller;
321
+ }
322
+ attributeChangedCallback(name, oldValue, newValue) {
323
+ // Drop-zone copy is presentation-only; everything else re-creates the queue.
324
+ if (name !== "drop-label" && name !== "drop-message") {
325
+ this.scheduleStart();
326
+ }
327
+ super.attributeChangedCallback(name, oldValue, newValue);
328
+ }
329
+ connectedCallback() {
330
+ super.connectedCallback();
331
+ this.scheduleStart();
332
+ }
333
+ disconnectedCallback() {
334
+ this.teardownController();
335
+ }
336
+ addFiles(files) {
337
+ return this.controller?.addFiles(files) ?? [];
338
+ }
339
+ start() {
340
+ this.controller?.start();
341
+ }
342
+ cancelItem(itemId) {
343
+ this.controller?.cancelItem(itemId);
344
+ }
345
+ retryItem(itemId) {
346
+ this.controller?.retryItem(itemId);
347
+ }
348
+ removeItem(itemId) {
349
+ this.controller?.removeItem(itemId);
350
+ }
351
+ clearCompleted() {
352
+ this.controller?.clearCompleted();
353
+ }
354
+ updateStringAttribute(name, value) {
355
+ if (value === null || value === "") {
356
+ this.removeAttribute(name);
357
+ return;
358
+ }
359
+ this.setAttribute(name, value);
360
+ }
361
+ scheduleStart() {
362
+ if (this.pendingStart) {
363
+ return;
364
+ }
365
+ this.pendingStart = true;
366
+ queueMicrotask(() => {
367
+ this.pendingStart = false;
368
+ this.startController();
369
+ });
370
+ }
371
+ startController() {
372
+ if (!this.isConnected) {
373
+ return;
374
+ }
375
+ const config = this.readConfig();
376
+ if (!config) {
377
+ this.teardownController();
378
+ if (this.isRendered) {
379
+ this.update();
380
+ }
381
+ return;
382
+ }
383
+ this.teardownController();
384
+ const controller = new ContentUploaderController(config);
385
+ this.controller = controller;
386
+ this.subscribeToController(controller);
387
+ if (this.isRendered) {
388
+ this.update();
389
+ }
390
+ }
391
+ readConfig() {
392
+ if (!this.transportValue || !this.folderId || !this.token) {
393
+ return null;
394
+ }
395
+ return {
396
+ autoStart: this.autoStart,
397
+ concurrency: this.concurrency,
398
+ extensions: this.extensions.length ? this.extensions : undefined,
399
+ folderId: this.folderId,
400
+ language: this.language ?? undefined,
401
+ maxFileSizeBytes: this.maxFileSize,
402
+ token: this.token,
403
+ transport: this.transportValue,
404
+ };
405
+ }
406
+ subscribeToController(controller) {
407
+ const events = [
408
+ ["itemAdded", "item-added"],
409
+ ["itemRejected", "item-rejected"],
410
+ ["itemStarted", "item-started"],
411
+ ["itemProgress", "item-progress"],
412
+ ["itemSucceeded", "item-succeeded"],
413
+ ["itemFailed", "item-failed"],
414
+ ["itemCancelled", "item-cancelled"],
415
+ ["itemRemoved", "item-removed"],
416
+ ["queueChanged", "queue-changed"],
417
+ ["queueDrained", "queue-drained"],
418
+ ];
419
+ this.unsubscribeFns = events.map(([eventName, domEventName]) => controller.subscribe(eventName, payload => {
420
+ this.dispatchEvent(new CustomEvent(domEventName, {
421
+ bubbles: true,
422
+ composed: true,
423
+ detail: payload,
424
+ }));
425
+ if (this.isRendered) {
426
+ this.update();
427
+ }
428
+ }));
429
+ }
430
+ teardownController() {
431
+ for (const unsubscribe of this.unsubscribeFns) {
432
+ unsubscribe();
433
+ }
434
+ this.unsubscribeFns = [];
435
+ this.controller?.destroy();
436
+ this.controller = null;
437
+ this.queueSignature = "";
438
+ }
439
+ renderTemplate() {
440
+ if (!this.shadowRoot) {
441
+ return;
442
+ }
443
+ this.shadowRoot.innerHTML = `
444
+ <style>${elementStyles}</style>
445
+ <div part="panel">
446
+ <box-drop-zone part="drop-zone"></box-drop-zone>
447
+ <ul part="queue" aria-label="Upload queue"></ul>
448
+ <footer part="footer" hidden>
449
+ <span part="summary" role="status" aria-live="polite"></span>
450
+ <button type="button" part="clear-completed">Clear completed</button>
451
+ </footer>
452
+ </div>
453
+ `;
454
+ this.dropZoneEl = this.shadowRoot.querySelector('[part="drop-zone"]');
455
+ this.queueEl = this.shadowRoot.querySelector('[part="queue"]');
456
+ this.footerEl = this.shadowRoot.querySelector('[part="footer"]');
457
+ this.summaryEl = this.shadowRoot.querySelector('[part="summary"]');
458
+ }
459
+ setupListeners() {
460
+ this.dropZoneEl.addEventListener("files-selected", event => {
461
+ const files = event.detail?.files ?? [];
462
+ if (files.length) {
463
+ this.addFiles(files);
464
+ }
465
+ });
466
+ this.queueEl.addEventListener("click", event => {
467
+ const button = event.target.closest('[part="row-action"]');
468
+ if (!button || !this.queueEl.contains(button)) {
469
+ return;
470
+ }
471
+ const itemId = button.getAttribute("data-item-id");
472
+ const action = button.getAttribute("data-action");
473
+ if (!itemId || !action) {
474
+ return;
475
+ }
476
+ if (action === "cancel") {
477
+ this.cancelItem(itemId);
478
+ }
479
+ else if (action === "retry") {
480
+ this.retryItem(itemId);
481
+ }
482
+ else if (action === "remove") {
483
+ this.removeItem(itemId);
484
+ }
485
+ });
486
+ this.footerEl.querySelector('[part="clear-completed"]')?.addEventListener("click", () => {
487
+ this.clearCompleted();
488
+ });
489
+ }
490
+ rebuildQueue(items) {
491
+ this.queueEl.innerHTML = items
492
+ .map(item => {
493
+ const actions = rowActions(item.status)
494
+ .map(({ action, label }) => `
495
+ <button
496
+ type="button"
497
+ part="row-action"
498
+ data-action="${action}"
499
+ data-item-id="${escapeHtml(item.id)}"
500
+ aria-label="${label} ${escapeHtml(item.name)}"
501
+ >${label}</button>
502
+ `)
503
+ .join("");
504
+ return `
505
+ <li part="row" data-item-id="${escapeHtml(item.id)}" data-status="${item.status}">
506
+ <div part="row-header">
507
+ <span part="row-name">${escapeHtml(item.name)}</span>
508
+ <span part="row-size">${escapeHtml(formatItemSize(item.size))}</span>
509
+ <span part="row-status">${STATUS_LABELS[item.status]}</span>
510
+ </div>
511
+ <box-progress-bar
512
+ part="row-progress"
513
+ label="${escapeHtml(`${item.name} upload progress`)}"
514
+ max="100"
515
+ value="${String(Math.round(item.progress * 100))}"
516
+ ></box-progress-bar>
517
+ ${item.errorMessage ? `<p part="row-error">${escapeHtml(item.errorMessage)}</p>` : ""}
518
+ <div part="row-actions">${actions}</div>
519
+ </li>
520
+ `;
521
+ })
522
+ .join("");
523
+ }
524
+ patchQueueProgress(items) {
525
+ for (const item of items) {
526
+ const row = Array.from(this.queueEl.children).find(child => child.dataset.itemId === item.id);
527
+ const bar = row?.querySelector('[part="row-progress"]');
528
+ if (bar) {
529
+ bar.value = Math.round(item.progress * 100);
530
+ }
531
+ }
532
+ }
533
+ update() {
534
+ if (!this.queueEl) {
535
+ return;
536
+ }
537
+ this.dropZoneEl.label = this.dropLabel;
538
+ this.dropZoneEl.message = this.dropMessage;
539
+ const items = this.controller?.getState().items ?? [];
540
+ // Structural changes rebuild the rows; progress ticks patch the bars in
541
+ // place so in-row focus (Cancel while uploading) survives.
542
+ const signature = JSON.stringify(items.map(item => [item.id, item.status, item.errorMessage ?? null]));
543
+ if (signature !== this.queueSignature) {
544
+ this.queueSignature = signature;
545
+ this.rebuildQueue(items);
546
+ }
547
+ this.patchQueueProgress(items);
548
+ const summary = summarizeUploadQueue(items);
549
+ this.footerEl.hidden = summary.total === 0;
550
+ const active = summary.queued + summary.uploading;
551
+ this.summaryEl.textContent =
552
+ active > 0
553
+ ? `Uploading ${String(summary.uploading)} · ${String(summary.queued)} queued`
554
+ : `${String(summary.succeeded)} of ${String(summary.total)} uploaded${summary.failed ? ` · ${String(summary.failed)} failed` : ""}`;
555
+ }
556
+ }
557
+ DropZone.register();
558
+ ProgressBar.register();
559
+ ContentUploader.register();