@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
@@ -0,0 +1,201 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { SplitView } from "../../components/layout/split-view.js";
3
+ import { Button } from "../../components/actions/button.js";
4
+ import { CodeBlock } from "../../components/output/code-block.js";
5
+ import { boeControl, boeRadius } from "../../foundations/geometry/index.js";
6
+ import { boeStatusGlyph, boeStatusStyles } from "../../foundations/status/index.js";
7
+ import { boeEntrance, boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
8
+ import { callFailed, callStatus, formatCallHttp } from "./types.js";
9
+ const DEFAULT_TAG_NAME = "box-call-console";
10
+ export async function copyCallText(text, doc = document) {
11
+ try {
12
+ await doc.defaultView?.navigator.clipboard.writeText(text);
13
+ if (doc.defaultView?.navigator.clipboard)
14
+ return true;
15
+ }
16
+ catch { }
17
+ const active = doc.activeElement;
18
+ const textarea = doc.createElement("textarea");
19
+ textarea.value = text;
20
+ textarea.style.cssText = "position:fixed;opacity:0";
21
+ doc.body.append(textarea);
22
+ textarea.select();
23
+ try {
24
+ return doc.execCommand?.("copy") ?? false;
25
+ }
26
+ finally {
27
+ textarea.remove();
28
+ active?.focus({ preventScroll: true });
29
+ }
30
+ }
31
+ export class CallConsole extends BaseElement {
32
+ static tagName = DEFAULT_TAG_NAME;
33
+ model = null;
34
+ unsubscribe;
35
+ selected = "";
36
+ clearing = false;
37
+ rowSignatures = new WeakMap();
38
+ get callController() { return this.model; }
39
+ set callController(value) { this.unsubscribe?.(); this.model = value; if (this.isConnected)
40
+ this.listen(); if (this.isRendered)
41
+ this.update(); }
42
+ listen() { this.unsubscribe?.(); this.unsubscribe = this.model?.subscribe(() => this.update()); }
43
+ connectedCallback() { super.connectedCallback(); this.listen(); }
44
+ disconnectedCallback() { this.unsubscribe?.(); }
45
+ renderTemplate() {
46
+ SplitView.register();
47
+ Button.register();
48
+ CodeBlock.register();
49
+ this.shadowRoot.innerHTML = `<style>:host{display:block;min-width:0;color:var(--boe-token-text-text,#222);font:inherit}:host([hidden]){display:none !important}
50
+ :host{container-type:inline-size;font-size:14px;line-height:1.5}
51
+ *{box-sizing:border-box} [hidden]{display:none!important}
52
+ [part="console"]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:${boeRadius.large};background:var(--boe-token-surface-surface,#fff);overflow:hidden}
53
+ header,.filters,.pane-heading{display:flex;gap:.75rem;flex-wrap:wrap;align-items:center;padding:1rem}
54
+ header{justify-content:space-between} h2,h3,p{margin:0} h2{font-size:16px} h3{font-size:14px} small,.muted{color:var(--boe-token-text-text-secondary,#666);font-size:12px}
55
+ #connection{display:inline-flex;align-items:center;gap:.4rem;font-size:12px}
56
+ .filters{border-block:1px solid var(--boe-token-stroke-stroke,#ddd);background:var(--boe-token-surface-surface-secondary,#f7f7f7);padding:.75rem 1rem}
57
+ label{display:flex;align-items:center;gap:.5rem;font-size:13px} .search{flex:1;min-width:10rem} .search input{width:100%}
58
+ input[type="search"],select{min-height:${boeControl.height};border:1px solid var(--boe-token-stroke-stroke,#ccc);border-radius:${boeRadius.control};background:var(--boe-token-surface-surface,#fff);color:inherit;font:inherit;padding:.35rem .65rem;min-width:0}
59
+ input[type="checkbox"]{accent-color:var(--boe-token-surface-surface-brand,#0061d5)}
60
+ button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--boe-token-surface-surface-brand,#0061d5);outline-offset:-2px}
61
+ .pane-heading{justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd)}
62
+ #list{overflow:auto;max-height:34rem;min-height:20rem;padding:.35rem}
63
+ [part="call"]{font:inherit;color:inherit;border:1px solid transparent;background:transparent;border-radius:${boeRadius.med};width:100%;display:grid;grid-template-columns:minmax(0,1fr) auto;text-align:left;gap:.3rem .75rem;padding:.75rem;cursor:pointer;${boeEntrance()}}
64
+ [part="call"]:hover{background:var(--boe-token-surface-surface-secondary,#f5f5f5)}
65
+ [part="call"][aria-current="true"]{background:var(--boe-token-surface-item-surface-selected,#eaf3ff);border-color:var(--boe-token-surface-surface-brand,#0061d5)}
66
+ [part="call"] span{min-width:0;overflow-wrap:anywhere} [part="call"] .state{display:flex;gap:.4rem;align-items:center;font-size:12px} [part="call"] .summary{font-weight:600} [part="call"] .duration{text-align:right;font-variant-numeric:tabular-nums;font-size:12px}
67
+ #inspector{padding:1rem;min-width:0;max-height:38rem;overflow:auto} #summary{font-size:15px;overflow-wrap:anywhere} #metadata{margin:.25rem 0 1rem;overflow-wrap:anywhere} .code-heading{margin:1rem 0 .5rem}
68
+ #outcome{padding:.6rem .75rem;margin:.75rem 0;border-radius:${boeRadius.med};background:var(--boe-token-surface-surface-secondary,#f5f5f5);overflow-wrap:anywhere}
69
+ #copy-status{font-size:12px;color:var(--boe-token-text-text-secondary,#666)}
70
+ #list-empty,#detail-empty{padding:2.5rem 1rem;text-align:center;color:var(--boe-token-text-text-secondary,#666)}
71
+ [part="skeleton"]{height:3rem;margin:.5rem;background:var(--boe-token-surface-surface-secondary,#eee);border-radius:${boeRadius.med}}
72
+ box-code-block::part(pre){max-height:18rem;overflow:auto}
73
+ @container(max-width:640px){box-split-view::part(split-view){grid-template-columns:minmax(0,1fr)!important}box-split-view::part(separator){display:none}#list{min-height:0;max-height:17rem}#inspector{border-top:1px solid var(--boe-token-stroke-stroke,#ddd)}.search{flex-basis:100%}}
74
+ ${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}</style>
75
+ <section part="console" aria-label="API call console">
76
+ <header><div><h2>API calls</h2><small>Inspect requests, responses, and tool outcomes</small></div><span id="connection" role="status"></span><box-button id="retry" label="Reconnect" tone="neutral" hidden></box-button></header>
77
+ <div class="filters"><label class="search">Search <input id="search" type="search" placeholder="Method, URL, or summary"></label><label>Service <select id="service" aria-label="Service"><option value="">All services</option></select></label><label><input type="checkbox" id="errors"> Errors only</label><box-button id="clear" label="Clear calls" tone="neutral"></box-button></div>
78
+ <box-split-view resizable ratio="0.42" label="Calls and request inspector"><section slot="primary" aria-label="Calls"><div class="pane-heading"><h3>Requests</h3><span id="count" class="muted" role="status"></span></div><div id="list"></div><p id="list-empty" role="status" hidden></p></section><section aria-label="Request inspector"><div class="pane-heading"><h3>Inspector</h3><box-button data-copy="both" label="Copy both" tone="neutral"></box-button><span id="copy-status" role="status"></span></div><p id="detail-empty">Select a request to inspect its details.</p><div id="inspector" hidden><h3 id="summary"></h3><p id="metadata" class="muted"></p><p id="outcome" role="status" hidden></p><h3 class="code-heading">Request</h3><box-code-block id="request" copy-label="Copy request" wrap></box-code-block><h3 class="code-heading">Response</h3><box-code-block id="response" copy-label="Copy response" wrap></box-code-block></div></section></box-split-view></section>`;
79
+ }
80
+ setupListeners() {
81
+ this.shadowRoot.querySelector("#list").addEventListener("keydown", event => {
82
+ const e = event;
83
+ const rows = Array.from(this.shadowRoot.querySelectorAll('[part="call"]'));
84
+ const current = rows.indexOf(e.target);
85
+ if (current < 0 || !["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key))
86
+ return;
87
+ e.preventDefault();
88
+ const next = e.key === "Home" ? 0 : e.key === "End" ? rows.length - 1 : (current + (e.key === "ArrowDown" ? 1 : -1) + rows.length) % rows.length;
89
+ rows[next].focus();
90
+ rows[next].click();
91
+ });
92
+ const filter = () => { this.update(); this.dispatchEvent(new CustomEvent("filters-changed", { bubbles: true, composed: true, detail: this.filters })); };
93
+ this.shadowRoot.querySelector("#service").addEventListener("change", filter);
94
+ this.shadowRoot.querySelector("#errors").addEventListener("change", filter);
95
+ this.shadowRoot.querySelector("#search").addEventListener("input", filter);
96
+ this.shadowRoot.querySelector("#retry").addEventListener("click", () => { void this.model?.connect(); });
97
+ this.shadowRoot.querySelector("#clear").addEventListener("click", async () => {
98
+ if (!this.model || this.clearing)
99
+ return;
100
+ this.clearing = true;
101
+ this.update();
102
+ try {
103
+ await this.model.clear();
104
+ this.dispatchEvent(new CustomEvent("calls-cleared", { bubbles: true, composed: true }));
105
+ }
106
+ catch {
107
+ this.shadowRoot.querySelector("#copy-status").textContent = "Unable to clear calls. Try again.";
108
+ }
109
+ finally {
110
+ this.clearing = false;
111
+ this.update();
112
+ }
113
+ });
114
+ this.shadowRoot.querySelectorAll("[data-copy]").forEach(b => b.addEventListener("click", async () => {
115
+ const entry = this.model?.entries.find(e => e.id === this.selected);
116
+ if (!entry)
117
+ return;
118
+ const ok = await copyCallText(formatCallHttp(entry, b.dataset.copy));
119
+ this.shadowRoot.querySelector("#copy-status").textContent = ok ? "Copied" : "Copy unavailable; select the text to copy";
120
+ }));
121
+ }
122
+ get filters() {
123
+ return { service: this.shadowRoot.querySelector("#service").value, errorsOnly: this.shadowRoot.querySelector("#errors").checked, query: this.shadowRoot.querySelector("#search").value };
124
+ }
125
+ update() {
126
+ if (!this.isRendered)
127
+ return;
128
+ const state = this.model?.connection ?? "connecting";
129
+ const connection = this.shadowRoot.querySelector("#connection");
130
+ connection.textContent = this.model ? ({ live: "Live", connecting: "Connecting…", reconnecting: "Reconnecting…", unavailable: "Endpoint unavailable" }[state]) : "Not connected";
131
+ this.shadowRoot.querySelector("#retry").hidden = !this.model || state === "live" || state === "connecting";
132
+ const clear = this.shadowRoot.querySelector("#clear");
133
+ clear.disabled = this.clearing || !this.model?.entries.length;
134
+ clear.label = this.clearing ? "Clearing…" : "Clear calls";
135
+ const service = this.shadowRoot.querySelector("#service");
136
+ const services = [...new Set(this.model?.entries.map(e => e.service) ?? [])];
137
+ for (const name of services)
138
+ if (!Array.from(service.options).some(o => o.value === name)) {
139
+ const option = document.createElement("option");
140
+ option.value = name;
141
+ option.textContent = name;
142
+ service.append(option);
143
+ }
144
+ const errors = this.shadowRoot.querySelector("#errors").checked;
145
+ const entries = this.model?.entries.filter(e => (!service.value || e.service === service.value) && (!errors || callFailed(e))) ?? [];
146
+ const query = this.filters.query.trim().toLowerCase();
147
+ const filtered = entries.filter(e => `${e.method} ${e.url} ${e.summary} ${e.service}`.toLowerCase().includes(query));
148
+ entries.splice(0, entries.length, ...filtered);
149
+ if (!entries.some(e => e.id === this.selected))
150
+ this.selected = entries[0]?.id ?? "";
151
+ const list = this.shadowRoot.querySelector("#list");
152
+ const focused = this.shadowRoot.activeElement?.getAttribute("data-call-id");
153
+ list.querySelectorAll('[part="skeleton"]').forEach(el => el.remove());
154
+ const existing = new Map(Array.from(list.children).map(el => [el.getAttribute("data-call-id"), el]));
155
+ for (const [index, entry] of entries.entries()) {
156
+ let button = existing.get(entry.id);
157
+ existing.delete(entry.id);
158
+ if (!button) {
159
+ button = document.createElement("button");
160
+ button.type = "button";
161
+ button.setAttribute("part", "call");
162
+ button.dataset.callId = entry.id;
163
+ button.addEventListener("click", () => { this.selected = entry.id; this.update(); this.dispatchEvent(new CustomEvent("call-selected", { bubbles: true, composed: true, detail: { id: entry.id } })); });
164
+ }
165
+ button.setAttribute("aria-current", String(entry.id === this.selected));
166
+ const labels = [callStatus(entry), entry.service, entry.summary, entry.pending ? "In progress" : `${Math.round(entry.durationMs)} ms`];
167
+ const signature = JSON.stringify(labels);
168
+ if (this.rowSignatures.get(button) !== signature) {
169
+ button.replaceChildren(...labels.map((text, index) => { const span = document.createElement("span"); span.textContent = text; span.className = ["state", "muted", "summary", "duration"][index]; if (index === 0) {
170
+ const icon = document.createElement("span");
171
+ icon.innerHTML = boeStatusGlyph(entry.pending ? "active" : callFailed(entry) ? "failed" : entry.expected ? "skipped" : "done");
172
+ span.prepend(icon);
173
+ } return span; }));
174
+ this.rowSignatures.set(button, signature);
175
+ }
176
+ if (list.children[index] !== button)
177
+ list.insertBefore(button, list.children[index] ?? null);
178
+ if (entry.id === focused)
179
+ button.focus({ preventScroll: true });
180
+ }
181
+ existing.forEach(el => el.remove());
182
+ if (!entries.length && state === "connecting" && this.model)
183
+ list.innerHTML = '<div part="skeleton"></div><div part="skeleton"></div><div part="skeleton"></div>';
184
+ this.shadowRoot.querySelector("#count").textContent = `${entries.length} of ${this.model?.entries.length ?? 0}`;
185
+ const empty = this.shadowRoot.querySelector("#list-empty");
186
+ empty.hidden = entries.length > 0 || !!this.model && state === "connecting";
187
+ empty.textContent = !this.model ? "Connect a call controller to inspect requests." : state === "unavailable" ? "The /calls endpoint is unavailable. Start the agent, then reconnect." : state === "reconnecting" ? "Connection interrupted. Reconnect to load calls." : this.model.entries.length ? "No requests match these filters." : "No calls yet. New requests will appear here.";
188
+ const entry = entries.find(e => e.id === this.selected);
189
+ this.shadowRoot.querySelector('[data-copy="both"]').disabled = !entry;
190
+ this.shadowRoot.querySelector("#inspector").hidden = !entry;
191
+ this.shadowRoot.querySelector("#detail-empty").hidden = !!entry;
192
+ this.shadowRoot.querySelector("#summary").textContent = entry?.summary ?? "";
193
+ this.shadowRoot.querySelector("#metadata").textContent = entry ? `${entry.service} · ${entry.method} · ${entry.pending ? "In progress" : `${entry.durationMs} ms`} · ${new Date(entry.startedAt).toLocaleTimeString()}` : "";
194
+ const outcome = this.shadowRoot.querySelector("#outcome");
195
+ outcome.hidden = !entry || !(entry.error || entry.rpcError || entry.expected || entry.pending);
196
+ outcome.textContent = entry?.rpcError ? `Tool failed despite HTTP ${entry.status}: ${entry.rpcError}` : entry?.error ?? entry?.expected ?? (entry?.pending ? "Waiting for the response…" : "");
197
+ this.shadowRoot.querySelector("#request").code = entry ? formatCallHttp(entry, "request") : "";
198
+ this.shadowRoot.querySelector("#response").code = entry ? entry.pending ? "Response pending" : formatCallHttp(entry, "response") : "";
199
+ }
200
+ }
201
+ CallConsole.register();
@@ -0,0 +1,23 @@
1
+ import type { CallConnection, CallEntry, CallEvent } from "./types.js";
2
+ export interface CallConsoleTransport {
3
+ snapshot(signal: AbortSignal): Promise<CallEntry[]>;
4
+ subscribe(onEvent: (event: CallEvent) => void, onConnection: (state: CallConnection) => void): () => void;
5
+ clear(): Promise<void>;
6
+ }
7
+ /** Transport injection keeps networking out of the component. */
8
+ export declare class CallConsoleController {
9
+ readonly transport: CallConsoleTransport;
10
+ entries: CallEntry[];
11
+ connection: CallConnection;
12
+ private listeners;
13
+ private abort?;
14
+ private close?;
15
+ constructor(transport: CallConsoleTransport);
16
+ subscribe(fn: () => void): () => void;
17
+ private emit;
18
+ apply(event: CallEvent): void;
19
+ connect(): Promise<void>;
20
+ clear(): Promise<void>;
21
+ disconnect(): void;
22
+ destroy(): void;
23
+ }
@@ -0,0 +1,50 @@
1
+ /** Transport injection keeps networking out of the component. */
2
+ export class CallConsoleController {
3
+ transport;
4
+ entries = [];
5
+ connection = "connecting";
6
+ listeners = new Set();
7
+ abort;
8
+ close;
9
+ constructor(transport) {
10
+ this.transport = transport;
11
+ }
12
+ subscribe(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); }
13
+ emit() { this.listeners.forEach(fn => fn()); }
14
+ apply(event) {
15
+ if (event.type === "snapshot")
16
+ this.entries = event.entries;
17
+ else if (event.type === "clear")
18
+ this.entries = [];
19
+ else
20
+ this.entries = this.entries.some(e => e.id === event.entry.id) ? this.entries.map(e => e.id === event.entry.id ? event.entry : e) : [event.entry, ...this.entries];
21
+ this.emit();
22
+ }
23
+ async connect() {
24
+ this.disconnect();
25
+ const abort = new AbortController();
26
+ this.abort = abort;
27
+ this.connection = "connecting";
28
+ this.emit();
29
+ try {
30
+ const entries = await this.transport.snapshot(abort.signal);
31
+ if (abort.signal.aborted)
32
+ return;
33
+ this.apply({ type: "snapshot", entries });
34
+ this.close = this.transport.subscribe(event => { if (!abort.signal.aborted)
35
+ this.apply(event); }, state => { if (!abort.signal.aborted) {
36
+ this.connection = state;
37
+ this.emit();
38
+ } });
39
+ }
40
+ catch (error) {
41
+ if (!abort.signal.aborted) {
42
+ this.connection = error instanceof Error && error.message === "404" ? "unavailable" : "reconnecting";
43
+ this.emit();
44
+ }
45
+ }
46
+ }
47
+ async clear() { await this.transport.clear(); this.apply({ type: "clear" }); }
48
+ disconnect() { this.abort?.abort(); this.close?.(); this.close = undefined; }
49
+ destroy() { this.disconnect(); this.listeners.clear(); }
50
+ }
@@ -0,0 +1,3 @@
1
+ import type { CallConsoleTransport } from "./controller.js";
2
+ /** Optional browser adapter for the documented snapshot/SSE/DELETE contract. */
3
+ export declare function createCallConsoleTransport(base?: string): CallConsoleTransport;
@@ -0,0 +1,26 @@
1
+ /** Optional browser adapter for the documented snapshot/SSE/DELETE contract. */
2
+ export function createCallConsoleTransport(base = "/calls") {
3
+ return {
4
+ async snapshot(signal) { const response = await fetch(base, { signal }); if (!response.ok)
5
+ throw new Error(String(response.status)); return await response.json(); },
6
+ subscribe(onEvent, onConnection) {
7
+ const events = new EventSource(`${base}/stream`);
8
+ events.onopen = () => onConnection("live");
9
+ events.onerror = () => onConnection("reconnecting");
10
+ for (const type of ["snapshot", "call", "clear"])
11
+ events.addEventListener(type, event => {
12
+ try {
13
+ const data = JSON.parse(event.data);
14
+ if (data.type === type)
15
+ onEvent(data);
16
+ }
17
+ catch {
18
+ onConnection("reconnecting");
19
+ }
20
+ });
21
+ return () => events.close();
22
+ },
23
+ async clear() { const response = await fetch(base, { method: "DELETE" }); if (!response.ok)
24
+ throw new Error(String(response.status)); },
25
+ };
26
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./controller.js";
3
+ export * from "./http-transport.js";
4
+ export * from "./call-console.js";
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./controller.js";
3
+ export * from "./http-transport.js";
4
+ export * from "./call-console.js";
@@ -0,0 +1,32 @@
1
+ export interface CallEntry {
2
+ id: string;
3
+ startedAt: number;
4
+ durationMs: number;
5
+ pending: boolean;
6
+ service: string;
7
+ summary: string;
8
+ method: string;
9
+ url: string;
10
+ requestHeaders: Record<string, string>;
11
+ requestBody?: string;
12
+ status: number;
13
+ statusText: string;
14
+ responseHeaders: Record<string, string>;
15
+ responseBody?: string;
16
+ error?: string;
17
+ expected?: string;
18
+ rpcError?: string;
19
+ }
20
+ export type CallEvent = {
21
+ type: "snapshot";
22
+ entries: CallEntry[];
23
+ } | {
24
+ type: "call";
25
+ entry: CallEntry;
26
+ } | {
27
+ type: "clear";
28
+ };
29
+ export type CallConnection = "connecting" | "live" | "reconnecting" | "unavailable";
30
+ export declare const callFailed: (entry: CallEntry) => boolean;
31
+ export declare const callStatus: (entry: CallEntry) => string;
32
+ export declare const formatCallHttp: (entry: CallEntry, side?: "request" | "response" | "both") => string;
@@ -0,0 +1,8 @@
1
+ export const callFailed = (entry) => !entry.pending && Boolean(entry.error || entry.rpcError || (!entry.expected && (entry.status < 200 || entry.status >= 300)));
2
+ export const callStatus = (entry) => entry.pending ? "Pending" : entry.rpcError ? "Tool error" : entry.error ? "Failed" : entry.expected ? "Expected" : callFailed(entry) ? "Failed" : "Done";
3
+ export const formatCallHttp = (entry, side = "both") => {
4
+ const headers = (h) => Object.entries(h).map(([k, v]) => `${k}: ${v}`).join("\n");
5
+ const request = `${entry.method} ${entry.url}\n${headers(entry.requestHeaders)}\n\n${entry.requestBody ?? ""}`;
6
+ const response = `HTTP ${entry.status} ${entry.statusText}\n${headers(entry.responseHeaders)}\n\n${entry.responseBody ?? ""}${entry.rpcError || entry.error ? `\n${entry.rpcError ?? entry.error}` : ""}`;
7
+ return side === "request" ? request : side === "response" ? response : `${request}\n\n${response}`;
8
+ };
@@ -206,6 +206,8 @@ export class ExplorerActionMenu extends BaseElement {
206
206
  if (this.itemIdValue && actionId) {
207
207
  const item = this.controllerValue?.getState().items.find(entry => entry.id === this.itemIdValue) ?? null;
208
208
  const action = this.controllerValue?.getItemActions(this.itemIdValue).find(entry => entry.id === actionId) ?? null;
209
+ if (!action || action.disabled)
210
+ return;
209
211
  this.controllerValue?.invokeItemAction(this.itemIdValue, actionId);
210
212
  if (item && action) {
211
213
  this.dispatchEvent(new CustomEvent(toKebabCase("itemActionInvoked"), {
@@ -249,7 +251,7 @@ export class ExplorerActionMenu extends BaseElement {
249
251
  });
250
252
  return;
251
253
  }
252
- const menuItems = Array.from(this.shadowRoot.querySelectorAll('[part="menu-item"]'));
254
+ const menuItems = Array.from(this.shadowRoot.querySelectorAll('[part="menu-item"]:not(:disabled)'));
253
255
  handleRovingKeydown(keyboardEvent, menuItems, { orientation: "vertical" });
254
256
  });
255
257
  }
@@ -269,7 +271,7 @@ export class ExplorerActionMenu extends BaseElement {
269
271
  }
270
272
  const menuMarkup = this.open
271
273
  ? `<div id="${this.menuId}" part="menu" role="menu">${actions
272
- .map(action => `<button type="button" part="menu-item" role="menuitem" data-action-id="${escapeHtml(action.id)}">${escapeHtml(action.label)}</button>`)
274
+ .map(action => `<button type="button" part="menu-item" role="menuitem" data-action-id="${escapeHtml(action.id)}" ${action.disabled ? 'disabled aria-disabled="true" tabindex="-1"' : ''}>${escapeHtml(action.label)}</button>`)
273
275
  .join("")}</div>`
274
276
  : "";
275
277
  this.hostEl.innerHTML = `
@@ -286,12 +288,12 @@ export class ExplorerActionMenu extends BaseElement {
286
288
  </div>
287
289
  `;
288
290
  if (this.open) {
289
- const menuItems = Array.from(this.shadowRoot?.querySelectorAll('[part="menu-item"]') ?? []);
291
+ const menuItems = Array.from(this.shadowRoot?.querySelectorAll('[part="menu-item"]:not(:disabled)') ?? []);
290
292
  applyRovingTabindex(menuItems, 0);
291
293
  }
292
294
  if (justOpened) {
293
295
  queueMicrotask(() => {
294
- this.shadowRoot?.querySelector('[part="menu-item"]')?.focus();
296
+ this.shadowRoot?.querySelector('[part="menu-item"]:not(:disabled)')?.focus();
295
297
  });
296
298
  return;
297
299
  }
@@ -302,7 +304,7 @@ export class ExplorerActionMenu extends BaseElement {
302
304
  });
303
305
  return;
304
306
  }
305
- if (focusedTrigger) {
307
+ if (focusedTrigger || (focusedActionId && !this.open)) {
306
308
  queueMicrotask(() => {
307
309
  this.shadowRoot?.querySelector('[part="trigger"]')?.focus();
308
310
  });
@@ -9,6 +9,7 @@ const toKebabCase = (value) => value.replace(/[A-Z]/g, letter => `-${letter.toLo
9
9
  const elementStyles = `
10
10
  :host {
11
11
  display: block;
12
+ min-inline-size: 0;
12
13
  color: inherit;
13
14
  font: inherit;
14
15
  }
@@ -33,14 +34,15 @@ const elementStyles = `
33
34
 
34
35
  [part="search"] {
35
36
  flex: 1 1 14rem;
36
- min-width: 12rem;
37
+ min-width: 0;
38
+ max-width: 100%;
37
39
  }
38
40
 
39
41
  [part="status"],
40
42
  [part="selection-count"] {
41
43
  color: var(--boe-token-text-text-secondary, #6f6f6f);
42
44
  font-size: 0.85rem;
43
- white-space: nowrap;
45
+ overflow-wrap: anywhere;
44
46
  padding-bottom: 0.45rem;
45
47
  }
46
48
 
@@ -54,6 +56,8 @@ const elementStyles = `
54
56
 
55
57
  [part="refresh"],
56
58
  [part="clear-selection"] {
59
+ max-inline-size: 100%;
60
+ overflow-wrap: anywhere;
57
61
  appearance: none;
58
62
  font: inherit;
59
63
  font-size: 0.88rem;
@@ -728,7 +728,8 @@ export class ContentUploader extends BaseElement {
728
728
  </div>
729
729
  <box-progress-bar
730
730
  part="row-progress"
731
- label="${escapeHtml(`${item.name} upload progress`)}"
731
+ hide-label
732
+ label="${escapeHtml(item.name)}"
732
733
  max="100"
733
734
  value="${String(Math.round(item.progress * 100))}"
734
735
  ></box-progress-bar>
@@ -302,6 +302,7 @@ export class FileRequestBuilder extends BaseElement {
302
302
  if (!host) {
303
303
  return;
304
304
  }
305
+ const focusedSetting = this.shadowRoot.activeElement?.getAttribute("data-setting-id");
305
306
  host.innerHTML = `
306
307
  <section part="builder">
307
308
  <header part="header">
@@ -318,6 +319,8 @@ export class FileRequestBuilder extends BaseElement {
318
319
  </section>
319
320
  `;
320
321
  this.shadowRoot.querySelectorAll("[data-setting-id]").forEach(input => {
322
+ if (focusedSetting === input.dataset.settingId)
323
+ input.focus({ preventScroll: true });
321
324
  input.addEventListener("change", () => {
322
325
  const settingId = input.dataset.settingId;
323
326
  if (!settingId) {
@@ -0,0 +1,48 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ import { type FlowKind, type FlowNode, type NodePath } from "./model.js";
3
+ export type InspectorRenderer = (node: FlowNode, container: HTMLElement) => void | (() => void);
4
+ /** Host-owned document editing with accessible navigation and responsive inspection. */
5
+ export declare class FlowBuilder extends BaseElement {
6
+ static readonly tagName = "box-flow-builder";
7
+ private nodesValue;
8
+ private catalogValue;
9
+ private selectedValue;
10
+ private invalidValue;
11
+ private renderer?;
12
+ private cleanupInspector?;
13
+ private inspected;
14
+ private renderedRenderer?;
15
+ private observer?;
16
+ private narrow;
17
+ private insertion?;
18
+ private invoker?;
19
+ private spine;
20
+ private palette;
21
+ private picker;
22
+ private popup;
23
+ private aside;
24
+ private inspector;
25
+ private drawer;
26
+ private error;
27
+ get nodes(): FlowNode[];
28
+ set nodes(value: FlowNode[]);
29
+ get catalog(): readonly FlowKind[];
30
+ set catalog(value: readonly FlowKind[]);
31
+ get selected(): FlowNode | null;
32
+ set selected(node: FlowNode | null);
33
+ get renderInspector(): InspectorRenderer | undefined;
34
+ set renderInspector(renderer: InspectorRenderer | undefined);
35
+ connectedCallback(): void;
36
+ disconnectedCallback(): void;
37
+ private setNarrow;
38
+ protected renderTemplate(): void;
39
+ protected setupListeners(): void;
40
+ private closePicker;
41
+ private insert;
42
+ select(node: FlowNode | null): void;
43
+ /** Mark validation without opening a sheet over the error on a narrow screen. */
44
+ setValidation(message: string, path?: NodePath, document?: unknown): void;
45
+ refresh(): void;
46
+ protected update(): void;
47
+ private updateInspector;
48
+ }