@unofficialbox/box-open-elements 0.9.0 → 0.11.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 +1 -16
- 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 +38 -0
- package/dist/components/feedback/toast.js +189 -13
- 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/components/overlays/command-palette.d.ts +1 -0
- package/dist/components/overlays/command-palette.js +42 -3
- package/dist/components/overlays/context-menu.js +11 -1
- package/dist/components/overlays/dialog.d.ts +1 -0
- package/dist/components/overlays/dialog.js +43 -3
- package/dist/components/overlays/drawer.d.ts +1 -3
- package/dist/components/overlays/drawer.js +4 -20
- package/dist/components/overlays/guide-tooltip.js +15 -1
- package/dist/components/overlays/popover.js +16 -1
- package/dist/components/overlays/shortcuts-overlay.d.ts +1 -0
- package/dist/components/overlays/shortcuts-overlay.js +40 -3
- package/dist/components/overlays/tooltip.js +15 -1
- package/dist/element-maps.d.ts +5 -3
- 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/foundations/overlay/index.d.ts +1 -0
- package/dist/foundations/overlay/index.js +1 -0
- package/dist/foundations/overlay/top-layer.d.ts +62 -0
- package/dist/foundations/overlay/top-layer.js +95 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -1
- package/dist/patterns/audit/audit-log.js +0 -1
- 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/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,24 +1,9 @@
|
|
|
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 { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
|
|
4
5
|
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
5
6
|
const DEFAULT_TAG_NAME = "box-alert";
|
|
6
|
-
const toneAccessibleLabel = (tone) => {
|
|
7
|
-
switch (tone) {
|
|
8
|
-
case "success":
|
|
9
|
-
return "Success";
|
|
10
|
-
case "error":
|
|
11
|
-
return "Error";
|
|
12
|
-
case "warning":
|
|
13
|
-
return "Warning";
|
|
14
|
-
case "inprogress":
|
|
15
|
-
return "In progress";
|
|
16
|
-
case "info":
|
|
17
|
-
return "Info";
|
|
18
|
-
default:
|
|
19
|
-
return tone.charAt(0).toUpperCase() + tone.slice(1);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
7
|
const alertStyles = `
|
|
23
8
|
:host {
|
|
24
9
|
display: block;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
/** One point in a record's lifecycle. */
|
|
3
|
+
export interface PathStage {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Optional detail shown under the label on the current stage. */
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
export type StageState = "complete" | "current" | "upcoming" | "error";
|
|
10
|
+
/**
|
|
11
|
+
* Shape of the path.
|
|
12
|
+
*
|
|
13
|
+
* `chevron` is the ribbon: every stage is a filled block carrying its own
|
|
14
|
+
* label, and the notches make the sequence read as directed. `base` is the
|
|
15
|
+
* marker rail — dots joined by a connector, with the label beneath — which is
|
|
16
|
+
* the shape Salesforce's progress indicator calls its base type, and the one
|
|
17
|
+
* that survives a long sequence without the labels colliding.
|
|
18
|
+
*/
|
|
19
|
+
export type PathVariant = "chevron" | "base";
|
|
20
|
+
/**
|
|
21
|
+
* Narrow an author-supplied variant, falling back to `chevron`.
|
|
22
|
+
*
|
|
23
|
+
* A typo should render the default shape rather than an unstyled row, which is
|
|
24
|
+
* what an unrecognised `data-variant` would produce — every variant rule is
|
|
25
|
+
* scoped to a known value.
|
|
26
|
+
*/
|
|
27
|
+
export declare const resolvePathVariant: (value: string | null | undefined) => PathVariant;
|
|
28
|
+
/** Attribute payloads are author input — validate every record. */
|
|
29
|
+
export declare const isPathStageRecord: (value: unknown) => value is PathStage;
|
|
30
|
+
/**
|
|
31
|
+
* State of each stage relative to the current one.
|
|
32
|
+
*
|
|
33
|
+
* `hasError` reports a failure at the stage the record is currently sitting
|
|
34
|
+
* at — the shape Salesforce's progress indicator uses, and the right one here:
|
|
35
|
+
* a lifecycle fails where it stopped, so an error is a property of the current
|
|
36
|
+
* position rather than of an arbitrary stage. Stages behind it stay complete;
|
|
37
|
+
* the work up to the failure did happen.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resolveStageStates: (stages: readonly PathStage[], currentId: string, hasError?: boolean) => StageState[];
|
|
40
|
+
/**
|
|
41
|
+
* Horizontal chevron lifecycle tracker for record headers — Draft → In
|
|
42
|
+
* Review → Approved → Executed.
|
|
43
|
+
*
|
|
44
|
+
* Distinct from `box-progress-steps`, which is a vertical setup rail for a
|
|
45
|
+
* task the reader is working through: this states where a *record* sits in
|
|
46
|
+
* its lifecycle, is read-only, and belongs in a header rather than a form.
|
|
47
|
+
*
|
|
48
|
+
* Rendered as an ordered list with the current stage marked `aria-current`,
|
|
49
|
+
* so the sequence and the position are both available without relying on the
|
|
50
|
+
* chevron geometry — which is decoration, and collapses on narrow viewports.
|
|
51
|
+
*/
|
|
52
|
+
export declare class Path extends BaseElement {
|
|
53
|
+
static readonly tagName: string;
|
|
54
|
+
static get observedAttributes(): string[];
|
|
55
|
+
private pathEl;
|
|
56
|
+
private stagesRaw;
|
|
57
|
+
private stagesCache;
|
|
58
|
+
get label(): string;
|
|
59
|
+
set label(value: string);
|
|
60
|
+
/**
|
|
61
|
+
* Shape of the path: `chevron` (default) draws a continuous directed ribbon;
|
|
62
|
+
* `rounded` draws separated pills. An unknown value falls back to `chevron`
|
|
63
|
+
* rather than rendering an unstyled row.
|
|
64
|
+
*/
|
|
65
|
+
get variant(): PathVariant;
|
|
66
|
+
set variant(value: PathVariant);
|
|
67
|
+
/** Id of the stage the record is at. An unknown id leaves every stage upcoming. */
|
|
68
|
+
get current(): string;
|
|
69
|
+
set current(value: string);
|
|
70
|
+
/**
|
|
71
|
+
* The record failed at the stage it is currently on — approval rejected, a
|
|
72
|
+
* signature declined. Earlier stages stay complete: the failure is where the
|
|
73
|
+
* lifecycle stopped, not a reset of everything behind it.
|
|
74
|
+
*/
|
|
75
|
+
get hasError(): boolean;
|
|
76
|
+
set hasError(value: boolean);
|
|
77
|
+
get stages(): PathStage[];
|
|
78
|
+
set stages(value: PathStage[]);
|
|
79
|
+
/** State of each stage, in order. */
|
|
80
|
+
get states(): StageState[];
|
|
81
|
+
protected renderTemplate(): void;
|
|
82
|
+
protected setupListeners(): void;
|
|
83
|
+
protected update(): void;
|
|
84
|
+
}
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
3
|
+
const DEFAULT_TAG_NAME = "box-path";
|
|
4
|
+
const escapeHtml = (value) => value
|
|
5
|
+
.replaceAll("&", "&")
|
|
6
|
+
.replaceAll("<", "<")
|
|
7
|
+
.replaceAll(">", ">")
|
|
8
|
+
.replaceAll('"', """)
|
|
9
|
+
.replaceAll("'", "'");
|
|
10
|
+
const PATH_VARIANTS = new Set(["chevron", "base"]);
|
|
11
|
+
/** Assistive text for each state; colour and glyph are not enough on their own. */
|
|
12
|
+
const STATE_LABELS = {
|
|
13
|
+
complete: "Completed",
|
|
14
|
+
current: "Current stage",
|
|
15
|
+
upcoming: "Not started",
|
|
16
|
+
error: "Error",
|
|
17
|
+
};
|
|
18
|
+
/** Non-colour glyph for the states that carry one. */
|
|
19
|
+
const STATE_MARKERS = {
|
|
20
|
+
complete: "✓",
|
|
21
|
+
error: "!",
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Narrow an author-supplied variant, falling back to `chevron`.
|
|
25
|
+
*
|
|
26
|
+
* A typo should render the default shape rather than an unstyled row, which is
|
|
27
|
+
* what an unrecognised `data-variant` would produce — every variant rule is
|
|
28
|
+
* scoped to a known value.
|
|
29
|
+
*/
|
|
30
|
+
export const resolvePathVariant = (value) => PATH_VARIANTS.has(value) ? value : "chevron";
|
|
31
|
+
/** Attribute payloads are author input — validate every record. */
|
|
32
|
+
export const isPathStageRecord = (value) => {
|
|
33
|
+
if (typeof value !== "object" || value === null) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const stage = value;
|
|
37
|
+
return (typeof stage.id === "string" &&
|
|
38
|
+
stage.id.length > 0 &&
|
|
39
|
+
typeof stage.label === "string" &&
|
|
40
|
+
stage.label.length > 0);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* State of each stage relative to the current one.
|
|
44
|
+
*
|
|
45
|
+
* `hasError` reports a failure at the stage the record is currently sitting
|
|
46
|
+
* at — the shape Salesforce's progress indicator uses, and the right one here:
|
|
47
|
+
* a lifecycle fails where it stopped, so an error is a property of the current
|
|
48
|
+
* position rather than of an arbitrary stage. Stages behind it stay complete;
|
|
49
|
+
* the work up to the failure did happen.
|
|
50
|
+
*/
|
|
51
|
+
export const resolveStageStates = (stages, currentId, hasError = false) => {
|
|
52
|
+
const index = stages.findIndex(stage => stage.id === currentId);
|
|
53
|
+
// An unknown current id means nothing is complete — better than silently
|
|
54
|
+
// marking the whole path done because the host sent a stale value.
|
|
55
|
+
return stages.map((_, position) => index === -1
|
|
56
|
+
? "upcoming"
|
|
57
|
+
: position < index
|
|
58
|
+
? "complete"
|
|
59
|
+
: position === index
|
|
60
|
+
? hasError
|
|
61
|
+
? "error"
|
|
62
|
+
: "current"
|
|
63
|
+
: "upcoming");
|
|
64
|
+
};
|
|
65
|
+
const elementStyles = `
|
|
66
|
+
:host {
|
|
67
|
+
display: block;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[part="path"] {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-wrap: wrap;
|
|
73
|
+
/* stretch, not start: every stage is the height of the tallest, so a
|
|
74
|
+
stage carrying a description does not stand proud of its
|
|
75
|
+
neighbours. The old rule let each stage size itself and the row
|
|
76
|
+
came out ragged. */
|
|
77
|
+
align-items: stretch;
|
|
78
|
+
margin: 0;
|
|
79
|
+
padding: 0;
|
|
80
|
+
list-style: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Sizing is taken from box-segmented-control, deliberately: both are a
|
|
84
|
+
horizontal row of equal-weight labels, and they should read at the
|
|
85
|
+
same density rather than each inventing a height. */
|
|
86
|
+
[part="stage"] {
|
|
87
|
+
position: relative;
|
|
88
|
+
flex: 1 1 0;
|
|
89
|
+
min-inline-size: 6rem;
|
|
90
|
+
display: flex;
|
|
91
|
+
/* A row, so a stage carrying a description stays one line tall.
|
|
92
|
+
Stacking it doubled the height of every stage in the row —
|
|
93
|
+
align-items: stretch means the tallest stage sets the rest — for a
|
|
94
|
+
detail that belongs beside the label, not under it. The base
|
|
95
|
+
variant, which has the vertical room, stacks instead. */
|
|
96
|
+
flex-direction: row;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
text-align: center;
|
|
100
|
+
gap: 0.4rem;
|
|
101
|
+
padding: 0.45em 1em;
|
|
102
|
+
font-size: 0.82rem;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
line-height: 1.2;
|
|
105
|
+
background: var(--boe-token-surface-surface-hover, #f4f4f4);
|
|
106
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
107
|
+
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[part="stage"][data-state="complete"] {
|
|
111
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 18%, transparent);
|
|
112
|
+
color: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 72%, black 28%);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[part="stage"][data-state="current"] {
|
|
116
|
+
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
117
|
+
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[part="stage"][data-state="error"] {
|
|
122
|
+
background: var(--boe-token-surface-status-surface-error, #ed3757);
|
|
123
|
+
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
124
|
+
font-weight: 700;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Assistive-only: the state is otherwise carried by fill and a glyph,
|
|
128
|
+
neither of which reaches a screen reader. aria-current covers the
|
|
129
|
+
current stage but has nothing to say about complete or error. */
|
|
130
|
+
[part="stage-state"] {
|
|
131
|
+
position: absolute;
|
|
132
|
+
inline-size: 1px;
|
|
133
|
+
block-size: 1px;
|
|
134
|
+
padding: 0;
|
|
135
|
+
margin: -1px;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
clip-path: inset(50%);
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
border: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Marker and label together. A row in the ribbon shapes, where the
|
|
143
|
+
check reads as a prefix to the label; a column in the base rail,
|
|
144
|
+
where the marker is the rail itself and the label hangs beneath it.
|
|
145
|
+
One structure, one axis to flip — no per-variant markup. */
|
|
146
|
+
[part="stage-heading"] {
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
[part="stage-label"] {
|
|
153
|
+
display: block;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
[part="stage-description"] {
|
|
157
|
+
display: block;
|
|
158
|
+
font-size: 0.72rem;
|
|
159
|
+
font-weight: 400;
|
|
160
|
+
opacity: 0.85;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* A chevron is too narrow to carry the description: at four stages in a
|
|
164
|
+
760px header each one gets ~190px, and "In Review · With Morgan Lee"
|
|
165
|
+
wrapped to a second line, which — with align-items: stretch — took
|
|
166
|
+
the whole row from 27.5px to 43.3px. Salesforce's path type shows
|
|
167
|
+
labels only for the same reason. The detail stays in the DOM and is
|
|
168
|
+
still reachable via ::part for a host that has the width for it.
|
|
169
|
+
The base rail, which stacks, shows it by default. */
|
|
170
|
+
[part="path"][data-variant="chevron"] [part="stage-description"] {
|
|
171
|
+
display: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
[part="stage-marker"] {
|
|
175
|
+
margin-inline-end: 0.3rem;
|
|
176
|
+
font-weight: 700;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Current and upcoming stages carry no glyph. In the ribbon shapes that
|
|
180
|
+
marker would only contribute stray margin; the base variant re-shows
|
|
181
|
+
it, because there the marker *is* the rail. */
|
|
182
|
+
[part="stage-marker"]:empty {
|
|
183
|
+
display: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ── variant: chevron (default) ──────────────────────────────────────
|
|
187
|
+
A notch cut from the following stage plus an arrow on this one, so
|
|
188
|
+
the path reads as a directed sequence rather than a row of pills.
|
|
189
|
+
Drawn with clip-path so no extra elements are needed. The inline
|
|
190
|
+
padding absorbs the arrow so centred text stays optically centred
|
|
191
|
+
rather than drifting into the notch. */
|
|
192
|
+
[part="path"][data-variant="chevron"] [part="stage"] {
|
|
193
|
+
padding-inline: 1.35em;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[part="path"][data-variant="chevron"] [part="stage"]:first-child {
|
|
197
|
+
border-start-start-radius: 999px;
|
|
198
|
+
border-end-start-radius: 999px;
|
|
199
|
+
padding-inline-start: 1em;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
[part="path"][data-variant="chevron"] [part="stage"]:last-child {
|
|
203
|
+
border-start-end-radius: 999px;
|
|
204
|
+
border-end-end-radius: 999px;
|
|
205
|
+
padding-inline-end: 1em;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
[part="path"][data-variant="chevron"] [part="stage"]:not(:last-child) {
|
|
209
|
+
clip-path: polygon(0 0, calc(100% - 0.7rem) 0, 100% 50%, calc(100% - 0.7rem) 100%, 0 100%, 0.7rem 50%);
|
|
210
|
+
margin-inline-end: -0.55rem;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[part="path"][data-variant="chevron"] [part="stage"]:first-child:not(:last-child) {
|
|
214
|
+
clip-path: polygon(0 0, calc(100% - 0.7rem) 0, 100% 50%, calc(100% - 0.7rem) 100%, 0 100%);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[part="path"][data-variant="chevron"] [part="stage"]:last-child:not(:first-child) {
|
|
218
|
+
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0.7rem 50%);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* ── variant: base ───────────────────────────────────────────────────
|
|
222
|
+
A marker rail: a dot per stage on a connector line, label beneath.
|
|
223
|
+
Unlike the ribbon shapes this keeps its labels out of the fill, so a
|
|
224
|
+
long sequence stays readable where chevrons would crush the text.
|
|
225
|
+
|
|
226
|
+
The markers all occupy the same 1.25rem box whatever their state —
|
|
227
|
+
upcoming ones are shrunk with a transform, which is purely visual —
|
|
228
|
+
so the connector can sit at one fixed offset instead of chasing a
|
|
229
|
+
per-state centre. */
|
|
230
|
+
[part="path"][data-variant="base"] [part="stage"] {
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
background: none;
|
|
233
|
+
color: var(--boe-token-text-text, #1a1a1a);
|
|
234
|
+
/* Top-aligned, not centred: the connector is drawn at one fixed
|
|
235
|
+
offset from the stage's top edge, so every marker has to sit at
|
|
236
|
+
that same offset. Centring moved the current stage's marker up by
|
|
237
|
+
the height of its description and left the line joining nothing. */
|
|
238
|
+
justify-content: flex-start;
|
|
239
|
+
padding: 0 0.5rem;
|
|
240
|
+
gap: 0.4rem;
|
|
241
|
+
font-weight: 500;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* The segment joining this stage back to the previous one. Painted from
|
|
245
|
+
the previous marker's centre (-50%) to this one's, which lands
|
|
246
|
+
correctly because every stage is an equal flex share. */
|
|
247
|
+
[part="path"][data-variant="base"] [part="stage"]:not(:first-child)::before {
|
|
248
|
+
content: "";
|
|
249
|
+
position: absolute;
|
|
250
|
+
inset-block-start: calc(0.625rem - 1px);
|
|
251
|
+
inset-inline-end: 50%;
|
|
252
|
+
inline-size: 100%;
|
|
253
|
+
block-size: 2px;
|
|
254
|
+
background: var(--boe-token-stroke-stroke, #e8e8e8);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Reached stages pull the line in behind them: the connector is blue up
|
|
258
|
+
to wherever the record got to, grey from there on. */
|
|
259
|
+
/* The failed stage counts as reached, so its incoming segment stays
|
|
260
|
+
brand-coloured: the record did travel that far, and only the marker
|
|
261
|
+
reports the failure. */
|
|
262
|
+
[part="path"][data-variant="base"] [part="stage"]:not([data-state="upcoming"])::before {
|
|
263
|
+
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
[part="path"][data-variant="base"] [part="stage-heading"] {
|
|
267
|
+
flex-direction: column;
|
|
268
|
+
gap: 0.4rem;
|
|
269
|
+
inline-size: 100%;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
[part="path"][data-variant="base"] [part="stage-marker"] {
|
|
273
|
+
position: relative;
|
|
274
|
+
z-index: 1;
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
inline-size: 1.25rem;
|
|
279
|
+
block-size: 1.25rem;
|
|
280
|
+
margin-inline-end: 0;
|
|
281
|
+
border-radius: 999px;
|
|
282
|
+
font-size: 0.68rem;
|
|
283
|
+
line-height: 1;
|
|
284
|
+
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
285
|
+
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
286
|
+
transition: transform ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
[part="path"][data-variant="base"] [part="stage"][data-state="upcoming"] [part="stage-marker"] {
|
|
290
|
+
background: var(--boe-token-stroke-stroke, #e8e8e8);
|
|
291
|
+
transform: scale(0.45);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
[part="path"][data-variant="base"] [part="stage"][data-state="error"] [part="stage-marker"] {
|
|
295
|
+
background: var(--boe-token-surface-status-surface-error, #ed3757);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
[part="path"][data-variant="base"] [part="stage"][data-state="current"] [part="stage-label"],
|
|
299
|
+
[part="path"][data-variant="base"] [part="stage"][data-state="error"] [part="stage-label"] {
|
|
300
|
+
font-weight: 700;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@media (prefers-reduced-motion: reduce) {
|
|
304
|
+
[part="stage"],
|
|
305
|
+
[part="path"][data-variant="base"] [part="stage-marker"] {
|
|
306
|
+
transition: none;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@media (max-width: 34rem) {
|
|
311
|
+
/* Chevrons collapse below the width where they stop being legible;
|
|
312
|
+
the states still read from tone and the completed marker. */
|
|
313
|
+
[part="path"][data-variant="chevron"] [part="stage"],
|
|
314
|
+
[part="path"][data-variant="chevron"] [part="stage"]:first-child:not(:last-child),
|
|
315
|
+
[part="path"][data-variant="chevron"] [part="stage"]:last-child:not(:first-child) {
|
|
316
|
+
clip-path: none;
|
|
317
|
+
margin-inline-end: 0;
|
|
318
|
+
padding-inline: 1em;
|
|
319
|
+
border-radius: 0;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
`;
|
|
323
|
+
/**
|
|
324
|
+
* Horizontal chevron lifecycle tracker for record headers — Draft → In
|
|
325
|
+
* Review → Approved → Executed.
|
|
326
|
+
*
|
|
327
|
+
* Distinct from `box-progress-steps`, which is a vertical setup rail for a
|
|
328
|
+
* task the reader is working through: this states where a *record* sits in
|
|
329
|
+
* its lifecycle, is read-only, and belongs in a header rather than a form.
|
|
330
|
+
*
|
|
331
|
+
* Rendered as an ordered list with the current stage marked `aria-current`,
|
|
332
|
+
* so the sequence and the position are both available without relying on the
|
|
333
|
+
* chevron geometry — which is decoration, and collapses on narrow viewports.
|
|
334
|
+
*/
|
|
335
|
+
export class Path extends BaseElement {
|
|
336
|
+
static tagName = DEFAULT_TAG_NAME;
|
|
337
|
+
static get observedAttributes() {
|
|
338
|
+
return ["current", "has-error", "label", "stages", "variant"];
|
|
339
|
+
}
|
|
340
|
+
pathEl;
|
|
341
|
+
stagesRaw = null;
|
|
342
|
+
stagesCache = [];
|
|
343
|
+
get label() {
|
|
344
|
+
return this.getAttribute("label") ?? "Lifecycle";
|
|
345
|
+
}
|
|
346
|
+
set label(value) {
|
|
347
|
+
this.setAttribute("label", value);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Shape of the path: `chevron` (default) draws a continuous directed ribbon;
|
|
351
|
+
* `rounded` draws separated pills. An unknown value falls back to `chevron`
|
|
352
|
+
* rather than rendering an unstyled row.
|
|
353
|
+
*/
|
|
354
|
+
get variant() {
|
|
355
|
+
return resolvePathVariant(this.getAttribute("variant"));
|
|
356
|
+
}
|
|
357
|
+
set variant(value) {
|
|
358
|
+
this.setAttribute("variant", value);
|
|
359
|
+
}
|
|
360
|
+
/** Id of the stage the record is at. An unknown id leaves every stage upcoming. */
|
|
361
|
+
get current() {
|
|
362
|
+
return this.getAttribute("current") ?? "";
|
|
363
|
+
}
|
|
364
|
+
set current(value) {
|
|
365
|
+
this.setAttribute("current", value);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The record failed at the stage it is currently on — approval rejected, a
|
|
369
|
+
* signature declined. Earlier stages stay complete: the failure is where the
|
|
370
|
+
* lifecycle stopped, not a reset of everything behind it.
|
|
371
|
+
*/
|
|
372
|
+
get hasError() {
|
|
373
|
+
return this.hasAttribute("has-error");
|
|
374
|
+
}
|
|
375
|
+
set hasError(value) {
|
|
376
|
+
this.toggleAttribute("has-error", value);
|
|
377
|
+
}
|
|
378
|
+
get stages() {
|
|
379
|
+
const raw = this.getAttribute("stages");
|
|
380
|
+
if (!raw) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
if (raw !== this.stagesRaw) {
|
|
384
|
+
this.stagesRaw = raw;
|
|
385
|
+
try {
|
|
386
|
+
const parsed = JSON.parse(raw);
|
|
387
|
+
this.stagesCache =
|
|
388
|
+
Array.isArray(parsed) && parsed.every(isPathStageRecord)
|
|
389
|
+
? parsed
|
|
390
|
+
: [];
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
this.stagesCache = [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return [...this.stagesCache];
|
|
397
|
+
}
|
|
398
|
+
set stages(value) {
|
|
399
|
+
if (value.length) {
|
|
400
|
+
this.setAttribute("stages", JSON.stringify(value));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.removeAttribute("stages");
|
|
404
|
+
}
|
|
405
|
+
/** State of each stage, in order. */
|
|
406
|
+
get states() {
|
|
407
|
+
return resolveStageStates(this.stages, this.current, this.hasError);
|
|
408
|
+
}
|
|
409
|
+
renderTemplate() {
|
|
410
|
+
if (!this.shadowRoot) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.shadowRoot.innerHTML = `<style>${elementStyles}</style><ol part="path"></ol>`;
|
|
414
|
+
this.pathEl = this.shadowRoot.querySelector('[part="path"]');
|
|
415
|
+
}
|
|
416
|
+
setupListeners() {
|
|
417
|
+
// Read-only: a record's lifecycle position is not something a header edits.
|
|
418
|
+
}
|
|
419
|
+
update() {
|
|
420
|
+
if (!this.pathEl) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const stages = this.stages;
|
|
424
|
+
const variant = this.variant;
|
|
425
|
+
const states = resolveStageStates(stages, this.current, this.hasError);
|
|
426
|
+
this.pathEl.setAttribute("aria-label", this.label);
|
|
427
|
+
this.pathEl.dataset.variant = variant;
|
|
428
|
+
this.pathEl.innerHTML = stages
|
|
429
|
+
.map((stage, index) => {
|
|
430
|
+
const state = states[index];
|
|
431
|
+
// A second, non-colour signal for the states that have one. Always
|
|
432
|
+
// emitted: the base variant styles this into the rail's marker, and an
|
|
433
|
+
// empty one is hidden by CSS rather than by a branch here.
|
|
434
|
+
const marker = `<span part="stage-marker" aria-hidden="true">${STATE_MARKERS[state] ?? ""}</span>`;
|
|
435
|
+
const isCurrentPosition = state === "current" || state === "error";
|
|
436
|
+
return `
|
|
437
|
+
<li
|
|
438
|
+
part="stage"
|
|
439
|
+
data-stage-id="${escapeHtml(stage.id)}"
|
|
440
|
+
data-state="${state}"
|
|
441
|
+
${isCurrentPosition ? 'aria-current="step"' : ""}
|
|
442
|
+
${state === "error" ? 'aria-invalid="true"' : ""}
|
|
443
|
+
>
|
|
444
|
+
<span part="stage-state">${STATE_LABELS[state]}</span>
|
|
445
|
+
<span part="stage-heading">${marker}<span part="stage-label">${escapeHtml(stage.label)}</span></span>
|
|
446
|
+
${isCurrentPosition && stage.description
|
|
447
|
+
? `<span part="stage-description">${escapeHtml(stage.description)}</span>`
|
|
448
|
+
: ""}
|
|
449
|
+
</li>
|
|
450
|
+
`;
|
|
451
|
+
})
|
|
452
|
+
.join("");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
Path.register();
|
|
@@ -1,14 +1,84 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import type { BoeGridPlacement, ResolvedBoeGridPlacement } from "../../foundations/layout/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Shape of the placeholder.
|
|
5
|
+
*
|
|
6
|
+
* `box` is a single rectangle sized by `width`/`height` — the original
|
|
7
|
+
* behaviour, and still the default. `line` is a stack of text lines. `grid` is
|
|
8
|
+
* a column layout whose regions stand in for cards, tiles or panels.
|
|
9
|
+
*/
|
|
10
|
+
export type SkeletonVariant = "box" | "line" | "grid";
|
|
11
|
+
/**
|
|
12
|
+
* Narrow an author-supplied variant, falling back to `box`.
|
|
13
|
+
*
|
|
14
|
+
* A typo should render the original single rectangle rather than an empty
|
|
15
|
+
* element: every multi-part branch is scoped to a known value.
|
|
16
|
+
*/
|
|
17
|
+
export declare const resolveSkeletonVariant: (value: string | null | undefined) => SkeletonVariant;
|
|
18
|
+
/**
|
|
19
|
+
* The columns in Adobe Spectrum's responsive grid.
|
|
20
|
+
*
|
|
21
|
+
* Delegated to the layout foundation that `box-grid` also builds on: a
|
|
22
|
+
* skeleton stands in for a real layout, so if the two disagreed about what a
|
|
23
|
+
* column is worth the placeholder would stop matching what replaces it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SKELETON_DEFAULT_COLUMNS = 12;
|
|
26
|
+
/** One region of the grid, in Spectrum's terms. */
|
|
27
|
+
export type SkeletonGridItem = BoeGridPlacement;
|
|
28
|
+
/** A resolved region: every field present, every value in range. */
|
|
29
|
+
export type ResolvedSkeletonGridItem = ResolvedBoeGridPlacement;
|
|
30
|
+
/** Attribute payloads are author input — validate every record. */
|
|
31
|
+
export declare const isSkeletonGridItemRecord: (value: unknown) => value is BoeGridPlacement;
|
|
32
|
+
/**
|
|
33
|
+
* Fit a region to the grid, clamping every value to the declared totals.
|
|
34
|
+
*
|
|
35
|
+
* Still exported here because it is part of this component's published
|
|
36
|
+
* surface; the behaviour now lives in the foundation.
|
|
37
|
+
*/
|
|
38
|
+
export declare const resolveSkeletonGridItem: (item: BoeGridPlacement, columns: number, rows: number) => ResolvedBoeGridPlacement;
|
|
2
39
|
export declare class Skeleton extends BaseElement {
|
|
3
40
|
static readonly tagName: string;
|
|
4
41
|
static get observedAttributes(): string[];
|
|
5
42
|
private skeletonEl;
|
|
43
|
+
private rootEl;
|
|
6
44
|
private appliedWidth;
|
|
7
45
|
private appliedHeight;
|
|
46
|
+
private renderedVariant;
|
|
47
|
+
private itemsRaw;
|
|
48
|
+
private itemsCache;
|
|
8
49
|
get width(): string;
|
|
9
50
|
set width(value: string);
|
|
10
51
|
get height(): string;
|
|
11
52
|
set height(value: string);
|
|
53
|
+
/** Shape of the placeholder. An unknown value falls back to `box`. */
|
|
54
|
+
get variant(): SkeletonVariant;
|
|
55
|
+
set variant(value: SkeletonVariant);
|
|
56
|
+
/** Lines drawn by the `line` variant. Values below 1 render a single line. */
|
|
57
|
+
get lines(): number;
|
|
58
|
+
set lines(value: number);
|
|
59
|
+
/** Columns in the `grid` variant. Defaults to Spectrum's twelve. */
|
|
60
|
+
get columns(): number;
|
|
61
|
+
set columns(value: number);
|
|
62
|
+
/** Rows in the `grid` variant. Also the ceiling for any region's `rowSpan`. */
|
|
63
|
+
get rows(): number;
|
|
64
|
+
set rows(value: number);
|
|
65
|
+
/** Height of one grid row; a region spanning two is twice this plus a gutter. */
|
|
66
|
+
get rowHeight(): string;
|
|
67
|
+
set rowHeight(value: string);
|
|
68
|
+
/**
|
|
69
|
+
* Regions of the `grid` variant, in order.
|
|
70
|
+
*
|
|
71
|
+
* With none given the grid is filled with single-column regions — `rows` ×
|
|
72
|
+
* `columns` of them — so `rows` and `columns` alone describe a uniform grid.
|
|
73
|
+
*/
|
|
74
|
+
get items(): SkeletonGridItem[];
|
|
75
|
+
set items(value: SkeletonGridItem[]);
|
|
76
|
+
/** The grid's regions as rendered: clamped to `columns` and `rows`. */
|
|
77
|
+
get resolvedItems(): ResolvedSkeletonGridItem[];
|
|
12
78
|
protected renderTemplate(): void;
|
|
13
79
|
protected update(): void;
|
|
80
|
+
/** Render the `line` or `grid` variant, which draw many bars rather than one. */
|
|
81
|
+
private updateGroup;
|
|
82
|
+
private renderLines;
|
|
83
|
+
private renderGrid;
|
|
14
84
|
}
|