@unofficialbox/box-open-elements 0.18.0 → 0.20.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 (217) hide show
  1. package/dist/components/actions/button-group.js +6 -0
  2. package/dist/components/actions/button.js +6 -0
  3. package/dist/components/actions/icon-button.js +6 -0
  4. package/dist/components/actions/link-button.js +6 -0
  5. package/dist/components/actions/menu-item.js +6 -0
  6. package/dist/components/actions/menu.js +6 -0
  7. package/dist/components/actions/segmented-control.js +6 -0
  8. package/dist/components/actions/toolbar.js +6 -0
  9. package/dist/components/collections/card.js +6 -0
  10. package/dist/components/collections/carousel.js +6 -0
  11. package/dist/components/collections/check-list.d.ts +10 -0
  12. package/dist/components/collections/check-list.js +8 -0
  13. package/dist/components/collections/datalist-item.js +6 -0
  14. package/dist/components/collections/document-list.d.ts +10 -0
  15. package/dist/components/collections/document-list.js +8 -0
  16. package/dist/components/collections/draggable-list.js +6 -0
  17. package/dist/components/collections/fact-list.d.ts +10 -0
  18. package/dist/components/collections/fact-list.js +8 -0
  19. package/dist/components/collections/grid-view.js +6 -0
  20. package/dist/components/collections/pagination.js +6 -0
  21. package/dist/components/collections/result-blocks.d.ts +54 -0
  22. package/dist/components/collections/result-blocks.js +71 -0
  23. package/dist/components/collections/table.js +6 -0
  24. package/dist/components/collections/thumbnail-card.js +6 -0
  25. package/dist/components/collections/tree-grid.js +6 -0
  26. package/dist/components/collections/tree.js +6 -0
  27. package/dist/components/feedback/alert.js +1 -1
  28. package/dist/components/feedback/badge.js +1 -1
  29. package/dist/components/feedback/badgeable.js +6 -0
  30. package/dist/components/feedback/chip.js +6 -0
  31. package/dist/components/feedback/due-badge.js +6 -0
  32. package/dist/components/feedback/empty-state.js +6 -0
  33. package/dist/components/feedback/error-mask.js +6 -0
  34. package/dist/components/feedback/help-text.js +6 -0
  35. package/dist/components/feedback/indicator.js +1 -1
  36. package/dist/components/feedback/nudge.js +6 -0
  37. package/dist/components/feedback/path.js +6 -0
  38. package/dist/components/feedback/progress-bar.d.ts +9 -0
  39. package/dist/components/feedback/progress-bar.js +35 -3
  40. package/dist/components/feedback/progress-ring.js +6 -0
  41. package/dist/components/feedback/progress-steps.js +6 -0
  42. package/dist/components/feedback/skeleton.js +6 -0
  43. package/dist/components/feedback/spinner.js +6 -0
  44. package/dist/components/feedback/status-icon.d.ts +10 -0
  45. package/dist/components/feedback/status-icon.js +26 -0
  46. package/dist/components/feedback/toast.js +1 -1
  47. package/dist/components/files/drop-zone.js +1 -1
  48. package/dist/components/forms/calendar.js +6 -0
  49. package/dist/components/forms/category-selector.js +6 -0
  50. package/dist/components/forms/checkbox-group.js +6 -0
  51. package/dist/components/forms/checkbox.js +6 -0
  52. package/dist/components/forms/color-picker.js +6 -0
  53. package/dist/components/forms/combobox.js +6 -0
  54. package/dist/components/forms/date-field.js +6 -0
  55. package/dist/components/forms/dropdown.js +6 -0
  56. package/dist/components/forms/dual-listbox.js +6 -0
  57. package/dist/components/forms/fieldset.js +6 -0
  58. package/dist/components/forms/multi-select.js +6 -0
  59. package/dist/components/forms/number-input.js +6 -0
  60. package/dist/components/forms/pill-cloud.js +6 -0
  61. package/dist/components/forms/pill-selector-dropdown.js +6 -0
  62. package/dist/components/forms/radio-group.js +6 -0
  63. package/dist/components/forms/range-slider.js +6 -0
  64. package/dist/components/forms/rating.js +6 -0
  65. package/dist/components/forms/rich-text-input.js +6 -0
  66. package/dist/components/forms/search-field.js +6 -0
  67. package/dist/components/forms/select.js +6 -0
  68. package/dist/components/forms/slider.js +6 -0
  69. package/dist/components/forms/spin-button.js +6 -0
  70. package/dist/components/forms/switch.js +6 -0
  71. package/dist/components/forms/tag-input.js +6 -0
  72. package/dist/components/forms/text-area.js +6 -0
  73. package/dist/components/forms/text-field.js +6 -0
  74. package/dist/components/forms/tile-group.js +1 -1
  75. package/dist/components/forms/time-field.js +6 -0
  76. package/dist/components/identity/avatar.js +6 -0
  77. package/dist/components/identity/contact-datalist-item.js +6 -0
  78. package/dist/components/identity/persona.js +6 -0
  79. package/dist/components/layout/app-shell.js +6 -0
  80. package/dist/components/layout/divider.js +6 -0
  81. package/dist/components/layout/grid.js +1 -1
  82. package/dist/components/layout/nav-sidebar.js +6 -0
  83. package/dist/components/layout/section.js +6 -0
  84. package/dist/components/layout/sidebar-toggle-button.js +6 -0
  85. package/dist/components/layout/split-view.js +6 -0
  86. package/dist/components/navigation/accordion.js +6 -0
  87. package/dist/components/navigation/breadcrumb.js +6 -0
  88. package/dist/components/navigation/tabs.js +6 -0
  89. package/dist/components/output/code-block.js +1 -1
  90. package/dist/components/output/formatted-value.js +1 -1
  91. package/dist/components/overlays/command-palette.js +6 -0
  92. package/dist/components/overlays/context-menu.js +6 -0
  93. package/dist/components/overlays/guide-tooltip.js +6 -0
  94. package/dist/components/overlays/popover.js +6 -0
  95. package/dist/components/overlays/shortcuts-overlay.js +6 -0
  96. package/dist/components/overlays/tooltip.js +6 -0
  97. package/dist/components/visuals/illustration.js +6 -0
  98. package/dist/element-maps.d.ts +39 -19
  99. package/dist/entries/agent-workspace.d.ts +1 -0
  100. package/dist/entries/agent-workspace.js +1 -0
  101. package/dist/entries/call-console.d.ts +1 -0
  102. package/dist/entries/call-console.js +1 -0
  103. package/dist/entries/check-list.d.ts +1 -0
  104. package/dist/entries/check-list.js +1 -0
  105. package/dist/entries/document-list.d.ts +1 -0
  106. package/dist/entries/document-list.js +1 -0
  107. package/dist/entries/fact-list.d.ts +1 -0
  108. package/dist/entries/fact-list.js +1 -0
  109. package/dist/entries/result-blocks.d.ts +1 -0
  110. package/dist/entries/result-blocks.js +1 -0
  111. package/dist/entries/run-summary.d.ts +1 -0
  112. package/dist/entries/run-summary.js +1 -0
  113. package/dist/entries/status-icon.d.ts +1 -0
  114. package/dist/entries/status-icon.js +1 -0
  115. package/dist/foundations/motion/behaviors.d.ts +8 -0
  116. package/dist/foundations/motion/behaviors.js +24 -0
  117. package/dist/foundations/motion/index.d.ts +2 -0
  118. package/dist/foundations/motion/index.js +2 -0
  119. package/dist/foundations/motion/scroll-pin.d.ts +19 -0
  120. package/dist/foundations/motion/scroll-pin.js +60 -0
  121. package/dist/foundations/motion/tokens.d.ts +2 -0
  122. package/dist/foundations/motion/tokens.js +2 -0
  123. package/dist/foundations/profiles/registry.d.ts +3 -0
  124. package/dist/foundations/profiles/registry.js +3 -0
  125. package/dist/foundations/profiles/types.d.ts +3 -0
  126. package/dist/foundations/status/index.d.ts +6 -0
  127. package/dist/foundations/status/index.js +33 -0
  128. package/dist/index.d.ts +10 -0
  129. package/dist/index.js +10 -0
  130. package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
  131. package/dist/patterns/agent-chat/agent-chat.js +141 -21
  132. package/dist/patterns/agent-chat/controller.d.ts +3 -1
  133. package/dist/patterns/agent-chat/controller.js +92 -16
  134. package/dist/patterns/agent-chat/index.d.ts +1 -0
  135. package/dist/patterns/agent-chat/index.js +1 -0
  136. package/dist/patterns/agent-chat/ndjson.d.ts +5 -0
  137. package/dist/patterns/agent-chat/ndjson.js +41 -0
  138. package/dist/patterns/agent-chat/types.d.ts +44 -1
  139. package/dist/patterns/agent-workspace/agent-workspace.d.ts +28 -0
  140. package/dist/patterns/agent-workspace/agent-workspace.js +215 -0
  141. package/dist/patterns/agent-workspace/controller.d.ts +38 -0
  142. package/dist/patterns/agent-workspace/controller.js +65 -0
  143. package/dist/patterns/agent-workspace/index.d.ts +2 -0
  144. package/dist/patterns/agent-workspace/index.js +2 -0
  145. package/dist/patterns/audit/activity-density.js +6 -0
  146. package/dist/patterns/audit/audit-log.js +6 -0
  147. package/dist/patterns/call-console/call-console.d.ts +17 -0
  148. package/dist/patterns/call-console/call-console.js +107 -0
  149. package/dist/patterns/call-console/controller.d.ts +23 -0
  150. package/dist/patterns/call-console/controller.js +50 -0
  151. package/dist/patterns/call-console/http-transport.d.ts +3 -0
  152. package/dist/patterns/call-console/http-transport.js +26 -0
  153. package/dist/patterns/call-console/index.d.ts +4 -0
  154. package/dist/patterns/call-console/index.js +4 -0
  155. package/dist/patterns/call-console/types.d.ts +32 -0
  156. package/dist/patterns/call-console/types.js +8 -0
  157. package/dist/patterns/comments/comment-thread.js +6 -0
  158. package/dist/patterns/content-explorer/adapters/action-menu.js +6 -0
  159. package/dist/patterns/content-explorer/adapters/breadcrumbs.js +6 -0
  160. package/dist/patterns/content-explorer/adapters/list.js +6 -0
  161. package/dist/patterns/content-explorer/adapters/table.js +6 -0
  162. package/dist/patterns/content-explorer/adapters/toolbar.js +6 -0
  163. package/dist/patterns/content-explorer/content-explorer.js +6 -0
  164. package/dist/patterns/content-picker/content-picker.js +6 -0
  165. package/dist/patterns/content-sidebar/content-sidebar.js +6 -0
  166. package/dist/patterns/content-uploader/content-uploader.js +8 -1
  167. package/dist/patterns/diff/compare-view.js +6 -0
  168. package/dist/patterns/diff/diff-viewer.js +6 -0
  169. package/dist/patterns/file-request/file-request-builder.js +6 -0
  170. package/dist/patterns/form-wizard/form-wizard.js +6 -0
  171. package/dist/patterns/form-wizard/wizard-summary.js +6 -0
  172. package/dist/patterns/governance/governance-panel.js +6 -0
  173. package/dist/patterns/insights/bar-chart.js +6 -0
  174. package/dist/patterns/insights/chart-panel.js +6 -0
  175. package/dist/patterns/insights/donut-chart.js +6 -0
  176. package/dist/patterns/insights/line-chart.js +6 -0
  177. package/dist/patterns/insights/metric-card.js +6 -0
  178. package/dist/patterns/item/bulk-action-bar.js +6 -0
  179. package/dist/patterns/item/item-details-panel.js +6 -0
  180. package/dist/patterns/item/item-form.js +6 -0
  181. package/dist/patterns/item/preview-header.js +6 -0
  182. package/dist/patterns/lineage/lineage-graph.js +6 -0
  183. package/dist/patterns/lineage/provenance-strip.js +6 -0
  184. package/dist/patterns/metadata/metadata-filter-builder.js +6 -0
  185. package/dist/patterns/metadata/metadata-inspector.js +6 -0
  186. package/dist/patterns/notifications/notification-bell.js +6 -0
  187. package/dist/patterns/notifications/notification-inbox.js +6 -0
  188. package/dist/patterns/preview/annotation-inspector.js +6 -0
  189. package/dist/patterns/preview/annotation-toolbar.js +6 -0
  190. package/dist/patterns/preview/preview-element.js +6 -0
  191. package/dist/patterns/run/index.d.ts +2 -0
  192. package/dist/patterns/run/index.js +2 -0
  193. package/dist/patterns/run/progress.d.ts +24 -0
  194. package/dist/patterns/run/progress.js +32 -0
  195. package/dist/patterns/run/run-summary.d.ts +21 -0
  196. package/dist/patterns/run/run-summary.js +82 -0
  197. package/dist/patterns/run/run-trace.d.ts +1 -0
  198. package/dist/patterns/run/run-trace.js +20 -3
  199. package/dist/patterns/search/filter-bar.js +6 -0
  200. package/dist/patterns/search/saved-view-picker.js +6 -0
  201. package/dist/patterns/search/search-results-header.js +6 -0
  202. package/dist/patterns/share/access-stats.js +6 -0
  203. package/dist/patterns/share/collaborator-avatars.js +6 -0
  204. package/dist/patterns/share/invite-collaborators-modal.js +6 -0
  205. package/dist/patterns/share/permission-matrix.js +6 -0
  206. package/dist/patterns/share/presence.js +6 -0
  207. package/dist/patterns/share/share-panel.js +6 -0
  208. package/dist/patterns/share/unified-share-modal.js +6 -0
  209. package/dist/patterns/signature/signature-ceremony.js +6 -0
  210. package/dist/patterns/task/review-queue-item.js +6 -0
  211. package/dist/patterns/task/task-assignment-panel.js +6 -0
  212. package/dist/patterns/timeline/timeline.js +6 -0
  213. package/dist/patterns/versions/version-graph.js +6 -0
  214. package/dist/patterns/versions/version-list.js +6 -0
  215. package/dist/patterns/work-queue/work-queue.js +6 -0
  216. package/dist/patterns/work-queue/workload-board.js +6 -0
  217. package/package.json +13 -1
