@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,747 @@
1
+ import { ContentPickerController } from "./controller.js";
2
+ import { formatItemMetaLine } from "../content-explorer/adapters/item-summary.js";
3
+ import { BaseElement } from "../../core/index.js";
4
+ import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
5
+ import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
6
+ const DEFAULT_TAG_NAME = "box-content-picker";
7
+ const escapeHtml = (value) => value
8
+ .replaceAll("&", "&")
9
+ .replaceAll("<", "&lt;")
10
+ .replaceAll(">", "&gt;")
11
+ .replaceAll('"', "&quot;")
12
+ .replaceAll("'", "&#39;");
13
+ const readPositiveNumber = (value) => {
14
+ if (!value) {
15
+ return undefined;
16
+ }
17
+ const parsed = Number.parseInt(value, 10);
18
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
19
+ };
20
+ const readTokenList = (value) => value
21
+ ? value
22
+ .split(",")
23
+ .map(entry => entry.trim())
24
+ .filter(Boolean)
25
+ : [];
26
+ const PICKER_ITEM_TYPES = new Set(["file", "folder", "web_link"]);
27
+ const elementStyles = `
28
+ :host {
29
+ display: block;
30
+ color: inherit;
31
+ font: inherit;
32
+ }
33
+
34
+ section {
35
+ display: grid;
36
+ gap: ${boePanel.gap};
37
+ padding: ${boePanel.padding};
38
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
39
+ border-radius: ${boePanel.radius};
40
+ background: var(--boe-token-surface-surface, #ffffff);
41
+ transition: opacity ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
42
+ }
43
+
44
+ section[aria-busy="true"] {
45
+ opacity: 0.65;
46
+ }
47
+
48
+ [part="folder"] {
49
+ display: grid;
50
+ gap: 0.35rem;
51
+ }
52
+
53
+ [part="folder"] h2 {
54
+ margin: 0;
55
+ font-size: 1.1rem;
56
+ font-weight: 700;
57
+ line-height: 1.2;
58
+ color: var(--boe-token-text-text, #222222);
59
+ }
60
+
61
+ [part="breadcrumbs"] {
62
+ display: flex;
63
+ flex-wrap: wrap;
64
+ align-items: center;
65
+ gap: 0.2rem;
66
+ }
67
+
68
+ [part="breadcrumb"] {
69
+ appearance: none;
70
+ font: inherit;
71
+ font-size: 0.875rem;
72
+ padding: 0.25rem 0.55rem;
73
+ border: none;
74
+ border-radius: ${boeRadius.med};
75
+ background: transparent;
76
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
77
+ cursor: pointer;
78
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
79
+ }
80
+
81
+ [part="breadcrumb"]:hover {
82
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 8%, transparent);
83
+ color: var(--boe-token-surface-surface-brand, #0061d5);
84
+ }
85
+
86
+ [part="breadcrumb"]:focus-visible {
87
+ outline: none;
88
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
89
+ }
90
+
91
+ [part="breadcrumb-separator"] {
92
+ color: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 55%, transparent);
93
+ font-size: 0.875rem;
94
+ }
95
+
96
+ [part="error"] {
97
+ margin: 0;
98
+ padding: 0.55rem 0.7rem;
99
+ border-radius: ${boeRadius.large};
100
+ font-size: 0.9rem;
101
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, var(--boe-token-surface-surface, #ffffff));
102
+ border: 1px solid color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 34%, transparent);
103
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 72%, black 28%);
104
+ }
105
+
106
+ [part="items"] {
107
+ list-style: none;
108
+ margin: 0;
109
+ padding: 0;
110
+ display: grid;
111
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 62%, transparent);
112
+ border-radius: ${boePanel.radius};
113
+ overflow: hidden;
114
+ }
115
+
116
+ [part="items"] > li {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: ${boePanel.gap};
120
+ padding: 0.15rem 0.45rem;
121
+ background: var(--boe-token-surface-surface, #ffffff);
122
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
123
+ }
124
+
125
+ [part="items"] > li + li {
126
+ border-top: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 46%, transparent);
127
+ }
128
+
129
+ [part="items"] > li:hover {
130
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
131
+ }
132
+
133
+ [part="items"] > li:has([part="item"][aria-selected="true"]) {
134
+ background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
135
+ }
136
+
137
+ [part="item"] {
138
+ appearance: none;
139
+ font: inherit;
140
+ font-size: 0.95rem;
141
+ flex: 1;
142
+ display: grid;
143
+ gap: 0.15rem;
144
+ text-align: left;
145
+ padding: 0.45rem 0.45rem;
146
+ border: none;
147
+ border-radius: ${boeRadius.med};
148
+ background: transparent;
149
+ color: var(--boe-token-text-text, #222222);
150
+ cursor: pointer;
151
+ transition: color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
152
+ }
153
+
154
+ [part="item"]:disabled {
155
+ cursor: not-allowed;
156
+ opacity: 0.5;
157
+ }
158
+
159
+ [part="item-name"],
160
+ [part="item-meta"] {
161
+ overflow: hidden;
162
+ text-overflow: ellipsis;
163
+ white-space: nowrap;
164
+ }
165
+
166
+ [part="item-meta"] {
167
+ font-size: 0.78rem;
168
+ font-weight: 400;
169
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
170
+ }
171
+
172
+ [part="item"]:hover:not(:disabled) {
173
+ color: var(--boe-token-surface-surface-brand, #0061d5);
174
+ }
175
+
176
+ [part="item"]:focus-visible {
177
+ outline: none;
178
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
179
+ }
180
+
181
+ [part="item"][aria-selected="true"] {
182
+ font-weight: 600;
183
+ color: var(--boe-token-surface-surface-brand, #0061d5);
184
+ }
185
+
186
+ [part="load-more-region"] {
187
+ display: flex;
188
+ justify-content: center;
189
+ }
190
+
191
+ [part="load-more"] {
192
+ appearance: none;
193
+ font: inherit;
194
+ font-size: 0.875rem;
195
+ font-weight: 600;
196
+ padding: 0.4rem 0.7rem;
197
+ border-radius: ${boeRadius.control};
198
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
199
+ background: var(--boe-token-surface-surface, #ffffff);
200
+ color: var(--boe-token-text-text, #222222);
201
+ cursor: pointer;
202
+ }
203
+
204
+ [part="load-more"]:hover {
205
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
206
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
207
+ }
208
+
209
+ [part="footer"] {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: ${boePanel.gap};
213
+ padding-top: 0.2rem;
214
+ border-top: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 62%, transparent);
215
+ }
216
+
217
+ [part="selection-count"] {
218
+ flex: 1;
219
+ font-size: 0.85rem;
220
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
221
+ }
222
+
223
+ [part="cancel"],
224
+ [part="choose"] {
225
+ appearance: none;
226
+ font: inherit;
227
+ font-size: 0.875rem;
228
+ font-weight: 600;
229
+ min-height: 2.1rem;
230
+ padding: 0.4rem 0.9rem;
231
+ border-radius: ${boeRadius.control};
232
+ cursor: pointer;
233
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, border-color ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, box-shadow ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
234
+ }
235
+
236
+ [part="cancel"] {
237
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
238
+ background: var(--boe-token-surface-surface, #ffffff);
239
+ color: var(--boe-token-text-text, #222222);
240
+ }
241
+
242
+ [part="cancel"]:hover {
243
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
244
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
245
+ }
246
+
247
+ [part="choose"] {
248
+ border: 1px solid transparent;
249
+ background: var(--boe-token-surface-surface-brand, #0061d5);
250
+ color: var(--boe-token-text-text-on-brand, #ffffff);
251
+ }
252
+
253
+ [part="choose"]:hover:not(:disabled) {
254
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 88%, black 12%);
255
+ }
256
+
257
+ [part="choose"]:disabled {
258
+ cursor: not-allowed;
259
+ opacity: 0.55;
260
+ }
261
+
262
+ [part="cancel"]:focus-visible,
263
+ [part="choose"]:focus-visible {
264
+ outline: none;
265
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
266
+ }
267
+ `;
268
+ export class ContentPicker extends BaseElement {
269
+ static tagName = DEFAULT_TAG_NAME;
270
+ static get observedAttributes() {
271
+ return [
272
+ "cancel-label",
273
+ "choose-label",
274
+ "extensions",
275
+ "language",
276
+ "max-selectable",
277
+ "page-size",
278
+ "root-folder-id",
279
+ "selectable-types",
280
+ "token",
281
+ ];
282
+ }
283
+ controller = null;
284
+ pendingStart = false;
285
+ focusItemId = null;
286
+ /**
287
+ * Set when a rebuild destroyed in-element focus before its target existed
288
+ * (e.g. navigation is still loading the next folder); the next update keeps
289
+ * trying to restore until a target renders.
290
+ */
291
+ pendingFocusRestore = false;
292
+ unsubscribeFns = [];
293
+ transportValue = null;
294
+ get language() {
295
+ return this.getAttribute("language");
296
+ }
297
+ set language(value) {
298
+ this.updateStringAttribute("language", value);
299
+ }
300
+ get pageSize() {
301
+ return readPositiveNumber(this.getAttribute("page-size"));
302
+ }
303
+ set pageSize(value) {
304
+ if (typeof value === "number" && value > 0) {
305
+ this.setAttribute("page-size", String(value));
306
+ return;
307
+ }
308
+ this.removeAttribute("page-size");
309
+ }
310
+ get rootFolderId() {
311
+ return this.getAttribute("root-folder-id");
312
+ }
313
+ set rootFolderId(value) {
314
+ this.updateStringAttribute("root-folder-id", value);
315
+ }
316
+ get token() {
317
+ return this.getAttribute("token");
318
+ }
319
+ set token(value) {
320
+ this.updateStringAttribute("token", value);
321
+ }
322
+ get maxSelectable() {
323
+ return readPositiveNumber(this.getAttribute("max-selectable"));
324
+ }
325
+ set maxSelectable(value) {
326
+ if (typeof value === "number" && value > 0) {
327
+ this.setAttribute("max-selectable", String(value));
328
+ return;
329
+ }
330
+ this.removeAttribute("max-selectable");
331
+ }
332
+ /** Comma-separated in markup, e.g. `extensions="pdf,docx"`. */
333
+ get extensions() {
334
+ return readTokenList(this.getAttribute("extensions"));
335
+ }
336
+ set extensions(value) {
337
+ if (value.length) {
338
+ this.setAttribute("extensions", value.join(","));
339
+ return;
340
+ }
341
+ this.removeAttribute("extensions");
342
+ }
343
+ /** Comma-separated in markup, e.g. `selectable-types="file,web_link"`. */
344
+ get selectableTypes() {
345
+ return readTokenList(this.getAttribute("selectable-types")).filter((entry) => PICKER_ITEM_TYPES.has(entry));
346
+ }
347
+ set selectableTypes(value) {
348
+ if (value.length) {
349
+ this.setAttribute("selectable-types", value.join(","));
350
+ return;
351
+ }
352
+ this.removeAttribute("selectable-types");
353
+ }
354
+ get chooseLabel() {
355
+ return this.getAttribute("choose-label") ?? "Choose";
356
+ }
357
+ set chooseLabel(value) {
358
+ this.setAttribute("choose-label", value);
359
+ }
360
+ get cancelLabel() {
361
+ return this.getAttribute("cancel-label") ?? "Cancel";
362
+ }
363
+ set cancelLabel(value) {
364
+ this.setAttribute("cancel-label", value);
365
+ }
366
+ get transport() {
367
+ return this.transportValue;
368
+ }
369
+ set transport(value) {
370
+ this.transportValue = value;
371
+ this.scheduleStart();
372
+ }
373
+ get state() {
374
+ return this.controller?.getState() ?? null;
375
+ }
376
+ get explorerState() {
377
+ return this.controller?.explorer.getState() ?? null;
378
+ }
379
+ /** The live session controller. Null until configured and connected. */
380
+ get pickerController() {
381
+ return this.controller;
382
+ }
383
+ attributeChangedCallback(name, oldValue, newValue) {
384
+ // Labels are presentation-only; everything else re-creates the session.
385
+ if (name !== "choose-label" && name !== "cancel-label") {
386
+ this.scheduleStart();
387
+ }
388
+ super.attributeChangedCallback(name, oldValue, newValue);
389
+ }
390
+ connectedCallback() {
391
+ super.connectedCallback();
392
+ this.scheduleStart();
393
+ }
394
+ disconnectedCallback() {
395
+ this.teardownController();
396
+ }
397
+ async navigateTo(folderId) {
398
+ await this.controller?.explorer.navigateTo(folderId);
399
+ }
400
+ async search(query) {
401
+ await this.controller?.explorer.search(query);
402
+ }
403
+ async clearSearch() {
404
+ await this.controller?.explorer.clearSearch();
405
+ }
406
+ async loadNextPage() {
407
+ await this.controller?.explorer.loadNextPage();
408
+ }
409
+ togglePick(itemId) {
410
+ this.controller?.togglePick(itemId);
411
+ }
412
+ clearPicks() {
413
+ this.controller?.clearPicks();
414
+ }
415
+ choose() {
416
+ return this.controller?.choose() ?? null;
417
+ }
418
+ cancel() {
419
+ this.controller?.cancel();
420
+ }
421
+ updateStringAttribute(name, value) {
422
+ if (value === null || value === "") {
423
+ this.removeAttribute(name);
424
+ return;
425
+ }
426
+ this.setAttribute(name, value);
427
+ }
428
+ scheduleStart() {
429
+ if (this.pendingStart) {
430
+ return;
431
+ }
432
+ this.pendingStart = true;
433
+ queueMicrotask(() => {
434
+ this.pendingStart = false;
435
+ void this.startController();
436
+ });
437
+ }
438
+ async startController() {
439
+ if (!this.isConnected) {
440
+ return;
441
+ }
442
+ const config = this.readConfig();
443
+ if (!config) {
444
+ this.teardownController();
445
+ if (this.isRendered) {
446
+ this.update();
447
+ }
448
+ return;
449
+ }
450
+ this.teardownController();
451
+ const controller = new ContentPickerController(config);
452
+ this.controller = controller;
453
+ this.subscribeToController(controller);
454
+ if (this.isRendered) {
455
+ this.update();
456
+ }
457
+ await controller.connect();
458
+ }
459
+ readConfig() {
460
+ if (!this.transportValue || !this.rootFolderId || !this.token) {
461
+ return null;
462
+ }
463
+ return {
464
+ extensions: this.extensions.length ? this.extensions : undefined,
465
+ language: this.language ?? undefined,
466
+ maxSelectable: this.maxSelectable,
467
+ pageSize: this.pageSize,
468
+ rootFolderId: this.rootFolderId,
469
+ selectableTypes: this.selectableTypes.length ? this.selectableTypes : undefined,
470
+ token: this.token,
471
+ transport: this.transportValue,
472
+ };
473
+ }
474
+ subscribeToController(controller) {
475
+ const rerender = () => {
476
+ if (this.isRendered) {
477
+ this.update();
478
+ }
479
+ };
480
+ const pickerEvents = [
481
+ ["cancelled", "cancelled"],
482
+ ["chosen", "chosen"],
483
+ ["selectionChanged", "selection-changed"],
484
+ ["selectionRejected", "selection-rejected"],
485
+ ];
486
+ this.unsubscribeFns = [
487
+ ...pickerEvents.map(([eventName, domEventName]) => controller.subscribe(eventName, payload => {
488
+ this.dispatchEvent(new CustomEvent(domEventName, {
489
+ bubbles: true,
490
+ composed: true,
491
+ detail: payload,
492
+ }));
493
+ rerender();
494
+ })),
495
+ controller.explorer.subscribe("itemActivated", ({ item }) => {
496
+ this.dispatchEvent(new CustomEvent("item-activated", {
497
+ bubbles: true,
498
+ composed: true,
499
+ detail: { item },
500
+ }));
501
+ }),
502
+ controller.explorer.subscribe("loadingChanged", rerender),
503
+ controller.explorer.subscribe("loadSucceeded", rerender),
504
+ controller.explorer.subscribe("searchSucceeded", rerender),
505
+ controller.explorer.subscribe("loadFailed", rerender),
506
+ controller.explorer.subscribe("selectionChanged", rerender),
507
+ controller.explorer.subscribe("breadcrumbsChanged", rerender),
508
+ controller.explorer.subscribe("paginationChanged", rerender),
509
+ ];
510
+ }
511
+ teardownController() {
512
+ for (const unsubscribe of this.unsubscribeFns) {
513
+ unsubscribe();
514
+ }
515
+ this.unsubscribeFns = [];
516
+ this.controller?.destroy();
517
+ this.controller = null;
518
+ }
519
+ /**
520
+ * Only interactive rows (pickable items and navigable folders) participate
521
+ * in keyboard navigation — disabled rows cannot take focus, so including
522
+ * them would strand arrow-key movement and the roving tab stop.
523
+ */
524
+ getFocusableItemIds() {
525
+ const controller = this.controller;
526
+ if (!controller) {
527
+ return [];
528
+ }
529
+ return controller.explorer
530
+ .getState()
531
+ .items.filter(item => controller.isItemPickable(item) || item.type === "folder")
532
+ .map(item => item.id);
533
+ }
534
+ renderTemplate() {
535
+ if (!this.shadowRoot) {
536
+ return;
537
+ }
538
+ this.shadowRoot.innerHTML = `
539
+ <style>${elementStyles}</style>
540
+ <div part="content-host"></div>
541
+ `;
542
+ }
543
+ update() {
544
+ if (!this.shadowRoot) {
545
+ return;
546
+ }
547
+ const controller = this.controller;
548
+ const explorerState = controller?.explorer.getState();
549
+ const pickerState = controller?.getState();
550
+ const breadcrumbMarkup = explorerState?.breadcrumbs.length
551
+ ? `<nav part="breadcrumbs" aria-label="Breadcrumbs">${explorerState.breadcrumbs
552
+ .map(crumb => `<button type="button" part="breadcrumb" data-folder-id="${escapeHtml(crumb.id)}" aria-label="Open ${escapeHtml(crumb.name)}">${escapeHtml(crumb.name)}</button>`)
553
+ .join('<span part="breadcrumb-separator">/</span>')}</nav>`
554
+ : "";
555
+ const folderMarkup = explorerState?.currentFolder
556
+ ? `<header part="folder">${breadcrumbMarkup}<h2>${escapeHtml(explorerState.currentFolder.name)}</h2></header>`
557
+ : `<header part="folder"><h2>No folder loaded</h2></header>`;
558
+ // Resolve the roving tab stop against the rows that can actually take
559
+ // focus; a stale target (e.g. an id from the previous folder) falls back
560
+ // to the first interactive row so the list always keeps a tab stop.
561
+ const focusableItemIds = this.getFocusableItemIds();
562
+ const focusTarget = this.focusItemId && focusableItemIds.includes(this.focusItemId)
563
+ ? this.focusItemId
564
+ : (focusableItemIds[0] ?? "");
565
+ // Keep the stale target while the collection is transiently empty
566
+ // (mid-navigation) so the pending focus restore can land on the fallback.
567
+ if (focusTarget) {
568
+ this.focusItemId = focusTarget;
569
+ }
570
+ const itemsMarkup = explorerState?.items.length
571
+ ? explorerState.items
572
+ .map(item => {
573
+ const pickable = controller?.isItemPickable(item) ?? false;
574
+ const picked = controller?.isPicked(item.id) ?? false;
575
+ const navigable = item.type === "folder";
576
+ const interactive = pickable || navigable;
577
+ const meta = formatItemMetaLine(item);
578
+ return `<li data-item-id="${escapeHtml(item.id)}" role="presentation">
579
+ <button
580
+ type="button"
581
+ part="item"
582
+ role="option"
583
+ data-item-id="${escapeHtml(item.id)}"
584
+ data-item-type="${escapeHtml(item.type)}"
585
+ data-pickable="${pickable ? "true" : "false"}"
586
+ aria-selected="${picked ? "true" : "false"}"
587
+ aria-label="${escapeHtml(item.name)}"
588
+ tabindex="${item.id === focusTarget ? "0" : "-1"}"
589
+ ${interactive ? "" : 'disabled aria-disabled="true"'}
590
+ ><span part="item-name">${escapeHtml(item.name)}</span>${meta ? `<span part="item-meta">${escapeHtml(meta)}</span>` : ""}</button>
591
+ </li>`;
592
+ })
593
+ .join("")
594
+ : `<li role="presentation">No items loaded</li>`;
595
+ const loadMoreMarkup = explorerState?.pagination.hasMoreItems && !explorerState.loading
596
+ ? `<div part="load-more-region"><button type="button" part="load-more">Load more</button></div>`
597
+ : "";
598
+ const errorMarkup = explorerState?.error
599
+ ? `<p part="error">${escapeHtml(explorerState.error.message)}</p>`
600
+ : "";
601
+ const selectedCount = pickerState?.selectedItems.length ?? 0;
602
+ const max = pickerState?.maxSelectable ?? null;
603
+ const countText = max === null ? `${selectedCount} selected` : `${selectedCount} of ${max} selected`;
604
+ const footerMarkup = `
605
+ <footer part="footer">
606
+ <span part="selection-count" aria-live="polite">${escapeHtml(countText)}</span>
607
+ <button type="button" part="cancel">${escapeHtml(this.cancelLabel)}</button>
608
+ <button type="button" part="choose" ${pickerState?.canChoose ? "" : "disabled"}>${escapeHtml(this.chooseLabel)}</button>
609
+ </footer>
610
+ `;
611
+ const host = this.shadowRoot.querySelector('[part="content-host"]');
612
+ if (!host) {
613
+ return;
614
+ }
615
+ // Sample focus BEFORE the rebuild wipes the focused node.
616
+ const hadFocusInside = this.isFocusInsideHost();
617
+ host.innerHTML = `
618
+ <section aria-busy="${explorerState?.loading ? "true" : "false"}">
619
+ ${folderMarkup}
620
+ ${errorMarkup}
621
+ <ul part="items" role="listbox" aria-label="Picker items" aria-multiselectable="${max === 1 ? "false" : "true"}">${itemsMarkup}</ul>
622
+ ${loadMoreMarkup}
623
+ ${footerMarkup}
624
+ </section>
625
+ `;
626
+ this.shadowRoot.querySelectorAll('[part="breadcrumb"]').forEach(node => {
627
+ node.addEventListener("click", event => {
628
+ const folderId = event.currentTarget.getAttribute("data-folder-id");
629
+ if (folderId) {
630
+ void this.navigateTo(folderId);
631
+ }
632
+ });
633
+ });
634
+ this.shadowRoot.querySelector('[part="load-more"]')?.addEventListener("click", () => {
635
+ void this.loadNextPage();
636
+ });
637
+ this.shadowRoot.querySelector('[part="cancel"]')?.addEventListener("click", () => {
638
+ this.cancel();
639
+ });
640
+ this.shadowRoot.querySelector('[part="choose"]')?.addEventListener("click", () => {
641
+ this.choose();
642
+ });
643
+ this.shadowRoot.querySelectorAll('[part="item"]').forEach(node => {
644
+ node.addEventListener("click", event => {
645
+ if (event.detail > 1) {
646
+ return;
647
+ }
648
+ const target = event.currentTarget;
649
+ const itemId = target.getAttribute("data-item-id");
650
+ if (!itemId) {
651
+ return;
652
+ }
653
+ this.focusItemId = itemId;
654
+ // Un-pickable folders navigate on click; everything pickable toggles.
655
+ if (target.getAttribute("data-pickable") === "true") {
656
+ this.togglePick(itemId);
657
+ }
658
+ else if (target.getAttribute("data-item-type") === "folder") {
659
+ void this.controller?.explorer.activateItem(itemId);
660
+ }
661
+ });
662
+ node.addEventListener("dblclick", event => {
663
+ const target = event.currentTarget;
664
+ const itemId = target.getAttribute("data-item-id");
665
+ if (itemId && target.getAttribute("data-item-type") === "folder") {
666
+ this.focusItemId = itemId;
667
+ void this.controller?.explorer.activateItem(itemId);
668
+ }
669
+ });
670
+ node.addEventListener("keydown", event => {
671
+ const keyboardEvent = event;
672
+ const target = event.currentTarget;
673
+ const itemId = target.getAttribute("data-item-id") ?? "";
674
+ const itemIds = this.getFocusableItemIds();
675
+ const currentIndex = itemIds.indexOf(itemId);
676
+ let nextIndex = currentIndex;
677
+ if (keyboardEvent.key === "ArrowDown") {
678
+ nextIndex = Math.min(itemIds.length - 1, currentIndex + 1);
679
+ }
680
+ else if (keyboardEvent.key === "ArrowUp") {
681
+ nextIndex = Math.max(0, currentIndex - 1);
682
+ }
683
+ else if (keyboardEvent.key === "Home") {
684
+ nextIndex = 0;
685
+ }
686
+ else if (keyboardEvent.key === "End") {
687
+ nextIndex = itemIds.length - 1;
688
+ }
689
+ else if (keyboardEvent.key === " ") {
690
+ keyboardEvent.preventDefault();
691
+ this.focusItemId = itemId;
692
+ if (target.getAttribute("data-pickable") === "true") {
693
+ this.togglePick(itemId);
694
+ }
695
+ return;
696
+ }
697
+ else if (keyboardEvent.key === "Enter") {
698
+ keyboardEvent.preventDefault();
699
+ this.focusItemId = itemId;
700
+ if (target.getAttribute("data-item-type") === "folder") {
701
+ void this.controller?.explorer.activateItem(itemId);
702
+ }
703
+ else if (target.getAttribute("data-pickable") === "true") {
704
+ this.togglePick(itemId);
705
+ }
706
+ return;
707
+ }
708
+ else {
709
+ return;
710
+ }
711
+ keyboardEvent.preventDefault();
712
+ const nextItemId = itemIds[nextIndex];
713
+ if (nextItemId) {
714
+ this.focusItemId = nextItemId;
715
+ if (this.isRendered) {
716
+ this.update();
717
+ }
718
+ }
719
+ });
720
+ });
721
+ // Restore focus only when it already lived inside this element before the
722
+ // rebuild — an unrelated state update must never steal focus. A restore
723
+ // whose target has not rendered yet (navigation still loading) stays
724
+ // pending and is retried on the next update.
725
+ if (this.focusItemId && (hadFocusInside || this.pendingFocusRestore)) {
726
+ this.pendingFocusRestore = true;
727
+ queueMicrotask(() => {
728
+ if (!this.pendingFocusRestore) {
729
+ return;
730
+ }
731
+ const target = Array.from(this.shadowRoot?.querySelectorAll('[part="item"]') ?? []).find(node => node.dataset.itemId === this.focusItemId);
732
+ if (target) {
733
+ this.pendingFocusRestore = false;
734
+ target.focus();
735
+ }
736
+ });
737
+ }
738
+ }
739
+ isFocusInsideHost() {
740
+ const active = document.activeElement;
741
+ if (!active) {
742
+ return false;
743
+ }
744
+ return active === this || (this.shadowRoot?.contains(active) ?? false);
745
+ }
746
+ }
747
+ ContentPicker.register();