@unofficialbox/box-open-elements 0.21.0 → 0.23.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 (115) hide show
  1. package/README.md +13 -12
  2. package/dist/components/actions/menu-item.js +1 -1
  3. package/dist/components/actions/menu.js +1 -1
  4. package/dist/components/collections/carousel.js +1 -1
  5. package/dist/components/collections/draggable-list.js +1 -1
  6. package/dist/components/collections/grid-view.js +1 -1
  7. package/dist/components/collections/pagination.js +1 -1
  8. package/dist/components/collections/result-blocks.d.ts +13 -1
  9. package/dist/components/collections/result-blocks.js +22 -10
  10. package/dist/components/collections/tree-grid.js +1 -1
  11. package/dist/components/feedback/alert.js +1 -1
  12. package/dist/components/feedback/chip.js +1 -1
  13. package/dist/components/feedback/empty-state.js +1 -1
  14. package/dist/components/feedback/nudge.js +1 -1
  15. package/dist/components/feedback/status-icon.d.ts +2 -0
  16. package/dist/components/feedback/status-icon.js +6 -5
  17. package/dist/components/feedback/toast.js +1 -1
  18. package/dist/components/files/drop-zone.js +1 -1
  19. package/dist/components/forms/category-selector.js +1 -1
  20. package/dist/components/forms/checkbox-group.js +1 -1
  21. package/dist/components/forms/checkbox.js +1 -1
  22. package/dist/components/forms/combobox.js +1 -1
  23. package/dist/components/forms/date-field.js +1 -1
  24. package/dist/components/forms/dropdown.js +1 -1
  25. package/dist/components/forms/multi-select.js +1 -1
  26. package/dist/components/forms/number-input.js +1 -1
  27. package/dist/components/forms/radio-group.js +1 -1
  28. package/dist/components/forms/range-slider.js +1 -1
  29. package/dist/components/forms/slider.js +1 -1
  30. package/dist/components/forms/tile-group.js +1 -1
  31. package/dist/components/layout/sidebar-toggle-button.js +1 -1
  32. package/dist/components/navigation/accordion.js +1 -1
  33. package/dist/components/navigation/tabs.js +1 -1
  34. package/dist/components/output/code-block.js +1 -1
  35. package/dist/components/overlays/dialog.js +1 -1
  36. package/dist/components/overlays/drawer.js +1 -1
  37. package/dist/components/overlays/guide-tooltip.js +1 -1
  38. package/dist/components/overlays/popover.js +1 -1
  39. package/dist/components/overlays/tooltip.js +1 -1
  40. package/dist/foundations/motion/scroll-pin.d.ts +10 -2
  41. package/dist/foundations/motion/scroll-pin.js +43 -7
  42. package/dist/foundations/motion/tokens.d.ts +1 -0
  43. package/dist/foundations/motion/tokens.js +1 -0
  44. package/dist/foundations/profiles/built-ins.js +8 -0
  45. package/dist/foundations/profiles/registry.d.ts +1 -0
  46. package/dist/foundations/profiles/registry.js +1 -0
  47. package/dist/foundations/profiles/types.d.ts +1 -0
  48. package/dist/foundations/status/index.d.ts +4 -2
  49. package/dist/foundations/status/index.js +10 -6
  50. package/dist/foundations/theming/controller.js +3 -1
  51. package/dist/foundations/tokens/box-defaults.d.ts +2 -0
  52. package/dist/foundations/tokens/box-defaults.js +32 -5
  53. package/dist/foundations/tokens/index.d.ts +1 -1
  54. package/dist/foundations/tokens/index.js +1 -1
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/patterns/agent-chat/agent-chat.js +9 -10
  58. package/dist/patterns/agent-chat/controller.js +8 -4
  59. package/dist/patterns/agent-chat/ndjson.d.ts +1 -0
  60. package/dist/patterns/agent-chat/ndjson.js +20 -4
  61. package/dist/patterns/agent-chat/types.d.ts +7 -0
  62. package/dist/patterns/agent-workspace/agent-workspace.d.ts +13 -1
  63. package/dist/patterns/agent-workspace/agent-workspace.js +47 -15
  64. package/dist/patterns/agent-workspace/controller.d.ts +7 -1
  65. package/dist/patterns/agent-workspace/controller.js +41 -3
  66. package/dist/patterns/call-console/call-console.d.ts +10 -0
  67. package/dist/patterns/call-console/call-console.js +23 -8
  68. package/dist/patterns/call-console/controller.d.ts +2 -1
  69. package/dist/patterns/call-console/controller.js +5 -3
  70. package/dist/patterns/call-console/http-transport.js +13 -2
  71. package/dist/patterns/call-console/types.d.ts +1 -1
  72. package/dist/patterns/call-console/types.js +2 -2
  73. package/dist/patterns/comments/comment-thread.js +1 -1
  74. package/dist/patterns/content-explorer/adapters/action-menu.js +1 -1
  75. package/dist/patterns/content-explorer/adapters/list.js +1 -1
  76. package/dist/patterns/content-explorer/adapters/table.js +1 -1
  77. package/dist/patterns/content-explorer/adapters/toolbar.js +1 -1
  78. package/dist/patterns/content-uploader/content-uploader.js +1 -1
  79. package/dist/patterns/file-request/file-request-builder.js +1 -1
  80. package/dist/patterns/flow-builder/flow-builder.d.ts +45 -14
  81. package/dist/patterns/flow-builder/flow-builder.js +54 -11
  82. package/dist/patterns/flow-builder/model.d.ts +57 -9
  83. package/dist/patterns/flow-builder/model.js +20 -8
  84. package/dist/patterns/flow-builder/primitives.d.ts +17 -7
  85. package/dist/patterns/flow-builder/primitives.js +80 -16
  86. package/dist/patterns/governance/governance-panel.js +1 -1
  87. package/dist/patterns/insights/chart-panel.js +1 -1
  88. package/dist/patterns/insights/metric-card.js +1 -1
  89. package/dist/patterns/item/bulk-action-bar.js +1 -1
  90. package/dist/patterns/item/item-details-panel.js +1 -1
  91. package/dist/patterns/item/item-form.js +1 -1
  92. package/dist/patterns/item/preview-header.js +1 -1
  93. package/dist/patterns/metadata/metadata-filter-builder.js +1 -1
  94. package/dist/patterns/metadata/metadata-inspector.js +1 -1
  95. package/dist/patterns/preview/annotation-inspector.js +1 -1
  96. package/dist/patterns/preview/annotation-toolbar.js +1 -1
  97. package/dist/patterns/run/progress.d.ts +1 -1
  98. package/dist/patterns/run/progress.js +1 -1
  99. package/dist/patterns/run/run-summary.js +18 -4
  100. package/dist/patterns/run/run-trace.js +19 -5
  101. package/dist/patterns/search/filter-bar.js +1 -1
  102. package/dist/patterns/search/saved-view-picker.js +1 -1
  103. package/dist/patterns/search/search-results-header.js +1 -1
  104. package/dist/patterns/share/invite-collaborators-modal.js +1 -1
  105. package/dist/patterns/share/permission-matrix.js +1 -1
  106. package/dist/patterns/share/share-panel.js +1 -1
  107. package/dist/patterns/share/unified-share-modal.js +1 -1
  108. package/dist/patterns/task/review-queue-item.js +1 -1
  109. package/dist/patterns/task/task-assignment-panel.js +1 -1
  110. package/dist/patterns/undo/index.js +5 -3
  111. package/docs/api-guidelines.md +284 -0
  112. package/docs/foundations/motion.md +83 -0
  113. package/docs/patterns/agent-ui-guidelines.md +57 -0
  114. package/docs/patterns/agent-ui.md +197 -0
  115. package/package.json +6 -2
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { applyRovingTabindex, handleRovingKeydown } from "../../foundations/a11y/index.js";
3
- import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
3
+ import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
4
4
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
5
5
  import { boePanel } from "../../foundations/geometry/index.js";
