@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,385 @@
1
+ import { groupShortcutCommands, isCommandDescriptorRecord, splitShortcutKeys, } from "./command-types.js";
2
+ import { BaseElement } from "../../core/index.js";
3
+ import { FocusRestore, trapTabKey } from "../../foundations/a11y/index.js";
4
+ import { boeOverlay, boePanel, boeRadius } from "../../foundations/geometry/index.js";
5
+ const DEFAULT_TAG_NAME = "box-shortcuts-overlay";
6
+ const escapeHtml = (value) => value
7
+ .replaceAll("&", "&")
8
+ .replaceAll("<", "&lt;")
9
+ .replaceAll(">", "&gt;")
10
+ .replaceAll('"', "&quot;")
11
+ .replaceAll("'", "&#39;");
12
+ /** Editable targets where a bare-character hotkey must never fire. */
13
+ const isTypingTarget = (target) => {
14
+ const element = target;
15
+ if (!element || typeof element.tagName !== "string") {
16
+ return false;
17
+ }
18
+ if (element.isContentEditable) {
19
+ return true;
20
+ }
21
+ const tag = element.tagName;
22
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
23
+ };
24
+ /**
25
+ * The element the key was actually typed into. `event.target` is retargeted to
26
+ * the shadow host once an event crosses a shadow boundary, and this library
27
+ * wraps native controls in shadow DOM — so reading `target` on a document
28
+ * listener sees `box-text-field`, not the `input` inside it.
29
+ */
30
+ const keyOrigin = (event) => event.composedPath()[0] ?? event.target;
31
+ const elementStyles = `
32
+ [hidden] {
33
+ display: none !important;
34
+ }
35
+
36
+ :host {
37
+ display: contents;
38
+ }
39
+
40
+ [part="backdrop"] {
41
+ position: fixed;
42
+ inset: 0;
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ padding: 1rem;
47
+ background: ${boeOverlay.modalBackdrop};
48
+ z-index: 1000;
49
+ }
50
+
51
+ [part="sheet"] {
52
+ inline-size: min(34rem, 100%);
53
+ max-block-size: 80vh;
54
+ display: grid;
55
+ grid-template-rows: auto 1fr;
56
+ border-radius: ${boeOverlay.modalRadius};
57
+ border: ${boePanel.border};
58
+ background: ${boePanel.background};
59
+ box-shadow: ${boeOverlay.shadow};
60
+ overflow: hidden;
61
+ }
62
+
63
+ [part="header"] {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ padding: 0.85rem 1rem;
68
+ border-block-end: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
69
+ }
70
+
71
+ [part="title"] {
72
+ margin: 0;
73
+ font: inherit;
74
+ font-size: 1rem;
75
+ font-weight: 700;
76
+ color: var(--boe-token-text-text, #1f1e1b);
77
+ }
78
+
79
+ [part="close"] {
80
+ margin-inline-start: auto;
81
+ appearance: none;
82
+ font: inherit;
83
+ font-size: 0.8rem;
84
+ padding: 0.25rem 0.55rem;
85
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
86
+ border-radius: ${boeRadius.control};
87
+ background: var(--boe-token-surface-surface, #ffffff);
88
+ color: var(--boe-token-text-text, #222222);
89
+ cursor: pointer;
90
+ }
91
+
92
+ [part="close"]:hover {
93
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
94
+ }
95
+
96
+ [part="groups"] {
97
+ margin: 0;
98
+ padding: 0.6rem 1rem 1rem;
99
+ list-style: none;
100
+ overflow-y: auto;
101
+ }
102
+
103
+ [part="group-label"] {
104
+ display: block;
105
+ padding-block: 0.7rem 0.3rem;
106
+ font-size: 0.7rem;
107
+ font-weight: 700;
108
+ letter-spacing: 0.08em;
109
+ text-transform: uppercase;
110
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
111
+ }
112
+
113
+ [part="rows"] {
114
+ margin: 0;
115
+ padding: 0;
116
+ list-style: none;
117
+ display: grid;
118
+ }
119
+
120
+ [part="row"] {
121
+ display: flex;
122
+ align-items: baseline;
123
+ gap: 0.75rem;
124
+ padding: 0.35rem 0;
125
+ }
126
+
127
+ [part="row-label"] {
128
+ color: var(--boe-token-text-text, #1f1e1b);
129
+ }
130
+
131
+ [part="keys"] {
132
+ margin-inline-start: auto;
133
+ display: inline-flex;
134
+ align-items: center;
135
+ gap: 0.2rem;
136
+ white-space: nowrap;
137
+ }
138
+
139
+ [part="key"] {
140
+ min-inline-size: 1.4rem;
141
+ padding: 0.1rem 0.35rem;
142
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
143
+ border-block-end-width: 2px;
144
+ border-radius: ${boeRadius.size};
145
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
146
+ font-family: ui-monospace, monospace;
147
+ font-size: 0.72rem;
148
+ text-align: center;
149
+ color: var(--boe-token-text-text, #1f1e1b);
150
+ }
151
+
152
+ [part="key-separator"] {
153
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
154
+ font-size: 0.7rem;
155
+ }
156
+
157
+ [part="empty"] {
158
+ padding: 1.4rem 1rem;
159
+ text-align: center;
160
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
161
+ }
162
+
163
+ [part="close"]:focus-visible {
164
+ outline: none;
165
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
166
+ }
167
+ `;
168
+ /**
169
+ * The keyboard shortcuts sheet that makes keyboard-first real — the pair to
170
+ * `box-command-palette`.
171
+ *
172
+ * It reads the *same* `CommandDescriptor[]` the palette does and lists only
173
+ * the commands that declare a `shortcut`. One catalogue driving both surfaces
174
+ * is the point: a shortcut cannot end up documented but unreachable, or
175
+ * reachable but undocumented, because there is only one place to add it.
176
+ *
177
+ * Keys render as `<kbd>` elements rather than a run of text, so the markup
178
+ * says what it means.
179
+ */
180
+ export class ShortcutsOverlay extends BaseElement {
181
+ static tagName = DEFAULT_TAG_NAME;
182
+ static get observedAttributes() {
183
+ return ["commands", "heading", "hotkey", "open"];
184
+ }
185
+ hostEl;
186
+ focusRestore = new FocusRestore();
187
+ wasOpen = false;
188
+ commandsRaw = null;
189
+ commandsCache = [];
190
+ onDocumentKeydown = (event) => {
191
+ if (this.open) {
192
+ // Escape closes from anywhere. Focus can end up outside the sheet — the
193
+ // browser chrome, a host that moves it — and a modal that only closes
194
+ // while it still holds focus is a trap. The sheet's own handler runs
195
+ // first and clears `open`, so this never dismisses twice.
196
+ if (event.key === "Escape") {
197
+ event.preventDefault();
198
+ this.dismiss();
199
+ }
200
+ return;
201
+ }
202
+ const hotkey = this.hotkey;
203
+ if (!hotkey) {
204
+ return;
205
+ }
206
+ // A bare-character hotkey must never fire while someone is typing —
207
+ // "?" is a legitimate character in every text field on the page.
208
+ if (isTypingTarget(keyOrigin(event))) {
209
+ return;
210
+ }
211
+ if (event.metaKey || event.ctrlKey || event.altKey) {
212
+ return;
213
+ }
214
+ if (event.key === hotkey) {
215
+ event.preventDefault();
216
+ this.open = true;
217
+ }
218
+ };
219
+ get open() {
220
+ return this.hasAttribute("open");
221
+ }
222
+ set open(value) {
223
+ this.toggleAttribute("open", value);
224
+ }
225
+ get heading() {
226
+ return this.getAttribute("heading") ?? "Keyboard shortcuts";
227
+ }
228
+ set heading(value) {
229
+ this.setAttribute("heading", value);
230
+ }
231
+ /**
232
+ * Character that opens the sheet. Defaults to `?`, the convention. Matched
233
+ * against the produced character, so the Shift needed to type it is already
234
+ * accounted for. Set empty to let the host own the trigger.
235
+ */
236
+ get hotkey() {
237
+ const raw = this.getAttribute("hotkey");
238
+ return raw === null ? "?" : raw;
239
+ }
240
+ set hotkey(value) {
241
+ this.setAttribute("hotkey", value);
242
+ }
243
+ /** The same catalogue `box-command-palette` takes; validated per record. */
244
+ get commands() {
245
+ const raw = this.getAttribute("commands");
246
+ if (!raw) {
247
+ return [];
248
+ }
249
+ if (raw !== this.commandsRaw) {
250
+ this.commandsRaw = raw;
251
+ try {
252
+ const parsed = JSON.parse(raw);
253
+ this.commandsCache =
254
+ Array.isArray(parsed) && parsed.every(isCommandDescriptorRecord)
255
+ ? parsed
256
+ : [];
257
+ }
258
+ catch {
259
+ this.commandsCache = [];
260
+ }
261
+ }
262
+ return [...this.commandsCache];
263
+ }
264
+ set commands(value) {
265
+ if (value.length) {
266
+ this.setAttribute("commands", JSON.stringify(value));
267
+ return;
268
+ }
269
+ this.removeAttribute("commands");
270
+ }
271
+ /** Only the commands that declare a shortcut, in catalogue order. */
272
+ get documentedCommands() {
273
+ return groupShortcutCommands(this.commands).flatMap(group => group.commands);
274
+ }
275
+ show() {
276
+ this.open = true;
277
+ }
278
+ hide() {
279
+ this.open = false;
280
+ }
281
+ connectedCallback() {
282
+ super.connectedCallback();
283
+ document.addEventListener("keydown", this.onDocumentKeydown);
284
+ }
285
+ disconnectedCallback() {
286
+ document.removeEventListener("keydown", this.onDocumentKeydown);
287
+ }
288
+ dismiss() {
289
+ this.dispatchEvent(new CustomEvent("dismissed", { bubbles: true, composed: true }));
290
+ this.open = false;
291
+ }
292
+ renderTemplate() {
293
+ if (!this.shadowRoot) {
294
+ return;
295
+ }
296
+ this.shadowRoot.innerHTML = `<style>${elementStyles}</style><div part="host"></div>`;
297
+ this.hostEl = this.shadowRoot.querySelector('[part="host"]');
298
+ }
299
+ setupListeners() {
300
+ this.hostEl.addEventListener("pointerdown", event => {
301
+ const target = event.target;
302
+ if (target.closest('[part="sheet"]')) {
303
+ return;
304
+ }
305
+ if (target.closest('[part="backdrop"]')) {
306
+ this.dismiss();
307
+ }
308
+ });
309
+ this.hostEl.addEventListener("click", event => {
310
+ if (event.target.closest('[part="close"]')) {
311
+ this.dismiss();
312
+ }
313
+ });
314
+ this.hostEl.addEventListener("keydown", event => {
315
+ const keyboardEvent = event;
316
+ const sheet = this.hostEl.querySelector('[part="sheet"]');
317
+ if (!sheet) {
318
+ return;
319
+ }
320
+ if (keyboardEvent.key === "Escape") {
321
+ keyboardEvent.preventDefault();
322
+ this.dismiss();
323
+ return;
324
+ }
325
+ if (keyboardEvent.key === "Tab") {
326
+ trapTabKey(keyboardEvent, sheet);
327
+ }
328
+ });
329
+ }
330
+ update() {
331
+ if (!this.hostEl) {
332
+ return;
333
+ }
334
+ if (!this.open) {
335
+ const wasOpen = this.wasOpen;
336
+ this.hostEl.innerHTML = "";
337
+ this.wasOpen = false;
338
+ if (wasOpen) {
339
+ this.focusRestore.restore();
340
+ }
341
+ return;
342
+ }
343
+ const justOpened = !this.wasOpen;
344
+ this.wasOpen = true;
345
+ if (justOpened) {
346
+ this.focusRestore.capture();
347
+ }
348
+ const groups = groupShortcutCommands(this.commands);
349
+ const sections = groups
350
+ .map(group => {
351
+ const rows = group.commands
352
+ .map(command => {
353
+ const keys = splitShortcutKeys(command.shortcut ?? "")
354
+ .map(key => `<kbd part="key">${escapeHtml(key)}</kbd>`)
355
+ .join(`<span part="key-separator" aria-hidden="true">+</span>`);
356
+ return `
357
+ <li part="row" data-command-id="${escapeHtml(command.id)}">
358
+ <span part="row-label">${escapeHtml(command.label)}</span>
359
+ <span part="keys" aria-label="${escapeHtml(command.shortcut ?? "")}">${keys}</span>
360
+ </li>
361
+ `;
362
+ })
363
+ .join("");
364
+ return `<li part="group" data-group-key="${escapeHtml(group.key)}"><span part="group-label">${escapeHtml(group.label)}</span><ul part="rows" role="list">${rows}</ul></li>`;
365
+ })
366
+ .join("");
367
+ this.hostEl.innerHTML = `
368
+ <div part="backdrop">
369
+ <div part="sheet" role="dialog" aria-modal="true" aria-label="${escapeHtml(this.heading)}">
370
+ <div part="header">
371
+ <h2 part="title">${escapeHtml(this.heading)}</h2>
372
+ <button type="button" part="close">Close</button>
373
+ </div>
374
+ ${groups.length
375
+ ? `<ul part="groups" role="list">${sections}</ul>`
376
+ : `<div part="empty">No keyboard shortcuts are defined.</div>`}
377
+ </div>
378
+ </div>
379
+ `;
380
+ if (justOpened) {
381
+ this.hostEl.querySelector('[part="close"]')?.focus();
382
+ }
383
+ }
384
+ }
385
+ ShortcutsOverlay.register();
@@ -51,7 +51,7 @@ const tooltipStyles = `
51
51
  border-radius: ${boeRadius.xlarge};
52
52
  border: 1px solid color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, rgba(255, 255, 255, 0.08));
53
53
  background: var(--boe-token-surface-tooltip-surface, #4e4e4e);
54
- color: rgba(255, 255, 255, 0.94);
54
+ color: #ffffff;
55
55
  box-shadow: 0 16px 28px rgba(16, 24, 32, 0.18);
56
56
  line-height: 1.45;
57
57
  }
@@ -4,3 +4,7 @@ export type { EventMap, Unsubscribe } from "./event-emitter.js";
4
4
  export { BaseElement } from "./element.js";
5
5
  export { FORM_DESCRIPTION_ID, FORM_ERROR_MESSAGE_ID, FormAssociatedElement, boeFormFieldErrorStyles, boeFormFieldSupportStyles, formDataFromNamedValues, formDataFromRange, formDescriptionMarkup, formErrorMessageMarkup, getMirroredFormValue, rangeFromFormValue, stringValuesFromFormValue, } from "./form-associated.js";
6
6
  export type { FormValue } from "./form-associated.js";
7
+ export { resolveRowWindow, sameRowWindow } from "./virtualize.js";
8
+ export type { RowWindow, WindowMetrics } from "./virtualize.js";
9
+ export { buildOffsetIndex, offsetOfItem, resolveOffsetWindow } from "./offset-window.js";
10
+ export type { OffsetIndex, OffsetWindowMetrics } from "./offset-window.js";
@@ -2,3 +2,5 @@ export { Controller } from "./controller.js";
2
2
  export { TypedEventEmitter } from "./event-emitter.js";
3
3
  export { BaseElement } from "./element.js";
4
4
  export { FORM_DESCRIPTION_ID, FORM_ERROR_MESSAGE_ID, FormAssociatedElement, boeFormFieldErrorStyles, boeFormFieldSupportStyles, formDataFromNamedValues, formDataFromRange, formDescriptionMarkup, formErrorMessageMarkup, getMirroredFormValue, rangeFromFormValue, stringValuesFromFormValue, } from "./form-associated.js";
5
+ export { resolveRowWindow, sameRowWindow } from "./virtualize.js";
6
+ export { buildOffsetIndex, offsetOfItem, resolveOffsetWindow } from "./offset-window.js";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Row windowing for lists whose rows are *not* all the same height.
3
+ *
4
+ * `resolveRowWindow` (./virtualize.ts) multiplies a row count by one row
5
+ * height. That is exact and O(1), and it is the right answer for a flat table
6
+ * of uniform rows — but it cannot describe a surface where the items differ:
7
+ * a grouped audit log alternates short headings with taller event rows, a
8
+ * collapsed group contributes only its heading, and an expanded detail row adds
9
+ * height the count knows nothing about. Feeding those to the fixed-height
10
+ * engine does not degrade gracefully; the spacers under-report the real height,
11
+ * the scroll range stops matching the scrollbar, and `scrollTop` maps to the
12
+ * wrong item. That is why `box-table` refuses to window expandable rows rather
13
+ * than approximating them.
14
+ *
15
+ * The general answer is a cumulative offset index: the running pixel offset of
16
+ * every item, built once, then binary-searched per scroll. Building is O(n) and
17
+ * searching is O(log n), which is the right split — heights change when the
18
+ * data or the collapse state changes, not on every scroll event.
19
+ *
20
+ * Heights are still *estimates* until something measures them. This engine
21
+ * takes whatever heights it is given and is exact with respect to them; keeping
22
+ * them honest is the caller's job, the same bargain `row-height` strikes.
23
+ */
24
+ import type { RowWindow } from "./virtualize.js";
25
+ export interface OffsetIndex {
26
+ /**
27
+ * Running offsets, length `count + 1`. `offsets[i]` is the top of item `i`;
28
+ * `offsets[count]` is the total height, so the height of item `i` is
29
+ * `offsets[i + 1] - offsets[i]` with no special case for the last one.
30
+ */
31
+ readonly offsets: readonly number[];
32
+ readonly count: number;
33
+ readonly totalHeight: number;
34
+ }
35
+ export interface OffsetWindowMetrics {
36
+ /** Height of the scrolling viewport, in pixels. */
37
+ viewportHeight: number;
38
+ /** Current scroll offset of the viewport, in pixels. */
39
+ scrollTop: number;
40
+ /** Extra items rendered above and below the visible band. Default 4. */
41
+ overscan?: number;
42
+ }
43
+ /**
44
+ * The cumulative offset index for a list of item heights.
45
+ *
46
+ * A non-finite or negative height contributes zero rather than poisoning every
47
+ * subsequent offset with `NaN`. Heights arrive from measurement and from host
48
+ * estimates; one bad value should cost that item its space, not break the
49
+ * scrollbar for the whole collection.
50
+ */
51
+ export declare const buildOffsetIndex: (heights: readonly number[]) => OffsetIndex;
52
+ /**
53
+ * The rendered slice for a scroll position, plus the spacer heights.
54
+ *
55
+ * Same contract as `resolveRowWindow`, so the two engines are interchangeable
56
+ * from a component's point of view:
57
+ * - `paddingTop + (height of the rendered items) + paddingBottom` always equals
58
+ * `totalHeight`, so the scroll range never drifts as items scroll by.
59
+ * - The window always covers the visible band; overscan only ever adds items.
60
+ * - Degenerate geometry yields an empty window rather than a guess.
61
+ */
62
+ export declare const resolveOffsetWindow: (index: OffsetIndex, metrics: OffsetWindowMetrics) => RowWindow;
63
+ /**
64
+ * The pixel offset of an item, for scrolling it into view.
65
+ *
66
+ * Out-of-range indices clamp to the ends rather than returning `undefined`:
67
+ * callers are keyboard handlers reaching for "the last one", and a clamp is
68
+ * what they would write anyway.
69
+ */
70
+ export declare const offsetOfItem: (index: OffsetIndex, item: number) => number;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Row windowing for lists whose rows are *not* all the same height.
3
+ *
4
+ * `resolveRowWindow` (./virtualize.ts) multiplies a row count by one row
5
+ * height. That is exact and O(1), and it is the right answer for a flat table
6
+ * of uniform rows — but it cannot describe a surface where the items differ:
7
+ * a grouped audit log alternates short headings with taller event rows, a
8
+ * collapsed group contributes only its heading, and an expanded detail row adds
9
+ * height the count knows nothing about. Feeding those to the fixed-height
10
+ * engine does not degrade gracefully; the spacers under-report the real height,
11
+ * the scroll range stops matching the scrollbar, and `scrollTop` maps to the
12
+ * wrong item. That is why `box-table` refuses to window expandable rows rather
13
+ * than approximating them.
14
+ *
15
+ * The general answer is a cumulative offset index: the running pixel offset of
16
+ * every item, built once, then binary-searched per scroll. Building is O(n) and
17
+ * searching is O(log n), which is the right split — heights change when the
18
+ * data or the collapse state changes, not on every scroll event.
19
+ *
20
+ * Heights are still *estimates* until something measures them. This engine
21
+ * takes whatever heights it is given and is exact with respect to them; keeping
22
+ * them honest is the caller's job, the same bargain `row-height` strikes.
23
+ */
24
+ const DEFAULT_OVERSCAN = 4;
25
+ const EMPTY_INDEX = { offsets: [0], count: 0, totalHeight: 0 };
26
+ /**
27
+ * The cumulative offset index for a list of item heights.
28
+ *
29
+ * A non-finite or negative height contributes zero rather than poisoning every
30
+ * subsequent offset with `NaN`. Heights arrive from measurement and from host
31
+ * estimates; one bad value should cost that item its space, not break the
32
+ * scrollbar for the whole collection.
33
+ */
34
+ export const buildOffsetIndex = (heights) => {
35
+ if (heights.length === 0)
36
+ return EMPTY_INDEX;
37
+ const offsets = new Array(heights.length + 1);
38
+ offsets[0] = 0;
39
+ let running = 0;
40
+ for (let index = 0; index < heights.length; index++) {
41
+ const height = heights[index] ?? 0;
42
+ running += Number.isFinite(height) && height > 0 ? height : 0;
43
+ offsets[index + 1] = running;
44
+ }
45
+ return { offsets, count: heights.length, totalHeight: running };
46
+ };
47
+ /**
48
+ * The index of the last item whose top is at or before `offset`.
49
+ *
50
+ * Binary search rather than a scan: this runs per scroll frame, and a linear
51
+ * walk over ten thousand offsets is the cost windowing exists to avoid.
52
+ * Zero-height items make offsets non-strictly-increasing, so ties resolve to
53
+ * the *first* of a run — the alternative is a window that starts inside a
54
+ * cluster of collapsed items and leaves the one above it unrendered.
55
+ */
56
+ const findItemAt = (offsets, count, offset) => {
57
+ let low = 0;
58
+ let high = count - 1;
59
+ let found = 0;
60
+ while (low <= high) {
61
+ const mid = (low + high) >> 1;
62
+ if ((offsets[mid] ?? 0) <= offset) {
63
+ found = mid;
64
+ low = mid + 1;
65
+ }
66
+ else {
67
+ high = mid - 1;
68
+ }
69
+ }
70
+ // Walk back over a run of equal offsets (zero-height items) so the window
71
+ // starts at the first item sharing this offset, not an arbitrary one.
72
+ const at = offsets[found] ?? 0;
73
+ while (found > 0 && (offsets[found - 1] ?? 0) === at)
74
+ found--;
75
+ return found;
76
+ };
77
+ /**
78
+ * The rendered slice for a scroll position, plus the spacer heights.
79
+ *
80
+ * Same contract as `resolveRowWindow`, so the two engines are interchangeable
81
+ * from a component's point of view:
82
+ * - `paddingTop + (height of the rendered items) + paddingBottom` always equals
83
+ * `totalHeight`, so the scroll range never drifts as items scroll by.
84
+ * - The window always covers the visible band; overscan only ever adds items.
85
+ * - Degenerate geometry yields an empty window rather than a guess.
86
+ */
87
+ export const resolveOffsetWindow = (index, metrics) => {
88
+ const { offsets, count, totalHeight } = index;
89
+ const overscan = Math.max(0, Math.floor(metrics.overscan ?? DEFAULT_OVERSCAN));
90
+ if (count <= 0 || totalHeight <= 0) {
91
+ return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: 0, totalHeight };
92
+ }
93
+ if (metrics.viewportHeight <= 0) {
94
+ // An unmeasured viewport is the normal state during first paint and under
95
+ // jsdom. Rendering everything there would defeat the purpose exactly when
96
+ // it matters most.
97
+ return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: totalHeight, totalHeight };
98
+ }
99
+ // Clamp a bounced or past-the-end scrollTop: rubber-banding reports both, and
100
+ // neither may produce a window outside the collection.
101
+ const maxScroll = Math.max(0, totalHeight - metrics.viewportHeight);
102
+ const top = Math.min(Math.max(0, metrics.scrollTop), maxScroll);
103
+ const bottom = top + metrics.viewportHeight;
104
+ const firstVisible = findItemAt(offsets, count, top);
105
+ // Last item that starts before the viewport's bottom edge. Scanning forward
106
+ // from firstVisible is bounded by what fits on screen plus overscan, which is
107
+ // the point of windowing — no walk over the whole collection.
108
+ let lastVisible = firstVisible;
109
+ while (lastVisible + 1 < count && (offsets[lastVisible + 1] ?? 0) < bottom) {
110
+ lastVisible++;
111
+ }
112
+ const startIndex = Math.max(0, firstVisible - overscan);
113
+ const endIndex = Math.min(count, lastVisible + 1 + overscan);
114
+ return {
115
+ startIndex,
116
+ endIndex,
117
+ paddingTop: offsets[startIndex] ?? 0,
118
+ paddingBottom: totalHeight - (offsets[endIndex] ?? totalHeight),
119
+ totalHeight,
120
+ };
121
+ };
122
+ /**
123
+ * The pixel offset of an item, for scrolling it into view.
124
+ *
125
+ * Out-of-range indices clamp to the ends rather than returning `undefined`:
126
+ * callers are keyboard handlers reaching for "the last one", and a clamp is
127
+ * what they would write anyway.
128
+ */
129
+ export const offsetOfItem = (index, item) => {
130
+ if (index.count <= 0)
131
+ return 0;
132
+ const clamped = Math.min(Math.max(0, Math.floor(item)), index.count);
133
+ return index.offsets[clamped] ?? 0;
134
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Row windowing for long lists.
3
+ *
4
+ * A ten-thousand-row audit log rendered whole is ten thousand DOM rows: the
5
+ * first paint stalls, every subsequent patch walks the lot, and the browser
6
+ * holds layout boxes nobody will ever scroll to. Windowing renders only the
7
+ * rows near the viewport and pads the rest with spacer height, so the scrollbar
8
+ * still describes the whole collection.
9
+ *
10
+ * The arithmetic lives here, pure, for the usual reason: jsdom has no layout,
11
+ * so a DOM-level test of a windowed surface can only ever observe zeroes. It is
12
+ * shared rather than per-component so `box-table`, `box-audit-log`, and
13
+ * `box-tree-grid` cannot drift into three different definitions of "near".
14
+ *
15
+ * Fixed row height only. Measured/variable heights are a different algorithm
16
+ * (a cumulative offset index that reflows as rows are measured); pretending one
17
+ * function covers both is how windowing engines end up subtly wrong at the
18
+ * boundaries.
19
+ */
20
+ export interface WindowMetrics {
21
+ /** Total rows in the collection, not the rendered subset. */
22
+ totalRows: number;
23
+ /** Fixed height of one row, in pixels. */
24
+ rowHeight: number;
25
+ /** Height of the scrolling viewport, in pixels. */
26
+ viewportHeight: number;
27
+ /** Current scroll offset of the viewport, in pixels. */
28
+ scrollTop: number;
29
+ /**
30
+ * Extra rows rendered above and below the visible band, so a fast scroll
31
+ * does not expose blank space before the next frame. Default 4.
32
+ */
33
+ overscan?: number;
34
+ }
35
+ export interface RowWindow {
36
+ /** First rendered row index, inclusive. */
37
+ startIndex: number;
38
+ /** Last rendered row index, exclusive — safe for `slice(startIndex, endIndex)`. */
39
+ endIndex: number;
40
+ /** Spacer height above the rendered rows, in pixels. */
41
+ paddingTop: number;
42
+ /** Spacer height below the rendered rows, in pixels. */
43
+ paddingBottom: number;
44
+ /** Height of the whole collection, for the scroll range. */
45
+ totalHeight: number;
46
+ }
47
+ /**
48
+ * The rendered slice for a scroll position, plus the spacer heights that keep
49
+ * the scrollbar honest.
50
+ *
51
+ * Guarantees callers can rely on:
52
+ * - `paddingTop + (endIndex - startIndex) * rowHeight + paddingBottom` always
53
+ * equals `totalHeight`, so the scroll range never drifts as rows scroll by —
54
+ * a windowed list whose height wobbles makes the scrollbar jump under the
55
+ * pointer.
56
+ * - The window always covers the visible band; overscan only ever adds rows.
57
+ * - Degenerate geometry (no rows, unmeasured viewport, non-positive row height)
58
+ * yields an empty window rather than a guess. An unmeasured viewport is the
59
+ * normal state during first paint and under jsdom, and rendering *everything*
60
+ * there would defeat the purpose at exactly the moment it matters most.
61
+ */
62
+ export declare const resolveRowWindow: (metrics: WindowMetrics) => RowWindow;
63
+ /**
64
+ * Whether a scroll moved far enough to need a re-render.
65
+ *
66
+ * Scroll fires continuously; re-rendering on every event would rebuild the row
67
+ * markup dozens of times per second to show the same rows. Comparing the
68
+ * resolved window is cheaper and exact — the render is needed precisely when
69
+ * the slice changes.
70
+ */
71
+ export declare const sameRowWindow: (a: RowWindow, b: RowWindow) => boolean;