@unofficialbox/box-open-elements 0.19.0 → 0.21.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 (126) hide show
  1. package/dist/components/actions/menu-item.d.ts +2 -0
  2. package/dist/components/actions/menu-item.js +4 -0
  3. package/dist/components/collections/card.js +6 -0
  4. package/dist/components/collections/check-list.d.ts +10 -0
  5. package/dist/components/collections/check-list.js +8 -0
  6. package/dist/components/collections/document-list.d.ts +10 -0
  7. package/dist/components/collections/document-list.js +8 -0
  8. package/dist/components/collections/fact-list.d.ts +10 -0
  9. package/dist/components/collections/fact-list.js +8 -0
  10. package/dist/components/collections/result-blocks.d.ts +54 -0
  11. package/dist/components/collections/result-blocks.js +74 -0
  12. package/dist/components/feedback/alert.d.ts +2 -0
  13. package/dist/components/feedback/alert.js +17 -13
  14. package/dist/components/feedback/progress-bar.d.ts +9 -0
  15. package/dist/components/feedback/progress-bar.js +29 -3
  16. package/dist/components/feedback/status-icon.d.ts +10 -0
  17. package/dist/components/feedback/status-icon.js +26 -0
  18. package/dist/components/feedback/toast.d.ts +10 -1
  19. package/dist/components/feedback/toast.js +69 -27
  20. package/dist/components/forms/number-input.d.ts +1 -0
  21. package/dist/components/forms/number-input.js +9 -6
  22. package/dist/components/forms/search-field.js +16 -0
  23. package/dist/components/overlays/context-menu.d.ts +2 -0
  24. package/dist/components/overlays/context-menu.js +7 -0
  25. package/dist/components/overlays/dialog.js +19 -3
  26. package/dist/components/overlays/popover.js +6 -1
  27. package/dist/element-maps.d.ts +49 -19
  28. package/dist/entries/agent-workspace.d.ts +1 -0
  29. package/dist/entries/agent-workspace.js +1 -0
  30. package/dist/entries/call-console.d.ts +1 -0
  31. package/dist/entries/call-console.js +1 -0
  32. package/dist/entries/check-list.d.ts +1 -0
  33. package/dist/entries/check-list.js +1 -0
  34. package/dist/entries/document-list.d.ts +1 -0
  35. package/dist/entries/document-list.js +1 -0
  36. package/dist/entries/fact-list.d.ts +1 -0
  37. package/dist/entries/fact-list.js +1 -0
  38. package/dist/entries/result-blocks.d.ts +1 -0
  39. package/dist/entries/result-blocks.js +1 -0
  40. package/dist/entries/run-summary.d.ts +1 -0
  41. package/dist/entries/run-summary.js +1 -0
  42. package/dist/entries/status-icon.d.ts +1 -0
  43. package/dist/entries/status-icon.js +1 -0
  44. package/dist/foundations/a11y/announcer.d.ts +12 -0
  45. package/dist/foundations/a11y/announcer.js +75 -0
  46. package/dist/foundations/a11y/focus.js +46 -25
  47. package/dist/foundations/a11y/index.d.ts +2 -0
  48. package/dist/foundations/a11y/index.js +1 -0
  49. package/dist/foundations/icons/box-iconography.d.ts +472 -472
  50. package/dist/foundations/icons/box-iconography.generated.d.ts +1151 -679
  51. package/dist/foundations/icons/box-iconography.generated.js +1006 -3588
  52. package/dist/foundations/icons/glyphs/index.d.ts +1 -0
  53. package/dist/foundations/icons/glyphs/index.js +2 -0
  54. package/dist/foundations/icons/glyphs/package.json +1 -0
  55. package/dist/foundations/motion/behaviors.d.ts +8 -0
  56. package/dist/foundations/motion/behaviors.js +24 -0
  57. package/dist/foundations/motion/index.d.ts +2 -0
  58. package/dist/foundations/motion/index.js +2 -0
  59. package/dist/foundations/motion/scroll-pin.d.ts +19 -0
  60. package/dist/foundations/motion/scroll-pin.js +60 -0
  61. package/dist/foundations/motion/tokens.d.ts +2 -0
  62. package/dist/foundations/motion/tokens.js +2 -0
  63. package/dist/foundations/overlay/positioning.js +7 -0
  64. package/dist/foundations/profiles/registry.d.ts +3 -0
  65. package/dist/foundations/profiles/registry.js +3 -0
  66. package/dist/foundations/profiles/types.d.ts +3 -0
  67. package/dist/foundations/status/index.d.ts +6 -0
  68. package/dist/foundations/status/index.js +35 -0
  69. package/dist/foundations/tokens/box-dark.js +4 -1
  70. package/dist/foundations/tokens/box-defaults.js +3 -0
  71. package/dist/foundations/tokens/registry.d.ts +3 -0
  72. package/dist/foundations/tokens/registry.js +3 -0
  73. package/dist/foundations/tokens/types.d.ts +3 -0
  74. package/dist/index.d.ts +12 -0
  75. package/dist/index.js +12 -0
  76. package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
  77. package/dist/patterns/agent-chat/agent-chat.js +146 -24
  78. package/dist/patterns/agent-chat/controller.d.ts +3 -1
  79. package/dist/patterns/agent-chat/controller.js +92 -16
  80. package/dist/patterns/agent-chat/index.d.ts +1 -0
  81. package/dist/patterns/agent-chat/index.js +1 -0
  82. package/dist/patterns/agent-chat/ndjson.d.ts +5 -0
  83. package/dist/patterns/agent-chat/ndjson.js +41 -0
  84. package/dist/patterns/agent-chat/types.d.ts +44 -1
  85. package/dist/patterns/agent-workspace/agent-workspace.d.ts +28 -0
  86. package/dist/patterns/agent-workspace/agent-workspace.js +215 -0
  87. package/dist/patterns/agent-workspace/controller.d.ts +38 -0
  88. package/dist/patterns/agent-workspace/controller.js +65 -0
  89. package/dist/patterns/agent-workspace/index.d.ts +2 -0
  90. package/dist/patterns/agent-workspace/index.js +2 -0
  91. package/dist/patterns/call-console/call-console.d.ts +20 -0
  92. package/dist/patterns/call-console/call-console.js +201 -0
  93. package/dist/patterns/call-console/controller.d.ts +23 -0
  94. package/dist/patterns/call-console/controller.js +50 -0
  95. package/dist/patterns/call-console/http-transport.d.ts +3 -0
  96. package/dist/patterns/call-console/http-transport.js +26 -0
  97. package/dist/patterns/call-console/index.d.ts +4 -0
  98. package/dist/patterns/call-console/index.js +4 -0
  99. package/dist/patterns/call-console/types.d.ts +32 -0
  100. package/dist/patterns/call-console/types.js +8 -0
  101. package/dist/patterns/content-explorer/adapters/action-menu.js +7 -5
  102. package/dist/patterns/content-explorer/adapters/toolbar.js +6 -2
  103. package/dist/patterns/content-uploader/content-uploader.js +2 -1
  104. package/dist/patterns/file-request/file-request-builder.js +3 -0
  105. package/dist/patterns/flow-builder/flow-builder.d.ts +48 -0
  106. package/dist/patterns/flow-builder/flow-builder.js +200 -0
  107. package/dist/patterns/flow-builder/index.d.ts +3 -0
  108. package/dist/patterns/flow-builder/index.js +3 -0
  109. package/dist/patterns/flow-builder/model.d.ts +33 -0
  110. package/dist/patterns/flow-builder/model.js +28 -0
  111. package/dist/patterns/flow-builder/primitives.d.ts +47 -0
  112. package/dist/patterns/flow-builder/primitives.js +192 -0
  113. package/dist/patterns/lineage/lineage-graph.js +26 -6
  114. package/dist/patterns/notifications/notification-inbox.js +19 -3
  115. package/dist/patterns/preview/annotation-inspector.js +2 -2
  116. package/dist/patterns/run/index.d.ts +2 -0
  117. package/dist/patterns/run/index.js +2 -0
  118. package/dist/patterns/run/progress.d.ts +24 -0
  119. package/dist/patterns/run/progress.js +32 -0
  120. package/dist/patterns/run/run-summary.d.ts +21 -0
  121. package/dist/patterns/run/run-summary.js +82 -0
  122. package/dist/patterns/run/run-trace.d.ts +1 -0
  123. package/dist/patterns/run/run-trace.js +14 -3
  124. package/dist/patterns/undo/index.d.ts +19 -0
  125. package/dist/patterns/undo/index.js +106 -0
  126. package/package.json +23 -2
