@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
@@ -123,29 +123,48 @@ const resolveFetch = (providedFetch) => {
123
123
  }
124
124
  return globalThis.fetch.bind(globalThis);
125
125
  };
126
- const createFolderItemsUrl = (apiBaseUrl, folderId, fields, limit, offset) => {
126
+ /** Box folder-items sort param values match our vocabulary directly. */
127
+ const applySortParams = (url, sortBy, sortDirection) => {
128
+ if (sortBy) {
129
+ url.searchParams.set("sort", sortBy);
130
+ url.searchParams.set("direction", sortDirection ?? "ASC");
131
+ }
132
+ };
133
+ const createFolderItemsUrl = (apiBaseUrl, folderId, fields, request) => {
127
134
  const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/folders/${folderId}/items`);
128
135
  url.searchParams.set("fields", fields.join(","));
129
- if (typeof limit === "number") {
130
- url.searchParams.set("limit", String(limit));
136
+ if (typeof request.limit === "number") {
137
+ url.searchParams.set("limit", String(request.limit));
131
138
  }
132
- if (typeof offset === "number") {
133
- url.searchParams.set("offset", String(offset));
139
+ if (typeof request.offset === "number") {
140
+ url.searchParams.set("offset", String(request.offset));
134
141
  }
142
+ applySortParams(url, request.sortBy, request.sortDirection);
143
+ return url.toString();
144
+ };
145
+ const createFolderInfoUrl = (apiBaseUrl, folderId) => {
146
+ const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/folders/${folderId}`);
147
+ url.searchParams.set("fields", "id,name,path_collection");
135
148
  return url.toString();
136
149
  };
