@unofficialbox/box-open-elements 0.10.0 → 0.12.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 (50) hide show
  1. package/dist/components/feedback/alert.js +2 -22
  2. package/dist/components/feedback/nudge.js +0 -1
  3. package/dist/components/feedback/path.d.ts +84 -0
  4. package/dist/components/feedback/path.js +455 -0
  5. package/dist/components/feedback/skeleton.d.ts +70 -0
  6. package/dist/components/feedback/skeleton.js +231 -1
  7. package/dist/components/feedback/toast.d.ts +37 -0
  8. package/dist/components/feedback/toast.js +182 -17
  9. package/dist/components/feedback/tone.d.ts +16 -0
  10. package/dist/components/feedback/tone.js +31 -0
  11. package/dist/components/layout/grid.d.ts +41 -0
  12. package/dist/components/layout/grid.js +152 -0
  13. package/dist/element-maps.d.ts +7 -3
  14. package/dist/entries/comment-thread.d.ts +1 -0
  15. package/dist/entries/comment-thread.js +1 -0
  16. package/dist/entries/grid.d.ts +1 -0
  17. package/dist/entries/grid.js +1 -0
  18. package/dist/entries/path.d.ts +1 -0
  19. package/dist/entries/path.js +1 -0
  20. package/dist/foundations/layout/grid.d.ts +70 -0
  21. package/dist/foundations/layout/grid.js +84 -0
  22. package/dist/foundations/layout/index.d.ts +1 -0
  23. package/dist/foundations/layout/index.js +3 -0
  24. package/dist/index.d.ts +5 -1
  25. package/dist/index.js +6 -1
  26. package/dist/patterns/audit/audit-log.js +0 -1
  27. package/dist/patterns/comments/comment-thread.d.ts +79 -0
  28. package/dist/patterns/comments/comment-thread.js +561 -0
  29. package/dist/patterns/comments/index.d.ts +2 -0
  30. package/dist/patterns/comments/index.js +5 -0
  31. package/dist/patterns/comments/types.d.ts +39 -0
  32. package/dist/patterns/comments/types.js +8 -0
  33. package/dist/patterns/lineage/lineage-graph.d.ts +7 -0
  34. package/dist/patterns/lineage/lineage-graph.js +67 -6
  35. package/dist/patterns/lineage/provenance-strip.js +106 -15
  36. package/dist/patterns/notifications/notification-inbox.js +2 -18
  37. package/dist/patterns/preview/annotation-thread.d.ts +67 -48
  38. package/dist/patterns/preview/annotation-thread.js +96 -440
  39. package/dist/patterns/timeline/timeline.js +44 -12
  40. package/dist/patterns/versions/graph-arrows.d.ts +22 -0
  41. package/dist/patterns/versions/graph-arrows.js +13 -0
  42. package/dist/patterns/versions/index.d.ts +1 -0
  43. package/dist/patterns/versions/index.js +1 -0
  44. package/dist/patterns/versions/version-graph.d.ts +7 -0
  45. package/dist/patterns/versions/version-graph.js +54 -6
  46. package/package.json +1 -1
  47. package/dist/components/feedback/stage-path.d.ts +0 -44
  48. package/dist/components/feedback/stage-path.js +0 -235
  49. package/dist/entries/stage-path.d.ts +0 -1
  50. package/dist/entries/stage-path.js +0 -1
@@ -1,8 +1,29 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
+ import { toneAccessibleLabel } from "./tone.js";
2
3
  import { boeRadius, boeSpace } from "../../foundations/geometry/index.js";
3
4
  import { boeFocusVisibleStyles } from "../../foundations/tokens/index.js";
4
5
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
5
6
  const DEFAULT_TAG_NAME = "box-toast";