@@ -17,6 +17,8 @@ const elementStyles = `
17
17
 
18
18
  :host {
19
19
  display: block;
20
+ min-inline-size: 0;
21
+ container: boe-notification-inbox / inline-size;
20
22
  color: inherit;
21
23
  font: inherit;
22
24
  }
@@ -29,6 +31,7 @@ const elementStyles = `
29
31
 
30
32
  [part="panel"] {
31
33
  display: grid;
34
+ grid-template-columns: minmax(0, 1fr);
32
35
  grid-template-rows: auto auto 1fr;
33
36
  gap: ${boePanel.gap};
34
37
  padding: ${boePanel.padding};
@@ -40,11 +43,13 @@ const elementStyles = `
40
43
 
41
44
  [part="header"] {
42
45
  display: flex;
46
+ flex-wrap: wrap;
43
47
  align-items: center;
44
48
  gap: 0.5rem;
45
49
  }
46
50
 
47
51
  [part="title"] {
52
+ overflow-wrap: anywhere;
48
53
  margin: 0;
49
54
  font: inherit;
50
55
  font-size: 1.05rem;
@@ -56,7 +61,7 @@ const elementStyles = `
56
61
  padding: 0.1rem 0.45rem;
57
62
  border-radius: 999px;
58
63
  background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 14%, transparent);
