@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.
- package/dist/components/feedback/alert.js +2 -22
- package/dist/components/feedback/nudge.js +0 -1
- package/dist/components/feedback/path.d.ts +84 -0
- package/dist/components/feedback/path.js +455 -0
- package/dist/components/feedback/skeleton.d.ts +70 -0
- package/dist/components/feedback/skeleton.js +231 -1
- package/dist/components/feedback/toast.d.ts +37 -0
- package/dist/components/feedback/toast.js +182 -17
- package/dist/components/feedback/tone.d.ts +16 -0
- package/dist/components/feedback/tone.js +31 -0
- package/dist/components/layout/grid.d.ts +41 -0
- package/dist/components/layout/grid.js +152 -0
- package/dist/element-maps.d.ts +7 -3
- package/dist/entries/comment-thread.d.ts +1 -0
- package/dist/entries/comment-thread.js +1 -0
- package/dist/entries/grid.d.ts +1 -0
- package/dist/entries/grid.js +1 -0
- package/dist/entries/path.d.ts +1 -0
- package/dist/entries/path.js +1 -0
- package/dist/foundations/layout/grid.d.ts +70 -0
- package/dist/foundations/layout/grid.js +84 -0
- package/dist/foundations/layout/index.d.ts +1 -0
- package/dist/foundations/layout/index.js +3 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +6 -1
- package/dist/patterns/audit/audit-log.js +0 -1
- package/dist/patterns/comments/comment-thread.d.ts +79 -0
- package/dist/patterns/comments/comment-thread.js +561 -0
- package/dist/patterns/comments/index.d.ts +2 -0
- package/dist/patterns/comments/index.js +5 -0
- package/dist/patterns/comments/types.d.ts +39 -0
- package/dist/patterns/comments/types.js +8 -0
- package/dist/patterns/lineage/lineage-graph.d.ts +7 -0
- package/dist/patterns/lineage/lineage-graph.js +67 -6
- package/dist/patterns/lineage/provenance-strip.js +106 -15
- package/dist/patterns/notifications/notification-inbox.js +2 -18
- package/dist/patterns/preview/annotation-thread.d.ts +67 -48
- package/dist/patterns/preview/annotation-thread.js +96 -440
- package/dist/patterns/timeline/timeline.js +44 -12
- package/dist/patterns/versions/graph-arrows.d.ts +22 -0
- package/dist/patterns/versions/graph-arrows.js +13 -0
- package/dist/patterns/versions/index.d.ts +1 -0
- package/dist/patterns/versions/index.js +1 -0
- package/dist/patterns/versions/version-graph.d.ts +7 -0
- package/dist/patterns/versions/version-graph.js +54 -6
- package/package.json +1 -1
- package/dist/components/feedback/stage-path.d.ts +0 -44
- package/dist/components/feedback/stage-path.js +0 -235
- package/dist/entries/stage-path.d.ts +0 -1
- 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
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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:
|
|
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", "
|
|
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
|
-
|
|
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="
|
|
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"
|
|
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
|
-
|
|
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();
|
package/dist/element-maps.d.ts
CHANGED
|
@@ -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 {
|
|
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-
|
|
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-
|
|
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";
|