@@ -0,0 +1,60 @@
1
+ /** Scroll following is changed by user intent, never by resize-induced scroll events. */
2
+ export class ScrollPinController {
3
+ viewport;
4
+ onChange;
5
+ pinned = true;
6
+ cleanup = [];
7
+ observer;
8
+ pointer = false;
9
+ lastTop = 0;
10
+ touchY = 0;
11
+ constructor(viewport, onChange = () => { }) {
12
+ this.viewport = viewport;
13
+ this.onChange = onChange;
14
+ }
15
+ connect(content = this.viewport) {
16
+ this.disconnect();
17
+ const on = (kind, fn) => {
18
+ this.viewport.addEventListener(kind, fn);
19
+ this.cleanup.push(() => this.viewport.removeEventListener(kind, fn));
20
+ };
21
+ on("wheel", e => { if (e.deltaY < 0)
22
+ this.unpin(); });
23
+ on("keydown", e => { if (["ArrowUp", "PageUp", "Home"].includes(e.key))
24
+ this.unpin(); });
25
+ on("touchstart", e => { this.touchY = e.touches[0]?.clientY ?? 0; });
26
+ on("touchmove", e => { const y = e.touches[0]?.clientY ?? this.touchY; if (y > this.touchY)
27
+ this.unpin(); this.touchY = y; });
28
+ on("pointerdown", () => { this.pointer = true; this.lastTop = this.viewport.scrollTop; });
29
+ const release = () => { this.pointer = false; };
30
+ this.viewport.ownerDocument.addEventListener("pointerup", release);
31
+ this.cleanup.push(() => this.viewport.ownerDocument.removeEventListener("pointerup", release));
32
+ on("scroll", () => {
33
+ if (this.pointer && this.viewport.scrollTop < this.lastTop)
34
+ this.unpin();
35
+ this.lastTop = this.viewport.scrollTop;
36
+ if (this.atBottom())
37
+ this.pinned = true;
38
+ this.notify();
39
+ });
40
+ if (typeof ResizeObserver !== "undefined") {
41
+ this.observer = new ResizeObserver(() => this.contentChanged());
42
+ this.observer.observe(this.viewport);
43
+ if (content !== this.viewport)
44
+ this.observer.observe(content);
45
+ }
46
+ }
47
+ atBottom() { return this.viewport.scrollHeight - this.viewport.clientHeight - this.viewport.scrollTop < 40; }
48
+ notify() { this.onChange(!this.pinned && !this.atBottom()); }
49
+ unpin() { this.pinned = false; this.notify(); }
50
+ contentChanged(newMessage = false) { if (newMessage)
51
+ this.pinned = true; if (this.pinned)
52
+ this.viewport.scrollTop = this.viewport.scrollHeight; this.notify(); }
53
+ jump() {
54
+ this.pinned = true;
55
+ const reduced = this.viewport.ownerDocument.defaultView?.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
56
+ this.viewport.scrollTo?.({ top: this.viewport.scrollHeight, behavior: reduced ? "instant" : "smooth" });
57
+ this.onChange(false);
58
+ }
59
+ disconnect() { this.cleanup.forEach(fn => fn()); this.cleanup = []; this.observer?.disconnect(); }
60
+ }
@@ -6,6 +6,8 @@
6
6
  * prefers-reduced-motion policy is one place to update.