59
- color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 78%, black 22%);
64
+ color: var(--boe-token-text-status-text-error, #b92340);
60
65
  font-size: 0.72rem;
61
66
  font-weight: 700;
62
67
  }
@@ -134,7 +139,7 @@ const elementStyles = `
134
139
  text-transform: none;
135
140
  letter-spacing: normal;
136
141
  font-size: 0.7rem;
137
- color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 78%, black 22%);
142
+ color: var(--boe-token-text-status-text-error, #b92340);
138
143
  }
139
144
 
140
145
  [part="items"] {
@@ -147,7 +152,7 @@ const elementStyles = `
147
152
 
148
153
  [part="item"] {
149
154
  display: grid;
150
- grid-template-columns: auto 1fr auto;
155
+ grid-template-columns: auto minmax(0, 1fr) auto;
151
156
  gap: 0.5rem;
152
157
  align-items: start;
153
158
  padding: 0.5rem 0.55rem;
@@ -174,6 +179,7 @@ const elementStyles = `
174
179
 
175
180
  [part="item-body"] {
176
181
  display: grid;
182
+ overflow-wrap: anywhere;
177
183
  gap: 0.15rem;
178
184
  min-inline-size: 0;
179
185
  }
@@ -223,6 +229,7 @@ const elementStyles = `
223
229
 
224
230
  [part="item-actions"] {
225
231
  display: flex;
232
+ flex-wrap: wrap;
226
233
  gap: 0.25rem;
227
234
  }
228
235
 
@@ -251,6 +258,15 @@ const elementStyles = `
251
258
  text-align: center;
252
259
  }
253
260
 
261
+ @container boe-notification-inbox (max-width: 32rem) {
262
+ [part="item"] {
263
+ grid-template-columns: auto minmax(0, 1fr);
264
+ }
265
+ [part="item-actions"] {
266
+ grid-column: 2;
267
+ }
268
+ }
269
+
254
270
  [part="filter"]:focus-visible,
255
271
  [part="mark-all"]:focus-visible,
256
272
  [part="item-title"]:focus-visible,
@@ -66,7 +66,7 @@ const elementStyles = `
66
66
  padding: 0.65rem;
67
67
  border-radius: ${boePanel.radius};
68
68
  border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 48%, transparent);
69
- background: rgba(255, 255, 255, 0.72);
69
+ background: var(--boe-token-surface-surface, #ffffff);
70
70
  }
71
71
 