6
6
  const DEFAULT_TAG_NAME = "box-bulk-action-bar";
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-item-details-panel";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&")
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-item-form";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&")
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-preview-header";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&")
@@ -1,5 +1,5 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
- import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
2
+ import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
3
3
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
4
4
  const DEFAULT_TAG_NAME = "box-metadata-filter-builder";
5
5
  const escapeHtml = (value) => value
@@ -1,5 +1,5 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
- import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
2
+ import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
3
3
  import { boePanel } from "../../foundations/geometry/index.js";
4
4
  const DEFAULT_TAG_NAME = "box-metadata-inspector";
5
5
  const escapeHtml = (value) => value
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-annotation-inspector";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&")
@@ -1,7 +1,7 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { applyRovingTabindex, handleRovingKeydown } from "../../foundations/a11y/index.js";
3
3
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
4
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
4
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
5
5
  const DEFAULT_TAG_NAME = "box-annotation-toolbar";
6
6
  const escapeHtml = (value) => value
7
7
  .replaceAll("&", "&")
@@ -10,7 +10,7 @@ export interface RunTurn {
10
10
  todos: AgentTodo[];
11
11
  startedAt: number;
12
12
  endedAt?: number;
13
- incomplete?: boolean;
13
+ incomplete?: boolean | string;
14
14
  }
