@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
@@ -3,6 +3,8 @@ export declare class MenuItem extends BaseElement {
3
3
  static readonly tagName: string;
4
4
  static get observedAttributes(): string[];
5
5
  private itemEl;
6
+ /** Let composed menus move focus without reaching into the shadow root. */
7
+ focus(options?: FocusOptions): void;
6
8
  get disabled(): boolean;
7
9
  set disabled(value: boolean);
8
10
  get label(): string;
@@ -67,6 +67,10 @@ export class MenuItem extends BaseElement {
67
67
  return ["disabled", "label", "selected", "value"];
68
68
  }
69
69
  itemEl;
70
+ /** Let composed menus move focus without reaching into the shadow root. */
71
+ focus(options) {
72
+ this.itemEl?.focus(options);
73
+ }
70
74
  get disabled() {
71
75
  return this.hasAttribute("disabled");
72
76
  }
@@ -25,6 +25,11 @@ const cardStyles = `
25
25
  0 14px 28px rgba(15, 23, 42, 0.04);
26
26
  }
27
27
 
28
+ [part="header"],
29
+ [part="body"] {
30
+ min-width: 0;
31
+ }
32
+
28
33
  [part="header"] {
29
34
  display: grid;
30
35
  gap: 0.35rem;
@@ -48,6 +53,7 @@ const cardStyles = `
48
53
  }
49
54
 