72
72
  [part="annotation-header"] {
@@ -254,7 +254,7 @@ const elementStyles = `
254
254
  [part="action"][data-tone="primary"] {
255
255
  border-color: transparent;
256
256
  background: var(--boe-token-surface-surface-brand, #0061d5);
257
- color: #fff;
257
+ color: var(--boe-token-text-text-on-brand, #ffffff);
258
258
  }
259
259
  `;
260
260
  export class AnnotationInspector extends BaseElement {
@@ -1,2 +1,4 @@
1
1
  export * from "./types.js";
2
2
  export * from "./run-trace.js";
3
+ export * from "./progress.js";
4
+ export * from "./run-summary.js";
@@ -2,3 +2,5 @@
2
2
  export * from "./types.js";
3
3
  // Machine execution trace surface
4
4
  export * from "./run-trace.js";
5
+ export * from "./progress.js";
6
+ export * from "./run-summary.js";
@@ -0,0 +1,24 @@
1
+ import { type StatusKind } from "../../foundations/status/index.js";
2
+ import { type RunStep } from "./types.js";
3
+ export interface AgentTodo {
4
+ id: string;
5
+ content: string;
6
+ status: "pending" | "in_progress" | "completed" | "skipped";
7
+ }
8
+ export interface RunTurn {
9
+ steps: RunStep[];
10
+ todos: AgentTodo[];
11
+ startedAt: number;
12
+ endedAt?: number;
13
+ incomplete?: boolean;
14
+ }
15
+ export declare function formatElapsed(ms: number): string;
16
+ export declare function splitStepTitle(title: string): {
17
+ source?: string;
18
+ action: string;
19
+ };
20
+ export declare function progress(turn: RunTurn, failed?: boolean, now?: number, threshold?: number): {
21
+ kind: StatusKind;
22
+ label: string;
23
+ elapsed?: string;
24
+ };
@@ -0,0 +1,32 @@
1
+ import { toStatusKind } from "../../foundations/status/index.js";
2
+ import { resolveRunSteps } from "./types.js";
3
+ export function formatElapsed(ms) {
4
+ if (!Number.isFinite(ms) || ms < 0)
5
+ return "";
6
+ if (ms < 100)
7
+ return "<0.1 s";
8
+ if (ms < 10000)
9
+ return `${(ms / 1000).toFixed(1)} s`;
10
+ const seconds = Math.round(ms / 1000);
11
+ return seconds < 60 ? `${seconds} s` : `${Math.floor(seconds / 60)} min ${seconds % 60} s`;
12
+ }
13
+ export function splitStepTitle(title) {
14
+ const at = title.indexOf(" · ");
15
+ return at < 0 ? { action: title } : { source: title.slice(0, at), action: title.slice(at + 3) };
16
+ }
17
+ export function progress(turn, failed = false, now = Date.now(), threshold = 2000) {
18
+ const resolved = resolveRunSteps(turn.steps);
19
+ if (turn.endedAt === undefined) {
20
+ const step = [...resolved.steps].reverse().find(s => s.status === "running");
21
+ const label = turn.todos.find(t => t.status === "in_progress")?.content ?? (step ? splitStepTitle(step.step.title).action : "Thinking");
22
+ const elapsed = Math.max(0, now - turn.startedAt);
23
+ return { kind: "active", label: `${label}…`, ...(elapsed >= threshold ? { elapsed: `${Math.floor(elapsed / 1000)} s` } : {}) };
24
+ }
25
+ const took = formatElapsed(turn.endedAt - turn.startedAt);
26
+ if (failed || resolved.status === "failed")
27
+ return { kind: "failed", label: `Stopped after ${took}` };
28
+ if (turn.incomplete)
29
+ return { kind: "warning", label: `Cut short after ${took}` };
30
+ const warnings = resolved.steps.filter(s => toStatusKind(s.status) === "warning").length;
31
+ return { kind: warnings ? "warning" : "done", label: `Worked for ${took}${warnings ? ` · ${warnings} warning${warnings === 1 ? "" : "s"}` : ""}` };
32
+ }
@@ -0,0 +1,21 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { type RunTurn } from "./progress.js";
3
+ export declare class RunSummary extends BaseElement {
4
+ static readonly tagName = "box-run-summary";
5
+ static get observedAttributes(): string[];
6
+ private value;
7
+ private timer;
8
+ private planSignature;
9
+ private traceSignature;
10
+ get turn(): RunTurn;
11
+ set turn(turn: RunTurn);
12
+ get open(): boolean;
13
+ set open(value: boolean);
14
+ connectedCallback(): void;
15
+ disconnectedCallback(): void;
16
+ private syncTimer;
17
+ protected renderTemplate(): void;
18
+ protected setupListeners(): void;
19
+ private updateLabel;
20
+ protected update(): void;
21
+ }
@@ -0,0 +1,82 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { boeDisclosureStyles, boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
3
+ import { StatusIcon } from "../../components/feedback/status-icon.js";
4
+ import { toStatusKind } from "../../foundations/status/index.js";
5
+ import { RunTrace } from "./run-trace.js";
6
+ import { progress } from "./progress.js";
7
+ const DEFAULT_TAG_NAME = "box-run-summary";
8
+ export class RunSummary extends BaseElement {
9
+ static tagName = DEFAULT_TAG_NAME;
10
+ static get observedAttributes() { return ["open", "failed", "elapsed-threshold"]; }
11
+ value = { steps: [], todos: [], startedAt: 0, endedAt: 0 };
12
+ timer;
13
+ planSignature = "";
14
+ traceSignature = "";
15
+ get turn() { return this.value; }
16
+ set turn(turn) { this.value = turn; if (this.isRendered)
17
+ this.update(); }
18
+ get open() { return this.hasAttribute("open"); }
19
+ set open(value) { this.toggleAttribute("open", value); }
20
+ connectedCallback() { super.connectedCallback(); this.syncTimer(); }
21
+ disconnectedCallback() { clearInterval(this.timer); this.timer = undefined; }
22
+ syncTimer() {
23
+ if (this.value.endedAt === undefined && this.isConnected && !this.timer)
24
+ this.timer = setInterval(() => this.updateLabel(), 1000);
25
+ if (this.value.endedAt !== undefined) {
26
+ clearInterval(this.timer);
27
+ this.timer = undefined;
28
+ }
29
+ }
30
+ renderTemplate() {
31
+ StatusIcon.register();
32
+ RunTrace.register();
33
+ this.shadowRoot.innerHTML = `<style>:host{display:block;font:inherit;color:var(--boe-token-text-text-secondary,#666)}:host([hidden]){display:none !important}
34
+ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:.5rem 0;display:flex;gap:.5rem;align-items:center}
35
+ [part="clock"]{font-variant-numeric:tabular-nums} ul{padding:0;list-style:none} li{display:flex;gap:.5rem;margin:.5rem 0}
36
+ @keyframes boe-shimmer{50%{opacity:.6}} [data-running] [part="label"]{animation:boe-shimmer 1.4s ease-in-out infinite}
37
+ ${boeDisclosureStyles()}${boeEntranceKeyframes}${boeReducedMotionPolicy}</style>
38
+ <button part="trigger" aria-expanded="false" aria-controls="details"><box-status-icon></box-status-icon><span part="label" role="status"></span><span part="clock" aria-hidden="true"></span><span aria-hidden="true">›</span></button>
39
+ <div id="details" part="disclosure" class="boe-disclosure" inert><div><ul part="plan"></ul><box-run-trace></box-run-trace></div></div>`;
40
+ }
41
+ setupListeners() { this.shadowRoot.querySelector("button").addEventListener("click", () => { this.open = !this.open; }); }
42
+ updateLabel() {
43
+ const p = progress(this.value, this.hasAttribute("failed"), Date.now(), Number(this.getAttribute("elapsed-threshold") ?? 2000));
44
+ const label = this.shadowRoot.querySelector('[part="label"]');
45
+ if (label.textContent !== p.label)
46
+ label.textContent = p.label;
47
+ this.shadowRoot.querySelector('[part="clock"]').textContent = p.elapsed ?? "";
48
+ this.shadowRoot.querySelector("box-status-icon").kind = p.kind;
49
+ this.shadowRoot.querySelector("button").toggleAttribute("data-running", p.kind === "active");
50
+ this.shadowRoot.querySelector("button").setAttribute("aria-label", p.label);
51
+ }
52
+ update() {
53
+ if (!this.isRendered)
54
+ return;
55
+ this.updateLabel();
56
+ this.syncTimer();
57
+ this.shadowRoot.querySelector("button").setAttribute("aria-expanded", String(this.open));
58
+ const disclosure = this.shadowRoot.querySelector("#details");
59
+ disclosure.toggleAttribute("data-open", this.open);
60
+ disclosure.inert = !this.open;
61
+ disclosure.setAttribute("aria-hidden", String(!this.open));
62
+ const signature = JSON.stringify(this.value.todos);
63
+ if (signature !== this.planSignature) {
64
+ this.planSignature = signature;
65
+ const list = this.shadowRoot.querySelector("ul");
66
+ list.replaceChildren();
67
+ for (const todo of this.value.todos) {
68
+ const li = document.createElement("li");
69
+ const icon = new StatusIcon();
70
+ icon.kind = toStatusKind(todo.status);
71
+ li.append(icon, document.createTextNode(todo.content));
72
+ list.append(li);
73
+ }
74
+ }
75
+ const traceSignature = JSON.stringify(this.value.steps);
76
+ if (traceSignature !== this.traceSignature) {
77
+ this.traceSignature = traceSignature;
78
+ this.shadowRoot.querySelector("box-run-trace").steps = this.value.steps;
79
+ }
80
+ }
81
+ }
82
+ RunSummary.register();
@@ -25,6 +25,7 @@ export declare class RunTrace extends BaseElement {
25
25
  private stepsRaw;
26
26
  private stepsCache;
27
27
  private readonly expandedIds;
28
+ private readonly previousStatuses;
28
29
  get heading(): string;
29
30
  set heading(value: string);
30
31
  get steps(): RunStep[];
@@ -2,6 +2,8 @@ import { formatRunDuration, formatRunSummary, isRunStepRecord, resolveRunSteps,
2
2
  import { ProgressBar } from "../../components/feedback/progress-bar.js";
3
3
  import { BaseElement } from "../../core/index.js";
4
4
  import { boePanel } from "../../foundations/geometry/index.js";
5
+ import { boeStatusGlyph, boeStatusStyles, toStatusKind } from "../../foundations/status/index.js";
6
+ import { boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
5
7
  const DEFAULT_TAG_NAME = "box-run-trace";
6
8
  /** Escape a value for use inside a double-quoted attribute selector. */
7
9
  const cssAttrValue = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
@@ -285,6 +287,7 @@ export class RunTrace extends BaseElement {
285
287
  stepsRaw = null;
286
288
  stepsCache = [];
287
289
  expandedIds = new Set();
290
+ previousStatuses = new Map();
288
291
  get heading() {
289
292
  return this.getAttribute("heading") ?? "Run";
290
293
  }
@@ -332,7 +335,11 @@ export class RunTrace extends BaseElement {
332
335
  if (!this.shadowRoot) {
333
336
  return;
334
337
  }
335
- this.shadowRoot.innerHTML = `<style>${elementStyles}</style><div part="host"></div>`;
338
+ this.shadowRoot.innerHTML = `<style>${elementStyles}
339
+ [part="step"] [part="marker"]{border:0;background:none;display:inline-flex}
340
+ ${boeEntranceKeyframes}${boeStatusStyles}
341
+ [data-settled] .boe-status[data-kind="done"], [data-settled] .boe-status[data-kind="done"] path { animation: none; }
342
+ ${boeReducedMotionPolicy}</style><div part="host"></div>`;
336
343
  this.hostEl = this.shadowRoot.querySelector('[part="host"]');
337
344
  }
338
345
  setupListeners() {
@@ -379,7 +386,7 @@ export class RunTrace extends BaseElement {
379
386
  <span part="child-label">${escapeHtml(child.label)}</span>
380
387
  ${clamped === null
381
388
  ? "<span></span>"
382
- : `<box-progress-bar label="${escapeHtml(child.label)} progress" value="${String(clamped)}"></box-progress-bar>`}
389
+ : `<box-progress-bar label="${escapeHtml(child.label)}" value="${String(clamped)}"></box-progress-bar>`}
383
390
  <span part="child-status">${RUN_STEP_STATUS_LABEL[status]}</span>
384
391
  </li>
385
392
  `;
@@ -395,6 +402,7 @@ export class RunTrace extends BaseElement {
395
402
  const rows = resolution.steps
396
403
  .map(entry => {
397
404
  const { step, status, position } = entry;
405
+ const settled = this.previousStatuses.get(step.id) === status;
398
406
  const duration = formatRunDuration(step.startedAt, step.finishedAt);
399
407
  // Slot-only detail counts: a host projecting logs for a step with no
400
408
  // JSON description still needs the toggle that reveals them.
@@ -412,7 +420,7 @@ export class RunTrace extends BaseElement {
412
420
  `;
413
421
  return `
414
422
  <li part="step" data-status="${status}" data-step-id="${escapeHtml(step.id)}" style="--run-tone:${STATUS_TONE[status]};">
415
- <span part="marker" aria-hidden="true"></span>
423
+ <span part="marker" aria-hidden="true" ${settled ? "data-settled" : ""}>${boeStatusGlyph(toStatusKind(status))}</span>
416
424
  <div part="step-body">
417
425
  <div part="topline">
418
426
  <span part="step-title">${escapeHtml(step.title)}</span>
@@ -430,6 +438,9 @@ export class RunTrace extends BaseElement {
430
438
  })
431
439
  .join("");
432
440
  const summary = formatRunSummary(resolution);
441
+ this.previousStatuses.clear();
442
+ for (const { step, status } of resolution.steps)
443
+ this.previousStatuses.set(step.id, status);
433
444
  this.hostEl.innerHTML = `
434
445
  <section part="panel" aria-label="${escapeHtml(this.heading)}">
435
446
  <div part="header">
@@ -0,0 +1,19 @@
1
+ import { Toast } from "../../components/feedback/toast.js";
2
+ /** Remove once and return an idempotent restore preserving the original object's identity. */
3
+ export declare function removeAt<T>(list: T[], index: number): () => void;
4
+ /** Never overwrite a key recreated after removal. */
5
+ export declare function removeKey<T>(map: Record<string, T>, key: string): () => void;
6
+ /** Restore into the current list, preserving intervening edits and clamping the index. */
7
+ export declare function removeFromMapList<T>(map: Record<string, T[]>, key: string, index: number): () => void;
8
+ export interface UndoOffer {
9
+ readonly toast: Toast | null;
10
+ undo(): void;
11
+ dispose(): void;
12
+ }
13
+ export interface UndoOptions {
14
+ container?: HTMLElement;
15
+ duration?: number;
16
+ onRestore?: () => void;
17
+ }
18
+ /** Offer a single document-scoped undo. Hosts call dispose on teardown. SSR-safe. */
19
+ export declare function offerUndo(message: string, restore: () => void, options?: UndoOptions): UndoOffer;
@@ -0,0 +1,106 @@
1
+ import { Toast } from "../../components/feedback/toast.js";
2
+ import { announce } from "../../foundations/a11y/index.js";
3
+ /** Remove once and return an idempotent restore preserving the original object's identity. */
4
+ export function removeAt(list, index) {
5
+ if (!Number.isInteger(index) || index < 0 || index >= list.length)
6
+ return () => { };
7
+ const item = list.splice(index, 1)[0];
8
+ let restored = false;
9
+ return () => {
10
+ if (restored)
11
+ return;
12
+ restored = true;
13
+ list.splice(Math.min(index, list.length), 0, item);
14
+ };
15
+ }
16
+ /** Never overwrite a key recreated after removal. */
17
+ export function removeKey(map, key) {
18
+ if (!Object.hasOwn(map, key))
19
+ return () => { };
20
+ const value = map[key];
21
+ delete map[key];
22
+ let restored = false;
23
+ return () => {
24
+ if (restored)
25
+ return;
26
+ restored = true;
27
+ if (!Object.hasOwn(map, key))
28
+ Object.defineProperty(map, key, { value, writable: true, enumerable: true, configurable: true });
29
+ };
30
+ }
31
+ /** Restore into the current list, preserving intervening edits and clamping the index. */
32
+ export function removeFromMapList(map, key, index) {
33
+ const list = Object.hasOwn(map, key) ? map[key] : undefined;
34
+ if (!list || !Number.isInteger(index) || index < 0 || index >= list.length)
35
+ return () => { };
36
+ const item = list.splice(index, 1)[0];
37
+ if (!list.length)
38
+ delete map[key];
39
+ let restored = false;
40
+ return () => {
41
+ if (restored)
42
+ return;
43
+ restored = true;
44
+ if (!Object.hasOwn(map, key))
45
+ Object.defineProperty(map, key, { value: list, writable: true, enumerable: true, configurable: true });
46
+ const target = map[key];
47
+ target.splice(Math.min(index, target.length), 0, item);
48
+ };
49
+ }
50
+ const offers = new WeakMap();
51
+ /** Offer a single document-scoped undo. Hosts call dispose on teardown. SSR-safe. */
52
+ export function offerUndo(message, restore, options = {}) {
53
+ const doc = options.container?.ownerDocument ?? globalThis.document;
54
+ if (!doc?.body)
55
+ return { toast: null, undo() { }, dispose() { } };
56
+ offers.get(doc)?.dispose();
57
+ const toast = doc.createElement(Toast.tagName);
58
+ const action = doc.createElement("button");
59
+ action.type = "button";
60
+ action.slot = "action";
61
+ action.textContent = "Undo";
62
+ const mac = /Mac|iPhone|iPad/.test(doc.defaultView?.navigator.platform ?? "");
63
+ const shortcut = mac ? "Command-Z" : "Control-Z";
64
+ action.setAttribute("aria-keyshortcuts", mac ? "Meta+Z" : "Control+Z");
65
+ toast.append(action);
66
+ let live = true;
67
+ const dispose = () => {
68
+ if (!live)
69
+ return;
70
+ live = false;
71
+ doc.removeEventListener("keydown", onKey);
72
+ toast.removeEventListener("open-changed", onOpen);
73
+ toast.hide();
74
+ toast.remove();
75
+ if (offers.get(doc) === offer)
76
+ offers.delete(doc);
77
+ };
78
+ const undo = () => {
79
+ if (!live)
80
+ return;
81
+ dispose();
82
+ restore();
83
+ options.onRestore?.();
84
+ announce("Restored", "polite", doc);
85
+ };
86
+ const onKey = (event) => {
87
+ if (event.defaultPrevented || event.isComposing || event.shiftKey || event.altKey || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "z")
88
+ return;
89
+ const editing = event.composedPath().some(target => target instanceof HTMLElement && (target.matches("input, textarea, select, [role=textbox]") || target.isContentEditable));
90
+ if (editing)
91
+ return;
92
+ event.preventDefault();
93
+ undo();
94
+ };
95
+ const onOpen = (event) => { if (!event.detail.open)
96
+ dispose(); };
97
+ const offer = { toast, undo, dispose };
98
+ offers.set(doc, offer);
99
+ action.addEventListener("click", undo);
100
+ toast.addEventListener("open-changed", onOpen);
101
+ doc.addEventListener("keydown", onKey);
102
+ (options.container ?? doc.body).append(toast);
103
+ // Toast owns the one persistent announcement, avoiding duplicate live messages.
104
+ toast.show(`${message}. Undo available with ${shortcut}.`, { duration: options.duration ?? 8000 });
105
+ return offer;
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unofficialbox/box-open-elements",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Box Open Elements: an open, framework-agnostic design system and web component library for Box-style experiences",
5
5
  "license": "MIT",
6
6
  "homepage": "https://unofficialbox.github.io/box-open-elements",
@@ -28,6 +28,14 @@
28
28
  "main": "dist/index.js",
29
29
  "types": "dist/index.d.ts",
30
30
  "exports": {
31
+ "./patterns/undo": {
32
+ "types": "./dist/patterns/undo/index.d.ts",
33
+ "import": "./dist/patterns/undo/index.js"
34
+ },
35
+ "./patterns/flow-builder": {
36
+ "types": "./dist/patterns/flow-builder/index.d.ts",
37
+ "import": "./dist/patterns/flow-builder/index.js"
38
+ },
31
39
  ".": {
32
40
  "types": "./dist/index.d.ts",
33
41
  "import": "./dist/index.js"
@@ -104,6 +112,18 @@
104
112
  "types": "./dist/patterns/lineage/index.d.ts",
105
113
  "import": "./dist/patterns/lineage/index.js"
106
114
  },
115
+ "./patterns/agent-workspace": {
116
+ "types": "./dist/patterns/agent-workspace/index.d.ts",
117
+ "import": "./dist/patterns/agent-workspace/index.js"
118
+ },
119
+ "./patterns/run": {
120
+ "types": "./dist/patterns/run/index.d.ts",
121
+ "import": "./dist/patterns/run/index.js"
122
+ },
123
+ "./patterns/call-console": {
124
+ "types": "./dist/patterns/call-console/index.d.ts",
125
+ "import": "./dist/patterns/call-console/index.js"
126
+ },
107
127
  "./patterns/agent-chat": {
108
128
  "types": "./dist/patterns/agent-chat/index.d.ts",
109
129
  "import": "./dist/patterns/agent-chat/index.js"
@@ -161,8 +181,9 @@
161
181
  "dist"
162
182
  ],
163
183
  "scripts": {
184
+ "bundles:check": "bun tools/adapters/bundle-check.ts",
164
185
  "build": "bun run build:core && bun run build:adapters",
165
- "build:core": "tsc -p tsconfig.json",
186
+ "build:core": "tsc -p tsconfig.json && cp src/foundations/icons/glyphs/package.json dist/foundations/icons/glyphs/package.json",
166
187
  "build:adapters": "bun run adapters:version && bun run --cwd packages/react build && bun run --cwd packages/angular build && bun run --cwd packages/vue build && bun run --cwd packages/svelte build",
167
188
  "build:watch": "tsc -p tsconfig.json --watch --preserveWatchOutput",
168
189
  "prepublishOnly": "bun run build",