@unofficialbox/box-open-elements 0.5.0 → 0.6.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 (219) hide show
  1. package/README.md +3 -3
  2. package/dist/components/collections/table.d.ts +57 -3
  3. package/dist/components/collections/table.js +339 -20
  4. package/dist/components/feedback/due-badge.d.ts +38 -0
  5. package/dist/components/feedback/due-badge.js +168 -0
  6. package/dist/components/feedback/due-types.d.ts +32 -0
  7. package/dist/components/feedback/due-types.js +91 -0
  8. package/dist/components/feedback/progress-steps.d.ts +51 -5
  9. package/dist/components/feedback/progress-steps.js +200 -22
  10. package/dist/components/feedback/stage-path.d.ts +44 -0
  11. package/dist/components/feedback/stage-path.js +235 -0
  12. package/dist/components/forms/select.d.ts +21 -0
  13. package/dist/components/forms/select.js +100 -3
  14. package/dist/components/forms/text-field.d.ts +21 -0
  15. package/dist/components/forms/text-field.js +100 -1
  16. package/dist/components/layout/nav-sidebar.d.ts +14 -0
  17. package/dist/components/layout/nav-sidebar.js +49 -0
  18. package/dist/components/layout/split-view.d.ts +14 -0
  19. package/dist/components/layout/split-view.js +80 -1
  20. package/dist/components/overlays/command-palette.d.ts +62 -0
  21. package/dist/components/overlays/command-palette.js +537 -0
  22. package/dist/components/overlays/command-types.d.ts +90 -0
  23. package/dist/components/overlays/command-types.js +259 -0
  24. package/dist/components/overlays/drawer.d.ts +18 -0
  25. package/dist/components/overlays/drawer.js +135 -9
  26. package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
  27. package/dist/components/overlays/shortcuts-overlay.js +385 -0
  28. package/dist/components/overlays/tooltip.js +1 -1
  29. package/dist/element-maps.d.ts +292 -0
  30. package/dist/element-maps.js +1 -0
  31. package/dist/entries/activity-density.d.ts +1 -0
  32. package/dist/entries/activity-density.js +1 -0
  33. package/dist/entries/agent-chat.d.ts +1 -0
  34. package/dist/entries/agent-chat.js +1 -0
  35. package/dist/entries/audit-log.d.ts +1 -0
  36. package/dist/entries/audit-log.js +1 -0
  37. package/dist/entries/command-palette.d.ts +1 -0
  38. package/dist/entries/command-palette.js +1 -0
  39. package/dist/entries/compare-view.d.ts +1 -0
  40. package/dist/entries/compare-view.js +1 -0
  41. package/dist/entries/diff-viewer.d.ts +1 -0
  42. package/dist/entries/diff-viewer.js +1 -0
  43. package/dist/entries/due-badge.d.ts +1 -0
  44. package/dist/entries/due-badge.js +1 -0
  45. package/dist/entries/form-wizard.d.ts +1 -0
  46. package/dist/entries/form-wizard.js +1 -0
  47. package/dist/entries/lineage-graph.d.ts +1 -0
  48. package/dist/entries/lineage-graph.js +1 -0
  49. package/dist/entries/notification-bell.d.ts +1 -0
  50. package/dist/entries/notification-bell.js +1 -0
  51. package/dist/entries/notification-inbox.d.ts +1 -0
  52. package/dist/entries/notification-inbox.js +1 -0
  53. package/dist/entries/provenance-strip.d.ts +1 -0
  54. package/dist/entries/provenance-strip.js +1 -0
  55. package/dist/entries/run-trace.d.ts +1 -0
  56. package/dist/entries/run-trace.js +1 -0
  57. package/dist/entries/shortcuts-overlay.d.ts +1 -0
  58. package/dist/entries/shortcuts-overlay.js +1 -0
  59. package/dist/entries/signature-ceremony.d.ts +1 -0
  60. package/dist/entries/signature-ceremony.js +1 -0
  61. package/dist/entries/stage-path.d.ts +1 -0
  62. package/dist/entries/stage-path.js +1 -0
  63. package/dist/entries/timeline.d.ts +1 -0
  64. package/dist/entries/timeline.js +1 -0
  65. package/dist/entries/version-graph.d.ts +1 -0
  66. package/dist/entries/version-graph.js +1 -0
  67. package/dist/entries/version-list.d.ts +1 -0
  68. package/dist/entries/version-list.js +1 -0
  69. package/dist/entries/wizard-summary.d.ts +1 -0
  70. package/dist/entries/wizard-summary.js +1 -0
  71. package/dist/entries/work-queue.d.ts +1 -0
  72. package/dist/entries/work-queue.js +1 -0
  73. package/dist/entries/workload-board.d.ts +1 -0
  74. package/dist/entries/workload-board.js +1 -0
  75. package/dist/index.d.ts +21 -0
  76. package/dist/index.js +21 -0
  77. package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
  78. package/dist/patterns/agent-chat/agent-chat.js +820 -0
  79. package/dist/patterns/agent-chat/controller.d.ts +27 -0
  80. package/dist/patterns/agent-chat/controller.js +182 -0
  81. package/dist/patterns/agent-chat/index.d.ts +3 -0
  82. package/dist/patterns/agent-chat/index.js +6 -0
  83. package/dist/patterns/agent-chat/types.d.ts +109 -0
  84. package/dist/patterns/agent-chat/types.js +1 -0
  85. package/dist/patterns/audit/activity-density.d.ts +68 -0
  86. package/dist/patterns/audit/activity-density.js +428 -0
  87. package/dist/patterns/audit/audit-log.d.ts +81 -0
  88. package/dist/patterns/audit/audit-log.js +794 -0
  89. package/dist/patterns/audit/index.d.ts +3 -0
  90. package/dist/patterns/audit/index.js +7 -0
  91. package/dist/patterns/audit/shared.d.ts +10 -0
  92. package/dist/patterns/audit/shared.js +50 -0
  93. package/dist/patterns/audit/types.d.ts +143 -0
  94. package/dist/patterns/audit/types.js +327 -0
  95. package/dist/patterns/content-explorer/actions/controller.js +12 -2
  96. package/dist/patterns/content-explorer/box-transport.js +117 -25
  97. package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
  98. package/dist/patterns/content-explorer/content-explorer.js +36 -3
  99. package/dist/patterns/content-explorer/contracts.d.ts +24 -1
  100. package/dist/patterns/content-explorer/contracts.js +68 -11
  101. package/dist/patterns/content-explorer/controller.d.ts +16 -1
  102. package/dist/patterns/content-explorer/controller.js +130 -4
  103. package/dist/patterns/content-explorer/navigation/controller.js +11 -9
  104. package/dist/patterns/content-explorer/types.d.ts +58 -1
  105. package/dist/patterns/content-picker/content-picker.d.ts +71 -0
  106. package/dist/patterns/content-picker/content-picker.js +747 -0
  107. package/dist/patterns/content-picker/controller.d.ts +38 -0
  108. package/dist/patterns/content-picker/controller.js +141 -0
  109. package/dist/patterns/content-picker/index.d.ts +3 -0
  110. package/dist/patterns/content-picker/index.js +6 -0
  111. package/dist/patterns/content-picker/types.d.ts +52 -0
  112. package/dist/patterns/content-picker/types.js +26 -0
  113. package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
  114. package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
  115. package/dist/patterns/content-sidebar/index.d.ts +2 -0
  116. package/dist/patterns/content-sidebar/index.js +4 -0
  117. package/dist/patterns/content-sidebar/types.d.ts +18 -0
  118. package/dist/patterns/content-sidebar/types.js +20 -0
  119. package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
  120. package/dist/patterns/content-uploader/box-transport.js +54 -0
  121. package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
  122. package/dist/patterns/content-uploader/content-uploader.js +559 -0
  123. package/dist/patterns/content-uploader/controller.d.ts +39 -0
  124. package/dist/patterns/content-uploader/controller.js +227 -0
  125. package/dist/patterns/content-uploader/index.d.ts +4 -0
  126. package/dist/patterns/content-uploader/index.js +8 -0
  127. package/dist/patterns/content-uploader/types.d.ts +120 -0
  128. package/dist/patterns/content-uploader/types.js +38 -0
  129. package/dist/patterns/diff/compare-view.d.ts +47 -0
  130. package/dist/patterns/diff/compare-view.js +276 -0
  131. package/dist/patterns/diff/diff-viewer.d.ts +42 -0
  132. package/dist/patterns/diff/diff-viewer.js +434 -0
  133. package/dist/patterns/diff/engine.d.ts +22 -0
  134. package/dist/patterns/diff/engine.js +276 -0
  135. package/dist/patterns/diff/index.d.ts +4 -0
  136. package/dist/patterns/diff/index.js +8 -0
  137. package/dist/patterns/diff/types.d.ts +69 -0
  138. package/dist/patterns/diff/types.js +24 -0
  139. package/dist/patterns/form-wizard/controller.d.ts +39 -0
  140. package/dist/patterns/form-wizard/controller.js +157 -0
  141. package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
  142. package/dist/patterns/form-wizard/form-wizard.js +468 -0
  143. package/dist/patterns/form-wizard/index.d.ts +4 -0
  144. package/dist/patterns/form-wizard/index.js +8 -0
  145. package/dist/patterns/form-wizard/types.d.ts +109 -0
  146. package/dist/patterns/form-wizard/types.js +83 -0
  147. package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
  148. package/dist/patterns/form-wizard/wizard-summary.js +324 -0
  149. package/dist/patterns/internal/safe-href.d.ts +17 -0
  150. package/dist/patterns/internal/safe-href.js +17 -0
  151. package/dist/patterns/lineage/index.d.ts +3 -0
  152. package/dist/patterns/lineage/index.js +6 -0
  153. package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
  154. package/dist/patterns/lineage/lineage-graph.js +434 -0
  155. package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
  156. package/dist/patterns/lineage/provenance-strip.js +194 -0
  157. package/dist/patterns/lineage/types.d.ts +53 -0
  158. package/dist/patterns/lineage/types.js +49 -0
  159. package/dist/patterns/notifications/index.d.ts +3 -0
  160. package/dist/patterns/notifications/index.js +6 -0
  161. package/dist/patterns/notifications/notification-bell.d.ts +38 -0
  162. package/dist/patterns/notifications/notification-bell.js +198 -0
  163. package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
  164. package/dist/patterns/notifications/notification-inbox.js +537 -0
  165. package/dist/patterns/notifications/types.d.ts +61 -0
  166. package/dist/patterns/notifications/types.js +108 -0
  167. package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
  168. package/dist/patterns/preview/annotation-inspector.js +239 -94
  169. package/dist/patterns/preview/annotation-thread.d.ts +25 -5
  170. package/dist/patterns/preview/annotation-thread.js +233 -82
  171. package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
  172. package/dist/patterns/preview/annotation-toolbar.js +162 -96
  173. package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
  174. package/dist/patterns/preview/content-preview-adapter.js +26 -1
  175. package/dist/patterns/preview/preview-element.d.ts +15 -2
  176. package/dist/patterns/preview/preview-element.js +223 -52
  177. package/dist/patterns/preview/provider-adapter.d.ts +47 -0
  178. package/dist/patterns/preview/provider-adapter.js +17 -0
  179. package/dist/patterns/run/index.d.ts +2 -0
  180. package/dist/patterns/run/index.js +4 -0
  181. package/dist/patterns/run/run-trace.d.ts +40 -0
  182. package/dist/patterns/run/run-trace.js +441 -0
  183. package/dist/patterns/run/types.d.ts +66 -0
  184. package/dist/patterns/run/types.js +152 -0
  185. package/dist/patterns/signature/index.d.ts +2 -0
  186. package/dist/patterns/signature/index.js +4 -0
  187. package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
  188. package/dist/patterns/signature/signature-ceremony.js +317 -0
  189. package/dist/patterns/signature/types.d.ts +68 -0
  190. package/dist/patterns/signature/types.js +89 -0
  191. package/dist/patterns/timeline/index.d.ts +2 -0
  192. package/dist/patterns/timeline/index.js +4 -0
  193. package/dist/patterns/timeline/timeline.d.ts +27 -0
  194. package/dist/patterns/timeline/timeline.js +430 -0
  195. package/dist/patterns/timeline/types.d.ts +36 -0
  196. package/dist/patterns/timeline/types.js +27 -0
  197. package/dist/patterns/versions/graph-layout.d.ts +43 -0
  198. package/dist/patterns/versions/graph-layout.js +159 -0
  199. package/dist/patterns/versions/index.d.ts +4 -0
  200. package/dist/patterns/versions/index.js +8 -0
  201. package/dist/patterns/versions/shared.d.ts +4 -0
  202. package/dist/patterns/versions/shared.js +13 -0
  203. package/dist/patterns/versions/types.d.ts +37 -0
  204. package/dist/patterns/versions/types.js +39 -0
  205. package/dist/patterns/versions/version-graph.d.ts +36 -0
  206. package/dist/patterns/versions/version-graph.js +438 -0
  207. package/dist/patterns/versions/version-list.d.ts +39 -0
  208. package/dist/patterns/versions/version-list.js +407 -0
  209. package/dist/patterns/work-queue/controller.d.ts +26 -0
  210. package/dist/patterns/work-queue/controller.js +150 -0
  211. package/dist/patterns/work-queue/index.d.ts +4 -0
  212. package/dist/patterns/work-queue/index.js +7 -0
  213. package/dist/patterns/work-queue/types.d.ts +128 -0
  214. package/dist/patterns/work-queue/types.js +45 -0
  215. package/dist/patterns/work-queue/work-queue.d.ts +48 -0
  216. package/dist/patterns/work-queue/work-queue.js +534 -0
  217. package/dist/patterns/work-queue/workload-board.d.ts +53 -0
  218. package/dist/patterns/work-queue/workload-board.js +608 -0
  219. package/package.json +89 -5
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./graph-layout.js";
3
+ export * from "./version-list.js";
4
+ export * from "./version-graph.js";
@@ -0,0 +1,8 @@
1
+ // Version model + validation
2
+ export * from "./types.js";
3
+ // Pure git-network layout engine (shared with future lineage graphs)
4
+ export * from "./graph-layout.js";
5
+ // Accessible core contract
6
+ export * from "./version-list.js";
7
+ // Git-style visualization layer
8
+ export * from "./version-graph.js";
@@ -0,0 +1,4 @@
1
+ import type { VersionStatus } from "./types.js";
2
+ /** Internal to the versions pattern — not part of the public barrel. */
3
+ export declare const escapeHtml: (value: string) => string;
4
+ export declare const STATUS_LABELS: Record<VersionStatus, string>;
@@ -0,0 +1,13 @@
1
+ /** Internal to the versions pattern — not part of the public barrel. */
2
+ export const escapeHtml = (value) => value
3
+ .replaceAll("&", "&amp;")
4
+ .replaceAll("<", "&lt;")
5
+ .replaceAll(">", "&gt;")
6
+ .replaceAll('"', "&quot;")
7
+ .replaceAll("'", "&#39;");
8
+ export const STATUS_LABELS = {
9
+ current: "Current",
10
+ executed: "Executed",
11
+ superseded: "Superseded",
12
+ abandoned: "Abandoned",
13
+ };
@@ -0,0 +1,37 @@
1
+ /** Node emphasis in the version tree: majors are milestones, merges close branches. */
2
+ export type VersionKind = "major" | "minor" | "merge" | "draft";
3
+ /** Lifecycle status; rendered as tone on both projections. */
4
+ export type VersionStatus = "current" | "executed" | "superseded" | "abandoned";
5
+ export interface VersionActor {
6
+ name: string;
7
+ initials?: string;
8
+ }
9
+ /**
10
+ * One version in a branch/merge history. `parents` carries the topology the
11
+ * way git does: a root has none, a branch point has many children, and a
12
+ * merge node lists more than one parent. Hosts list nodes in creation order —
13
+ * that order (not timestamps) breaks topological ties.
14
+ */
15
+ export interface VersionNode {
16
+ id: string;
17
+ /** Short display name: "v2.0", "Redline r2". */
18
+ label: string;
19
+ /** Ids of the versions this one was derived from. Empty/absent = root. */
20
+ parents?: string[];
21
+ kind?: VersionKind;
22
+ status?: VersionStatus;
23
+ actor?: VersionActor;
24
+ /** ISO timestamp; display only — `parents` defines the topology. */
25
+ timestamp?: string;
26
+ /** One-line description under the label. */
27
+ note?: string;
28
+ }
29
+ /** Detail payload for `compare-requested`; `baseId` is the older side. */
30
+ export interface VersionCompareDetail {
31
+ baseId: string;
32
+ targetId: string;
33
+ }
34
+ export declare const resolveVersionKind: (value: string | null | undefined) => VersionKind;
35
+ export declare const resolveVersionStatus: (value: string | null | undefined) => VersionStatus | null;
36
+ /** Attribute payloads are author input — validate every record, not just the array. */
37
+ export declare const isVersionNodeRecord: (value: unknown) => value is VersionNode;
@@ -0,0 +1,39 @@
1
+ const KINDS = new Set(["major", "minor", "merge", "draft"]);
2
+ const STATUSES = new Set(["current", "executed", "superseded", "abandoned"]);
3
+ export const resolveVersionKind = (value) => value && KINDS.has(value) ? value : "minor";
4
+ export const resolveVersionStatus = (value) => value && STATUSES.has(value) ? value : null;
5
+ /** Attribute payloads are author input — validate every record, not just the array. */
6
+ export const isVersionNodeRecord = (value) => {
7
+ if (typeof value !== "object" || value === null) {
8
+ return false;
9
+ }
10
+ const node = value;
11
+ if (typeof node.id !== "string" || node.id.length === 0 || typeof node.label !== "string") {
12
+ return false;
13
+ }
14
+ if (node.parents !== undefined) {
15
+ if (!Array.isArray(node.parents)) {
16
+ return false;
17
+ }
18
+ for (const parent of node.parents) {
19
+ if (typeof parent !== "string") {
20
+ return false;
21
+ }
22
+ }
23
+ }
24
+ for (const field of ["kind", "status", "timestamp", "note"]) {
25
+ if (node[field] !== undefined && typeof node[field] !== "string") {
26
+ return false;
27
+ }
28
+ }
29
+ if (node.actor !== undefined) {
30
+ const actor = node.actor;
31
+ if (typeof actor !== "object" || actor === null || typeof actor.name !== "string") {
32
+ return false;
33
+ }
34
+ if (actor.initials !== undefined && typeof actor.initials !== "string") {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ };
@@ -0,0 +1,36 @@
1
+ import type { VersionNode } from "./types.js";
2
+ import { BaseElement } from "../../core/index.js";
3
+ /**
4
+ * Git-network rendering of a version history (opportunity 4): lanes and
5
+ * branch/merge curves from the pure `computeVersionGraphLayout`, with one
6
+ * HTML button per node so activation, focus, and compare selection stay
7
+ * native. Click emits `version-selected`; a modified click (Shift/Ctrl/Meta)
8
+ * or the `toggleCompare` method pairs two nodes into `compare-requested`.
9
+ * `box-version-list` renders the same model as the accessible table-style
10
+ * fallback — pair them when the graph is the primary surface.
11
+ */
12
+ export declare class VersionGraph extends BaseElement {
13
+ static readonly tagName: string;
14
+ static get observedAttributes(): string[];
15
+ private versionsValue;
16
+ private compareIds;
17
+ private rovingId;
18
+ private hostEl;
19
+ get heading(): string;
20
+ set heading(value: string);
21
+ get versions(): VersionNode[];
22
+ set versions(value: VersionNode[]);
23
+ /** Ids currently toggled for compare, in selection order (max two). */
24
+ get compareSelection(): string[];
25
+ clearCompareSelection(): void;
26
+ /** Toggle a node in the compare pair; two toggled nodes emit `compare-requested`. */
27
+ toggleCompare(id: string): void;
28
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
29
+ private parseVersionsAttribute;
30
+ private pruneSelection;
31
+ private getVersion;
32
+ private nodeButtons;
33
+ protected renderTemplate(): void;
34
+ protected setupListeners(): void;
35
+ protected update(): void;
36
+ }
@@ -0,0 +1,438 @@
1
+ import { computeVersionGraphLayout, orderVersionsForDisplay } from "./graph-layout.js";
2
+ import { escapeHtml, STATUS_LABELS } from "./shared.js";
3
+ import { isVersionNodeRecord, resolveVersionKind, resolveVersionStatus } from "./types.js";
4
+ import { formatItemDate } from "../content-explorer/adapters/item-summary.js";
5
+ import { BaseElement } from "../../core/index.js";
6
+ import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
7
+ const DEFAULT_TAG_NAME = "box-version-graph";
8
+ const LANE_WIDTH = 26;
9
+ const ROW_HEIGHT = 46;
10
+ const EDGE_PAD = 8;
11
+ const elementStyles = `
12
+ [hidden] {
13
+ display: none !important;
14
+ }
15
+
16
+ :host {
17
+ display: block;
18
+ color: inherit;
19
+ font: inherit;
20
+ }
21
+
22
+ section[part="panel"] {
23
+ display: grid;
24
+ gap: ${boePanel.gap};
25
+ padding: ${boePanel.padding};
26
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
27
+ border-radius: ${boePanel.radius};
28
+ background: ${boePanel.background};
29
+ }
30
+
31
+ [part="title"] {
32
+ margin: 0;
33
+ font: inherit;
34
+ font-size: 1.1rem;
35
+ font-weight: 700;
36
+ color: var(--boe-token-text-text, #1f1e1b);
37
+ }
38
+
39
+ [part="graph"] {
40
+ position: relative;
41
+ overflow-x: auto;
42
+ }
43
+
44
+ [part="edges"] {
45
+ position: absolute;
46
+ inset-block-start: 0;
47
+ inset-inline-start: 0;
48
+ pointer-events: none;
49
+ }
50
+
51
+ [part="edge"] {
52
+ fill: none;
53
+ stroke: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 55%, transparent);
54
+ stroke-width: 2;
55
+ }
56
+
57
+ [part="rows"] {
58
+ list-style: none;
59
+ margin: 0;
60
+ padding: 0;
61
+ position: relative;
62
+ }
63
+
64
+ [part="row"] {
65
+ position: relative;
66
+ display: flex;
67
+ flex-wrap: wrap;
68
+ align-items: center;
69
+ gap: 0.45rem;
70
+ block-size: ${ROW_HEIGHT}px;
71
+ border-radius: ${boeRadius.med};
72
+ }
73
+
74
+ [part="row"][data-compare-selected="true"] {
75
+ background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
76
+ }
77
+
78
+ [part="node"] {
79
+ position: absolute;
80
+ inset-block-start: 50%;
81
+ transform: translate(-50%, -50%);
82
+ appearance: none;
83
+ padding: 0;
84
+ inline-size: 0.85rem;
85
+ block-size: 0.85rem;
86
+ border-radius: 999px;
87
+ border: 2px solid var(--boe-token-text-text-secondary, #6f6f6f);
88
+ background: var(--boe-token-surface-surface, #ffffff);
89
+ cursor: pointer;
90
+ }
91
+
92
+ [part="node"][data-kind="major"],
93
+ [part="node"][data-kind="merge"] {
94
+ background: var(--boe-token-text-text-secondary, #6f6f6f);
95
+ }
96
+
97
+ [part="node"][data-kind="major"] {
98
+ inline-size: 1.05rem;
99
+ block-size: 1.05rem;
100
+ }
101
+
102
+ [part="node"][data-kind="draft"] {
103
+ border-style: dashed;
104
+ }
105
+
106
+ [part="node"][data-status="current"] {
107
+ border-color: var(--boe-token-surface-surface-brand, #0061d5);
108
+ background: var(--boe-token-surface-surface-brand, #0061d5);
109
+ }
110
+
111
+ [part="node"][data-status="executed"] {
112
+ border-color: var(--boe-token-surface-status-surface-success, #26c281);
113
+ background: var(--boe-token-surface-status-surface-success, #26c281);
114
+ }
115
+
116
+ [part="node"][data-status="abandoned"] {
117
+ border-color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 70%, transparent);
118
+ background: transparent;
119
+ }
120
+
121
+ [part="node"][data-compare-selected="true"] {
122
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 30%, transparent);
123
+ }
124
+
125
+ [part="node"]:focus-visible {
126
+ outline: none;
127
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 45%, transparent);
128
+ }
129
+
130
+ [part="row-label"] {
131
+ font-weight: 700;
132
+ color: var(--boe-token-text-text, #1f1e1b);
133
+ }
134
+
135
+ [part="row"][data-kind="minor"] [part="row-label"],
136
+ [part="row"][data-kind="draft"] [part="row-label"] {
137
+ font-weight: 600;
138
+ font-size: 0.9rem;
139
+ }
140
+
141
+ [part="status"] {
142
+ display: inline-flex;
143
+ padding: 0.1rem 0.4rem;
144
+ border-radius: 999px;
145
+ background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
146
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
147
+ font-size: 0.72rem;
148
+ font-weight: 700;
149
+ }
150
+
151
+ [part="status"][data-status="current"] {
152
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 14%, transparent);
153
+ color: var(--boe-token-surface-surface-brand, #0061d5);
154
+ }
155
+
156
+ [part="status"][data-status="executed"] {
157
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 16%, transparent);
158
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 52%, black 48%);
159
+ }
160
+
161
+ [part="status"][data-status="abandoned"] {
162
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 12%, transparent);
163
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 74%, black 26%);
164
+ }
165
+
166
+ [part="row-meta"] {
167
+ font-size: 0.78rem;
168
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
169
+ }
170
+
171
+ [part="empty"] {
172
+ padding: ${boePanel.padding};
173
+ border-radius: ${boeRadius.large};
174
+ border: 1px dashed color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
175
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
176
+ }
177
+ `;
178
+ /**
179
+ * Git-network rendering of a version history (opportunity 4): lanes and
180
+ * branch/merge curves from the pure `computeVersionGraphLayout`, with one
181
+ * HTML button per node so activation, focus, and compare selection stay
182
+ * native. Click emits `version-selected`; a modified click (Shift/Ctrl/Meta)
183
+ * or the `toggleCompare` method pairs two nodes into `compare-requested`.
184
+ * `box-version-list` renders the same model as the accessible table-style
185
+ * fallback — pair them when the graph is the primary surface.
186
+ */
187
+ export class VersionGraph extends BaseElement {
188
+ static tagName = DEFAULT_TAG_NAME;
189
+ static get observedAttributes() {
190
+ return ["heading", "versions"];
191
+ }
192
+ versionsValue = [];
193
+ compareIds = [];
194
+ rovingId = null;
195
+ hostEl;
196
+ get heading() {
197
+ return this.getAttribute("heading") ?? "Version graph";
198
+ }
199
+ set heading(value) {
200
+ this.setAttribute("heading", value);
201
+ }
202
+ get versions() {
203
+ return this.versionsValue;
204
+ }
205
+ set versions(value) {
206
+ this.versionsValue = Array.isArray(value) ? value.filter(isVersionNodeRecord) : [];
207
+ this.pruneSelection();
208
+ if (this.isRendered) {
209
+ this.update();
210
+ }
211
+ }
212
+ /** Ids currently toggled for compare, in selection order (max two). */
213
+ get compareSelection() {
214
+ return [...this.compareIds];
215
+ }
216
+ clearCompareSelection() {
217
+ this.compareIds = [];
218
+ if (this.isRendered) {
219
+ this.update();
220
+ }
221
+ }
222
+ /** Toggle a node in the compare pair; two toggled nodes emit `compare-requested`. */
223
+ toggleCompare(id) {
224
+ if (!this.versionsValue.some(node => node.id === id)) {
225
+ return;
226
+ }
227
+ if (this.compareIds.includes(id)) {
228
+ this.compareIds = this.compareIds.filter(other => other !== id);
229
+ }
230
+ else {
231
+ this.compareIds = [...this.compareIds, id].slice(-2);
232
+ }
233
+ if (this.compareIds.length === 2) {
234
+ const ordered = orderVersionsForDisplay(this.versionsValue).map(node => node.id);
235
+ const [a, b] = this.compareIds;
236
+ const older = ordered.indexOf(a) > ordered.indexOf(b) ? a : b;
237
+ const newer = older === a ? b : a;
238
+ this.dispatchEvent(new CustomEvent("compare-requested", {
239
+ bubbles: true,
240
+ composed: true,
241
+ detail: { baseId: older, targetId: newer },
242
+ }));
243
+ }
244
+ if (this.isRendered) {
245
+ this.update();
246
+ }
247
+ }
248
+ attributeChangedCallback(name, oldValue, newValue) {
249
+ if (name === "versions") {
250
+ this.versionsValue = this.parseVersionsAttribute(newValue);
251
+ this.pruneSelection();
252
+ }
253
+ super.attributeChangedCallback(name, oldValue, newValue);
254
+ }
255
+ parseVersionsAttribute(raw) {
256
+ if (!raw) {
257
+ return [];
258
+ }
259
+ try {
260
+ const parsed = JSON.parse(raw);
261
+ return Array.isArray(parsed) ? parsed.filter(isVersionNodeRecord) : [];
262
+ }
263
+ catch {
264
+ return [];
265
+ }
266
+ }
267
+ pruneSelection() {
268
+ const known = new Set(this.versionsValue.map(node => node.id));
269
+ this.compareIds = this.compareIds.filter(id => known.has(id));
270
+ if (this.rovingId && !known.has(this.rovingId)) {
271
+ this.rovingId = null;
272
+ }
273
+ }
274
+ getVersion(id) {
275
+ return this.versionsValue.find(node => node.id === id);
276
+ }
277
+ nodeButtons() {
278
+ return Array.from(this.hostEl.querySelectorAll('[part="node"]'));
279
+ }
280
+ renderTemplate() {
281
+ if (!this.shadowRoot) {
282
+ return;
283
+ }
284
+ this.shadowRoot.innerHTML = `
285
+ <style>${elementStyles}</style>
286
+ <div part="content-host"></div>
287
+ `;
288
+ this.hostEl = this.shadowRoot.querySelector('[part="content-host"]');
289
+ }
290
+ setupListeners() {
291
+ this.hostEl.addEventListener("click", event => {
292
+ const target = event.target;
293
+ const nodeButton = target.closest('[part="node"]');
294
+ if (!nodeButton || !this.hostEl.contains(nodeButton)) {
295
+ return;
296
+ }
297
+ const id = nodeButton.getAttribute("data-id") ?? "";
298
+ const version = this.getVersion(id);
299
+ if (!version) {
300
+ return;
301
+ }
302
+ this.rovingId = id;
303
+ if (event.shiftKey || event.ctrlKey || event.metaKey) {
304
+ this.toggleCompare(id);
305
+ return;
306
+ }
307
+ this.dispatchEvent(new CustomEvent("version-selected", {
308
+ bubbles: true,
309
+ composed: true,
310
+ detail: { version },
311
+ }));
312
+ });
313
+ this.hostEl.addEventListener("keydown", event => {
314
+ const target = event.target;
315
+ if (target.getAttribute("part") !== "node") {
316
+ return;
317
+ }
318
+ const buttons = this.nodeButtons();
319
+ const index = buttons.indexOf(target);
320
+ if (index === -1) {
321
+ return;
322
+ }
323
+ let next = null;
324
+ if (event.key === "ArrowDown") {
325
+ next = Math.min(index + 1, buttons.length - 1);
326
+ }
327
+ else if (event.key === "ArrowUp") {
328
+ next = Math.max(index - 1, 0);
329
+ }
330
+ else if (event.key === "Home") {
331
+ next = 0;
332
+ }
333
+ else if (event.key === "End") {
334
+ next = buttons.length - 1;
335
+ }
336
+ if (next === null || next === index) {
337
+ return;
338
+ }
339
+ event.preventDefault();
340
+ const button = buttons[next];
341
+ this.rovingId = button.getAttribute("data-id");
342
+ for (const other of buttons) {
343
+ other.tabIndex = other === button ? 0 : -1;
344
+ }
345
+ button.focus();
346
+ });
347
+ }
348
+ update() {
349
+ if (!this.hostEl) {
350
+ return;
351
+ }
352
+ const active = this.shadowRoot?.activeElement;
353
+ const focusedId = active && this.hostEl.contains(active) && active.getAttribute("part") === "node"
354
+ ? active.getAttribute("data-id")
355
+ : null;
356
+ const layout = computeVersionGraphLayout(this.versionsValue);
357
+ const byId = new Map(this.versionsValue.map(node => [node.id, node]));
358
+ const graphWidth = EDGE_PAD * 2 + layout.laneCount * LANE_WIDTH;
359
+ const graphHeight = layout.rowCount * ROW_HEIGHT;
360
+ const centerX = (lane) => EDGE_PAD + lane * LANE_WIDTH + LANE_WIDTH / 2;
361
+ const centerY = (row) => row * ROW_HEIGHT + ROW_HEIGHT / 2;
362
+ const edgePaths = layout.edges
363
+ .map(edge => {
364
+ const x1 = centerX(edge.fromLane);
365
+ const y1 = centerY(edge.fromRow);
366
+ const x2 = centerX(edge.toLane);
367
+ const y2 = centerY(edge.toRow);
368
+ const d = edge.fromLane === edge.toLane
369
+ ? `M ${x1} ${y1} L ${x2} ${y2}`
370
+ : `M ${x1} ${y1} C ${x1} ${y1 - ROW_HEIGHT / 2}, ${x2} ${y2 + ROW_HEIGHT / 2}, ${x2} ${y2}`;
371
+ return `<path part="edge" data-from="${escapeHtml(edge.fromId)}" data-to="${escapeHtml(edge.toId)}" d="${d}"></path>`;
372
+ })
373
+ .join("");
374
+ const rovingId = this.rovingId && byId.has(this.rovingId)
375
+ ? this.rovingId
376
+ : (layout.placements[0]?.id ?? null);
377
+ const rows = layout.placements
378
+ .map(placement => {
379
+ const node = byId.get(placement.id);
380
+ const kind = resolveVersionKind(node.kind);
381
+ const status = resolveVersionStatus(node.status);
382
+ const compareSelected = this.compareIds.includes(node.id);
383
+ const when = node.timestamp ? formatItemDate(node.timestamp) : "";
384
+ const ariaLabel = [
385
+ node.label,
386
+ kind,
387
+ status ? STATUS_LABELS[status] : "",
388
+ node.actor?.name ?? "",
389
+ compareSelected ? "selected for comparison" : "",
390
+ ]
391
+ .filter(Boolean)
392
+ .join(", ");
393
+ return `
394
+ <li part="row" data-id="${escapeHtml(node.id)}" data-kind="${kind}"${status ? ` data-status="${status}"` : ""} data-compare-selected="${compareSelected ? "true" : "false"}" style="padding-inline-start: ${String(graphWidth + 10)}px;">
395
+ <button
396
+ type="button"
397
+ part="node"
398
+ data-id="${escapeHtml(node.id)}"
399
+ data-kind="${kind}"
400
+ ${status ? `data-status="${status}"` : ""}
401
+ data-compare-selected="${compareSelected ? "true" : "false"}"
402
+ tabindex="${node.id === rovingId ? "0" : "-1"}"
403
+ aria-label="${escapeHtml(ariaLabel)}"
404
+ style="inset-inline-start: ${String(centerX(placement.lane))}px;"
405
+ ></button>
406
+ <span part="row-label">${escapeHtml(node.label)}</span>
407
+ ${status ? `<span part="status" data-status="${status}">${STATUS_LABELS[status]}</span>` : ""}
408
+ <span part="row-meta">${[node.actor?.name, when].filter(Boolean).map(text => escapeHtml(text)).join(" · ")}</span>
409
+ </li>
410
+ `;
411
+ })
412
+ .join("");
413
+ this.hostEl.innerHTML = `
414
+ <section part="panel" aria-label="${escapeHtml(this.heading)}">
415
+ <h2 part="title">${escapeHtml(this.heading)}</h2>
416
+ ${layout.placements.length > 0
417
+ ? `
418
+ <div part="graph">
419
+ <svg part="edges" width="${String(graphWidth)}" height="${String(graphHeight)}" viewBox="0 0 ${String(graphWidth)} ${String(graphHeight)}" aria-hidden="true">${edgePaths}</svg>
420
+ <ol part="rows" role="list">${rows}</ol>
421
+ </div>
422
+ `
423
+ : `<div part="empty">No versions.</div>`}
424
+ </section>
425
+ `;
426
+ if (focusedId) {
427
+ // Compare data-id directly — ids are author input, and building a CSS
428
+ // selector from one can throw. Fall back to the current tab stop so
429
+ // focus stays inside the component when the focused node was removed.
430
+ const buttons = this.nodeButtons();
431
+ const target = buttons.find(button => button.getAttribute("data-id") === focusedId) ??
432
+ buttons.find(button => button.tabIndex === 0) ??
433
+ buttons[0];
434
+ target?.focus();
435
+ }
436
+ }
437
+ }
438
+ VersionGraph.register();
@@ -0,0 +1,39 @@
1
+ import type { VersionNode } from "./types.js";
2
+ import { BaseElement } from "../../core/index.js";
3
+ /**
4
+ * The accessible core of the versions surface (CLM gap 5): a topologically
5
+ * ordered, newest-first version history with status tones, two-version
6
+ * compare pairing, and restore/promote surfaced as intent events for the
7
+ * host's confirm-before-apply flow. `box-version-graph` renders the same
8
+ * model as a git-style network; this list is the contract, the graph is
9
+ * progressive enhancement.
10
+ */
11
+ export declare class VersionList extends BaseElement {
12
+ static readonly tagName: string;
13
+ static get observedAttributes(): string[];
14
+ private versionsValue;
15
+ private compareIds;
16
+ private hostEl;
17
+ get heading(): string;
18
+ set heading(value: string);
19
+ /** Enables the per-row Restore intent button (non-current rows). */
20
+ get canRestore(): boolean;
21
+ set canRestore(value: boolean);
22
+ /** Enables the per-row Promote intent button (non-current rows). */
23
+ get canPromote(): boolean;
24
+ set canPromote(value: boolean);
25
+ get versions(): VersionNode[];
26
+ set versions(value: VersionNode[]);
27
+ /** Ids currently toggled for compare, in selection order (max two). */
28
+ get compareSelection(): string[];
29
+ clearCompareSelection(): void;
30
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
31
+ private parseVersionsAttribute;
32
+ private pruneCompareSelection;
33
+ private getVersion;
34
+ private toggleCompare;
35
+ private rowHtml;
36
+ protected renderTemplate(): void;
37
+ protected setupListeners(): void;
38
+ protected update(): void;
39
+ }