@unofficialbox/box-open-elements 0.5.0 → 0.7.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 (227) hide show
  1. package/README.md +27 -73
  2. package/dist/components/collections/table.d.ts +113 -3
  3. package/dist/components/collections/table.js +540 -37
  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/core/index.d.ts +4 -0
  30. package/dist/core/index.js +2 -0
  31. package/dist/core/offset-window.d.ts +70 -0
  32. package/dist/core/offset-window.js +134 -0
  33. package/dist/core/virtualize.d.ts +71 -0
  34. package/dist/core/virtualize.js +67 -0
  35. package/dist/element-maps.d.ts +292 -0
  36. package/dist/element-maps.js +1 -0
  37. package/dist/entries/activity-density.d.ts +1 -0
  38. package/dist/entries/activity-density.js +1 -0
  39. package/dist/entries/agent-chat.d.ts +1 -0
  40. package/dist/entries/agent-chat.js +1 -0
  41. package/dist/entries/audit-log.d.ts +1 -0
  42. package/dist/entries/audit-log.js +1 -0
  43. package/dist/entries/command-palette.d.ts +1 -0
  44. package/dist/entries/command-palette.js +1 -0
  45. package/dist/entries/compare-view.d.ts +1 -0
  46. package/dist/entries/compare-view.js +1 -0
  47. package/dist/entries/diff-viewer.d.ts +1 -0
  48. package/dist/entries/diff-viewer.js +1 -0
  49. package/dist/entries/due-badge.d.ts +1 -0
  50. package/dist/entries/due-badge.js +1 -0
  51. package/dist/entries/form-wizard.d.ts +1 -0
  52. package/dist/entries/form-wizard.js +1 -0
  53. package/dist/entries/lineage-graph.d.ts +1 -0
  54. package/dist/entries/lineage-graph.js +1 -0
  55. package/dist/entries/notification-bell.d.ts +1 -0
  56. package/dist/entries/notification-bell.js +1 -0
  57. package/dist/entries/notification-inbox.d.ts +1 -0
  58. package/dist/entries/notification-inbox.js +1 -0
  59. package/dist/entries/provenance-strip.d.ts +1 -0
  60. package/dist/entries/provenance-strip.js +1 -0
  61. package/dist/entries/run-trace.d.ts +1 -0
  62. package/dist/entries/run-trace.js +1 -0
  63. package/dist/entries/shortcuts-overlay.d.ts +1 -0
  64. package/dist/entries/shortcuts-overlay.js +1 -0
  65. package/dist/entries/signature-ceremony.d.ts +1 -0
  66. package/dist/entries/signature-ceremony.js +1 -0
  67. package/dist/entries/stage-path.d.ts +1 -0
  68. package/dist/entries/stage-path.js +1 -0
  69. package/dist/entries/timeline.d.ts +1 -0
  70. package/dist/entries/timeline.js +1 -0
  71. package/dist/entries/version-graph.d.ts +1 -0
  72. package/dist/entries/version-graph.js +1 -0
  73. package/dist/entries/version-list.d.ts +1 -0
  74. package/dist/entries/version-list.js +1 -0
  75. package/dist/entries/wizard-summary.d.ts +1 -0
  76. package/dist/entries/wizard-summary.js +1 -0
  77. package/dist/entries/work-queue.d.ts +1 -0
  78. package/dist/entries/work-queue.js +1 -0
  79. package/dist/entries/workload-board.d.ts +1 -0
  80. package/dist/entries/workload-board.js +1 -0
  81. package/dist/index.d.ts +21 -0
  82. package/dist/index.js +21 -0
  83. package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
  84. package/dist/patterns/agent-chat/agent-chat.js +820 -0
  85. package/dist/patterns/agent-chat/controller.d.ts +27 -0
  86. package/dist/patterns/agent-chat/controller.js +182 -0
  87. package/dist/patterns/agent-chat/index.d.ts +3 -0
  88. package/dist/patterns/agent-chat/index.js +6 -0
  89. package/dist/patterns/agent-chat/types.d.ts +109 -0
  90. package/dist/patterns/agent-chat/types.js +1 -0
  91. package/dist/patterns/audit/activity-density.d.ts +68 -0
  92. package/dist/patterns/audit/activity-density.js +428 -0
  93. package/dist/patterns/audit/audit-log.d.ts +182 -0
  94. package/dist/patterns/audit/audit-log.js +1082 -0
  95. package/dist/patterns/audit/index.d.ts +3 -0
  96. package/dist/patterns/audit/index.js +7 -0
  97. package/dist/patterns/audit/shared.d.ts +10 -0
  98. package/dist/patterns/audit/shared.js +50 -0
  99. package/dist/patterns/audit/types.d.ts +143 -0
  100. package/dist/patterns/audit/types.js +327 -0
  101. package/dist/patterns/audit/window.d.ts +116 -0
  102. package/dist/patterns/audit/window.js +132 -0
  103. package/dist/patterns/content-explorer/actions/controller.js +12 -2
  104. package/dist/patterns/content-explorer/box-transport.js +117 -25
  105. package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
  106. package/dist/patterns/content-explorer/content-explorer.js +36 -3
  107. package/dist/patterns/content-explorer/contracts.d.ts +24 -1
  108. package/dist/patterns/content-explorer/contracts.js +68 -11
  109. package/dist/patterns/content-explorer/controller.d.ts +16 -1
  110. package/dist/patterns/content-explorer/controller.js +130 -4
  111. package/dist/patterns/content-explorer/navigation/controller.js +11 -9
  112. package/dist/patterns/content-explorer/types.d.ts +58 -1
  113. package/dist/patterns/content-picker/content-picker.d.ts +71 -0
  114. package/dist/patterns/content-picker/content-picker.js +747 -0
  115. package/dist/patterns/content-picker/controller.d.ts +38 -0
  116. package/dist/patterns/content-picker/controller.js +141 -0
  117. package/dist/patterns/content-picker/index.d.ts +3 -0
  118. package/dist/patterns/content-picker/index.js +6 -0
  119. package/dist/patterns/content-picker/types.d.ts +52 -0
  120. package/dist/patterns/content-picker/types.js +26 -0
  121. package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
  122. package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
  123. package/dist/patterns/content-sidebar/index.d.ts +2 -0
  124. package/dist/patterns/content-sidebar/index.js +4 -0
  125. package/dist/patterns/content-sidebar/types.d.ts +18 -0
  126. package/dist/patterns/content-sidebar/types.js +20 -0
  127. package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
  128. package/dist/patterns/content-uploader/box-transport.js +54 -0
  129. package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
  130. package/dist/patterns/content-uploader/content-uploader.js +559 -0
  131. package/dist/patterns/content-uploader/controller.d.ts +39 -0
  132. package/dist/patterns/content-uploader/controller.js +227 -0
  133. package/dist/patterns/content-uploader/index.d.ts +4 -0
  134. package/dist/patterns/content-uploader/index.js +8 -0
  135. package/dist/patterns/content-uploader/types.d.ts +120 -0
  136. package/dist/patterns/content-uploader/types.js +38 -0
  137. package/dist/patterns/diff/compare-view.d.ts +47 -0
  138. package/dist/patterns/diff/compare-view.js +276 -0
  139. package/dist/patterns/diff/diff-viewer.d.ts +42 -0
  140. package/dist/patterns/diff/diff-viewer.js +434 -0
  141. package/dist/patterns/diff/engine.d.ts +22 -0
  142. package/dist/patterns/diff/engine.js +276 -0
  143. package/dist/patterns/diff/index.d.ts +4 -0
  144. package/dist/patterns/diff/index.js +8 -0
  145. package/dist/patterns/diff/types.d.ts +69 -0
  146. package/dist/patterns/diff/types.js +24 -0
  147. package/dist/patterns/form-wizard/controller.d.ts +39 -0
  148. package/dist/patterns/form-wizard/controller.js +157 -0
  149. package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
  150. package/dist/patterns/form-wizard/form-wizard.js +468 -0
  151. package/dist/patterns/form-wizard/index.d.ts +4 -0
  152. package/dist/patterns/form-wizard/index.js +8 -0
  153. package/dist/patterns/form-wizard/types.d.ts +109 -0
  154. package/dist/patterns/form-wizard/types.js +83 -0
  155. package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
  156. package/dist/patterns/form-wizard/wizard-summary.js +324 -0
  157. package/dist/patterns/internal/safe-href.d.ts +17 -0
  158. package/dist/patterns/internal/safe-href.js +17 -0
  159. package/dist/patterns/lineage/index.d.ts +3 -0
  160. package/dist/patterns/lineage/index.js +6 -0
  161. package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
  162. package/dist/patterns/lineage/lineage-graph.js +434 -0
  163. package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
  164. package/dist/patterns/lineage/provenance-strip.js +194 -0
  165. package/dist/patterns/lineage/types.d.ts +53 -0
  166. package/dist/patterns/lineage/types.js +49 -0
  167. package/dist/patterns/notifications/index.d.ts +3 -0
  168. package/dist/patterns/notifications/index.js +6 -0
  169. package/dist/patterns/notifications/notification-bell.d.ts +38 -0
  170. package/dist/patterns/notifications/notification-bell.js +198 -0
  171. package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
  172. package/dist/patterns/notifications/notification-inbox.js +537 -0
  173. package/dist/patterns/notifications/types.d.ts +61 -0
  174. package/dist/patterns/notifications/types.js +108 -0
  175. package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
  176. package/dist/patterns/preview/annotation-inspector.js +239 -94
  177. package/dist/patterns/preview/annotation-thread.d.ts +25 -5
  178. package/dist/patterns/preview/annotation-thread.js +233 -82
  179. package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
  180. package/dist/patterns/preview/annotation-toolbar.js +162 -96
  181. package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
  182. package/dist/patterns/preview/content-preview-adapter.js +26 -1
  183. package/dist/patterns/preview/preview-element.d.ts +15 -2
  184. package/dist/patterns/preview/preview-element.js +223 -52
  185. package/dist/patterns/preview/provider-adapter.d.ts +47 -0
  186. package/dist/patterns/preview/provider-adapter.js +17 -0
  187. package/dist/patterns/run/index.d.ts +2 -0
  188. package/dist/patterns/run/index.js +4 -0
  189. package/dist/patterns/run/run-trace.d.ts +40 -0
  190. package/dist/patterns/run/run-trace.js +441 -0
  191. package/dist/patterns/run/types.d.ts +66 -0
  192. package/dist/patterns/run/types.js +152 -0
  193. package/dist/patterns/signature/index.d.ts +2 -0
  194. package/dist/patterns/signature/index.js +4 -0
  195. package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
  196. package/dist/patterns/signature/signature-ceremony.js +317 -0
  197. package/dist/patterns/signature/types.d.ts +68 -0
  198. package/dist/patterns/signature/types.js +89 -0
  199. package/dist/patterns/timeline/index.d.ts +2 -0
  200. package/dist/patterns/timeline/index.js +4 -0
  201. package/dist/patterns/timeline/timeline.d.ts +27 -0
  202. package/dist/patterns/timeline/timeline.js +430 -0
  203. package/dist/patterns/timeline/types.d.ts +36 -0
  204. package/dist/patterns/timeline/types.js +27 -0
  205. package/dist/patterns/versions/graph-layout.d.ts +43 -0
  206. package/dist/patterns/versions/graph-layout.js +159 -0
  207. package/dist/patterns/versions/index.d.ts +4 -0
  208. package/dist/patterns/versions/index.js +8 -0
  209. package/dist/patterns/versions/shared.d.ts +4 -0
  210. package/dist/patterns/versions/shared.js +13 -0
  211. package/dist/patterns/versions/types.d.ts +37 -0
  212. package/dist/patterns/versions/types.js +39 -0
  213. package/dist/patterns/versions/version-graph.d.ts +36 -0
  214. package/dist/patterns/versions/version-graph.js +438 -0
  215. package/dist/patterns/versions/version-list.d.ts +39 -0
  216. package/dist/patterns/versions/version-list.js +407 -0
  217. package/dist/patterns/work-queue/controller.d.ts +26 -0
  218. package/dist/patterns/work-queue/controller.js +150 -0
  219. package/dist/patterns/work-queue/index.d.ts +4 -0
  220. package/dist/patterns/work-queue/index.js +7 -0
  221. package/dist/patterns/work-queue/types.d.ts +128 -0
  222. package/dist/patterns/work-queue/types.js +45 -0
  223. package/dist/patterns/work-queue/work-queue.d.ts +48 -0
  224. package/dist/patterns/work-queue/work-queue.js +534 -0
  225. package/dist/patterns/work-queue/workload-board.d.ts +53 -0
  226. package/dist/patterns/work-queue/workload-board.js +608 -0
  227. package/package.json +89 -5