7
+ const TOAST_MODES = new Set(["dismissible", "sticky"]);
8
+ /** Narrow an author-supplied mode, falling back to `dismissible`. */
9
+ export const resolveToastMode = (value) => TOAST_MODES.has(value) ? value : "dismissible";
10
+ /**
11
+ * Status glyphs, one per tone.
12
+ *
13
+ * A toast is read at a glance and often out of the corner of an eye, so the
14
+ * shape has to carry the meaning before the colour does — a round tick and a
15
+ * warning triangle are distinguishable to a reader who cannot separate green
16
+ * from amber. Literal markup, never author input, so innerHTML is safe here.
17
+ */
18
+ const TONE_ICONS = {
19
+ info: `<svg viewBox="0 0 20 20" fill="currentColor" focusable="false"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 3.4a1.15 1.15 0 110 2.3 1.15 1.15 0 010-2.3zM11.1 15H8.9V9.3h2.2V15z"/></svg>`,
20
+ success: `<svg viewBox="0 0 20 20" fill="currentColor" focusable="false"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm4.06 5.86-5 5.2a1 1 0 01-1.44 0L5.94 11.3A1 1 0 117.38 9.9l1.04 1.08 4.28-4.52a1 1 0 011.36 1.38z"/></svg>`,
21
+ warning: `<svg viewBox="0 0 20 20" fill="currentColor" focusable="false"><path d="M9.13 2.6 1.4 15.9a1 1 0 00.87 1.5h15.46a1 1 0 00.87-1.5L10.87 2.6a1 1 0 00-1.74 0zM11.1 15.4H8.9v-2.2h2.2v2.2zm0-3.4H8.9V7.6h2.2V12z"/></svg>`,
22
+ error: `<svg viewBox="0 0 20 20" fill="currentColor" focusable="false"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 2c1.29 0 2.48.41 3.46 1.1L5.1 13.46A6 6 0 0110 4zm0 12c-1.29 0-2.48-.41-3.46-1.1l8.36-8.36A6 6 0 0110 16z"/></svg>`,
23
+ };
24
+ /** The tone glyph, falling back to the info mark for an unknown tone. */
25
+ const toneIcon = (tone) => TONE_ICONS[tone] ?? TONE_ICONS.info;
26
+ const DISMISS_ICON = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" focusable="false"><path d="M5.5 5.5l9 9M14.5 5.5l-9 9"/></svg>`;
6
27
  const toastStyles = `
7
28
  :host {
8
29
  display: block;
@@ -16,47 +37,120 @@ const toastStyles = `
16
37
  display: none;
17
38
  }
18
39
 