7
7
  */
8
8
  export declare const boeMotionDuration: {
9
+ readonly arrival: "var(--boe-profile-motion-arrival, 200ms)";
10
+ readonly panel: "var(--boe-profile-motion-panel, 320ms)";
9
11
  /** Quick accent transitions (charts, micro-feedback) */
10
12
  readonly fast: "var(--boe-profile-motion-fast, 120ms)";
11
13
  /**
@@ -6,6 +6,8 @@
6
6
  * prefers-reduced-motion policy is one place to update.
7
7
  */
8
8
  export const boeMotionDuration = {
9
+ arrival: "var(--boe-profile-motion-arrival, 200ms)",
10
+ panel: "var(--boe-profile-motion-panel, 320ms)",
9
11
  /** Quick accent transitions (charts, micro-feedback) */
10
12
  fast: "var(--boe-profile-motion-fast, 120ms)",
11
13
  /**
@@ -52,6 +52,9 @@ export declare const DESIGN_PROFILE_VARIABLES: {
52
52
  readonly primaryFocus: "--boe-profile-shadow-primary-focus";
53
53
  };
54
54
  readonly motion: {
55
+ readonly arrival: "--boe-profile-motion-arrival";
56
+ readonly panel: "--boe-profile-motion-panel";
57
+ readonly stagger: "--boe-profile-motion-stagger";
55
58
  readonly fast: "--boe-profile-motion-fast";
56
59
  readonly interactive: "--boe-profile-motion-interactive";
57
60
  readonly medium: "--boe-profile-motion-medium";
@@ -51,6 +51,9 @@ export const DESIGN_PROFILE_VARIABLES = {
51
51
  primaryFocus: "--boe-profile-shadow-primary-focus",
52
52
  },
53
53
  motion: {
54
+ arrival: "--boe-profile-motion-arrival",
55
+ panel: "--boe-profile-motion-panel",
56
+ stagger: "--boe-profile-motion-stagger",
54
57
  fast: "--boe-profile-motion-fast",
55
58
  interactive: "--boe-profile-motion-interactive",
56
59
  medium: "--boe-profile-motion-medium",
@@ -50,6 +50,9 @@ export interface DesignProfileElevation {
50
50
  primaryFocus?: string;
51
51
  }
52
52
  export interface DesignProfileMotion {
53
+ arrival?: string;
54
+ panel?: string;
55
+ stagger?: string;
53
56
  fast?: string;
54
57
  interactive?: string;
55
58
  medium?: string;
@@ -0,0 +1,6 @@
1
+ export type StatusKind = "pending" | "active" | "done" | "warning" | "failed" | "skipped";
2
+ export declare const toStatusKind: (status: string) => StatusKind;
3
+ export declare const statusLabel: (kind: StatusKind) => string;
4
+ /** Decorative markup. Pair with visible words or the StatusIcon component. */
5
+ export declare const boeStatusGlyph: (kind: StatusKind) => string;
6
+ export declare const boeStatusStyles = "\n.boe-status { flex: none; vertical-align: middle; fill: none; stroke: currentColor; stroke-width: 1.5; }\n.boe-status[data-kind=\"active\"] { color: var(--boe-token-surface-surface-brand, #0061d5); animation: boe-status-spin .8s linear infinite; }\n.boe-status[data-kind=\"active\"] circle { opacity: .25; }\n.boe-status[data-kind=\"done\"] { color: var(--boe-token-text-status-text-success, #187657); }\n.boe-status[data-kind=\"warning\"] { color: var(--boe-token-text-status-text-warning, #946400); }\n.boe-status[data-kind=\"failed\"] { color: var(--boe-token-text-status-text-error, #c52a46); }\n.boe-status[data-kind=\"skipped\"] circle { stroke-dasharray: 2 2; }\n.boe-status:is([data-kind=\"done\"], [data-kind=\"warning\"], [data-kind=\"failed\"]) circle { fill: currentColor; }\n.boe-status:is([data-kind=\"done\"], [data-kind=\"warning\"], [data-kind=\"failed\"]) path { stroke: var(--boe-token-surface-surface, #fff); stroke-linecap: round; }\n.boe-status[data-kind=\"done\"] { animation: boe-pop 200ms ease-out both; }\n.boe-status[data-kind=\"done\"] path { stroke-dasharray: 20; animation: boe-status-check 200ms ease-out both; }\n";
@@ -0,0 +1,33 @@
1
+ const mappings = {
2
+ pending: "pending", info: "pending", active: "active", running: "active", in_progress: "active",
3
+ done: "done", succeeded: "done", completed: "done", pass: "done", approved: "done",
4
+ warning: "warning", warn: "warning", failed: "failed", fail: "failed", error: "failed",
5
+ skipped: "skipped", rejected: "skipped",
6
+ };
7
+ export const toStatusKind = (status) => mappings[status] ?? "pending";
8
+ export const statusLabel = (kind) => ({
9
+ pending: "Pending", active: "In progress", done: "Done", warning: "Done with a warning",
10
+ failed: "Failed", skipped: "Skipped",
11
+ })[kind];
12
+ /** Decorative markup. Pair with visible words or the StatusIcon component. */
13
+ export const boeStatusGlyph = (kind) => {
14
+ const state = toStatusKind(kind);
15
+ const path = state === "done" ? '<path d="m4 8 3 3 5-6"/>'
16
+ : state === "failed" ? '<path d="m5 5 6 6m0-6-6 6"/>'
17
+ : state === "warning" ? '<path d="M5 8h6"/>'
18
+ : state === "active" ? '<path d="M8 1a7 7 0 0 1 7 7"/>' : "";
19
+ return `<svg class="boe-status" data-kind="${state}" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><circle cx="8" cy="8" r="7"/>${path}</svg>`;
20
+ };
21
+ export const boeStatusStyles = `
22
+ .boe-status { flex: none; vertical-align: middle; fill: none; stroke: currentColor; stroke-width: 1.5; }
23
+ .boe-status[data-kind="active"] { color: var(--boe-token-surface-surface-brand, #0061d5); animation: boe-status-spin .8s linear infinite; }
24
+ .boe-status[data-kind="active"] circle { opacity: .25; }
25
+ .boe-status[data-kind="done"] { color: var(--boe-token-text-status-text-success, #187657); }
26
+ .boe-status[data-kind="warning"] { color: var(--boe-token-text-status-text-warning, #946400); }
27
+ .boe-status[data-kind="failed"] { color: var(--boe-token-text-status-text-error, #c52a46); }
28
+ .boe-status[data-kind="skipped"] circle { stroke-dasharray: 2 2; }
29
+ .boe-status:is([data-kind="done"], [data-kind="warning"], [data-kind="failed"]) circle { fill: currentColor; }
30
+ .boe-status:is([data-kind="done"], [data-kind="warning"], [data-kind="failed"]) path { stroke: var(--boe-token-surface-surface, #fff); stroke-linecap: round; }
31
+ .boe-status[data-kind="done"] { animation: boe-pop 200ms ease-out both; }
32
+ .boe-status[data-kind="done"] path { stroke-dasharray: 20; animation: boe-status-check 200ms ease-out both; }
33
+ `;
package/dist/index.d.ts CHANGED
@@ -134,3 +134,13 @@ export * from "./patterns/task/index.js";
134
134
  export * from "./patterns/governance/index.js";
135
135
  export * from "./patterns/insights/index.js";
136
136
  export * from "./element-maps.js";
137
+ export * from "./components/feedback/status-icon.js";
138
+ export * from "./components/collections/fact-list.js";
139
+ export * from "./components/collections/check-list.js";
140
+ export * from "./components/collections/document-list.js";
141
+ export * from "./components/collections/result-blocks.js";
142
+ export * from "./patterns/run/run-summary.js";
143
+ export * from "./patterns/agent-workspace/index.js";
144
+ export * from "./patterns/call-console/index.js";
145
+ export * from "./foundations/status/index.js";
146
+ export * from "./patterns/run/progress.js";
package/dist/index.js CHANGED
@@ -148,3 +148,13 @@ export * from "./patterns/task/index.js";
148
148
  export * from "./patterns/governance/index.js";
149
149
  export * from "./patterns/insights/index.js";
150
150
  export * from "./element-maps.js";
151
+ export * from "./components/feedback/status-icon.js";
152
+ export * from "./components/collections/fact-list.js";
153
+ export * from "./components/collections/check-list.js";
154
+ export * from "./components/collections/document-list.js";
155
+ export * from "./components/collections/result-blocks.js";
156
+ export * from "./patterns/run/run-summary.js";
157
+ export * from "./patterns/agent-workspace/index.js";
158
+ export * from "./patterns/call-console/index.js";
159
+ export * from "./foundations/status/index.js";
160
+ export * from "./patterns/run/progress.js";
@@ -16,6 +16,15 @@ export declare class AgentChat extends BaseElement {
16
16
  static readonly tagName: string;
17
17
  static get observedAttributes(): string[];
18
18
  private controller;
19
+ private readonly scope;
20
+ private composing;
21
+ private scrollPin?;
22
+ private resizeObserver?;
23
+ private messageCount;
24
+ private jumpEl;
25
+ focusComposer(): void;
26
+ proposalAnchor(messageId: string, proposalId: string): string;
27
+ focusProposal(messageId: string, proposalId: string): void;
19
28
  private ownsController;
20
29
  private pendingStart;
21
30
  private unsubscribeFns;
@@ -3,6 +3,11 @@ import { isSafeHref } from "../internal/safe-href.js";
3
3
  import { BaseElement } from "../../core/index.js";
4
4
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
5
5
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
6
+ import { ScrollPinController, boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
7
+ import { boeStatusGlyph, boeStatusStyles } from "../../foundations/status/index.js";
8
+ import { ResultBlocks, resultDocumentIds } from "../../components/collections/result-blocks.js";
9
+ import { RunSummary } from "../run/run-summary.js";
10
+ let chatInstance = 0;
6
11
  const DEFAULT_TAG_NAME = "box-agent-chat";
7
12
  const escapeHtml = (value) => value
8
13
  .replaceAll("&", "&amp;")
@@ -28,6 +33,12 @@ const elementStyles = `
28
33
  font: inherit;
29
34
  }
30
35
 
36
+ /* The host's own display would otherwise beat the UA rule for [hidden],
37
+ leaving the element on screen when a host hides it. */
38
+ :host([hidden]) {
39
+ display: none !important;
40
+ }
41
+
31
42
  section[part="panel"] {
32
43
  display: grid;
33
44
  gap: ${boePanel.gap};
@@ -359,9 +370,22 @@ const elementStyles = `
359
370
  export class AgentChat extends BaseElement {
360
371
  static tagName = DEFAULT_TAG_NAME;
361
372
  static get observedAttributes() {
362
- return ["agent-name", "heading", "placeholder", "token"];
373
+ return ["agent-name", "heading", "placeholder", "token", "hidden", "hide-citations"];
363
374
  }
364
375
  controller = null;
376
+ scope = `boe-chat-${++chatInstance}`;
377
+ composing = false;
378
+ scrollPin;
379
+ resizeObserver;
380
+ messageCount = 0;
381
+ jumpEl;
382
+ focusComposer() { this.inputEl?.focus({ preventScroll: true }); }
383
+ proposalAnchor(messageId, proposalId) { return `${this.scope}-${encodeURIComponent(messageId)}-${encodeURIComponent(proposalId)}`; }
384
+ focusProposal(messageId, proposalId) {
385
+ const card = this.shadowRoot?.getElementById(this.proposalAnchor(messageId, proposalId));
386
+ card?.scrollIntoView({ block: "nearest" });
387
+ card?.focus({ preventScroll: true });
388
+ }
365
389
  ownsController = false;
366
390
  pendingStart = false;
367
391
  unsubscribeFns = [];
@@ -419,6 +443,8 @@ export class AgentChat extends BaseElement {
419
443
  this.adoptController(value);
420
444
  }
421
445
  attributeChangedCallback(name, oldValue, newValue) {
446
+ if (name === "hidden" && newValue === null && this.isRendered)
447
+ this.focusComposer();
422
448
  if (name === "token") {
423
449
  this.scheduleStart();
424
450
  }
@@ -436,10 +462,26 @@ export class AgentChat extends BaseElement {
436
462
  }
437
463
  connectedCallback() {
438
464
  super.connectedCallback();
465
+ if (this.controller && !this.ownsController && this.unsubscribeFns.length === 0)
466
+ this.subscribeToController(this.controller);
467
+ this.scrollPin?.connect();
468
+ if (typeof ResizeObserver !== "undefined") {
469
+ this.resizeObserver = new ResizeObserver(() => this.scrollPin?.contentChanged());
470
+ this.resizeObserver.observe(this.threadEl);
471
+ for (const child of Array.from(this.threadEl.children))
472
+ this.resizeObserver.observe(child);
473
+ }
439
474
  this.scheduleStart();
440
475
  }
441
476
  disconnectedCallback() {
442
- this.teardownController();
477
+ this.scrollPin?.disconnect();
478
+ this.resizeObserver?.disconnect();
479
+ if (this.ownsController)
480
+ this.teardownController();
481
+ else {
482
+ this.unsubscribeFns.forEach(fn => fn());
483
+ this.unsubscribeFns = [];
484
+ }
443
485
  }
444
486
  /** Send the composer's contents (or an explicit body) as a user turn. */
445
487
  async send(body) {
@@ -449,11 +491,13 @@ export class AgentChat extends BaseElement {
449
491
  }
450
492
  // Clear only once the controller has accepted the turn, so a refused
451
493
  // send (no controller, or a disconnected session) never loses typing.
452
- const sent = await this.controller?.send(text);
453
- if (sent && body === undefined && this.inputEl) {
494
+ const accepted = this.controller?.getState().connected;
495
+ const sending = this.controller?.send(text);
496
+ if (accepted && body === undefined && this.inputEl) {
454
497
  this.inputEl.value = "";
455
498
  this.syncComposer();
456
499
  }
500
+ await sending;
457
501
  }
458
502
  /** Stop the in-flight generation; the partial reply is kept. */
459
503
  stop() {
@@ -552,6 +596,13 @@ export class AgentChat extends BaseElement {
552
596
  const canResolve = Boolean(this.controller?.config.transport.resolveAction);
553
597
  return message.proposals
554
598
  .map(proposal => {
599
+ if (proposal.decision) {
600
+ const failed = proposal.outcome === "failed";
601
+ return `<div part="proposal" tabindex="-1" id="${this.proposalAnchor(message.id, proposal.id)}" data-proposal-id="${escapeHtml(proposal.id)}" data-decision="${escapeHtml(proposal.decision)}" ${failed ? 'role="alert"' : ""}>
602
+ <span part="decision">${boeStatusGlyph(failed ? "failed" : proposal.decision === "approved" ? "done" : "skipped")}${proposal.decision === "approved" ? failed ? "Approved · didn't complete" : "Approved" : "Rejected"} · <span part="proposal-title">${escapeHtml(proposal.title)}</span></span>
603
+ ${proposal.note ? `<p part="proposal-note">${escapeHtml(proposal.note)}</p>` : ""}
604
+ ${failed ? `<button part="retry" data-message-id="${escapeHtml(message.id)}">Try again</button>` : ""}</div>`;
605
+ }
555
606
  const params = (proposal.params ?? [])
556
607
  .map(param => `
557
608
  <dt part="param-label">${escapeHtml(param.label)}</dt>
@@ -561,20 +612,22 @@ export class AgentChat extends BaseElement {
561
612
  const actions = canResolve && !proposal.decision
562
613
  ? `
563
614
  <div part="proposal-actions">
564
- <button type="button" part="proposal-action" data-action="approve" data-proposal-id="${escapeHtml(proposal.id)}">Approve</button>
565
- <button type="button" part="proposal-action" data-action="reject" data-proposal-id="${escapeHtml(proposal.id)}">Reject</button>
566
- <button type="button" part="proposal-action" data-action="modify" data-proposal-id="${escapeHtml(proposal.id)}">Modify</button>
615
+ <button type="button" part="proposal-action" data-action="approve" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""} aria-busy="${proposal.resolving === "approved"}">${proposal.resolving === "approved" ? boeStatusGlyph("active") : ""}Approve</button>
616
+ <button type="button" part="proposal-action" data-action="reject" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""} aria-busy="${proposal.resolving === "rejected"}">${proposal.resolving === "rejected" ? boeStatusGlyph("active") : ""}Reject</button>
617
+ <button type="button" part="proposal-action" data-action="modify" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""}>Modify</button>
567
618
  </div>
568
619
  `
569
620
  : "";
570
621
  return `
571
- <div part="proposal" data-proposal-id="${escapeHtml(proposal.id)}"${proposal.decision ? ` data-decision="${escapeHtml(proposal.decision)}"` : ""}>
622
+ <div part="proposal" tabindex="-1" id="${this.proposalAnchor(message.id, proposal.id)}" data-proposal-id="${escapeHtml(proposal.id)}">
623
+ <span>${boeStatusGlyph("pending")} Needs your approval</span>
572
624
  <span part="proposal-title">${escapeHtml(proposal.title)}</span>
573
625
  ${proposal.summary ? `<p part="proposal-summary">${escapeHtml(proposal.summary)}</p>` : ""}
574
626
  ${params ? `<dl part="proposal-params">${params}</dl>` : ""}
575
627
  ${proposal.decision ? `<span part="decision">${proposal.decision === "approved" ? "Approved" : "Rejected"}</span>` : ""}
576
628
  ${proposal.note ? `<p part="proposal-note">${escapeHtml(proposal.note)}</p>` : ""}
577
629
  ${actions}
630
+ <p part="proposal-note">To change it, reply with the new values.</p>
578
631
  </div>
579
632
  `;
580
633
  })
@@ -583,7 +636,8 @@ export class AgentChat extends BaseElement {
583
636
  messageInnerHtml(message) {
584
637
  const name = message.role === "user" ? "You" : (message.actor?.name ?? this.agentName);
585
638
  const initials = message.actor?.initials ?? initialsOf(name);
586
- const citations = message.citations
639
+ const documentIds = resultDocumentIds(message.blocks ?? []);
640
+ const citations = (this.hasAttribute("hide-citations") ? [] : message.citations.filter(c => !documentIds.includes(c.id)))
587
641
  .map(citation => this.citationHtml(message.id, citation))
588
642
  .join("");
589
643
  return `
@@ -591,10 +645,14 @@ export class AgentChat extends BaseElement {
591
645
  <span part="avatar" aria-hidden="true">${escapeHtml(initials)}</span>
592
646
  <span part="author">${escapeHtml(name)}</span>
593
647
  </div>
594
- <p part="body">${escapeHtml(message.body)}${message.status === "streaming" ? `<span part="caret" aria-hidden="true"></span>` : ""}</p>
648
+ ${message.role === "agent" ? '<box-run-summary></box-run-summary>' : ""}
649
+ <p part="body">${escapeHtml(message.body)}${message.status === "streaming" && !message.blocks?.length ? `<span part="caret" aria-hidden="true"></span>` : ""}</p>
650
+ <box-result-blocks></box-result-blocks>
651
+ ${message.completeness?.status === "incomplete" ? '<p part="incomplete" role="status">This reply may be incomplete.</p>' : ""}
595
652
  ${message.status === "error" && message.errorMessage ? `<p part="error" role="alert">${escapeHtml(message.errorMessage)}</p>` : ""}
596
653
  ${citations ? `<div part="citations">${citations}</div>` : ""}
597
654
  ${this.proposalHtml(message)}
655
+ ${message.proposals.some(p => p.outcome === "failed") ? "" : (message.options ?? []).map((o, i) => `<button part="option" data-option="${i}">${escapeHtml(o.label)}</button>`).join("")}
598
656
  `;
599
657
  }
600
658
  /**
@@ -614,7 +672,12 @@ export class AgentChat extends BaseElement {
614
672
  },
615
673
  agentName: this.agentName,
616
674
  citations: message.citations.map(({ id, href, label }) => ({ id, href, label })),
617
- proposals: message.proposals.map(({ id, title, summary, params, decision, note }) => ({
675
+ hideCitations: this.hasAttribute("hide-citations"),
676
+ blocks: message.blocks,
677
+ completeness: message.completeness?.status,
678
+ options: message.options,
679
+ proposals: message.proposals.map(({ id, title, summary, params, decision, note, outcome, resolving }) => ({
680
+ outcome, resolving,
618
681
  id,
619
682
  title,
620
683
  summary,
@@ -650,21 +713,38 @@ export class AgentChat extends BaseElement {
650
713
  body.insertBefore(document.createTextNode(message.body), body.firstChild);
651
714
  }
652
715
  renderTemplate() {
716
+ ResultBlocks.register();
717
+ RunSummary.register();
653
718
  if (!this.shadowRoot) {
654
719
  return;
655
720
  }
656
721
  // The composer is part of the stable shell: a streaming reply patches
657
722
  // only [part="thread"], so typing is never interrupted.
658
723
  this.shadowRoot.innerHTML = `
659
- <style>${elementStyles}</style>
724
+ <style>${elementStyles}
725
+ [part="message"][data-role="agent"]{border:0;background:transparent;padding-inline:0;max-width:75ch;min-width:0}
726
+ [part="message"][data-role="user"]{background:var(--boe-token-surface-surface-secondary,#f4f4f4)}
727
+ [part="proposal"]{border:1px solid var(--boe-token-stroke-stroke,#ddd);background:var(--boe-token-surface-surface,#fff);box-shadow:var(--boe-profile-shadow-overlay,0 3px 14px #0001)}
728
+ [part="proposal"][data-decision]{border:0;background:transparent;box-shadow:none;padding-inline:0}
729
+ [part="decision"]{background:none;border-radius:0;padding:0;gap:.4rem;font:inherit}
730
+ [part="proposal-action"][data-action="approve"],[part="proposal-action"][aria-busy="true"]{background:var(--boe-token-surface-surface-brand,#0061d5);color:var(--boe-token-text-text-on-brand,#fff);border-color:transparent}
731
+ [part="proposal-action"]:disabled:not([aria-busy="true"]){opacity:.5}
732
+ [part="composer"]{position:relative;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:12px;padding:.4rem}
733
+ [part="input"]{border:0;resize:none;max-height:180px;padding-right:5rem;box-sizing:border-box}
734
+ [part="composer-actions"]{position:absolute;right:.5rem;bottom:.5rem}
735
+ [part="jump"]{justify-self:center;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:999px;padding:.4rem .8rem;background:var(--boe-token-surface-surface,#fff);color:inherit;cursor:pointer}
736
+ .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
737
+ ${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}</style>
660
738
  <section part="panel">
661
739
  <div part="header">
662
740
  <h2 part="title"></h2>
663
741
  <span part="status" role="status" aria-live="polite"></span>
664
742
  </div>
665
- <ol part="thread" role="log" aria-live="polite" aria-relevant="additions"></ol>
743
+ <ol part="thread" role="log" aria-live="polite" aria-relevant="additions" tabindex="0"></ol>
744
+ <button part="jump" hidden>Jump to latest</button>
666
745
  <div part="composer">
667
- <textarea part="input" rows="2"></textarea>
746
+ <label class="sr-only" id="${this.scope}-label">Message</label>
747
+ <textarea part="input" rows="2" aria-labelledby="${this.scope}-label"></textarea>
668
748
  <div part="composer-actions">
669
749
  <button type="button" part="stop" hidden>Stop</button>
670
750
  <button type="button" part="send">Send</button>
@@ -679,6 +759,11 @@ export class AgentChat extends BaseElement {
679
759
  this.statusEl = this.shadowRoot.querySelector('[part="status"]');
680
760
  }
681
761
  setupListeners() {
762
+ this.jumpEl = this.shadowRoot.querySelector('[part="jump"]');
763
+ this.scrollPin = new ScrollPinController(this.threadEl, show => { this.jumpEl.hidden = !show; });
764
+ this.jumpEl.addEventListener("click", () => this.scrollPin?.jump());
765
+ this.inputEl.addEventListener("compositionstart", () => { this.composing = true; });
766
+ this.inputEl.addEventListener("compositionend", () => { this.composing = false; });
682
767
  this.sendEl.addEventListener("click", () => {
683
768
  void this.send();
684
769
  });
@@ -687,7 +772,7 @@ export class AgentChat extends BaseElement {
687
772
  });
688
773
  this.inputEl.addEventListener("keydown", event => {
689
774
  // Enter sends, Shift+Enter makes a newline — the conversational default.
690
- if (event.key === "Enter" && !event.shiftKey) {
775
+ if (event.key === "Enter" && !event.shiftKey && !event.isComposing && !this.composing && event.keyCode !== 229) {
691
776
  event.preventDefault();
692
777
  void this.send();
693
778
  }
@@ -697,15 +782,31 @@ export class AgentChat extends BaseElement {
697
782
  });
698
783
  this.threadEl.addEventListener("click", event => {
699
784
  const target = event.target;
785
+ const messageId = target.closest('[part="message"]')?.getAttribute("data-message-id") ?? "";
786
+ const option = target.closest("[data-option]");
787
+ if (option) {
788
+ const prompt = this.controller?.getMessage(messageId)?.options?.[Number(option.dataset.option)]?.prompt;
789
+ if (prompt)
790
+ void this.send(prompt);
791
+ return;
792
+ }
793
+ if (target.closest('[part="retry"]')) {
794
+ const messages = this.controller?.getState().messages ?? [];
795
+ const index = messages.findIndex(m => m.id === messageId);
796
+ const request = messages.slice(0, index).reverse().find(m => m.role === "user");
797
+ if (request)
798
+ void this.send(request.body);
799
+ return;
800
+ }
700
801
  const proposalButton = target.closest('[part="proposal-action"]');
701
802
  if (proposalButton && this.threadEl.contains(proposalButton)) {
702
803
  const proposalId = proposalButton.getAttribute("data-proposal-id") ?? "";
703
804
  const action = proposalButton.getAttribute("data-action") ?? "";
704
805
  if (action === "approve") {
705
- void this.controller?.resolveAction(proposalId, "approved");
806
+ void this.controller?.resolveAction(proposalId, "approved", undefined, messageId);
706
807
  }
707
808
  else if (action === "reject") {
708
- void this.controller?.resolveAction(proposalId, "rejected");
809
+ void this.controller?.resolveAction(proposalId, "rejected", undefined, messageId);
709
810
  }
710
811
  else if (action === "modify") {
711
812
  // Modifying needs the host's own editor, so this surfaces intent.
@@ -738,6 +839,9 @@ export class AgentChat extends BaseElement {
738
839
  const streaming = this.controller?.getState().streaming ?? false;
739
840
  this.inputEl.placeholder = this.placeholder;
740
841
  this.sendEl.disabled = streaming || this.inputEl.value.trim().length === 0;
842
+ this.sendEl.hidden = streaming;
843
+ this.inputEl.style.height = "auto";
844
+ this.inputEl.style.height = `${Math.min(180, Math.max(56, this.inputEl.scrollHeight))}px`;
741
845
  this.stopEl.hidden = !streaming;
742
846
  this.statusEl.textContent = streaming ? "Agent is responding…" : "";
743
847
  }
@@ -756,7 +860,8 @@ export class AgentChat extends BaseElement {
756
860
  : null;
757
861
  const state = this.controller?.getState() ?? null;
758
862
  const messages = state?.messages ?? [];
759
- const atBottom = this.threadEl.scrollHeight - this.threadEl.scrollTop - this.threadEl.clientHeight < 40;
863
+ const newMessage = messages.length > this.messageCount;
864
+ this.messageCount = messages.length;
760
865
  this.shadowRoot.querySelector('[part="title"]').textContent = this.heading;
761
866
  if (messages.length === 0) {
762
867
  if (!this.threadEl.querySelector('[part="empty"]')) {
@@ -787,7 +892,13 @@ export class AgentChat extends BaseElement {
787
892
  else {
788
893
  node.setAttribute("data-signature", signature);
789
894
  node.setAttribute("data-status", message.status);
895
+ const summary = node.querySelector("box-run-summary");
896
+ const results = node.querySelector("box-result-blocks");
790
897
  node.innerHTML = this.messageInnerHtml(message);
898
+ if (summary)
899
+ node.querySelector("box-run-summary")?.replaceWith(summary);
900
+ if (results)
901
+ node.querySelector("box-result-blocks")?.replaceWith(results);
791
902
  }
792
903
  }
793
904
  else {
@@ -800,8 +911,19 @@ export class AgentChat extends BaseElement {
800
911
  this.threadEl.insertBefore(node, anchor);
801
912
  }
802
913
  previous = node;
914
+ this.resizeObserver?.observe(node);
915
+ const blocks = node.querySelector("box-result-blocks");
916
+ if (blocks && blocks.blocks !== message.blocks)
917
+ blocks.blocks = message.blocks ?? [];
918
+ const summary = node.querySelector("box-run-summary");
919
+ if (summary) {
920
+ summary.toggleAttribute("failed", message.status === "error");
921
+ summary.turn = { steps: message.trace ?? [], todos: message.todos ?? [], startedAt: message.startedAt ?? 0,
922
+ ...(message.endedAt !== undefined ? { endedAt: message.endedAt } : {}), incomplete: message.completeness?.status === "incomplete" };
923
+ }
803
924
  }
804
925
  for (const stale of existing.values()) {
926
+ this.resizeObserver?.unobserve(stale);
805
927
  stale.remove();
806
928
  }
807
929
  if (focusKey?.part) {
@@ -811,9 +933,7 @@ export class AgentChat extends BaseElement {
811
933
  target?.focus();
812
934
  }
813
935
  // Follow the stream only when the reader is already at the bottom.
814
- if (atBottom) {
815
- this.threadEl.scrollTop = this.threadEl.scrollHeight;
816
- }
936
+ this.scrollPin?.contentChanged(newMessage);
817
937
  this.syncComposer();
818
938
  }
819
939
  }
@@ -11,6 +11,8 @@ export declare class AgentChatController extends Controller<AgentChatState, Agen
11
11
  readonly config: AgentChatSessionConfig;
12
12
  private sendCounter;
13
13
  private activeAbortController;
14
+ private finishActive?;
15
+ private readonly resolving;
14
16
  constructor(config: AgentChatSessionConfig);
15
17
  connect(): void;
16
18
  disconnect(): void;
@@ -23,5 +25,5 @@ export declare class AgentChatController extends Controller<AgentChatState, Agen
23
25
  */
24
26
  send(body: string): Promise<AgentChatMessage | null>;
25
27
  /** Resolve a HITL proposal through the transport capability. */
26
- resolveAction(proposalId: string, decision: AgentActionDecision, note?: string): Promise<AgentActionProposal | null>;
28
+ resolveAction(proposalId: string, decision: AgentActionDecision, note?: string, messageId?: string): Promise<AgentActionProposal | null>;
27
29
  }