@@ -0,0 +1,428 @@
1
+ import { computeActivityDensity, formatUtcDay, formatUtcMonth, resolveAuditDay, } from "./types.js";
2
+ import { escapeHtml, resolveReferenceTime } from "./shared.js";
3
+ import { isTimelineEventRecord } from "../timeline/types.js";
4
+ import { BaseElement } from "../../core/index.js";
5
+ import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
6
+ const DEFAULT_TAG_NAME = "box-activity-density";
7
+ const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
8
+ const elementStyles = `
9
+ [hidden] {
10
+ display: none !important;
11
+ }
12
+
13
+ :host {
14
+ display: block;
15
+ color: inherit;
16
+ font: inherit;
17
+ }
18
+
19
+ [part="panel"] {
20
+ display: grid;
21
+ gap: ${boePanel.gap};
22
+ padding: ${boePanel.padding};
23
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
24
+ border-radius: ${boePanel.radius};
25
+ background: ${boePanel.background};
26
+ }
27
+
28
+ [part="title"] {
29
+ margin: 0;
30
+ font: inherit;
31
+ font-size: 1.1rem;
32
+ font-weight: 700;
33
+ color: var(--boe-token-text-text, #1f1e1b);
34
+ }
35
+
36
+ [part="scroller"] {
37
+ overflow-x: auto;
38
+ }
39
+
40
+ [part="grid"] {
41
+ border-collapse: separate;
42
+ border-spacing: 3px;
43
+ }
44
+
45
+ [part="caption"] {
46
+ caption-side: top;
47
+ text-align: start;
48
+ padding-block-end: 0.35rem;
49
+ font-size: 0.8rem;
50
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
51
+ }
52
+
53
+ [part="weekday"],
54
+ [part="month"] {
55
+ font-size: 0.66rem;
56
+ font-weight: 600;
57
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
58
+ text-align: end;
59
+ padding-inline-end: 0.25rem;
60
+ white-space: nowrap;
61
+ }
62
+
63
+ [part="month"] {
64
+ text-align: start;
65
+ padding: 0;
66
+ }
67
+
68
+ [part="cell"] {
69
+ inline-size: 0.85rem;
70
+ block-size: 0.85rem;
71
+ padding: 0;
72
+ border-radius: ${boeRadius.size};
73
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
74
+ }
75
+
76
+ [part="cell"][data-level="1"] {
77
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 28%, transparent);
78
+ }
79
+
80
+ [part="cell"][data-level="2"] {
81
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 50%, transparent);
82
+ }
83
+
84
+ [part="cell"][data-level="3"] {
85
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 74%, transparent);
86
+ }
87
+
88
+ [part="cell"][data-level="4"] {
89
+ background: var(--boe-token-surface-surface-brand, #0061d5);
90
+ }
91
+
92
+ [part="cell-button"] {
93
+ appearance: none;
94
+ display: block;
95
+ inline-size: 100%;
96
+ block-size: 100%;
97
+ min-block-size: 0.85rem;
98
+ padding: 0;
99
+ border: none;
100
+ border-radius: inherit;
101
+ background: none;
102
+ cursor: pointer;
103
+ }
104
+
105
+ [part="cell-button"]:focus-visible {
106
+ outline: 2px solid var(--boe-token-surface-surface-brand, #0061d5);
107
+ outline-offset: 2px;
108
+ }
109
+
110
+ [part="legend"] {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.3rem;
114
+ font-size: 0.72rem;
115
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
116
+ }
117
+
118
+ [part="legend-swatch"] {
119
+ inline-size: 0.7rem;
120
+ block-size: 0.7rem;
121
+ border-radius: ${boeRadius.size};
122
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
123
+ }
124
+
125
+ [part="legend-swatch"][data-level="1"] {
126
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 28%, transparent);
127
+ }
128
+
129
+ [part="legend-swatch"][data-level="2"] {
130
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 50%, transparent);
131
+ }
132
+
133
+ [part="legend-swatch"][data-level="3"] {
134
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 74%, transparent);
135
+ }
136
+
137
+ [part="legend-swatch"][data-level="4"] {
138
+ background: var(--boe-token-surface-surface-brand, #0061d5);
139
+ }
140
+ `;
141
+ /**
142
+ * The managerial companion to `box-audit-log`: a calendar heatmap of event
143
+ * volume per UTC day over a trailing window, for spotting throughput and
144
+ * quiet periods at a glance.
145
+ *
146
+ * Days with activity are buttons in a roving-tabindex grid (arrow keys move
147
+ * by day and by week, Home/End jump to the ends of the window), each labelled
148
+ * with its own count and date so the surface is understandable without the
149
+ * colour. Selecting one emits `day-selected` with that day's events — the
150
+ * drill-down into the log.
151
+ */
152
+ export class ActivityDensityStrip extends BaseElement {
153
+ static tagName = DEFAULT_TAG_NAME;
154
+ static get observedAttributes() {
155
+ return ["events", "heading", "reference-time", "weeks"];
156
+ }
157
+ titleEl;
158
+ gridEl;
159
+ captionEl;
160
+ monthsRowEl;
161
+ rowsEl;
162
+ signature = "";
163
+ /** Roving focus position, `week:weekday`. Null until the first render. */
164
+ activeKey = null;
165
+ renderedWeeks = 0;
166
+ /** Raw `events` attribute the parse cache was built from. */
167
+ eventsRaw = null;
168
+ eventsCache = [];
169
+ get heading() {
170
+ return this.getAttribute("heading") ?? "Activity density";
171
+ }
172
+ set heading(value) {
173
+ this.setAttribute("heading", value);
174
+ }
175
+ /**
176
+ * Memoized on the raw attribute for the same reason as `box-audit-log`:
177
+ * `density`, `eventsOn`, and `update` each read it, and an arrow keypress
178
+ * must not re-parse the payload. Returns a copy.
179
+ */
180
+ get events() {
181
+ const raw = this.getAttribute("events");
182
+ if (!raw) {
183
+ return [];
184
+ }
185
+ if (raw !== this.eventsRaw) {
186
+ this.eventsRaw = raw;
187
+ try {
188
+ const parsed = JSON.parse(raw);
189
+ this.eventsCache =
190
+ Array.isArray(parsed) && parsed.every(isTimelineEventRecord)
191
+ ? parsed
192
+ : [];
193
+ }
194
+ catch {
195
+ this.eventsCache = [];
196
+ }
197
+ }
198
+ return [...this.eventsCache];
199
+ }
200
+ set events(value) {
201
+ if (value.length) {
202
+ this.setAttribute("events", JSON.stringify(value));
203
+ return;
204
+ }
205
+ this.removeAttribute("events");
206
+ }
207
+ get weeks() {
208
+ const raw = Number.parseInt(this.getAttribute("weeks") ?? "", 10);
209
+ return Number.isFinite(raw) ? raw : 12;
210
+ }
211
+ set weeks(value) {
212
+ this.setAttribute("weeks", String(value));
213
+ }
214
+ get referenceTime() {
215
+ return this.getAttribute("reference-time") ?? "";
216
+ }
217
+ set referenceTime(value) {
218
+ this.setAttribute("reference-time", value);
219
+ }
220
+ /** The computed window — cells, totals, and the busiest day's count. */
221
+ get density() {
222
+ return computeActivityDensity(this.events, {
223
+ now: resolveReferenceTime(this.getAttribute("reference-time")),
224
+ weeks: this.weeks,
225
+ });
226
+ }
227
+ /** Every event on the given UTC day, in input order. */
228
+ eventsOn(date) {
229
+ return this.events.filter(event => resolveAuditDay(event) === date);
230
+ }
231
+ emitDaySelected(date, count) {
232
+ this.dispatchEvent(new CustomEvent("day-selected", {
233
+ bubbles: true,
234
+ composed: true,
235
+ detail: { date, count, events: this.eventsOn(date) },
236
+ }));
237
+ }
238
+ buttons() {
239
+ return Array.from(this.gridEl.querySelectorAll('[part="cell-button"]'));
240
+ }
241
+ /**
242
+ * Buttons oldest → newest. DOM order is weekday-major (the grid renders one
243
+ * row per weekday), so it is *not* chronological — anything that means
244
+ * "first" or "last day in the window" has to sort by date.
245
+ */
246
+ buttonsByDate() {
247
+ return this.buttons().sort((left, right) => (left.getAttribute("data-date") ?? "").localeCompare(right.getAttribute("data-date") ?? ""));
248
+ }
249
+ buttonAt(week, weekday) {
250
+ return this.gridEl.querySelector(`[part="cell-button"][data-week="${String(week)}"][data-weekday="${String(weekday)}"]`);
251
+ }
252
+ /** Walk in one grid direction until a day with activity is found. */
253
+ step(week, weekday, deltaWeek, deltaWeekday) {
254
+ // The rendered column count, not a recomputed window: a keypress must not
255
+ // re-parse the whole event payload.
256
+ const total = this.renderedWeeks;
257
+ let nextWeek = week + deltaWeek;
258
+ let nextWeekday = weekday + deltaWeekday;
259
+ while (nextWeek >= 0 && nextWeek < total && nextWeekday >= 0 && nextWeekday < 7) {
260
+ const button = this.buttonAt(nextWeek, nextWeekday);
261
+ if (button) {
262
+ return button;
263
+ }
264
+ nextWeek += deltaWeek;
265
+ nextWeekday += deltaWeekday;
266
+ }
267
+ return null;
268
+ }
269
+ focusButton(button) {
270
+ this.activeKey = `${button.getAttribute("data-week") ?? ""}:${button.getAttribute("data-weekday") ?? ""}`;
271
+ for (const candidate of this.buttons()) {
272
+ candidate.tabIndex = candidate === button ? 0 : -1;
273
+ }
274
+ button.focus();
275
+ }
276
+ /**
277
+ * A day with activity is a labelled button — that label is the accessible
278
+ * contract, since colour alone carries no meaning. A quiet day is an inert
279
+ * cell with a hover title: nothing to drill into, and no extra tab stop.
280
+ */
281
+ cellHtml(cell) {
282
+ const date = escapeHtml(cell.date);
283
+ const label = escapeHtml(`${String(cell.count)} ${cell.count === 1 ? "event" : "events"} on ${formatUtcDay(cell.date)}`);
284
+ const body = cell.count > 0
285
+ ? `<button type="button" part="cell-button" tabindex="-1" data-date="${date}" data-week="${String(cell.week)}" data-weekday="${String(cell.weekday)}" data-count="${String(cell.count)}" aria-label="${label}"></button>`
286
+ : "";
287
+ return `<td part="cell" data-level="${String(cell.level)}" data-date="${date}" data-count="${String(cell.count)}" title="${label}">${body}</td>`;
288
+ }
289
+ rebuild(density) {
290
+ this.renderedWeeks = density.weeks;
291
+ const byWeekday = new Map();
292
+ for (const cell of density.cells) {
293
+ const row = byWeekday.get(cell.weekday) ?? [];
294
+ row.push(cell);
295
+ byWeekday.set(cell.weekday, row);
296
+ }
297
+ // Column headers carry the month, printed once where it changes — the
298
+ // same sparse labelling a git contribution graph uses.
299
+ const firstRow = byWeekday.get(0) ?? [];
300
+ let lastMonth = "";
301
+ const monthCells = firstRow.map(cell => {
302
+ const month = cell.date.slice(0, 7);
303
+ const label = month === lastMonth ? "" : formatUtcMonth(cell.date);
304
+ lastMonth = month;
305
+ return `<th scope="col" part="month">${label}</th>`;
306
+ });
307
+ // Only the header row and the body are re-minted — the caption is part of
308
+ // the stable shell, so rebuilding the grid never drops it.
309
+ this.monthsRowEl.innerHTML = `<td></td>${monthCells.join("")}`;
310
+ this.rowsEl.innerHTML = WEEKDAY_LABELS.map((label, weekday) => {
311
+ const cells = (byWeekday.get(weekday) ?? []).map(cell => this.cellHtml(cell)).join("");
312
+ return `<tr><th scope="row" part="weekday">${label}</th>${cells}</tr>`;
313
+ }).join("");
314
+ }
315
+ renderTemplate() {
316
+ if (!this.shadowRoot) {
317
+ return;
318
+ }
319
+ this.shadowRoot.innerHTML = `
320
+ <style>${elementStyles}</style>
321
+ <section part="panel">
322
+ <h2 part="title"></h2>
323
+ <div part="scroller">
324
+ <table part="grid">
325
+ <caption part="caption"></caption>
326
+ <thead><tr part="months-row"></tr></thead>
327
+ <tbody part="rows"></tbody>
328
+ </table>
329
+ </div>
330
+ <div part="legend">
331
+ <span>Less</span>
332
+ ${[0, 1, 2, 3, 4]
333
+ .map(level => `<span part="legend-swatch" data-level="${String(level)}"></span>`)
334
+ .join("")}
335
+ <span>More</span>
336
+ </div>
337
+ </section>
338
+ `;
339
+ this.titleEl = this.shadowRoot.querySelector('[part="title"]');
340
+ this.gridEl = this.shadowRoot.querySelector('[part="grid"]');
341
+ this.captionEl = this.shadowRoot.querySelector('[part="caption"]');
342
+ this.monthsRowEl = this.shadowRoot.querySelector('[part="months-row"]');
343
+ this.rowsEl = this.shadowRoot.querySelector('[part="rows"]');
344
+ }
345
+ setupListeners() {
346
+ this.gridEl.addEventListener("click", event => {
347
+ const button = event.target.closest('[part="cell-button"]');
348
+ if (!button || !this.gridEl.contains(button)) {
349
+ return;
350
+ }
351
+ this.focusButton(button);
352
+ this.emitDaySelected(button.getAttribute("data-date") ?? "", Number(button.getAttribute("data-count") ?? "0"));
353
+ });
354
+ this.gridEl.addEventListener("keydown", event => {
355
+ const button = event.target.closest('[part="cell-button"]');
356
+ if (!button || !this.gridEl.contains(button)) {
357
+ return;
358
+ }
359
+ const week = Number(button.getAttribute("data-week") ?? "0");
360
+ const weekday = Number(button.getAttribute("data-weekday") ?? "0");
361
+ // Home/End mean the ends of the *window*, not the ends of the DOM.
362
+ const byDate = this.buttonsByDate();
363
+ let next = null;
364
+ switch (event.key) {
365
+ case "ArrowLeft":
366
+ next = this.step(week, weekday, -1, 0);
367
+ break;
368
+ case "ArrowRight":
369
+ next = this.step(week, weekday, 1, 0);
370
+ break;
371
+ case "ArrowUp":
372
+ next = this.step(week, weekday, 0, -1);
373
+ break;
374
+ case "ArrowDown":
375
+ next = this.step(week, weekday, 0, 1);
376
+ break;
377
+ case "Home":
378
+ next = byDate[0] ?? null;
379
+ break;
380
+ case "End":
381
+ next = byDate[byDate.length - 1] ?? null;
382
+ break;
383
+ default:
384
+ return;
385
+ }
386
+ if (next) {
387
+ event.preventDefault();
388
+ this.focusButton(next);
389
+ }
390
+ });
391
+ }
392
+ update() {
393
+ if (!this.gridEl) {
394
+ return;
395
+ }
396
+ this.titleEl.textContent = this.heading;
397
+ const density = this.density;
398
+ const signature = JSON.stringify([
399
+ this.getAttribute("events") ?? "",
400
+ this.weeks,
401
+ this.referenceTime,
402
+ ]);
403
+ if (signature !== this.signature) {
404
+ this.signature = signature;
405
+ this.rebuild(density);
406
+ // Cells are re-minted, so the previous roving position may be gone.
407
+ this.activeKey = null;
408
+ }
409
+ this.captionEl.textContent = `${String(density.total)} ${density.total === 1 ? "event" : "events"} from ${formatUtcDay(density.start)} to ${formatUtcDay(density.end)}`;
410
+ this.gridEl.setAttribute("aria-label", this.heading);
411
+ const all = this.buttons();
412
+ const byDate = this.buttonsByDate();
413
+ const active = (this.activeKey
414
+ ? all.find(button => `${button.getAttribute("data-week") ?? ""}:${button.getAttribute("data-weekday") ?? ""}` ===
415
+ this.activeKey)
416
+ : undefined) ??
417
+ // Default the tab stop to the most recent day with activity — the last
418
+ // by date, not the last in the weekday-major DOM order.
419
+ byDate[byDate.length - 1];
420
+ for (const button of all) {
421
+ button.tabIndex = button === active ? 0 : -1;
422
+ }
423
+ if (active) {
424
+ this.activeKey = `${active.getAttribute("data-week") ?? ""}:${active.getAttribute("data-weekday") ?? ""}`;
425
+ }
426
+ }
427
+ }
428
+ ActivityDensityStrip.register();
@@ -0,0 +1,182 @@
1
+ import type { AuditEvent, AuditFacets, AuditGroup, AuditGroupBy } from "./types.js";
2
+ import { BaseElement } from "../../core/index.js";
3
+ import type { RowWindow } from "../../core/index.js";
4
+ /**
5
+ * The aggregation, faceting, and export layer over the `box-timeline` event
6
+ * contract (opportunity 3 of `plans/component-opportunities.md`): the same
7
+ * records the flat feed renders, grouped into collapsible day/actor/action
8
+ * sections with counts, narrowed by facets and a date range, drillable to one
9
+ * workflow run by correlation id, and exportable as CSV.
10
+ *
11
+ * Aggregation is client-side and pure. `virtualize` windows the log for
12
+ * production-scale volumes; server-side paging remains a tracked depth
13
+ * limitation.
14
+ */
15
+ export declare class AuditLog extends BaseElement {
16
+ static readonly tagName: string;
17
+ static get observedAttributes(): string[];
18
+ private toolbarEl;
19
+ private groupByEl;
20
+ private actorSelectEl;
21
+ private actionSelectEl;
22
+ private fromInputEl;
23
+ private toInputEl;
24
+ private clearFiltersEl;
25
+ private exportEl;
26
+ private drilldownEl;
27
+ private drilldownIdEl;
28
+ private summaryEl;
29
+ private groupsEl;
30
+ private emptyEl;
31
+ private titleEl;
32
+ /** Section keys the reader has collapsed. Absent means expanded. */
33
+ private collapsedGroups;
34
+ /** The row window the last render used; null when it rendered everything. */
35
+ private lastWindow;
36
+ private scrollFrame;
37
+ private measuredHeights;
38
+ private measureFrame;
39
+ /** Row set the adoption budget below is counted against. */
40
+ private adoptedFor;
41
+ private adoptionCount;
42
+ private viewportObserver;
43
+ private contentSignature;
44
+ private optionsSignature;
45
+ /** Raw `events` attribute the parse cache was built from. */
46
+ private eventsRaw;
47
+ private eventsCache;
48
+ get heading(): string;
49
+ set heading(value: string);
50
+ /**
51
+ * Audit records; JSON payloads are validated per record before rendering.
52
+ *
53
+ * Memoized on the raw attribute so a single keystroke in the date field
54
+ * does not re-parse and re-validate the whole payload once per reader
55
+ * (`update`, `visibleEvents`, and each click-handler branch all read it).
56
+ * The cache invalidates itself when the attribute changes; the returned
57
+ * array is a copy, so a caller cannot mutate the log out from under it.
58
+ */
59
+ get events(): AuditEvent[];
60
+ set events(value: AuditEvent[]);
61
+ get groupBy(): AuditGroupBy;
62
+ set groupBy(value: AuditGroupBy);
63
+ get facets(): AuditFacets;
64
+ /** Replaces the whole selection — an omitted or empty facet is cleared. */
65
+ set facets(value: AuditFacets);
66
+ get exportable(): boolean;
67
+ set exportable(value: boolean);
68
+ get referenceTime(): string;
69
+ set referenceTime(value: string);
70
+ /** The events currently on screen: the full set narrowed by the facets. */
71
+ get visibleEvents(): AuditEvent[];
72
+ /**
73
+ * The sections the current facets and grouping produce.
74
+ *
75
+ * Public because windowing takes away the alternative: a host used to be
76
+ * able to read the sections off the DOM, and once only a slice is rendered,
77
+ * that stops being the whole picture.
78
+ */
79
+ get groups(): AuditGroup[];
80
+ toggleGroup(key: string, expanded?: boolean): void;
81
+ expandAll(): void;
82
+ collapseAll(): void;
83
+ /**
84
+ * Export what is on screen, not the whole set — an export that silently
85
+ * widens past the reader's filters is a compliance hazard. Returns the CSV
86
+ * and emits `export-requested` so the host owns delivery (download, mail,
87
+ * archive API).
88
+ */
89
+ exportCsv(): string;
90
+ /**
91
+ * Watch the scroller so a measurable — or resized — viewport re-plans.
92
+ *
93
+ * Separate from `setupListeners` because that runs *once*, on the element's
94
+ * first connection, while `disconnectedCallback` tears the observer down
95
+ * every time. A log removed and re-inserted would otherwise come back with
96
+ * its listeners intact and its observer gone, so resizing its container
97
+ * would silently stop re-planning. Idempotent, so both callers are safe.
98
+ */
99
+ private observeViewport;
100
+ connectedCallback(): void;
101
+ disconnectedCallback(): void;
102
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
103
+ /**
104
+ * Render only the sections near the viewport. Opt-in for the same reason
105
+ * `box-table`'s is: a windowed surface needs a bounded height to scroll
106
+ * within, and silently windowing an unbounded one would render nothing.
107
+ *
108
+ * Unlike the table, this *can* window a grouped, variable-height surface —
109
+ * it plans over a cumulative offset index rather than a row count times a
110
+ * row height. Grouping is the whole point of an audit log, so refusing to
111
+ * window it would mean refusing the only shape it comes in.
112
+ */
113
+ get virtualize(): boolean;
114
+ set virtualize(value: boolean);
115
+ /**
116
+ * Starting height estimates, in pixels — not contracts. The real heights
117
+ * depend on density, font, and how much an event's summary wraps; after the
118
+ * first paint the element measures one of each and uses that instead.
119
+ */
120
+ get rowHeight(): number;
121
+ get headingHeight(): number;
122
+ set rowHeight(value: number);
123
+ set headingHeight(value: number);
124
+ private declaredHeight;
125
+ /** The window the last render used, or null when it rendered every section. */
126
+ get renderedWindow(): RowWindow | null;
127
+ /**
128
+ * Adopt the real rendered heights, once, when they differ from the ones the
129
+ * last plan used. Re-planning with the measured heights settles immediately —
130
+ * the next measurement matches — so this cannot loop.
131
+ */
132
+ /** Mean rendered height of the elements matching a part, or 0 if none. */
133
+ private meanHeight;
134
+ /**
135
+ * Adopt the real rendered heights when they differ from the ones in use.
136
+ *
137
+ * Two things here exist because the obvious version ran away. Sampling *one*
138
+ * event row and adopting its height looks fine and is not: event rows are not
139
+ * uniform — `summary`, `evidence` and `correlationId` are each optional — so
140
+ * adopting the first rendered row's height moves the window, which renders a
141
+ * different first row, which measures differently, which moves the window.
142
+ * Measured in Chromium with every third event several lines taller: **55 full
143
+ * rebuilds in 0.9 seconds while nothing was scrolling**, the sampled height
144
+ * flipping between 33px and 166.5px.
145
+ *
146
+ * So: average across every rendered row rather than sampling one, which makes
147
+ * the estimate both more representative and far less sensitive to which rows
148
+ * happen to be on screen. And bound the adoptions per row set anyway, because
149
+ * "the average is stable" is an argument about typical data, and this is the
150
+ * code path where being wrong costs a frame-rate collapse rather than a
151
+ * slightly wrong scrollbar.
152
+ */
153
+ private measureRowHeights;
154
+ /**
155
+ * What counts as "the same row set" for the adoption budget: change the data,
156
+ * the grouping, the filters or the collapse state and the rows genuinely
157
+ * differ, so measuring again is warranted.
158
+ */
159
+ private heightSignature;
160
+ private currentPlan;
161
+ private now;
162
+ /**
163
+ * Grouping, flattening and indexing for the current data, computed once and
164
+ * reused until something that affects them changes.
165
+ *
166
+ * The scroll path asks "did the window move?" on every frame that is not
167
+ * skipped, and answering it used to re-parse the whole `events` attribute,
168
+ * re-filter, re-group, re-flatten and rebuild the offset array — then
169
+ * `update()` did all of it again. That is O(n) twice per frame on a surface
170
+ * whose entire purpose is not being O(n) at scale.
171
+ */
172
+ private rowCache;
173
+ private cachedRows;
174
+ private currentGroups;
175
+ private patchFacets;
176
+ private eventHtml;
177
+ private groupHtml;
178
+ private renderOptions;
179
+ protected renderTemplate(): void;
180
+ protected setupListeners(): void;
181
+ protected update(): void;
182
+ }