50
55
  [part="body"] {
56
+ overflow-x: auto;
51
57
  color: var(--boe-token-text-text-secondary, #6f6f6f);
52
58
  line-height: 1.55;
53
59
  }
@@ -0,0 +1,10 @@
1
+ import { ResultBlocks, type ResultBlock } from "./result-blocks.js";
2
+ export declare class CheckList extends ResultBlocks {
3
+ static readonly tagName = "box-check-list";
4
+ get rows(): Extract<ResultBlock, {
5
+ type: "checks";
6
+ }>["rows"];
7
+ set rows(rows: Extract<ResultBlock, {
8
+ type: "checks";
9
+ }>["rows"]);
10
+ }
@@ -0,0 +1,8 @@
1
+ import { ResultBlocks } from "./result-blocks.js";
2
+ const DEFAULT_TAG_NAME = "box-check-list";
3
+ export class CheckList extends ResultBlocks {
4
+ static tagName = DEFAULT_TAG_NAME;
5
+ get rows() { const b = this.blocks[0]; return b?.type === "checks" ? b.rows : []; }
6
+ set rows(rows) { this.blocks = [{ type: "checks", rows }]; }
7
+ }
8
+ CheckList.register();
@@ -0,0 +1,10 @@
1
+ import { ResultBlocks, type ResultBlock } from "./result-blocks.js";
2
+ export declare class DocumentList extends ResultBlocks {
3
+ static readonly tagName = "box-document-list";
4
+ get items(): Extract<ResultBlock, {
5
+ type: "documents";
6
+ }>["items"];
7
+ set items(items: Extract<ResultBlock, {
8
+ type: "documents";
9
+ }>["items"]);
10
+ }
@@ -0,0 +1,8 @@
1
+ import { ResultBlocks } from "./result-blocks.js";
2
+ const DEFAULT_TAG_NAME = "box-document-list";
3
+ export class DocumentList extends ResultBlocks {
4
+ static tagName = DEFAULT_TAG_NAME;
5
+ get items() { const b = this.blocks[0]; return b?.type === "documents" ? b.items : []; }
6
+ set items(items) { this.blocks = [{ type: "documents", items }]; }
7
+ }
8
+ DocumentList.register();
@@ -0,0 +1,10 @@
1
+ import { ResultBlocks, type ResultBlock } from "./result-blocks.js";
2
+ export declare class FactList extends ResultBlocks {
3
+ static readonly tagName = "box-fact-list";
4
+ get rows(): Extract<ResultBlock, {
5
+ type: "facts";
6
+ }>["rows"];
7
+ set rows(rows: Extract<ResultBlock, {
8
+ type: "facts";
9
+ }>["rows"]);
10
+ }
@@ -0,0 +1,8 @@
1
+ import { ResultBlocks } from "./result-blocks.js";
2
+ const DEFAULT_TAG_NAME = "box-fact-list";
3
+ export class FactList extends ResultBlocks {
4
+ static tagName = DEFAULT_TAG_NAME;
5
+ get rows() { const b = this.blocks[0]; return b?.type === "facts" ? b.rows : []; }
6
+ set rows(rows) { this.blocks = [{ type: "facts", rows }]; }
7
+ }
8
+ FactList.register();
@@ -0,0 +1,54 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ export type CheckStatus = "pass" | "warn" | "fail" | "info";
3
+ export type ResultBlock = {
4
+ id?: string;
5
+ } & ({
6
+ type: "facts";
7
+ title?: string;
8
+ rows: {
9
+ label: string;
10
+ value: string;
11
+ }[];
12
+ } | {
13
+ type: "checks";
14
+ title?: string;
15
+ rows: {
16
+ label: string;
17
+ value?: string;
18
+ detail?: string;
19
+ status: CheckStatus;
20
+ }[];
21
+ } | {
22
+ type: "table";
23
+ title?: string;
24
+ columns: string[];
25
+ rows: {
26
+ cells: string[];
27
+ status?: CheckStatus;
28
+ note?: string;
29
+ }[];
30
+ footnote?: string;
31
+ } | {
32
+ type: "documents";
33
+ title?: string;
34
+ items: {
35
+ id: string;
36
+ name: string;
37
+ detail?: string;
38
+ href?: string;
39
+ }[];
40
+ });
41
+ export declare const resultDocumentIds: (blocks: readonly ResultBlock[]) => string[];
42
+ /** Pure escaped renderer also usable by host-composed conversations. */
43
+ export declare function renderResultBlock(block: ResultBlock): string;
44
+ export declare const resultBlockStyles: string;
45
+ export declare class ResultBlocks extends BaseElement {
46
+ static readonly tagName: string;
47
+ private value;
48
+ get blocks(): ResultBlock[];
49
+ set blocks(value: ResultBlock[]);
50
+ get documentIds(): string[];
51
+ protected renderTemplate(): void;
52
+ protected setupListeners(): void;
53
+ protected update(): void;
54
+ }
@@ -0,0 +1,74 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { isSafeHref } from "../../patterns/internal/safe-href.js";
3
+ import { boeStatusGlyph, boeStatusStyles, statusLabel, toStatusKind } from "../../foundations/status/index.js";
4
+ import { boeEntrance, boeEntranceKeyframes, boeReducedMotionPolicy, boeStaggerStyles } from "../../foundations/motion/index.js";
5
+ const escape = (s) => s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
6
+ const verdict = (s) => ({ pass: "Within policy", warn: "Needs exception", fail: "Outside policy", info: "Information" })[s];
7
+ const glyph = (s) => boeStatusGlyph(toStatusKind(s));
8
+ export const resultDocumentIds = (blocks) => [...new Set(blocks.flatMap(b => b.type === "documents" ? b.items.map(i => i.id) : []))];
9
+ /** Pure escaped renderer also usable by host-composed conversations. */
10
+ export function renderResultBlock(block) {
11
+ const heading = block.title ? `<h3 part="title">${escape(block.title)}</h3>` : "";
12
+ if (block.type === "facts")
13
+ return `${heading}<dl part="facts">${block.rows.map(r => `<div part="row"><dt>${escape(r.label)}</dt><dd>${escape(r.value)}</dd></div>`).join("")}</dl>`;
14
+ if (block.type === "checks")
15
+ return `${heading}<ul part="checks">${block.rows.map(r => `<li part="row">${glyph(r.status)}<div><strong>${escape(r.label)}</strong>${r.detail ? `<p>${escape(r.detail)}</p>` : ""}</div><span part="verdict" data-status="${r.status}">${escape(r.value ?? verdict(r.status))}</span></li>`).join("")}</ul>`;
16
+ if (block.type === "documents")
17
+ return `${heading}<ul part="documents">${block.items.map(i => {
18
+ const content = `<svg aria-hidden="true" width="16" height="20" viewBox="0 0 16 20"><path d="M2 1h8l4 4v14H2zM10 1v5h4" fill="none" stroke="currentColor"/></svg><span><strong>${escape(i.name)}</strong>${i.detail ? `<small>${escape(i.detail)}</small>` : ""}</span>`;
19
+ return `<li part="row">${i.href && isSafeHref(i.href) ? `<a part="document" data-document-id="${escape(i.id)}" href="${escape(i.href)}" target="_blank" rel="noopener noreferrer">${content}</a>` : `<button part="document" data-document-id="${escape(i.id)}">${content}</button>`}</li>`;
20
+ }).join("")}</ul>`;
21
+ return `${heading}<div part="table-frame" role="region" aria-label="${escape(block.title ?? "Results")}" tabindex="0"><table><thead><tr>${block.columns.map(c => `<th scope="col">${escape(c)}</th>`).join("")}</tr></thead><tbody>${block.rows.map(r => `<tr>${r.cells.map((cell, index) => index === 0 ? `<th scope="row">${r.status ? `${glyph(r.status)}<span class="sr">${statusLabel(toStatusKind(r.status))}: </span>` : ""}${escape(cell)}${r.note ? `<small>${escape(r.note)}</small>` : ""}</th>` : `<td>${escape(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>${block.footnote ? `<p>${escape(block.footnote)}</p>` : ""}`;
22
+ }
23
+ export const resultBlockStyles = `
24
+ :host { display:block; min-width:0; color:var(--boe-token-text-text,#222); font:inherit; }
25
+ :host([hidden]){display:none !important} *{box-sizing:border-box} h3{font-size:1em;margin:0 0 .5rem} dl,ul{padding:0;margin:0;list-style:none}
26
+ [part="row"]{border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd);padding:.7rem 0;display:flex;gap:.6rem;align-items:baseline}
27
+ dt{color:var(--boe-token-text-text-secondary,#666)} dd{margin:0 0 0 auto;font-variant-numeric:tabular-nums}
28
+ [part="facts"] [part="row"]{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);overflow-wrap:anywhere}
29
+ [part="facts"] dd{margin:0;text-align:end}
30
+ @container boe-result-block (max-width:28rem){[part="facts"] [part="row"]{grid-template-columns:minmax(0,1fr);gap:.25rem}[part="facts"] dd{text-align:start}}
31
+ p{margin:.25rem 0;color:var(--boe-token-text-text-secondary,#666)} small{display:block;font-weight:normal;color:var(--boe-token-text-text-secondary,#666)}
32
+ [part="verdict"]{margin-left:auto;text-align:right} [data-status="warn"]{color:var(--boe-token-text-status-text-warning,#946400)} [data-status="fail"]{color:var(--boe-token-text-status-text-error,#c52a46)}
33
+ [part="document"]{display:flex;gap:.6rem;align-items:center;width:100%;text-align:left;color:inherit;font:inherit;background:none;border:0;text-decoration:none;cursor:pointer}
34
+ [part="table-frame"]{overflow:auto;max-width:100%} table{border-collapse:collapse;min-width:100%;font-variant-numeric:tabular-nums} td,th{padding:.7rem;text-align:left;border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd)} td:last-child{font-weight:600;background:var(--boe-token-surface-surface-secondary,#f7f7f7)}
35
+ .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
36
+ [part="block"]{${boeEntrance()}${boeStaggerStyles};margin-block:1rem;min-width:0;container:boe-result-block / inline-size}
37
+ ${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}`;
38
+ const DEFAULT_TAG_NAME = "box-result-blocks";
39
+ export class ResultBlocks extends BaseElement {
40
+ static tagName = DEFAULT_TAG_NAME;
41
+ value = [];
42
+ get blocks() { return this.value; }
43
+ set blocks(value) { this.value = value; if (this.isRendered)
44
+ this.update(); }
45
+ get documentIds() { return resultDocumentIds(this.value); }
46
+ renderTemplate() { this.shadowRoot.innerHTML = `<style>${resultBlockStyles}</style><div part="blocks"></div>`; }
47
+ setupListeners() {
48
+ this.shadowRoot.addEventListener("click", event => {
49
+ const row = event.target.closest("[data-document-id]");
50
+ if (row)
51
+ this.dispatchEvent(new CustomEvent("document-selected", { bubbles: true, composed: true, detail: { id: row.dataset.documentId } }));
52
+ });
53
+ }
54
+ update() {
55
+ const parent = this.shadowRoot.querySelector('[part="blocks"]');
56
+ this.value.forEach((block, index) => {
57
+ let node = parent.children[index];
58
+ const signature = JSON.stringify(block);
59
+ if (!node) {
60
+ node = document.createElement("section");
61
+ node.setAttribute("part", "block");
62
+ parent.append(node);
63
+ }
64
+ node.style.setProperty("--i", String(index));
65
+ if (node.dataset.signature !== signature) {
66
+ node.innerHTML = renderResultBlock(block);
67
+ node.dataset.signature = signature;
68
+ }
69
+ });
70
+ while (parent.children.length > this.value.length)
71
+ parent.lastElementChild.remove();
72
+ }
73
+ }
74
+ ResultBlocks.register();
@@ -22,6 +22,8 @@ export declare class Alert extends BaseElement {
22
22
  get tone(): string;
23
23
  set tone(value: string);
24
24
  connectedCallback(): void;
25
+ private announcement;
26
+ disconnectedCallback(): void;
25
27
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
26
28
  dismiss(): void;
27
29
  protected renderTemplate(): void;
@@ -1,4 +1,5 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
+ import { FeedbackAnnouncement } from "../../foundations/a11y/announcer.js";
2
3
  import { toneAccessibleLabel, toneIcon } from "./tone.js";
3
4
  import { boeRadius, boeSpace } from "../../foundations/geometry/index.js";
4
5
  import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
@@ -15,10 +16,8 @@ const alertStyles = `
15
16
  display: none !important;
16
17
  }
17
18
 
18
- /* The accent colours the glyph only. The tinted backgrounds below are pinned
19
- by the colour conformance manifest against upstream box-ui-elements and are
20
- deliberately not derived from it; upstream has no glyph, so adding one
21
- costs no conformance. */
19
+ /* Preserve upstream light tints, but mix against the active theme surface.
20
+ Glyphs use readable status ink, not decorative status fill colours. */
22
21
  [part="alert"] {
23
22
  --alert-accent: var(--boe-token-text-text-secondary, #6f6f6f);
24
23
 
@@ -36,23 +35,23 @@ const alertStyles = `
36
35
 
37
36
  [part="alert"][data-tone="info"] {
38
37
  --alert-accent: var(--boe-token-surface-surface-brand, #0061d5);
39
- background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, #fff);
38
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, var(--boe-token-surface-surface, #ffffff));
40
39
  }
41
40
 
42
41
  [part="alert"][data-tone="success"] {
43
- --alert-accent: var(--boe-token-surface-status-surface-success, #26c281);
44
- background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 10%, #fff);
42
+ --alert-accent: var(--boe-token-text-status-text-success, #187657);
43
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 10%, var(--boe-token-surface-surface, #ffffff));
45
44
  }
46
45
 
47
46
  [part="alert"][data-tone="error"] {
48
- --alert-accent: var(--boe-token-surface-status-surface-error, #ed3757);
49
- background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, #fff);
47
+ --alert-accent: var(--boe-token-text-status-text-error, #b92340);
48
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, var(--boe-token-surface-surface, #ffffff));
50
49
  }
51
50
 
52
51
  [part="alert"][data-tone="warning"],
53
52
  [part="alert"][data-tone="inprogress"] {
54
- --alert-accent: var(--boe-token-surface-status-surface-inprogress, #f5b31b);
55
- background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 10%, #fff);
53
+ --alert-accent: var(--boe-token-text-status-text-warning, #805600);
54
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 10%, var(--boe-token-surface-surface, #ffffff));
56
55
  }
57
56
 
58
57
  [part="icon"] {
@@ -202,9 +201,12 @@ export class Alert extends BaseElement {
202
201
  this.setAttribute("tone", value);
203
202
  }
204
203
  connectedCallback() {
205
- this.openValue = this.hasAttribute("open") || !this.hasAttribute("open");
206
204
  super.connectedCallback();
207
205
  }
206
+ announcement = new FeedbackAnnouncement();
207
+ disconnectedCallback() {
208
+ this.announcement.reset();
209
+ }
208
210
  attributeChangedCallback(name, oldValue, newValue) {
209
211
  if (name === "open") {
210
212
  this.openValue = this.hasAttribute("open");
@@ -224,7 +226,7 @@ export class Alert extends BaseElement {
224
226
  }
225
227
  this.shadowRoot.innerHTML = `
226
228
  <style>${alertStyles}</style>
227
- <div part="alert" role="status" aria-live="polite">
229
+ <div part="alert" role="group">
228
230
  <span part="icon" aria-hidden="true"></span>
229
231
  <div part="content">
230
232
  <span part="tone-label" class="sr-only"></span>
@@ -263,6 +265,8 @@ export class Alert extends BaseElement {
263
265
  const hasRich = this.hasRichContent();
264
266
  const visible = this.openValue && Boolean(this.heading || this.message || hasRich);
265
267
  this.hidden = !visible;
268
+ const richText = this.richSlot.assignedNodes({ flatten: true }).map(node => node.textContent ?? "").join(" ").trim();
269
+ this.announcement.update(this, visible ? [toneAccessibleLabel(this.tone), this.heading, this.message, richText].filter(Boolean).join(": ") : "", this.tone === "error" ? "assertive" : "polite");
266
270
  this.richSlot.classList.toggle("has-content", hasRich);
267
271
  if (!visible) {
268
272
  return;
@@ -9,6 +9,15 @@ export declare class ProgressBar extends BaseElement {
9
9
  private indicatorEl;
10
10
  get label(): string;
11
11
  set label(value: string);
12
+ /**
13
+ * Keep the label as the bar's accessible name but drop it from the layout.
14
+ *
15
+ * For a bar sitting under something that already names it — an upload row
16
+ * showing the filename above it — the visible label is a second copy of text
17
+ * the reader can already see.
18
+ */
19
+ get hideLabel(): boolean;
20
+ set hideLabel(value: boolean);
12
21
  get max(): number;
13
22
  set max(value: number);
14
23
  get value(): number;
@@ -35,6 +35,14 @@ const progressBarStyles = `
35
35
  color: var(--boe-token-text-text-secondary, #6f6f6f);
36
36
  }
37
37
 
38
+ /* Fully hidden rather than the sr-only clip the form fields use. There the
39
+ label element *is* the control's accessible name, so it has to stay
40
+ readable; here the name lives on [part="track"]'s aria-label, and leaving
41
+ a clipped copy behind would just make a screen reader read it twice. */
42
+ :host([hide-label]) [part="label"] {
43
+ display: none;
44
+ }
45
+
38
46
  [part="value"] {
39
47
  font-size: 0.86rem;
40
48
  font-weight: 700;
@@ -61,7 +69,7 @@ const progressBarStyles = `
61
69
  export class ProgressBar extends BaseElement {
62
70
  static tagName = DEFAULT_TAG_NAME;
63
71
  static get observedAttributes() {
64
- return ["label", "max", "value"];
72
+ return ["hide-label", "label", "max", "value"];
65
73
  }
66
74
  progressEl;
67
75
  labelEl;
@@ -74,6 +82,19 @@ export class ProgressBar extends BaseElement {
74
82
  set label(value) {
75
83
  this.setAttribute("label", value);
76
84
  }
85
+ /**
86
+ * Keep the label as the bar's accessible name but drop it from the layout.
87
+ *
88
+ * For a bar sitting under something that already names it — an upload row
89
+ * showing the filename above it — the visible label is a second copy of text
90
+ * the reader can already see.
91
+ */
92
+ get hideLabel() {
93
+ return this.hasAttribute("hide-label");
94
+ }
95
+ set hideLabel(value) {
96
+ this.toggleAttribute("hide-label", Boolean(value));
97
+ }
77
98
  get max() {
78
99
  return Number(this.getAttribute("max") ?? "100");
79
100
  }
@@ -92,7 +113,7 @@ export class ProgressBar extends BaseElement {
92
113
  }
93
114
  this.shadowRoot.innerHTML = `
94
115
  <style>${progressBarStyles}</style>
95
- <div part="progress" role="group">
116
+ <div part="progress">
96
117
  <div part="meta">
97
118
  <span part="label"></span>
98
119
  <span part="value"></span>
@@ -115,7 +136,12 @@ export class ProgressBar extends BaseElement {
115
136
  const max = Math.max(1, this.max);
116
137
  const value = clamp(this.value, 0, max);
117
138
  const percentage = Math.round((value / max) * 100);
118
- this.progressEl.setAttribute("aria-label", `${this.label} progress`);
139
+ // The name belongs to [part="track"] — the element carrying the
140
+ // progressbar role — and to nothing else. The wrapper used to be a
141
+ // role="group" named `${label} progress`, which both duplicated that name
142
+ // and stuttered: the default label is "Progress", so it announced
143
+ // "Progress progress", and the uploader's "<file> upload progress" became
144
+ // "<file> upload progress progress".
119
145
  this.labelEl.textContent = this.label;
120
146
  this.valueEl.textContent = `${percentage}%`;
121
147
  this.trackEl.setAttribute("aria-label", this.label);
@@ -0,0 +1,10 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { type StatusKind } from "../../foundations/status/index.js";
3
+ export declare class StatusIcon extends BaseElement {
4
+ static readonly tagName = "box-status-icon";
5
+ static get observedAttributes(): string[];
6
+ get kind(): StatusKind;
7
+ set kind(value: StatusKind);
8
+ protected renderTemplate(): void;
9
+ protected update(): void;
10
+ }
@@ -0,0 +1,26 @@
1
+ import { BaseElement } from "../../core/element.js";
2
+ import { boeStatusGlyph, boeStatusStyles, statusLabel, toStatusKind } from "../../foundations/status/index.js";
3
+ import { boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
4
+ const DEFAULT_TAG_NAME = "box-status-icon";
5
+ export class StatusIcon extends BaseElement {
6
+ static tagName = DEFAULT_TAG_NAME;
7
+ static get observedAttributes() { return ["kind"]; }
8
+ get kind() { return toStatusKind(this.getAttribute("kind") ?? "pending"); }
9
+ set kind(value) { this.setAttribute("kind", value); }
10
+ renderTemplate() {
11
+ this.shadowRoot.innerHTML = `<style>:host{display:inline-flex} :host([hidden]){display:none !important}
12
+ .label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
13
+ ${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}</style><span part="glyph"></span><span class="label" part="label"></span>`;
14
+ }
15
+ update() {
16
+ if (!this.isRendered)
17
+ return;
18
+ const glyph = this.shadowRoot.querySelector('[part="glyph"]');
19
+ if (glyph.getAttribute("data-kind") !== this.kind) {
20
+ glyph.setAttribute("data-kind", this.kind);
21
+ glyph.innerHTML = boeStatusGlyph(this.kind);
22
+ }
23
+ this.shadowRoot.querySelector('[part="label"]').textContent = statusLabel(this.kind);
24
+ }
25
+ }
26
+ StatusIcon.register();
@@ -2,7 +2,8 @@ import { BaseElement } from "../../core/index.js";
2
2
  /**
3
3
  * Whether the toast clears itself.
4
4
  *
5
- * `dismissible` auto-dismisses after `duration` and can also be closed by hand;
5
+ * `dismissible` auto-dismisses non-error feedback after `duration`;
6
+ * errors always persist until explicitly dismissed or replaced.
6
7
  * `sticky` stays until the reader closes it, whatever `duration` says. Both
7
8
  * carry the close control — a toast the reader cannot get rid of is a trap.
8
9
  */
@@ -14,6 +15,10 @@ export declare class Toast extends BaseElement {
14
15
  static get observedAttributes(): string[];
15
16
  private openValue;
16
17
  private timeoutId;
18
+ private remaining;
19
+ private deadline;
20
+ private holds;
21
+ private announcement;
17
22
  private toastEl;
18
23
  private iconEl;
19
24
  private headingEl;
@@ -28,6 +33,9 @@ export declare class Toast extends BaseElement {
28
33
  get open(): boolean;
29
34
  set open(value: boolean);
30
35
  private scheduleAutoDismiss;
36
+ private resumeTimer;
37
+ private hold;
38
+ private dismiss;
31
39
  get message(): string;
32
40
  set message(value: string);
33
41
  /**
@@ -56,6 +64,7 @@ export declare class Toast extends BaseElement {
56
64
  get tone(): string;
57
65
  set tone(value: string);
58
66
  disconnectedCallback(): void;
67
+ connectedCallback(): void;
59
68
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
60
69
  show(message?: string, options?: {
61
70
  duration?: number;