137
- const createSearchUrl = (apiBaseUrl, query, fields, ancestorFolderId, limit, offset) => {
150
+ const createSearchUrl = (apiBaseUrl, fields, request) => {
138
151
  const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/search`);
139
- url.searchParams.set("query", query);
152
+ url.searchParams.set("query", request.query);
140
153
  url.searchParams.set("fields", fields.join(","));
141
- if (ancestorFolderId) {
142
- url.searchParams.set("ancestor_folder_ids", ancestorFolderId);
154
+ if (request.ancestorFolderId) {
155
+ url.searchParams.set("ancestor_folder_ids", request.ancestorFolderId);
143
156
  }
144
- if (typeof limit === "number") {
145
- url.searchParams.set("limit", String(limit));
157
+ if (typeof request.limit === "number") {
158
+ url.searchParams.set("limit", String(request.limit));
146
159
  }
147
- if (typeof offset === "number") {
148
- url.searchParams.set("offset", String(offset));
160
+ if (typeof request.offset === "number") {
161
+ url.searchParams.set("offset", String(request.offset));
162
+ }
163
+ // Box search only sorts by modified date (or relevance, its default); name
164
+ // and size sorts stay client concerns there, so only "date" maps through.
165
+ if (request.sortBy === "date") {
166
+ url.searchParams.set("sort", "modified_at");
167
+ url.searchParams.set("direction", (request.sortDirection ?? "DESC") === "ASC" ? "ASC" : "DESC");
149
168
  }
150
169
  return url.toString();
151
170
  };
@@ -178,13 +197,14 @@ export const createBoxExplorerTransport = (options = {}) => {
178
197
  const apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API_BASE_URL;
179
198
  const fields = options.fields ?? DEFAULT_FIELDS;
180
199
  const fetchImpl = resolveFetch(options.fetch);
200
+ const requestHeaders = (request) => ({
201
+ Authorization: `Bearer ${request.token}`,
202
+ "Accept-Language": request.language ?? "en-US",
203
+ });
181
204
  return {
182
205
  async loadFolderItems(request) {
183
- const response = await fetchImpl(createFolderItemsUrl(apiBaseUrl, request.folderId, fields, request.limit, request.offset), {
184
- headers: {
185
- Authorization: `Bearer ${request.token}`,
186
- "Accept-Language": request.language ?? "en-US",
187
- },
206
+ const response = await fetchImpl(createFolderItemsUrl(apiBaseUrl, request.folderId, fields, request), {
207
+ headers: requestHeaders(request),
188
208
  method: "GET",
189
209
  signal: request.signal,
190
210
  });
@@ -194,9 +214,34 @@ export const createBoxExplorerTransport = (options = {}) => {
194
214
  const payload = (await response.json());
195
215
  const rawEntries = payload.entries ?? [];
196
216
  const { entries } = normalizeItems(rawEntries);
197
- const folder = normalizeFolder(payload, request.folderId);
217
+ // The real `GET /folders/:id/items` payload carries no folder metadata
218
+ // (no id/name/path_collection), which would degrade the folder header
219
+ // and breadcrumbs to raw ids. When the first page lacks it, fetch
220
+ // `GET /folders/:id` for the metadata — best-effort: a failed info
221
+ // request keeps the listing usable rather than failing it.
222
+ let folderSource = payload;
223
+ const missingMetadata = payload.name === undefined || payload.id === undefined || payload.path_collection === undefined;
224
+ if (!request.offset && missingMetadata) {
225
+ const info = await Promise.resolve()
226
+ .then(() => fetchImpl(createFolderInfoUrl(apiBaseUrl, request.folderId), {
227
+ headers: requestHeaders(request),
228
+ method: "GET",
229
+ signal: request.signal,
230
+ }))
231
+ .then(infoResponse => infoResponse?.ok ? infoResponse.json() : null)
232
+ .catch(() => null);
233
+ if (info) {
234
+ folderSource = {
235
+ ...payload,
236
+ id: payload.id ?? info.id,
237
+ name: payload.name ?? info.name,
238
+ path_collection: payload.path_collection ?? info.path_collection,
239
+ };
240
+ }
241
+ }
242
+ const folder = normalizeFolder(folderSource, request.folderId);
198
243
  return {
199
- breadcrumbs: normalizeBreadcrumbs(payload, folder),
244
+ breadcrumbs: normalizeBreadcrumbs(folderSource, folder),
200
245
  folder,
201
246
  folderId: request.folderId,
202
247
  items: entries,
@@ -204,11 +249,8 @@ export const createBoxExplorerTransport = (options = {}) => {
204
249
  };
205
250
  },
206
251
  async searchItems(request) {
207
- const response = await fetchImpl(createSearchUrl(apiBaseUrl, request.query, fields, request.ancestorFolderId, request.limit, request.offset), {
208
- headers: {
209
- Authorization: `Bearer ${request.token}`,
210
- "Accept-Language": request.language ?? "en-US",
211
- },
252
+ const response = await fetchImpl(createSearchUrl(apiBaseUrl, fields, request), {
253
+ headers: requestHeaders(request),
212
254
  method: "GET",
213
255
  signal: request.signal,
214
256
  });
@@ -225,5 +267,55 @@ export const createBoxExplorerTransport = (options = {}) => {
225
267
  pagination: toPagination(payload, rawEntries.length, request),
226
268
  };
227
269
  },
270
+ async createFolder(request) {
271
+ const response = await fetchImpl(`${apiBaseUrl.replace(/\/$/, "")}/folders`, {
272
+ headers: { ...requestHeaders(request), "Content-Type": "application/json" },
273
+ method: "POST",
274
+ body: JSON.stringify({ name: request.name, parent: { id: request.parentFolderId } }),
275
+ signal: request.signal,
276
+ });
277
+ if (!response.ok) {
278
+ throw new Error(await getErrorMessage(response));
279
+ }
280
+ const payload = (await response.json());
281
+ const item = normalizeBoxExplorerItem(payload);
282
+ if (!item) {
283
+ throw new Error("Box API returned an unsupported item for the created folder");
284
+ }
285
+ return item;
286
+ },
287
+ async renameItem(request) {
288
+ const segment = request.itemType === "folder" ? "folders" : request.itemType === "web_link" ? "web_links" : "files";
289
+ const response = await fetchImpl(`${apiBaseUrl.replace(/\/$/, "")}/${segment}/${request.itemId}`, {
290
+ headers: { ...requestHeaders(request), "Content-Type": "application/json" },
291
+ method: "PUT",
292
+ body: JSON.stringify({ name: request.name }),
293
+ signal: request.signal,
294
+ });
295
+ if (!response.ok) {
296
+ throw new Error(await getErrorMessage(response));
297
+ }
298
+ const payload = (await response.json());
299
+ const item = normalizeBoxExplorerItem(payload);
300
+ if (!item) {
301
+ throw new Error("Box API returned an unsupported item for the rename");
302
+ }
303
+ return item;
304
+ },
305
+ async deleteItem(request) {
306
+ const segment = request.itemType === "folder" ? "folders" : request.itemType === "web_link" ? "web_links" : "files";
307
+ const url = new URL(`${apiBaseUrl.replace(/\/$/, "")}/${segment}/${request.itemId}`);
308
+ if (request.itemType === "folder") {
309
+ url.searchParams.set("recursive", "true");
310
+ }
311
+ const response = await fetchImpl(url.toString(), {
312
+ headers: requestHeaders(request),
313
+ method: "DELETE",
314
+ signal: request.signal,
315
+ });
316
+ if (!response.ok && response.status !== 204) {
317
+ throw new Error(await getErrorMessage(response));
318
+ }
319
+ },
228
320
  };
229
321
  };
@@ -1,4 +1,5 @@
1
- import { type ExplorerItemAction, type ExplorerItem, type ExplorerItemGesture, type ExplorerSelectionMode, type ExplorerState, type ExplorerTransport } from "./types.js";
1
+ import { ContentExplorerController } from "./controller.js";
2
+ import { type ExplorerItemAction, type ExplorerItem, type ExplorerItemGesture, type ExplorerSelectionMode, type ExplorerSortState, type ExplorerState, type ExplorerTransport } from "./types.js";
2
3
  import { BaseElement } from "../../core/index.js";
3
4
  export interface ContentExplorerTemplateContext {
4
5
  element: ContentExplorer;
@@ -73,6 +74,16 @@ export declare class ContentExplorer extends BaseElement {
73
74
  toggleSelection(itemId: string): void;
74
75
  clearSelection(): void;
75
76
  invokeItemAction(itemId: string, actionId: string): void;
77
+ setSort(sort: ExplorerSortState | null): Promise<void>;
78
+ createFolder(name: string): Promise<ExplorerItem | null>;
79
+ renameItem(itemId: string, name: string): Promise<ExplorerItem | null>;
80
+ deleteItem(itemId: string): Promise<boolean>;
81
+ /**
82
+ * The live session controller, for pairing the shell with the standalone
83
+ * adapter elements (`box-explorer-toolbar`, `box-explorer-breadcrumbs`, …)
84
+ * or host-chrome bindings. Null until connected.
85
+ */
86
+ get explorerController(): ContentExplorerController | null;
76
87
  private updateStringAttribute;
77
88
  private scheduleStart;
78
89
  private startController;
@@ -82,4 +93,5 @@ export declare class ContentExplorer extends BaseElement {
82
93
  private getFocusableItemIds;
83
94
  protected renderTemplate(): void;
84
95
  protected update(): void;
96
+ private isFocusInsideHost;
85
97
  }
@@ -386,6 +386,26 @@ export class ContentExplorer extends BaseElement {
386
386
  invokeItemAction(itemId, actionId) {
387
387
  this.controller?.invokeItemAction(itemId, actionId);
388
388
  }
389
+ async setSort(sort) {
390
+ await this.controller?.setSort(sort);
391
+ }
392
+ async createFolder(name) {
393
+ return (await this.controller?.createFolder(name)) ?? null;
394
+ }
395
+ async renameItem(itemId, name) {
396
+ return (await this.controller?.renameItem(itemId, name)) ?? null;
397
+ }
398
+ async deleteItem(itemId) {
399
+ return (await this.controller?.deleteItem(itemId)) ?? false;
400
+ }
401
+ /**
402
+ * The live session controller, for pairing the shell with the standalone
403
+ * adapter elements (`box-explorer-toolbar`, `box-explorer-breadcrumbs`, …)
404
+ * or host-chrome bindings. Null until connected.
405
+ */
406
+ get explorerController() {
407
+ return this.controller;
408
+ }
389
409
  updateStringAttribute(name, value) {
390
410
  if (value === null || value === "") {
391
411
  this.removeAttribute(name);
@@ -564,6 +584,7 @@ export class ContentExplorer extends BaseElement {
564
584
  "data-item-id": item.id,
565
585
  part: "item-action",
566
586
  type: "button",
587
+ ...(action.disabled ? { disabled: "", "aria-disabled": "true" } : {}),
567
588
  });
568
589
  return this.templatesValue.renderItemAction
569
590
  ? this.templatesValue.renderItemAction({
@@ -594,7 +615,7 @@ export class ContentExplorer extends BaseElement {
594
615
  itemButtonAttributes,
595
616
  state,
596
617
  })
597
- : `<li data-item-id="${item.id}" role="presentation">
618
+ : `<li data-item-id="${escapeHtml(item.id)}" role="presentation">
598
619
  <button ${itemButtonAttributes}><span part="item-name">${escapeHtml(item.name)}</span>${meta ? `<span part="item-meta">${escapeHtml(meta)}</span>` : ""}</button>
599
620
  ${actions ? `<div part="item-actions">${actions}</div>` : ""}
600
621
  </li>`;
@@ -615,13 +636,15 @@ export class ContentExplorer extends BaseElement {
615
636
  if (!host) {
616
637
  return;
617
638
  }
639
+ // Sample focus BEFORE the rebuild wipes the focused node.
640
+ const hadFocusInside = this.isFocusInsideHost();
618
641
  host.innerHTML = `
619
642
  <section aria-busy="${state?.loading ? "true" : "false"}">
620
643
  ${folderMarkup}
621
644
  <p part="status" data-status="${statusText}" role="status" aria-live="polite">${statusText}</p>
622
645
  ${errorMarkup}
623
646
  <button type="button" part="refresh" aria-label="Refresh items" ${refreshDisabled}>Refresh</button>
624
- <ul part="items" role="listbox" aria-label="Explorer items">${itemsMarkup}</ul>
647
+ <ul part="items" role="listbox" aria-label="Explorer items" aria-multiselectable="${(this.selectionMode ?? "multiple") === "multiple" ? "true" : "false"}">${itemsMarkup}</ul>
625
648
  ${loadMoreMarkup ? `<div part="load-more-region">${loadMoreMarkup}</div>` : ""}
626
649
  </section>
627
650
  `;
@@ -727,13 +750,23 @@ export class ContentExplorer extends BaseElement {
727
750
  }
728
751
  });
729
752
  });