15
15
  export declare function formatElapsed(ms: number): string;
16
16
  export declare function splitStepTitle(title: string): {
@@ -26,7 +26,7 @@ export function progress(turn, failed = false, now = Date.now(), threshold = 200
26
26
  if (failed || resolved.status === "failed")
27
27
  return { kind: "failed", label: `Stopped after ${took}` };
28
28
  if (turn.incomplete)
29
- return { kind: "warning", label: `Cut short after ${took}` };
29
+ return { kind: "warning", label: `${typeof turn.incomplete === "string" ? turn.incomplete : "Cut short"} after ${took}` };
30
30
  const warnings = resolved.steps.filter(s => toStatusKind(s.status) === "warning").length;
31
31
  return { kind: warnings ? "warning" : "done", label: `Worked for ${took}${warnings ? ` · ${warnings} warning${warnings === 1 ? "" : "s"}` : ""}` };
32
32
  }
@@ -31,12 +31,14 @@ export class RunSummary extends BaseElement {
31
31
  StatusIcon.register();
32
32
  RunTrace.register();
33
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}
34
+ button,[part="static-line"]{font:inherit;color:inherit;background:none;border:0;padding:.5rem 0;display:flex;gap:.5rem;align-items:center}button{cursor:pointer}
35
35
  [part="clock"]{font-variant-numeric:tabular-nums} ul{padding:0;list-style:none} li{display:flex;gap:.5rem;margin:.5rem 0}
36
36
  @keyframes boe-shimmer{50%{opacity:.6}} [data-running] [part="label"]{animation:boe-shimmer 1.4s ease-in-out infinite}
37
+ [hidden]{display:none!important}[part="chevron"]{display:inline-block;transition:transform var(--boe-profile-motion-panel,320ms) ease} :host([open]) [part="chevron"]{transform:rotate(90deg)}
37
38
  ${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>`;
39
+ <button part="trigger" aria-expanded="false" aria-controls="details"><box-status-icon></box-status-icon><span part="label"></span><span part="clock" aria-hidden="true"></span><span part="chevron" aria-hidden="true">›</span></button>
40
+ <div part="static-line" hidden><box-status-icon></box-status-icon><span part="static-label"></span><span part="static-clock" aria-hidden="true"></span></div>
41
+ <div id="details" part="disclosure" class="boe-disclosure" inert><div><ul part="plan"></ul><box-run-trace variant="plain" exportparts="panel:trace-panel,step:trace-step,step-source:trace-step-source,step-title:trace-step-title,duration:trace-duration"></box-run-trace></div></div>`;
40
42
  }
41
43
  setupListeners() { this.shadowRoot.querySelector("button").addEventListener("click", () => { this.open = !this.open; }); }
42
44
  updateLabel() {
@@ -45,15 +47,27 @@ export class RunSummary extends BaseElement {
45
47
  if (label.textContent !== p.label)
46
48
  label.textContent = p.label;
47
49
  this.shadowRoot.querySelector('[part="clock"]').textContent = p.elapsed ?? "";
48
- this.shadowRoot.querySelector("box-status-icon").kind = p.kind;
50
+ this.shadowRoot.querySelector('[part="static-label"]').textContent = p.label;
51
+ this.shadowRoot.querySelector('[part="static-clock"]').textContent = p.elapsed ?? "";
52
+ this.shadowRoot.querySelectorAll("box-status-icon").forEach(icon => { icon.kind = p.kind; icon.hidden = p.kind === "done"; });
49
53
  this.shadowRoot.querySelector("button").toggleAttribute("data-running", p.kind === "active");
50
54
  this.shadowRoot.querySelector("button").setAttribute("aria-label", p.label);
55
+ const staticLabel = this.shadowRoot.querySelector('[part="static-label"]');
56
+ for (const target of [label, staticLabel]) {
57
+ if (p.kind === "active")
58
+ target.setAttribute("role", "status");
59
+ else
60
+ target.removeAttribute("role");
61
+ }
51
62
  }
52
63
  update() {
53
64
  if (!this.isRendered)
54
65
  return;
55
66
  this.updateLabel();
56
67
  this.syncTimer();
68
+ const hasDetails = this.value.steps.length > 0 || this.value.todos.length > 0;
69
+ this.shadowRoot.querySelector("button").hidden = !hasDetails;
70
+ this.shadowRoot.querySelector('[part="static-line"]').hidden = hasDetails;
57
71
  this.shadowRoot.querySelector("button").setAttribute("aria-expanded", String(this.open));
58
72
  const disclosure = this.shadowRoot.querySelector("#details");
59
73
  disclosure.toggleAttribute("data-open", this.open);
@@ -1,4 +1,5 @@
1
- import { formatRunDuration, formatRunSummary, isRunStepRecord, resolveRunSteps, } from "./types.js";
1
+ import { formatRunSummary, isRunStepRecord, resolveRunSteps, } from "./types.js";
2
+ import { formatElapsed, splitStepTitle } from "./progress.js";
2
3
  import { ProgressBar } from "../../components/feedback/progress-bar.js";
3
4
  import { BaseElement } from "../../core/index.js";
4
5
  import { boePanel } from "../../foundations/geometry/index.js";
@@ -250,6 +251,15 @@ const elementStyles = `
250
251
  color: var(--boe-token-text-text-secondary, #6f6f6f);
251
252
  }
252
253
 
254
+ :host([variant="plain"]) [part="panel"] { border: 0; border-radius: 0; background: none; padding: 0; }
255
+ :host([variant="plain"]) [part="header"],
256
+ :host([variant="plain"]) [part="summary"] { display: none; }
257
+ :host([variant="plain"]) [part="step"] { padding-block: 0.35rem; }
258
+ :host([variant="plain"]) [part="step"]::after { display: none; }
259
+ :host([variant="plain"]) [part="status"] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
260
+ :host([variant="plain"]) [part="toggle"] { display: none; }
261
+ [part="step-source"] { font-weight: 600; color: var(--boe-token-text-text-secondary, #6f6f6f); }
262
+
253
263
  .boe-sr-only {
254
264
  position: absolute;
255
265
  width: 1px;
@@ -281,7 +291,7 @@ const elementStyles = `
281
291
  export class RunTrace extends BaseElement {
282
292
  static tagName = DEFAULT_TAG_NAME;
283
293
  static get observedAttributes() {
284
- return ["heading", "steps"];
294
+ return ["heading", "steps", "variant"];
285
295
  }
286
296
  hostEl;
287
297
  stepsRaw = null;
@@ -403,12 +413,15 @@ export class RunTrace extends BaseElement {
403
413
  .map(entry => {
404
414
  const { step, status, position } = entry;
405
415
  const settled = this.previousStatuses.get(step.id) === status;
406
- const duration = formatRunDuration(step.startedAt, step.finishedAt);
416
+ const start = step.startedAt ? Date.parse(step.startedAt) : NaN;
417
+ const finish = step.finishedAt ? Date.parse(step.finishedAt) : NaN;
418
+ const duration = Number.isFinite(start) && Number.isFinite(finish) ? formatElapsed(finish - start) : "";
419
+ const title = splitStepTitle(step.title);
407
420
  // Slot-only detail counts: a host projecting logs for a step with no
408
421
  // JSON description still needs the toggle that reveals them.
409
422
  const hasSlotted = this.querySelector(`[slot="detail-${cssAttrValue(step.id)}"]`) !== null;
410
423
  const hasDetail = Boolean(step.description) || Boolean(step.children?.length) || hasSlotted;
411
- const expanded = this.expandedIds.has(step.id);
424
+ const expanded = this.hasAttribute("variant") && this.getAttribute("variant") === "plain" || this.expandedIds.has(step.id);
412
425
  // The detail slot renders even without JSON detail so a host can
413
426
  // project rich content (logs, links) for any step by id.
414
427
  const detail = `
@@ -423,7 +436,8 @@ export class RunTrace extends BaseElement {
423
436
  <span part="marker" aria-hidden="true" ${settled ? "data-settled" : ""}>${boeStatusGlyph(toStatusKind(status))}</span>
424
437
  <div part="step-body">
425
438
  <div part="topline">
426
- <span part="step-title">${escapeHtml(step.title)}</span>
439
+ ${title.source ? `<span part="step-source">${escapeHtml(title.source)}</span><span aria-hidden="true">·</span>` : ""}
440
+ <span part="step-title">${escapeHtml(title.action)}</span>
427
441
  <span part="status">${RUN_STEP_STATUS_LABEL[status]}</span>
428
442
  <span class="boe-sr-only">, step ${String(position)} of ${String(resolution.total)}</span>
429
443
  ${duration ? `<span part="duration">${duration}</span>` : ""}
@@ -1,5 +1,5 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
- import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
2
+ import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
3
3
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
4
4
  const DEFAULT_TAG_NAME = "box-filter-bar";
5
5
  const escapeHtml = (value) => value
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { applyRovingTabindex, handleRovingKeydown } from "../../foundations/a11y/index.js";
3
- import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
3
+ import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
4
4
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
5
5
  const DEFAULT_TAG_NAME = "box-saved-view-picker";
6
6
  const escapeHtml = (value) => value
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-search-results-header";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&amp;")
@@ -2,7 +2,7 @@ import { InviteCollaboratorsController } from "./invite-collaborators-controller
2
2
  import { BaseElement } from "../../core/index.js";
3
3
  import { FocusRestore, trapTabKey } from "../../foundations/a11y/index.js";
4
4
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
5
- import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
5
+ import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
6
6
  const DEFAULT_TAG_NAME = "box-invite-collaborators-modal";
7
7
  const elementStyles = `
8
8
  :host { display: contents; color: inherit; font: inherit; }
@@ -1,5 +1,5 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
- import { boeFocusRingShadow } from "../../foundations/tokens/index.js";
2
+ import { boeFocusRingShadow } from "../../foundations/tokens/interaction.js";
3
3
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
4
4
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
5
5
  const DEFAULT_TAG_NAME = "box-permission-matrix";
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-share-panel";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&amp;")
@@ -1,7 +1,7 @@
1
1
  import { UnifiedShareController } from "./unified-share-controller.js";
2
2
  import { BaseElement } from "../../core/index.js";
3
3
  import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
4
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
4
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
5
5
  const DEFAULT_TAG_NAME = "box-unified-share-modal";
6
6
  const escapeHtml = (value) => value
7
7
  .replaceAll("&", "&amp;")
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-review-queue-item";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&amp;")
@@ -1,6 +1,6 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { boePanel } from "../../foundations/geometry/index.js";
3
- import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
3
+ import { boeBrandInteractiveStyles, boeFocusVisibleStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
4
4
  const DEFAULT_TAG_NAME = "box-task-assignment-panel";
5
5
  const escapeHtml = (value) => value
6
6
  .replaceAll("&", "&amp;")
@@ -1,4 +1,5 @@
1
1
  import { Toast } from "../../components/feedback/toast.js";
2
+ import { Button } from "../../components/actions/button.js";
2
3
  import { announce } from "../../foundations/a11y/index.js";
3
4
  /** Remove once and return an idempotent restore preserving the original object's identity. */
4
5
  export function removeAt(list, index) {
@@ -55,10 +56,11 @@ export function offerUndo(message, restore, options = {}) {
55
56
  return { toast: null, undo() { }, dispose() { } };
56
57
  offers.get(doc)?.dispose();
57
58
  const toast = doc.createElement(Toast.tagName);
58
- const action = doc.createElement("button");
59
- action.type = "button";
59
+ const action = doc.createElement(Button.tagName);
60
+ action.tone = "neutral";
61
+ action.size = "small";
62
+ action.label = "Undo";
60
63
  action.slot = "action";
61
- action.textContent = "Undo";
62
64
  const mac = /Mac|iPhone|iPad/.test(doc.defaultView?.navigator.platform ?? "");
63
65
  const shortcut = mac ? "Command-Z" : "Control-Z";
64
66
  action.setAttribute("aria-keyshortcuts", mac ? "Meta+Z" : "Control+Z");
@@ -0,0 +1,284 @@
1
+ # Component API Guidelines
2
+
3
+ This document defines the low-opinionated API vocabulary to converge on across the component and pattern surface.
4
+
5
+ It is intentionally small. The goal is not to force every component into the same shape, but to make common concepts feel predictable. A human should be able to skim one component and guess another. An AI coding assistant should be able to infer the next likely property name, event name, and data shape without extra prompting.
6
+
7
+ ## Taxonomy layers
8
+
9
+ The system has three tiers (see [taxonomy.md](./taxonomy.md)):
10
+
11
+ - `Foundations` — design decisions as data: tokens, iconography, theming
12
+ - `Components` — single controls or small focused surfaces
13
+ - `Patterns` — assembled task surfaces and orchestrated workflow shells
14
+
15
+ API guidance in this document applies to components and patterns; compositional freedom should increase as surfaces move up the stack.
16
+
17
+ ## Design goals
18
+
19
+ The API should optimize for:
20
+
21
+ - predictable naming across components
22
+ - plain object schemas that are easy to generate and inspect
23
+ - event payloads that mirror component state closely
24
+ - clean public names at port time; compatibility aliases only when an already-published contract requires them
25
+ - simple escape hatches over large config surfaces
26
+
27
+ ## Import contract
28
+
29
+ The package import surface should stay predictable as the catalog grows:
30
+
31
+ - `@unofficialbox/box-open-elements`
32
+ - concise PascalCase exports and automatic registration for the full catalog
33
+ - `@unofficialbox/box-open-elements/core`
34
+ - shared runtime (event emitter, controller base)
35
+ - `@unofficialbox/box-open-elements/foundations/<module>`
36
+ - design-token registry, token bundles, iconography
37
+ - `@unofficialbox/box-open-elements/<name>`
38
+ - optimized component entrypoint that registers only that component
39
+ - `@unofficialbox/box-open-elements/patterns/<area>` and `@unofficialbox/box-open-elements/patterns/<area>/<module>`
40
+ - workflow areas: headless controllers, contracts, and composed surfaces
41
+
42
+ Examples:
43
+
44
+ ```ts
45
+ import { Accordion, Avatar, Button, Switch } from "@unofficialbox/box-open-elements";
46
+ import { TextField } from "@unofficialbox/box-open-elements/text-field";
47
+ import { applyDesignTokens } from "@unofficialbox/box-open-elements/foundations/tokens";
48
+ import { ExplorerSelectionController } from "@unofficialbox/box-open-elements/patterns/content-explorer/selection";
49
+ ```
50
+
51
+ Guidance:
52
+
53
+ - component classes use concise PascalCase names without `Box`, `Element`, or
54
+ `Component`
55
+ - importing a component registers its `box-*` custom element automatically
56
+ - the root entrypoint is convenient but may load the full catalog; use flat
57
+ component entrypoints in bundle-sensitive applications
58
+ - every component exposes idempotent `register()` for isolated registries and
59
+ test realms; normal consumers should not need to call it
60
+ - keep module-scope registration SSR-safe
61
+ - reserve explicit alias exports for compatibility cases where a public name intentionally differs from the file name
62
+ - keep the category structure synchronized with [components/catalog.md](./components/catalog.md) and [patterns/catalog.md](./patterns/catalog.md)
63
+
64
+ ## Baseline attributes
65
+
66
+ Use these names when the concept applies:
67
+
68
+ - `label` — primary accessible/display label for controls and compact components
69
+ - `value` — current selected or entered value for stateful components
70
+ - `name` — form field name for form-associated controls (`FormAssociatedElement`)
71
+ - `disabled` — disables interaction when the component is interactive
72
+ - `invalid` — marks a form control as invalid (sets `aria-invalid` and error chrome)
73
+ - `error-message` — validation message shown in `part="error-message"` when `invalid`
74
+ - `open` — visibility state for overlays and transient UI
75
+ - `tone` — visual semantic variant such as `primary`, `warning`, or `success`
76
+ - `size` — visual size when the component has a non-content-based footprint
77
+ - `heading` — short headline for content or status surfaces (named `heading`, not
78
+ `title`, to avoid colliding with the native `HTMLElement.title` tooltip property)
79
+ - `message` — supporting descriptive copy
80
+ - `description` — longer supporting copy when `message` would be ambiguous or too short
81
+ - `items` — structured collection data for rich hierarchical or menu-like inputs
82
+ - `options` — simpler choice data for flat selection controls
83
+
84
+ Form-associated controls extend `FormAssociatedElement` and submit through `ElementInternals`:
85
+
86
+ - Everyday string/boolean/number fields: `text-field`, `text-area`, `select`, `combobox`,
87
+ `search-field`, `checkbox`, `switch`, `radio-group`, `number-input`, `spin-button`,
88
+ `slider`, `date-field`, `time-field`, `dropdown`, `rating`, `color-picker`,
89
+ `rich-text-input`
90
+ - Multi-value fields submit `FormData` via `formDataFromNamedValues` (one entry per selected
91
+ value under `name`, or under `"value"` when `name` is empty): `checkbox-group`,
92
+ `multi-select`, `dual-listbox`, `tag-input`, `pill-cloud`, `pill-selector-dropdown`
93
+ - Range pairs submit `FormData` via `formDataFromRange` (`${name}-start` / `${name}-end`,
94
+ or `range-start` / `range-end` when `name` is empty): `range-slider`
95
+
96
+ Boolean controls also accept `value` (default `"on"`) as the submitted string when checked.
97
+ Empty multi-selections omit the control (`null`).
98
+
99
+ **Empty-name fallback keys.** When `name` is omitted, multi-value controls submit each
100
+ selected entry under `"value"`; range controls use `"range"` as the base for
101
+ `${base}-start` / `${base}-end` (that is, `range-start` and `range-end`).
102
+
103
+ ## Naming preferences
104
+
105
+ - Prefer `label` for controls.
106
+ - Prefer `heading` plus `message` for status and empty-state surfaces.
107
+ - Prefer `description` for longer secondary copy.
108
+ - Prefer `items` for rich objects and `options` for flat choices.
109
+ - Prefer `value-changed` for selection/input updates.
110
+ - Prefer `open-changed` for overlay visibility changes.
111
+ - Prefer `ratio-changed` for resizable split panes (`detail: { ratio }` on `split-view`).
112
+ - During predecessor ports, apply the preferred vocabulary directly instead of carrying old aliases forward. For an already-published API, document the migration and add an alias only when compatibility is an explicit requirement.
113
+
114
+ ## Shared event contract
115
+
116
+ Custom events should:
117
+
118
+ - bubble
119
+ - be composed
120
+ - use kebab-case names
121
+ - include a small `detail` payload
122
+ - keep the payload shape close to the component state shape
123
+
124
+ Recommended shared state-change events:
125
+
126
+ - `value-changed`
127
+ - `open-changed`
128
+ - `ratio-changed` (`detail: { ratio }` for `split-view`)
129
+ - `checked-changed`
130
+ - `selected-changed`
131
+
132
+ Recommended semantic intent events:
133
+
134
+ - `action`
135
+ - `confirm`
136
+ - `cancel`
137
+ - `dismiss`
138
+ - `search`
139
+ - `clear`
140
+ - `item-selected`
141
+ - `item-invoked`
142
+
143
+ Event payload guidance:
144
+
145
+ - `value-changed` should usually emit `{ value }`
146
+ - `open-changed` should usually emit `{ open }`
147
+ - `checked-changed` should usually emit `{ checked }`
148
+ - semantic events should include only the minimum context needed to act
149
+
150
+ Use native browser events such as `click`, `focus`, `blur`, `input`, and `change` when a custom wrapper would not add clarity.
151
+
152
+ ## Shared schema contract
153
+
154
+ Prefer plain serializable objects with stable keys. Avoid clever builder APIs or multiple parallel shapes for the same concept.
155
+
156
+ Recommended `option` shape for flat choices:
157
+
158
+ ```ts
159
+ type Option = {
160
+ id: string;
161
+ label: string;
162
+ value?: string;
163
+ disabled?: boolean;
164
+ description?: string;
165
+ icon?: string;
166
+ };
167
+ ```
168
+
169
+ Recommended `item` shape for richer collections:
170
+
171
+ ```ts
172
+ type Item = {
173
+ id: string;
174
+ label?: string;
175
+ name?: string;
176
+ type?: string;
177
+ disabled?: boolean;
178
+ description?: string;
179
+ children?: Item[];
180
+ };
181
+ ```
182
+
183
+ Recommended event detail examples:
184
+
185
+ ```ts
186
+ type ValueChangedDetail = { value: string | string[] };
187
+ type OpenChangedDetail = { open: boolean };
188
+ type CheckedChangedDetail = { checked: boolean };
189
+ type ItemSelectedDetail = { id: string; value?: string; label?: string };
190
+ ```
191
+
192
+ These shapes are intentionally boring. That is a feature. They are easy to author by hand and easy for AI coding assistants to infer and transform.
193
+
194
+ ## Collection rendering strategies
195
+
196
+ Collection components should stay strategy-agnostic when the underlying data can grow large.
197
+
198
+ Prefer exposing stable collection state such as:
199
+
200
+ - `items`
201
+ - `loading`
202
+ - `pagination`
203
+ - selection state
204
+ - commands such as `reload()` and `loadNextPage()`
205
+
206
+ Then let consumers choose the rendering strategy that fits their scale:
207
+
208
+ - `pagination` — best default when the backend is already page-oriented and users need stable positions, shareable states, or explicit counts
209
+ - `infinite scroll` — best when continuous browsing matters more than explicit page boundaries
210
+ - `windowing` — best when the DOM cost of rendering all visible items would become too high, even if the data is already loaded
211
+
212
+ Guidance:
213
+
214
+ - Do not hard-wire collection components to only one of these strategies unless the component is explicitly specialized.
215
+ - Prefer headless controller state that can power all three.
216
+ - Treat windowing as a rendering concern, not a transport concern.
217
+ - Treat pagination metadata as stable shared state, even if the UI ultimately renders via infinite scroll.
218
+
219
+ ## Accessibility
220
+
221
+ Accessibility semantics and keyboard behavior are part of the component contract, not optional polish. The full conventions live in [foundations/accessibility.md](./foundations/accessibility.md).
222
+
223
+ ## Render lifecycle
224
+
225
+ Web Components in this package extend `BaseElement` (`@unofficialbox/box-open-elements/core`):
226
+
227
+ - build the shadow tree once in `renderTemplate()`
228
+ - attach listeners once in `setupListeners()`
229
+ - patch state in `update()` — never reassign `shadowRoot.innerHTML` on attribute/property changes
230
+
231
+ See [architecture.md](./architecture.md#web-component-render-contract) for the full contract
232
+ (list-container rebuilds, focused-input value guarding).
233
+
234
+ ## Styling hooks
235
+
236
+ Shadow DOM components expose named internal styling targets with the `part` attribute. In docs and the docs site, refer to these as `Styling Hooks`: they let consumers style internal surfaces from outside the component without breaking encapsulation.
237
+
238
+ ```html
239
+ <box-button></box-button>
240
+ ```
241
+
242
+ ```css
243
+ box-button::part(button) {
244
+ border-radius: 999px;
245
+ }
246
+ ```
247
+
248
+ Recommended styling hook names stay structural and reusable:
249
+
250
+ - `label`
251
+ - `message`
252
+ - `title`
253
+ - `description`
254
+ - `trigger`
255
+ - `content`
256
+ - `item`
257
+ - `marker`
258
+ - `header-row`
259
+ - `cell`
260
+ - `empty`
261
+
262
+ Avoid overly presentational names unless the structure is unique to the component.
263
+
264
+ Components should also consume foundation tokens (`--boe-token-*`) with safe fallbacks, so they work with no design system registered and restyle automatically when one is active. Components **consume** tokens; shells and consumers **register / override** — do not invent per-component styling strategies. See [Token consumption vs shell / consumer overrides](./foundations/tokens.md#token-consumption-vs-shell--consumer-overrides).
265
+
266
+ ## AI-friendly authoring rules
267
+
268
+ To keep the system easy for AI coding assistants and human developers alike:
269
+
270
+ - prefer one obvious property name for each concept
271
+ - prefer one obvious event name for each state transition
272
+ - keep data shapes serializable and easy to inspect in logs
273
+ - document compatibility aliases when they exist
274
+ - show small copy-pasteable examples in docs and demos
275
+ - avoid requiring imperative setup when property binding is enough
276
+
277
+ ## Preview stack direction
278
+
279
+ Preview-related work stays layered:
280
+
281
+ - **Compositions** (patterns/preview) — preview headers, toolbars, sidebars, metadata panels, annotation surfaces around the rendering engine
282
+ - **Workflow shell** (patterns/preview) — a pluggable preview element that hosts the actual preview provider and orchestrates preview state
283
+
284
+ See [patterns/preview.md](./patterns/preview.md) for the provider-adapter contract and annotation-first priorities.