40
+ /* Fill, text colour and shadow track box-ui-elements' .notification and are
41
+ pinned by the colour conformance manifest — they are deliberately not
42
+ derived from the tone accent. The --toast-accent property exists only to
43
+ colour the status glyph, which upstream has no equivalent for, so adding it
44
+ costs no conformance.
45
+
46
+ There is no border. Upstream .notification has one, and box-open-elements
47
+ matched it until the outline was judged too heavy; the claim that pinned it
48
+ was retired rather than left describing something no longer painted. Tone
49
+ survives that in three places — the tinted fill, the full-strength glyph,
50
+ and the visually-hidden tone label — so it was never carried by the border
51
+ alone. */
19
52
  [part="toast"] {
53
+ --toast-accent: var(--boe-token-text-text, #222222);
54
+
20
55
  display: inline-flex;
21
- align-items: center;
56
+ /* Top, not centre: with a heading above the message the glyph belongs on
57
+ the heading's line, and centring floats it into the gap between them. */
58
+ align-items: flex-start;
22
59
  gap: ${boeSpace[3]};
23
60
  min-height: 48px;
24
61
  max-inline-size: min(100%, 572px);
25
62
  padding: 10px 10px 10px 20px;
26
- border: 2px solid var(--boe-token-text-text, #222222);
27
63
  border-radius: ${boeRadius.large};
28
64
  background: var(--boe-token-surface-surface-secondary, #f4f4f4);
29
65
  color: var(--boe-token-text-text, #222222);
30
66
  box-shadow: 0 2px 6px rgb(0 0 0 / 15%);
31
67
  }
32
68
 
69
+ /* The neutral toast keeps upstream's grey fill, so the glyph is the only
70
+ place the "this is information" signal can live. */
71
+ [part="toast"][data-tone="info"] {
72
+ --toast-accent: var(--boe-token-surface-surface-brand, #0061d5);
73
+ }
74
+
33
75
  [part="toast"][data-tone="success"] {
76
+ --toast-accent: var(--boe-token-surface-status-surface-success, #26c281);
34
77
  background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 20%, #fff);
35
- border-color: var(--boe-token-surface-status-surface-success, #26c281);
36
78
  }
37
79
 
38
80
  [part="toast"][data-tone="error"] {
81
+ --toast-accent: var(--boe-token-surface-status-surface-error, #ed3757);
39
82
  background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 20%, #fff);
40
- border-color: var(--boe-token-surface-status-surface-error, #ed3757);
41
83
  }
42
84
 
43
85
  [part="toast"][data-tone="warning"],
44
86
  [part="toast"][data-tone="inprogress"] {
87
+ --toast-accent: var(--boe-token-surface-status-surface-inprogress, #f5b31b);
45
88
  background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 20%, #fff);
46
- border-color: var(--boe-token-surface-status-surface-inprogress, #f5b31b);
47
89
  }
48
90
 
49
- [part="message"] {
91
+ .sr-only {
92
+ position: absolute;
93
+ inline-size: 1px;
94
+ block-size: 1px;
95
+ padding: 0;
96
+ margin: -1px;
97
+ overflow: hidden;
98
+ clip-path: inset(50%);
99
+ white-space: nowrap;
100
+ border: 0;
101
+ }
102
+
103
+ [part="icon"] {
104
+ flex: 0 0 auto;
105
+ inline-size: 20px;
106
+ block-size: 20px;
107
+ /* The accent at full strength: the glyph is the one element that should
108
+ read as the status colour rather than a darkened version of it. */
109
+ color: var(--toast-accent);
110
+ }
111
+
112
+ [part="icon"] svg {
113
+ display: block;
114
+ inline-size: 100%;
115
+ block-size: 100%;
116
+ }
117
+
118
+ [part="content"] {
50
119
  flex: 1 1 auto;
51
120
  min-inline-size: 0;
52
- padding-inline-end: ${boeSpace[3]};
121
+ padding-inline-end: ${boeSpace[2]};
122
+ }
123
+
124
+ [part="heading"] {
125
+ display: block;
53
126
  font-size: 15px;
54
127
  font-weight: 700;
128
+ line-height: 1.3;
129
+ overflow-wrap: anywhere;
130
+ }
131
+
132
+ /* Optional: with no heading the message stands alone, and an empty element
133
+ would still contribute its line box. */
134
+ [part="heading"]:empty {
135
+ display: none;
136
+ }
137
+
138
+ [part="message"] {
139
+ display: block;
140
+ font-size: 14px;
141
+ font-weight: 400;
55
142
  line-height: 1.35;
56
143
  /* Long unbroken tokens (URLs, filenames) wrap instead of overflowing. */
57
144
  overflow-wrap: anywhere;
58
145
  }
59
146
 
147
+ /* Without a heading the message is the toast's only line, so it carries the
148
+ weight the heading would have had. */
149
+ [part="heading"]:empty + [part="message"] {
150
+ font-size: 15px;
151
+ font-weight: 700;
152
+ }
153
+
60
154
  /* Optional action affordance (e.g. "Undo") before the dismiss button.
61
155
  Hidden — taking no flex gap — until content is assigned. */
62
156
  [part="action"] {
@@ -77,21 +171,33 @@ const toastStyles = `
77
171
  cursor: pointer;
78
172
  }
79
173
 
174
+ /* An icon button rather than a "Dismiss" label: the toast is chrome over the
175
+ reader's work, and a word-width button competes with the message it sits
176
+ beside. The accessible name is on the button, so nothing is lost. */
80
177
  [part="dismiss"] {
81
178
  appearance: none;
82
179
  flex: 0 0 auto;
83
- border: 1px solid var(--boe-token-text-text, #222222);
180
+ display: inline-flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ inline-size: 24px;
184
+ block-size: 24px;
185
+ padding: 4px;
186
+ border: 0;
84
187
  border-radius: ${boeRadius.med};
85
188
  background: transparent;
86
- color: var(--boe-token-text-text, #222222);
189
+ color: inherit;
87
190
  font: inherit;
88
- font-size: 13px;
89
- font-weight: 700;
90
- padding: 7px 13px;
91
191
  cursor: pointer;
92
192
  transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
93
193
  }
94
194
 
195
+ [part="dismiss"] svg {
196
+ display: block;
197
+ inline-size: 100%;
198
+ block-size: 100%;
199
+ }
200
+
95
201
  [part="dismiss"]:hover:not(:disabled) {
96
202
  background: rgb(0 0 0 / 6%);
97
203
  }
@@ -111,12 +217,16 @@ const toastStyles = `
111
217
  export class Toast extends BaseElement {
112
218
  static tagName = DEFAULT_TAG_NAME;
113
219
  static get observedAttributes() {
114
- return ["message", "open", "tone", "duration"];
220
+ return ["duration", "heading", "message", "mode", "open", "tone"];
115
221
  }
116
222
  openValue = false;
117
223
  timeoutId = null;
118
224
  toastEl;
225
+ iconEl;
226
+ headingEl;
227
+ toneLabelEl;
119
228
  messageEl;
229
+ renderedTone = null;
120
230
  actionSlot;
121
231
  dismissEl;
122
232
  /** Auto-dismiss delay in ms for the declarative `open` path. 0 = sticky. */
@@ -161,7 +271,8 @@ export class Toast extends BaseElement {
161
271
  clearTimeout(this.timeoutId);
162
272
  this.timeoutId = null;
163
273
  }
164
- if (duration > 0) {
274
+ // Sticky wins over any duration, including one passed to show().
275
+ if (duration > 0 && this.mode !== "sticky") {
165
276
  this.timeoutId = setTimeout(() => this.hide(), duration);
166
277
  }
167
278
  }
@@ -171,6 +282,41 @@ export class Toast extends BaseElement {
171
282
  set message(value) {
172
283
  this.setAttribute("message", value);
173
284
  }
285
+ /**
286
+ * Optional bold line above the message — "Upload failed" over "3 of 12 files
287
+ * could not be read". Named to match `box-alert`, which established the
288
+ * heading-plus-message shape in this family. Salesforce calls it `label`.
289
+ */
290
+ get heading() {
291
+ return this.getAttribute("heading") ?? "";
292
+ }
293
+ set heading(value) {
294
+ this.setAttribute("heading", value);
295
+ }
296
+ /**
297
+ * @deprecated Every toast is borderless now, so this does nothing.
298
+ *
299
+ * Kept because it shipped in 0.11.0 and removing it would break hosts that
300
+ * set it for no benefit — what they asked for is what they already get. The
301
+ * attribute is still reflected so those hosts read back what they wrote.
302
+ */
303
+ get borderless() {
304
+ return this.hasAttribute("borderless");
305
+ }
306
+ set borderless(value) {
307
+ this.toggleAttribute("borderless", value);
308
+ }
309
+ /**
310
+ * Whether the toast clears itself. `sticky` overrides any `duration`: a
311
+ * caller that has said "this one stays" should not have it taken away by a
312
+ * duration set elsewhere.
313
+ */
314
+ get mode() {
315
+ return resolveToastMode(this.getAttribute("mode"));
316
+ }
317
+ set mode(value) {
318
+ this.setAttribute("mode", value);
319
+ }
174
320
  get tone() {
175
321
  return this.getAttribute("tone") ?? "info";
176
322
  }
@@ -211,12 +357,20 @@ export class Toast extends BaseElement {
211
357
  this.shadowRoot.innerHTML = `
212
358
  <style>${toastStyles}</style>
213
359
  <div part="toast" role="status" aria-live="polite">
214
- <span part="message"></span>
360
+ <span part="icon" aria-hidden="true"></span>
361
+ <div part="content">
362
+ <span part="tone-label" class="sr-only"></span>
363
+ <span part="heading"></span>
364
+ <span part="message"></span>
365
+ </div>
215
366
  <slot name="action" part="action"></slot>
216
- <button type="button" part="dismiss">Dismiss</button>
367
+ <button type="button" part="dismiss" aria-label="Dismiss">${DISMISS_ICON}</button>
217
368
  </div>
218
369
  `;
219
370
  this.toastEl = this.shadowRoot.querySelector('[part="toast"]');
371
+ this.iconEl = this.shadowRoot.querySelector('[part="icon"]');
372
+ this.headingEl = this.shadowRoot.querySelector('[part="heading"]');
373
+ this.toneLabelEl = this.shadowRoot.querySelector('[part="tone-label"]');
220
374
  this.messageEl = this.shadowRoot.querySelector('[part="message"]');
221
375
  this.actionSlot = this.shadowRoot.querySelector('slot[name="action"]');
222
376
  this.dismissEl = this.shadowRoot.querySelector('[part="dismiss"]');
@@ -240,7 +394,18 @@ export class Toast extends BaseElement {
240
394
  if (!visible) {
241
395
  return;
242
396
  }
243
- this.toastEl.dataset.tone = this.tone;
397
+ const tone = this.tone;
398
+ this.toastEl.dataset.tone = tone;
399
+ // Only when it actually changes: this re-parses SVG markup, and update()
400
+ // runs on every attribute write.
401
+ if (tone !== this.renderedTone) {
402
+ this.iconEl.innerHTML = toneIcon(tone);
403
+ this.renderedTone = tone;
404
+ }
405
+ // The glyph is aria-hidden and the fill is colour, so without this the
406
+ // tone reaches a screen reader not at all.
407
+ this.toneLabelEl.textContent = toneAccessibleLabel(tone);
408
+ this.headingEl.textContent = this.heading;
244
409
  this.messageEl.textContent = this.message;
245
410
  }
246
411
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The status vocabulary shared by the feedback surfaces.
3
+ *
4
+ * `box-alert` and `box-toast` both state their tone in words for screen
5
+ * readers, and both must say the *same* words: a reader who hears "In progress"
6
+ * from an alert should not hear "Inprogress" from a toast reporting the same
7
+ * thing. One definition is what keeps them in step.
8
+ */
9
+ /**
10
+ * The tone as a word, for assistive technology.
11
+ *
12
+ * Tone is otherwise carried by fill and glyph, neither of which reaches a
13
+ * screen reader. An unrecognised tone is title-cased rather than dropped —
14
+ * a host that invents a tone should still get something spoken.
15
+ */
16
+ export declare const toneAccessibleLabel: (tone: string) => string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The status vocabulary shared by the feedback surfaces.
3
+ *
4
+ * `box-alert` and `box-toast` both state their tone in words for screen
5
+ * readers, and both must say the *same* words: a reader who hears "In progress"
6
+ * from an alert should not hear "Inprogress" from a toast reporting the same
7
+ * thing. One definition is what keeps them in step.
8
+ */
9
+ /**
10
+ * The tone as a word, for assistive technology.
11
+ *
12
+ * Tone is otherwise carried by fill and glyph, neither of which reaches a
13
+ * screen reader. An unrecognised tone is title-cased rather than dropped —
14
+ * a host that invents a tone should still get something spoken.
15
+ */
16
+ export const toneAccessibleLabel = (tone) => {
17
+ switch (tone) {
18
+ case "success":
19
+ return "Success";
20
+ case "error":
21
+ return "Error";
22
+ case "warning":
23
+ return "Warning";
24
+ case "inprogress":
25
+ return "In progress";
26
+ case "info":
27
+ return "Info";
28
+ default:
29
+ return tone.charAt(0).toUpperCase() + tone.slice(1);
30
+ }
31
+ };
@@ -0,0 +1,41 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ /**
3
+ * Adobe Spectrum's responsive grid, for laying out arbitrary content.
4
+ *
5
+ * Twelve columns by default, with gutters stepping 16 → 24 → 32 → 40 → 48px
6
+ * across Spectrum's breakpoints. Children declare their own placement:
7
+ *
8
+ * ```html
9
+ * <box-grid columns="12">
10
+ * <article data-span="8">Main</article>
11
+ * <aside data-span="4">Sidebar</aside>
12
+ * <footer data-span="6" data-offset="3">Centred</footer>
13
+ * </box-grid>
14
+ * ```
15
+ *
16
+ * Placement lives on the children rather than in a payload on the host,
17
+ * because the children are arbitrary content the author already writes — an
18
+ * `items` array would have to address them positionally and would silently
19
+ * mis-place everything the moment one was inserted.
20
+ *
21
+ * `data-offset` sets an explicit `grid-column-start`. That is the author's
22
+ * stated intent here, unlike `box-skeleton`, which generates its own regions
23
+ * and uses spacers so auto-placement is not disturbed.
24
+ */
25
+ export declare class Grid extends BaseElement {
26
+ static readonly tagName: string;
27
+ static get observedAttributes(): string[];
28
+ private gridEl;
29
+ /** Columns in the grid. Defaults to Spectrum's twelve. */
30
+ get columns(): number;
31
+ set columns(value: number);
32
+ /**
33
+ * Height of one row. Unset by default: a layout grid's rows should be as
34
+ * tall as their content, and pinning them would crop it.
35
+ */
36
+ get rowHeight(): string;
37
+ set rowHeight(value: string);
38
+ protected renderTemplate(): void;
39
+ protected setupListeners(): void;
40
+ protected update(): void;
41
+ }
@@ -0,0 +1,152 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ import { BOE_GRID_COLUMNS, BOE_GRID_GUTTER_PROPERTY, boeGridGutterStyles, resolveGridCount, } from "../../foundations/layout/index.js";
3
+ const DEFAULT_TAG_NAME = "box-grid";
4
+ /**
5
+ * How far a child's placement attributes are honoured.
6
+ *
7
+ * Placement is expressed in CSS rules generated per value, not computed per
8
+ * element, so the ceiling is a real limit rather than a guess. Twenty-four
9
+ * covers a twelve-column grid used at double density without emitting rules
10
+ * nobody will match.
11
+ */
12
+ const MAX_TRACKS = 24;
13
+ /**
14
+ * Placement rules, one per value.
15
+ *
16
+ * A slotted child cannot be given a computed `grid-column` without writing to
17
+ * the light DOM, which is the host's tree, not ours. Generating a rule per
18
+ * value keeps placement declarative and leaves the author's markup untouched.
19
+ *
20
+ * `grid-column-start` and `grid-column-end` are set by separate rules on
21
+ * purpose: they compose, so offset and span combine without needing a rule for
22
+ * every offset × span pair.
23
+ */
24
+ const placementRules = () => {
25
+ const rules = [];
26
+ for (let value = 1; value <= MAX_TRACKS; value += 1) {
27
+ rules.push(` ::slotted([data-span="${value}"]) { grid-column-end: span ${value}; }`);
28
+ rules.push(` ::slotted([data-row-span="${value}"]) { grid-row: span ${value}; }`);
29
+ }
30
+ for (let value = 1; value < MAX_TRACKS; value += 1) {
31
+ // An offset of N starts the region at column N + 1.
32
+ rules.push(` ::slotted([data-offset="${value}"]) { grid-column-start: ${value + 1}; }`);
33
+ }
34
+ return rules.join("\n");
35
+ };
36
+ const elementStyles = `
37
+ :host {
38
+ display: block;
39
+ color: inherit;
40
+ font: inherit;
41
+ }
42
+
43
+ :host([hidden]) {
44
+ display: none;
45
+ }
46
+
47
+ ${boeGridGutterStyles(":host")}
48
+
49
+ [part="grid"] {
50
+ display: grid;
51
+ grid-template-columns: repeat(var(--boe-grid-columns, ${BOE_GRID_COLUMNS}), minmax(0, 1fr));
52
+ gap: var(${BOE_GRID_GUTTER_PROPERTY});
53
+ }
54
+
55
+ /* Rows are only constrained when the author asks for them; otherwise each
56
+ row is as tall as its content, which is what a layout grid should do.
57
+ Spectrum's layout regions have no intrinsic height either — at minimum
58
+ they are as tall as the components inside them. */
59
+ :host([row-height]) [part="grid"] {
60
+ grid-auto-rows: var(--boe-grid-row-height);
61
+ }
62
+
63
+ /* Without this the slot itself is the grid item and every child lands in one
64
+ cell. display: contents dissolves it, so the assigned children become the
65
+ grid's own items. */
66
+ slot {
67
+ display: contents;
68
+ }
69
+
70
+ /* A region with no placement attributes occupies one column, so a bare list
71
+ of children reads as an even row. */
72
+ ::slotted(*) {
73
+ grid-column-end: span 1;
74
+ }
75
+
76
+ ${placementRules()}
77
+ `;
78
+ /**
79
+ * Adobe Spectrum's responsive grid, for laying out arbitrary content.
80
+ *
81
+ * Twelve columns by default, with gutters stepping 16 → 24 → 32 → 40 → 48px
82
+ * across Spectrum's breakpoints. Children declare their own placement:
83
+ *
84
+ * ```html
85
+ * <box-grid columns="12">
86
+ * <article data-span="8">Main</article>
87
+ * <aside data-span="4">Sidebar</aside>
88
+ * <footer data-span="6" data-offset="3">Centred</footer>
89
+ * </box-grid>
90
+ * ```
91
+ *
92
+ * Placement lives on the children rather than in a payload on the host,
93
+ * because the children are arbitrary content the author already writes — an
94
+ * `items` array would have to address them positionally and would silently
95
+ * mis-place everything the moment one was inserted.
96
+ *
97
+ * `data-offset` sets an explicit `grid-column-start`. That is the author's
98
+ * stated intent here, unlike `box-skeleton`, which generates its own regions
99
+ * and uses spacers so auto-placement is not disturbed.
100
+ */
101
+ export class Grid extends BaseElement {
102
+ static tagName = DEFAULT_TAG_NAME;
103
+ static get observedAttributes() {
104
+ return ["columns", "row-height"];
105
+ }
106
+ gridEl;
107
+ /** Columns in the grid. Defaults to Spectrum's twelve. */
108
+ get columns() {
109
+ return resolveGridCount(this.getAttribute("columns"), BOE_GRID_COLUMNS);
110
+ }
111
+ set columns(value) {
112
+ this.setAttribute("columns", String(value));
113
+ }
114
+ /**
115
+ * Height of one row. Unset by default: a layout grid's rows should be as
116
+ * tall as their content, and pinning them would crop it.
117
+ */
118
+ get rowHeight() {
119
+ return this.getAttribute("row-height") ?? "";
120
+ }
121
+ set rowHeight(value) {
122
+ if (value) {
123
+ this.setAttribute("row-height", value);
124
+ return;
125
+ }
126
+ this.removeAttribute("row-height");
127
+ }
128
+ renderTemplate() {
129
+ if (!this.shadowRoot) {
130
+ return;
131
+ }
132
+ this.shadowRoot.innerHTML = `<style>${elementStyles}</style><div part="grid"><slot></slot></div>`;
133
+ this.gridEl = this.shadowRoot.querySelector('[part="grid"]');
134
+ }
135
+ setupListeners() {
136
+ // Layout only: placement is declared by the children and applied in CSS.
137
+ }
138
+ update() {
139
+ if (!this.gridEl) {
140
+ return;
141
+ }
142
+ this.gridEl.style.setProperty("--boe-grid-columns", String(this.columns));
143
+ // Via the CSSOM rather than interpolated into markup. Note that custom
144
+ // properties are *not* validated the way a standard property like `width`
145
+ // is — they accept almost any token sequence — so this does not sanitise
146
+ // the value. What makes it safe is that the value never reaches an HTML
147
+ // string: a nonsensical one makes `grid-auto-rows` invalid at computed-value
148
+ // time, which falls back to `auto`, and rows simply size to their content.
149
+ this.gridEl.style.setProperty("--boe-grid-row-height", this.rowHeight);
150
+ }
151
+ }
152
+ Grid.register();
@@ -36,6 +36,7 @@ import type { CollaboratorAvatars } from "./patterns/share/collaborator-avatars.
36
36
  import type { ColorPicker } from "./components/forms/color-picker.js";
37
37
  import type { Combobox } from "./components/forms/combobox.js";
38
38
  import type { CommandPalette } from "./components/overlays/command-palette.js";
39
+ import type { CommentThread } from "./patterns/comments/comment-thread.js";
39
40
  import type { CompareView } from "./patterns/diff/compare-view.js";
40
41
  import type { ContactDatalistItem } from "./components/identity/contact-datalist-item.js";
41
42
  import type { ContentExplorer } from "./patterns/content-explorer/content-explorer.js";
@@ -48,6 +49,7 @@ import type { DateField } from "./components/forms/date-field.js";
48
49
  import type { Dialog } from "./components/overlays/dialog.js";
49
50
  import type { DiffViewer } from "./patterns/diff/diff-viewer.js";
50
51
  import type { Divider } from "./components/layout/divider.js";
52
+ import type { Grid } from "./components/layout/grid.js";
51
53
  import type { DonutChart } from "./patterns/insights/donut-chart.js";
52
54
  import type { DraggableList } from "./components/collections/draggable-list.js";
53
55
  import type { Drawer } from "./components/overlays/drawer.js";
@@ -124,7 +126,7 @@ import type { Slider } from "./components/forms/slider.js";
124
126
  import type { SpinButton } from "./components/forms/spin-button.js";
125
127
  import type { Spinner } from "./components/feedback/spinner.js";
126
128
  import type { SplitView } from "./components/layout/split-view.js";
127
- import type { StagePath } from "./components/feedback/stage-path.js";
129
+ import type { Path } from "./components/feedback/path.js";
128
130
  import type { Switch } from "./components/forms/switch.js";
129
131
  import type { Table } from "./components/collections/table.js";
130
132
  import type { Tabs } from "./components/navigation/tabs.js";
@@ -177,6 +179,7 @@ declare global {
177
179
  "box-color-picker": ColorPicker;
178
180
  "box-combobox": Combobox;
179
181
  "box-command-palette": CommandPalette;
182
+ "box-comment-thread": CommentThread;
180
183
  "box-compare-view": CompareView;
181
184
  "box-contact-datalist-item": ContactDatalistItem;
182
185
  "box-content-explorer": ContentExplorer;
@@ -189,6 +192,7 @@ declare global {
189
192
  "box-dialog": Dialog;
190
193
  "box-diff-viewer": DiffViewer;
191
194
  "box-divider": Divider;
195
+ "box-grid": Grid;
192
196
  "box-donut-chart": DonutChart;
193
197
  "box-draggable-list": DraggableList;
194
198
  "box-drawer": Drawer;
@@ -265,7 +269,7 @@ declare global {
265
269
  "box-spin-button": SpinButton;
266
270
  "box-spinner": Spinner;
267
271
  "box-split-view": SplitView;
268
- "box-stage-path": StagePath;
272
+ "box-path": Path;
269
273
  "box-switch": Switch;
270
274
  "box-table": Table;
271
275
  "box-tabs": Tabs;
@@ -289,4 +293,4 @@ declare global {
289
293
  }
290
294
  }
291
295
  /** Every element tag the library registers, for host-side narrowing. */
292
- export type BoxElementTagName = "box-access-stats" | "box-accordion" | "box-activity-density" | "box-agent-chat" | "box-alert" | "box-annotation-inspector" | "box-annotation-thread" | "box-annotation-toolbar" | "box-app-shell" | "box-audit-log" | "box-avatar" | "box-badge" | "box-badgeable" | "box-bar-chart" | "box-breadcrumb" | "box-bulk-action-bar" | "box-button" | "box-button-group" | "box-calendar" | "box-card" | "box-carousel" | "box-category-selector" | "box-chart-panel" | "box-checkbox" | "box-checkbox-group" | "box-chip" | "box-collaborator-avatars" | "box-color-picker" | "box-combobox" | "box-command-palette" | "box-compare-view" | "box-contact-datalist-item" | "box-content-explorer" | "box-content-picker" | "box-content-sidebar" | "box-content-uploader" | "box-context-menu" | "box-datalist-item" | "box-date-field" | "box-dialog" | "box-diff-viewer" | "box-divider" | "box-donut-chart" | "box-draggable-list" | "box-drawer" | "box-drop-zone" | "box-dropdown" | "box-dual-listbox" | "box-due-badge" | "box-empty-state" | "box-error-mask" | "box-explorer-action-menu" | "box-explorer-breadcrumbs" | "box-explorer-items" | "box-explorer-list" | "box-explorer-table" | "box-explorer-toolbar" | "box-fieldset" | "box-file-request-builder" | "box-filter-bar" | "box-form-wizard" | "box-governance-panel" | "box-grid-view" | "box-guide-tooltip" | "box-help-text" | "box-icon-button" | "box-illustration" | "box-invite-collaborators-modal" | "box-item-details-panel" | "box-item-form" | "box-line-chart" | "box-lineage-graph" | "box-link-button" | "box-menu" | "box-menu-item" | "box-metadata-filter-builder" | "box-metadata-inspector" | "box-metric-card" | "box-multi-select" | "box-nav-sidebar" | "box-notification-bell" | "box-notification-inbox" | "box-nudge" | "box-number-input" | "box-pagination" | "box-permission-matrix" | "box-persona" | "box-pill-cloud" | "box-pill-selector-dropdown" | "box-popover" | "box-presence" | "box-preview-element" | "box-preview-header" | "box-progress-bar" | "box-progress-ring" | "box-progress-steps" | "box-provenance-strip" | "box-radio-group" | "box-range-slider" | "box-rating" | "box-review-queue-item" | "box-rich-text-input" | "box-run-trace" | "box-saved-view-picker" | "box-search-field" | "box-search-results-header" | "box-section" | "box-segmented-control" | "box-select" | "box-share-panel" | "box-shortcuts-overlay" | "box-sidebar-toggle-button" | "box-signature-ceremony" | "box-skeleton" | "box-slider" | "box-spin-button" | "box-spinner" | "box-split-view" | "box-stage-path" | "box-switch" | "box-table" | "box-tabs" | "box-tag-input" | "box-task-assignment-panel" | "box-text-area" | "box-text-field" | "box-thumbnail-card" | "box-time-field" | "box-timeline" | "box-toast" | "box-tooltip" | "box-tree" | "box-tree-grid" | "box-unified-share-modal" | "box-version-graph" | "box-version-list" | "box-wizard-summary" | "box-work-queue" | "box-workload-board";
296
+ export type BoxElementTagName = "box-access-stats" | "box-accordion" | "box-activity-density" | "box-agent-chat" | "box-alert" | "box-annotation-inspector" | "box-annotation-thread" | "box-annotation-toolbar" | "box-app-shell" | "box-audit-log" | "box-avatar" | "box-badge" | "box-badgeable" | "box-bar-chart" | "box-breadcrumb" | "box-bulk-action-bar" | "box-button" | "box-button-group" | "box-calendar" | "box-card" | "box-carousel" | "box-category-selector" | "box-chart-panel" | "box-checkbox" | "box-checkbox-group" | "box-chip" | "box-collaborator-avatars" | "box-color-picker" | "box-combobox" | "box-command-palette" | "box-comment-thread" | "box-compare-view" | "box-contact-datalist-item" | "box-content-explorer" | "box-content-picker" | "box-content-sidebar" | "box-content-uploader" | "box-context-menu" | "box-datalist-item" | "box-date-field" | "box-dialog" | "box-diff-viewer" | "box-divider" | "box-grid" | "box-donut-chart" | "box-draggable-list" | "box-drawer" | "box-drop-zone" | "box-dropdown" | "box-dual-listbox" | "box-due-badge" | "box-empty-state" | "box-error-mask" | "box-explorer-action-menu" | "box-explorer-breadcrumbs" | "box-explorer-items" | "box-explorer-list" | "box-explorer-table" | "box-explorer-toolbar" | "box-fieldset" | "box-file-request-builder" | "box-filter-bar" | "box-form-wizard" | "box-governance-panel" | "box-grid-view" | "box-guide-tooltip" | "box-help-text" | "box-icon-button" | "box-illustration" | "box-invite-collaborators-modal" | "box-item-details-panel" | "box-item-form" | "box-line-chart" | "box-lineage-graph" | "box-link-button" | "box-menu" | "box-menu-item" | "box-metadata-filter-builder" | "box-metadata-inspector" | "box-metric-card" | "box-multi-select" | "box-nav-sidebar" | "box-notification-bell" | "box-notification-inbox" | "box-nudge" | "box-number-input" | "box-pagination" | "box-permission-matrix" | "box-persona" | "box-pill-cloud" | "box-pill-selector-dropdown" | "box-popover" | "box-presence" | "box-preview-element" | "box-preview-header" | "box-progress-bar" | "box-progress-ring" | "box-progress-steps" | "box-provenance-strip" | "box-radio-group" | "box-range-slider" | "box-rating" | "box-review-queue-item" | "box-rich-text-input" | "box-run-trace" | "box-saved-view-picker" | "box-search-field" | "box-search-results-header" | "box-section" | "box-segmented-control" | "box-select" | "box-share-panel" | "box-shortcuts-overlay" | "box-sidebar-toggle-button" | "box-signature-ceremony" | "box-skeleton" | "box-slider" | "box-spin-button" | "box-spinner" | "box-split-view" | "box-path" | "box-switch" | "box-table" | "box-tabs" | "box-tag-input" | "box-task-assignment-panel" | "box-text-area" | "box-text-field" | "box-thumbnail-card" | "box-time-field" | "box-timeline" | "box-toast" | "box-tooltip" | "box-tree" | "box-tree-grid" | "box-unified-share-modal" | "box-version-graph" | "box-version-list" | "box-wizard-summary" | "box-work-queue" | "box-workload-board";
@@ -0,0 +1 @@
1
+ export * from "../patterns/comments/index.js";
@@ -0,0 +1 @@
1
+ export * from "../patterns/comments/index.js";
@@ -0,0 +1 @@
1
+ export * from "../components/layout/grid.js";
@@ -0,0 +1 @@
1
+ export * from "../components/layout/grid.js";
@@ -0,0 +1 @@
1
+ export * from "../components/feedback/path.js";
@@ -0,0 +1 @@
1
+ export * from "../components/feedback/path.js";