730
- if (this.focusItemId) {
753
+ // Restore focus only when it already lived inside this element before the
754
+ // rebuild — an unrelated state update (loading tick, selection change from
755
+ // the API) must never steal focus from elsewhere on the page.
756
+ if (this.focusItemId && hadFocusInside) {
731
757
  queueMicrotask(() => {
732
758
  const target = Array.from(this.shadowRoot?.querySelectorAll('[part="item"]') ?? []).find(node => node.dataset.itemId === this.focusItemId);
733
759
  target?.focus();
734
760
  });
735
761
  }
736
762
  }
763
+ isFocusInsideHost() {
764
+ const active = document.activeElement;
765
+ if (!active) {
766
+ return false;
767
+ }
768
+ return active === this || (this.shadowRoot?.contains(active) ?? false);
769
+ }
737
770
  }
738
771
  SearchResultsHeader.register();
739
772
  ContentExplorer.register();
@@ -1,4 +1,4 @@
1
- import type { ExplorerFetchLike, ExplorerSearchResult, ExplorerTransport, ExplorerTransportResult } from "./types.js";
1
+ import type { ExplorerFetchLike, ExplorerItem, ExplorerSearchResult, ExplorerSortBy, ExplorerSortDirection, ExplorerTransport, ExplorerTransportResult } from "./types.js";
2
2
  export interface ContentExplorerRequestContext {
3
3
  locale?: string;
4
4
  requestId?: string;
@@ -8,6 +8,8 @@ export interface ContentExplorerListFolderInput {
8
8
  folderId: string;
9
9
  limit?: number;
10
10
  offset?: number;
11
+ sortBy?: ExplorerSortBy;
12
+ sortDirection?: ExplorerSortDirection;
11
13
  context?: ContentExplorerRequestContext;
12
14
  }
13
15
  export interface ContentExplorerSearchInput {
@@ -15,11 +17,32 @@ export interface ContentExplorerSearchInput {
15
17
  ancestorFolderId?: string;
16
18
  limit?: number;
17
19
  offset?: number;
20
+ sortBy?: ExplorerSortBy;
21
+ sortDirection?: ExplorerSortDirection;
22
+ context?: ContentExplorerRequestContext;
23
+ }
24
+ export interface ContentExplorerCreateFolderInput {
25
+ parentFolderId: string;
26
+ name: string;
27
+ context?: ContentExplorerRequestContext;
28
+ }
29
+ export interface ContentExplorerRenameItemInput {
30
+ itemId: string;
31
+ itemType: ExplorerItem["type"];
32
+ name: string;
33
+ context?: ContentExplorerRequestContext;
34
+ }
35
+ export interface ContentExplorerDeleteItemInput {
36
+ itemId: string;
37
+ itemType: ExplorerItem["type"];
18
38
  context?: ContentExplorerRequestContext;
19
39
  }
20
40
  export interface ContentExplorerDataSource {
21
41
  listFolderItems(input: ContentExplorerListFolderInput): Promise<ExplorerTransportResult>;
22
42
  search?(input: ContentExplorerSearchInput): Promise<ExplorerSearchResult>;
43
+ createFolder?(input: ContentExplorerCreateFolderInput): Promise<ExplorerItem>;
44
+ renameItem?(input: ContentExplorerRenameItemInput): Promise<ExplorerItem>;
45
+ deleteItem?(input: ContentExplorerDeleteItemInput): Promise<void>;
23
46
  }
24
47
  export interface ContentExplorerHttpDataSourceOptions {
25
48
  baseUrl?: string;
@@ -13,15 +13,22 @@ const resolveUrlBase = () => {
13
13
  }
14
14
  return "http://localhost";
15
15
  };
16
- const defaultListFolderUrl = (baseUrl, input) => {
17
- const root = baseUrl.replace(/\/$/, "");
18
- const url = new URL(`${root}/folders/${encodeURIComponent(input.folderId)}/items`, resolveUrlBase());
16
+ const applyPageAndSortParams = (url, input) => {
19
17
  if (typeof input.limit === "number") {
20
18
  url.searchParams.set("limit", String(input.limit));
21
19
  }
22
20
  if (typeof input.offset === "number") {
23
21
  url.searchParams.set("offset", String(input.offset));
24
22
  }
23
+ if (input.sortBy) {
24
+ url.searchParams.set("sortBy", input.sortBy);
25
+ url.searchParams.set("sortDirection", input.sortDirection ?? "ASC");
26
+ }
27
+ };
28
+ const defaultListFolderUrl = (baseUrl, input) => {
29
+ const root = baseUrl.replace(/\/$/, "");
30
+ const url = new URL(`${root}/folders/${encodeURIComponent(input.folderId)}/items`, resolveUrlBase());
31
+ applyPageAndSortParams(url, input);
25
32
  return url.toString();
26
33
  };
27
34
  const defaultSearchUrl = (baseUrl, input) => {
@@ -31,12 +38,7 @@ const defaultSearchUrl = (baseUrl, input) => {
31
38
  if (input.ancestorFolderId) {
32
39
  url.searchParams.set("ancestorFolderId", input.ancestorFolderId);
33
40
  }
34
- if (typeof input.limit === "number") {
35
- url.searchParams.set("limit", String(input.limit));
36
- }
37
- if (typeof input.offset === "number") {
38
- url.searchParams.set("offset", String(input.offset));
39
- }
41
+ applyPageAndSortParams(url, input);
40
42
  return url.toString();
41
43
  };
42
44
  const getErrorMessage = async (response) => {
@@ -58,6 +60,8 @@ export const createExplorerTransportFromDataSource = (dataSource) => {
58
60
  folderId: request.folderId,
59
61
  limit: request.limit,
60
62
  offset: request.offset,
63
+ sortBy: request.sortBy,
64
+ sortDirection: request.sortDirection,
61
65
  context: {
62
66
  locale: request.language,
63
67
  signal: request.signal,
@@ -72,35 +76,69 @@ export const createExplorerTransportFromDataSource = (dataSource) => {
72
76
  ancestorFolderId: request.ancestorFolderId,
73
77
  limit: request.limit,
74
78
  offset: request.offset,
79
+ sortBy: request.sortBy,
80
+ sortDirection: request.sortDirection,
75
81
  context: {
76
82
  locale: request.language,
77
83
  signal: request.signal,
78
84
  },
79
85
  });
80
86
  }
87
+ if (dataSource.createFolder) {
88
+ const createFolder = dataSource.createFolder.bind(dataSource);
89
+ transport.createFolder = request => createFolder({
90
+ parentFolderId: request.parentFolderId,
91
+ name: request.name,
92
+ context: { locale: request.language, signal: request.signal },
93
+ });
94
+ }
95
+ if (dataSource.renameItem) {
96
+ const renameItem = dataSource.renameItem.bind(dataSource);
97
+ transport.renameItem = request => renameItem({
98
+ itemId: request.itemId,
99
+ itemType: request.itemType,
100
+ name: request.name,
101
+ context: { locale: request.language, signal: request.signal },
102
+ });
103
+ }
104
+ if (dataSource.deleteItem) {
105
+ const deleteItem = dataSource.deleteItem.bind(dataSource);
106
+ transport.deleteItem = request => deleteItem({
107
+ itemId: request.itemId,
108
+ itemType: request.itemType,
109
+ context: { locale: request.language, signal: request.signal },
110
+ });
111
+ }
81
112
  return transport;
82
113
  };
83
114
  export const createHttpContentExplorerDataSource = (options = {}) => {
84
115
  const fetchImpl = resolveFetch(options.fetch);
85
116
  const baseUrl = options.baseUrl ?? "/api/content-explorer";
86
- const requestJson = async (url, input) => {
117
+ const request = async (url, input, init = { method: "GET" }) => {
87
118
  const response = await fetchImpl(url, {
88
- method: "GET",
119
+ method: init.method,
89
120
  credentials: "same-origin",
90
121
  cache: "no-store",
91
122
  headers: {
92
123
  accept: "application/json",
124
+ ...(init.body !== undefined ? { "content-type": "application/json" } : {}),
93
125
  ...(input.context?.locale ? { "accept-language": input.context.locale } : {}),
94
126
  ...(input.context?.requestId ? { "x-request-id": input.context.requestId } : {}),
95
127
  ...options.headers,
96
128
  },
129
+ ...(init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),
97
130
  signal: input.context?.signal,
98
131
  });
99
132
  if (!response.ok) {
100
133
  throw new Error(await getErrorMessage(response));
101
134
  }
135
+ return response;
136
+ };
137
+ const requestJson = async (url, input, init) => {
138
+ const response = await request(url, input, init);
102
139
  return (await response.json());
103
140
  };
141
+ const root = baseUrl.replace(/\/$/, "");
104
142
  return {
105
143
  async listFolderItems(input) {
106
144
  const url = options.buildListFolderUrl?.(input) ?? defaultListFolderUrl(baseUrl, input);
@@ -110,5 +148,24 @@ export const createHttpContentExplorerDataSource = (options = {}) => {
110
148
  const url = options.buildSearchUrl?.(input) ?? defaultSearchUrl(baseUrl, input);
111
149
  return requestJson(url, input);
112
150
  },
151
+ async createFolder(input) {
152
+ const url = new URL(`${root}/folders`, resolveUrlBase()).toString();
153
+ return requestJson(url, input, {
154
+ method: "POST",
155
+ body: { parentFolderId: input.parentFolderId, name: input.name },
156
+ });
157
+ },
158
+ async renameItem(input) {
159
+ const url = new URL(`${root}/items/${encodeURIComponent(input.itemId)}`, resolveUrlBase()).toString();
160
+ return requestJson(url, input, {
161
+ method: "PUT",
162
+ body: { itemType: input.itemType, name: input.name },
163
+ });
164
+ },
165
+ async deleteItem(input) {
166
+ const url = new URL(`${root}/items/${encodeURIComponent(input.itemId)}`, resolveUrlBase());
167
+ url.searchParams.set("itemType", input.itemType);
168
+ await request(url.toString(), input, { method: "DELETE" });
169
+ },
113
170
  };
114
171
  };
@@ -1,8 +1,9 @@
1
1
  import { Controller } from "../../core/controller.js";
2
- import type { ExplorerEvents, ExplorerItem, ExplorerSelectionMode, ExplorerSessionConfig, ExplorerState } from "./types.js";
2
+ import type { ExplorerEvents, ExplorerItem, ExplorerSelectionMode, ExplorerSessionConfig, ExplorerSortBy, ExplorerSortDirection, ExplorerSortState, ExplorerState } from "./types.js";
3
3
  export declare class ContentExplorerController extends Controller<ExplorerState, ExplorerEvents> {
4
4
  readonly config: ExplorerSessionConfig;
5
5
  private activeLoadRequestId;
6
+ private activeLoadAbortController;
6
7
  private readonly actionsController;
7
8
  private readonly collectionController;
8
9
  private readonly navigationController;
@@ -24,6 +25,20 @@ export declare class ContentExplorerController extends Controller<ExplorerState,
24
25
  getItemActions(itemId: string): import("./types.js").ExplorerItemAction[];
25
26
  invokeItemAction(itemId: string, actionId: string): void;
26
27
  activateItem(itemId: string): Promise<void>;
28
+ /**
29
+ * Set (or clear) the collection sort and reload from the first page. The
30
+ * transport receives the sort with every request; passing `null` restores
31
+ * the server's default ordering.
32
+ */
33
+ setSort(sort: ExplorerSortState | null): Promise<void>;
34
+ setSort(sortBy: ExplorerSortBy, direction: ExplorerSortDirection): Promise<void>;
35
+ /** Create a folder in the current folder, then refresh the collection. */
36
+ createFolder(name: string): Promise<ExplorerItem | null>;
37
+ /** Rename an item in the current collection, then refresh. */
38
+ renameItem(itemId: string, name: string): Promise<ExplorerItem | null>;
39
+ /** Delete an item from the current collection, then refresh. */
40
+ deleteItem(itemId: string): Promise<boolean>;
41
+ private runMutation;
27
42
  reload(): Promise<void>;
28
43
  refresh(): Promise<void>;
29
44
  loadNextPage(): Promise<void>;