@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which ends of a graph edge carry an arrowhead.
|
|
3
|
+
*
|
|
4
|
+
* Shared by `box-version-graph` and `box-lineage-graph`, which draw the same
|
|
5
|
+
* edge geometry from the same layout and should not disagree about what an
|
|
6
|
+
* arrow means. Lives here rather than in either component because the lineage
|
|
7
|
+
* graph already depends on this directory for `computeVersionGraphLayout`, and
|
|
8
|
+
* the reverse dependency would be a cycle.
|
|
9
|
+
*/
|
|
10
|
+
export type GraphArrows = "none" | "start" | "end" | "both";
|
|
11
|
+
/**
|
|
12
|
+
* Narrow an author-supplied value, falling back to `end`.
|
|
13
|
+
*
|
|
14
|
+
* `end` is the default because these are directed graphs: the layout builds
|
|
15
|
+
* every edge as parent → child, so a single head on the child is the reading
|
|
16
|
+
* that matches the data. A typo should not silently turn the direction off.
|
|
17
|
+
*/
|
|
18
|
+
export declare const resolveGraphArrows: (value: string | null | undefined) => GraphArrows;
|
|
19
|
+
/** Whether the head at the child end is drawn. */
|
|
20
|
+
export declare const hasEndArrow: (arrows: GraphArrows) => boolean;
|
|
21
|
+
/** Whether the head at the parent end is drawn. */
|
|
22
|
+
export declare const hasStartArrow: (arrows: GraphArrows) => boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const GRAPH_ARROWS = new Set(["none", "start", "end", "both"]);
|
|
2
|
+
/**
|
|
3
|
+
* Narrow an author-supplied value, falling back to `end`.
|
|
4
|
+
*
|
|
5
|
+
* `end` is the default because these are directed graphs: the layout builds
|
|
6
|
+
* every edge as parent → child, so a single head on the child is the reading
|
|
7
|
+
* that matches the data. A typo should not silently turn the direction off.
|
|
8
|
+
*/
|
|
9
|
+
export const resolveGraphArrows = (value) => GRAPH_ARROWS.has(value) ? value : "end";
|
|
10
|
+
/** Whether the head at the child end is drawn. */
|
|
11
|
+
export const hasEndArrow = (arrows) => arrows === "end" || arrows === "both";
|
|
12
|
+
/** Whether the head at the parent end is drawn. */
|
|
13
|
+
export const hasStartArrow = (arrows) => arrows === "start" || arrows === "both";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export * from "./types.js";
|
|
3
3
|
// Pure git-network layout engine (shared with future lineage graphs)
|
|
4
4
|
export * from "./graph-layout.js";
|
|
5
|
+
export * from "./graph-arrows.js";
|
|
5
6
|
// Accessible core contract
|
|
6
7
|
export * from "./version-list.js";
|
|
7
8
|
// Git-style visualization layer
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GraphArrows } from "./graph-arrows.js";
|
|
1
2
|
import type { VersionNode } from "./types.js";
|
|
2
3
|
import { BaseElement } from "../../core/index.js";
|
|
3
4
|
/**
|
|
@@ -12,6 +13,12 @@ import { BaseElement } from "../../core/index.js";
|
|
|
12
13
|
export declare class VersionGraph extends BaseElement {
|
|
13
14
|
static readonly tagName: string;
|
|
14
15
|
static get observedAttributes(): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Which ends of each edge carry an arrowhead. Defaults to `end`, the head on
|
|
18
|
+
* the child, because the layout builds edges parent → child.
|
|
19
|
+
*/
|
|
20
|
+
get arrows(): GraphArrows;
|
|
21
|
+
set arrows(value: GraphArrows);
|
|
15
22
|
private versionsValue;
|
|
16
23
|
private compareIds;
|
|
17
24
|
private rovingId;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computeVersionGraphLayout, orderVersionsForDisplay } from "./graph-layout.js";
|
|
2
|
+
import { hasEndArrow, hasStartArrow, resolveGraphArrows } from "./graph-arrows.js";
|
|
2
3
|
import { escapeHtml, STATUS_LABELS } from "./shared.js";
|
|
3
4
|
import { isVersionNodeRecord, resolveVersionKind, resolveVersionStatus } from "./types.js";
|
|
4
5
|
import { formatItemDate } from "../content-explorer/adapters/item-summary.js";
|
|
@@ -8,6 +9,14 @@ const DEFAULT_TAG_NAME = "box-version-graph";
|
|
|
8
9
|
const LANE_WIDTH = 26;
|
|
9
10
|
const ROW_HEIGHT = 46;
|
|
10
11
|
const EDGE_PAD = 8;
|
|
12
|
+
/**
|
|
13
|
+
* How far short of a node's centre an edge stops.
|
|
14
|
+
*
|
|
15
|
+
* The arrowhead marks the path's end vertex, so an edge running to the centre
|
|
16
|
+
* would bury its head under the node disc. The largest node here is ~1.05rem
|
|
17
|
+
* across, so backing off 9px leaves the head just clear of the biggest one.
|
|
18
|
+
*/
|
|
19
|
+
const NODE_EDGE_OFFSET = 9;
|
|
11
20
|
const elementStyles = `
|
|
12
21
|
[hidden] {
|
|
13
22
|
display: none !important;
|
|
@@ -48,6 +57,13 @@ const elementStyles = `
|
|
|
48
57
|
pointer-events: none;
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
/* The arrowhead. Same colour as the stroke it terminates, stated
|
|
61
|
+
rather than inherited: context-stroke is not supported everywhere. */
|
|
62
|
+
[part="edge-arrow"] {
|
|
63
|
+
fill: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 55%, transparent);
|
|
64
|
+
stroke: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
[part="edge"] {
|
|
52
68
|
fill: none;
|
|
53
69
|
stroke: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 55%, transparent);
|
|
@@ -187,7 +203,17 @@ const elementStyles = `
|
|
|
187
203
|
export class VersionGraph extends BaseElement {
|
|
188
204
|
static tagName = DEFAULT_TAG_NAME;
|
|
189
205
|
static get observedAttributes() {
|
|
190
|
-
return ["heading", "versions"];
|
|
206
|
+
return ["arrows", "heading", "versions"];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Which ends of each edge carry an arrowhead. Defaults to `end`, the head on
|
|
210
|
+
* the child, because the layout builds edges parent → child.
|
|
211
|
+
*/
|
|
212
|
+
get arrows() {
|
|
213
|
+
return resolveGraphArrows(this.getAttribute("arrows"));
|
|
214
|
+
}
|
|
215
|
+
set arrows(value) {
|
|
216
|
+
this.setAttribute("arrows", value);
|
|
191
217
|
}
|
|
192
218
|
versionsValue = [];
|
|
193
219
|
compareIds = [];
|
|
@@ -359,16 +385,38 @@ export class VersionGraph extends BaseElement {
|
|
|
359
385
|
const graphHeight = layout.rowCount * ROW_HEIGHT;
|
|
360
386
|
const centerX = (lane) => EDGE_PAD + lane * LANE_WIDTH + LANE_WIDTH / 2;
|
|
361
387
|
const centerY = (row) => row * ROW_HEIGHT + ROW_HEIGHT / 2;
|
|
388
|
+
const arrows = this.arrows;
|
|
389
|
+
const startMarker = hasStartArrow(arrows) ? ` marker-start="url(#boe-graph-arrow)"` : "";
|
|
390
|
+
const endMarker = hasEndArrow(arrows) ? ` marker-end="url(#boe-graph-arrow)"` : "";
|
|
362
391
|
const edgePaths = layout.edges
|
|
363
392
|
.map(edge => {
|
|
364
393
|
const x1 = centerX(edge.fromLane);
|
|
365
394
|
const y1 = centerY(edge.fromRow);
|
|
366
395
|
const x2 = centerX(edge.toLane);
|
|
367
396
|
const y2 = centerY(edge.toRow);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
397
|
+
// One curve rule for every edge, rather than a straight line for
|
|
398
|
+
// same-lane edges and a cubic for lane changes: two rules made the
|
|
399
|
+
// graph read as two different diagrams. A cubic whose ends share an x
|
|
400
|
+
// degenerates to a straight vertical on its own, so same-lane edges
|
|
401
|
+
// still render straight without being a special case.
|
|
402
|
+
//
|
|
403
|
+
// Control points are pulled *along* the direction of travel — half the
|
|
404
|
+
// vertical distance each — so the bend always leaves the parent
|
|
405
|
+
// downward and enters the child from above. The previous version
|
|
406
|
+
// offset them by a fixed half-row in the wrong direction (up from the
|
|
407
|
+
// start, down from the end on a downward edge), so an edge spanning
|
|
408
|
+
// one row bowed backwards into an S while a longer one looked almost
|
|
409
|
+
// straight.
|
|
410
|
+
// Stop at each node's edge rather than its centre. A marker sits on
|
|
411
|
+
// the path's end vertex, so a vertex inside the node disc buries it —
|
|
412
|
+
// and an edge that starts and ends at the rim reads as connecting two
|
|
413
|
+
// nodes rather than as passing through them, which is what React Flow
|
|
414
|
+
// gets from anchoring edges to handles.
|
|
415
|
+
const yStart = y1 + Math.sign(y2 - y1) * NODE_EDGE_OFFSET;
|
|
416
|
+
const yEnd = y2 + Math.sign(y1 - y2) * NODE_EDGE_OFFSET;
|
|
417
|
+
const dy = (yEnd - yStart) / 2;
|
|
418
|
+
const d = `M ${x1} ${yStart} C ${x1} ${yStart + dy}, ${x2} ${yEnd - dy}, ${x2} ${yEnd}`;
|
|
419
|
+
return `<path part="edge" data-from="${escapeHtml(edge.fromId)}" data-to="${escapeHtml(edge.toId)}" d="${d}"${startMarker}${endMarker}></path>`;
|
|
372
420
|
})
|
|
373
421
|
.join("");
|
|
374
422
|
const rovingId = this.rovingId && byId.has(this.rovingId)
|
|
@@ -416,7 +464,7 @@ export class VersionGraph extends BaseElement {
|
|
|
416
464
|
${layout.placements.length > 0
|
|
417
465
|
? `
|
|
418
466
|
<div part="graph">
|
|
419
|
-
<svg part="edges" width="${String(graphWidth)}" height="${String(graphHeight)}" viewBox="0 0 ${String(graphWidth)} ${String(graphHeight)}" aria-hidden="true">${edgePaths}</svg>
|
|
467
|
+
<svg part="edges" width="${String(graphWidth)}" height="${String(graphHeight)}" viewBox="0 0 ${String(graphWidth)} ${String(graphHeight)}" aria-hidden="true"><defs><marker id="boe-graph-arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" d="M 0 0 L 8 4 L 0 8 z"></path></marker></defs>${edgePaths}</svg>
|
|
420
468
|
<ol part="rows" role="list">${rows}</ol>
|
|
421
469
|
</div>
|
|
422
470
|
`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unofficialbox/box-open-elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Box Open Elements: an open, framework-agnostic design system and web component library for Box-style experiences",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://unofficialbox.github.io/box-open-elements",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
/** One point in a record's lifecycle. */
|
|
3
|
-
export interface StagePathStage {
|
|
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";
|
|
10
|
-
/** Attribute payloads are author input — validate every record. */
|
|
11
|
-
export declare const isStagePathStageRecord: (value: unknown) => value is StagePathStage;
|
|
12
|
-
/** State of each stage relative to the current one. */
|
|
13
|
-
export declare const resolveStageStates: (stages: readonly StagePathStage[], currentId: string) => StageState[];
|
|
14
|
-
/**
|
|
15
|
-
* Horizontal chevron lifecycle tracker for record headers — Draft → In
|
|
16
|
-
* Review → Approved → Executed.
|
|
17
|
-
*
|
|
18
|
-
* Distinct from `box-progress-steps`, which is a vertical setup rail for a
|
|
19
|
-
* task the reader is working through: this states where a *record* sits in
|
|
20
|
-
* its lifecycle, is read-only, and belongs in a header rather than a form.
|
|
21
|
-
*
|
|
22
|
-
* Rendered as an ordered list with the current stage marked `aria-current`,
|
|
23
|
-
* so the sequence and the position are both available without relying on the
|
|
24
|
-
* chevron geometry — which is decoration, and collapses on narrow viewports.
|
|
25
|
-
*/
|
|
26
|
-
export declare class StagePath extends BaseElement {
|
|
27
|
-
static readonly tagName: string;
|
|
28
|
-
static get observedAttributes(): string[];
|
|
29
|
-
private pathEl;
|
|
30
|
-
private stagesRaw;
|
|
31
|
-
private stagesCache;
|
|
32
|
-
get label(): string;
|
|
33
|
-
set label(value: string);
|
|
34
|
-
/** Id of the stage the record is at. An unknown id leaves every stage upcoming. */
|
|
35
|
-
get current(): string;
|
|
36
|
-
set current(value: string);
|
|
37
|
-
get stages(): StagePathStage[];
|
|
38
|
-
set stages(value: StagePathStage[]);
|
|
39
|
-
/** State of each stage, in order. */
|
|
40
|
-
get states(): StageState[];
|
|
41
|
-
protected renderTemplate(): void;
|
|
42
|
-
protected setupListeners(): void;
|
|
43
|
-
protected update(): void;
|
|
44
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
3
|
-
const DEFAULT_TAG_NAME = "box-stage-path";
|
|
4
|
-
const escapeHtml = (value) => value
|
|
5
|
-
.replaceAll("&", "&")
|
|
6
|
-
.replaceAll("<", "<")
|
|
7
|
-
.replaceAll(">", ">")
|
|
8
|
-
.replaceAll('"', """)
|
|
9
|
-
.replaceAll("'", "'");
|
|
10
|
-
/** Attribute payloads are author input — validate every record. */
|
|
11
|
-
export const isStagePathStageRecord = (value) => {
|
|
12
|
-
if (typeof value !== "object" || value === null) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
const stage = value;
|
|
16
|
-
return (typeof stage.id === "string" &&
|
|
17
|
-
stage.id.length > 0 &&
|
|
18
|
-
typeof stage.label === "string" &&
|
|
19
|
-
stage.label.length > 0);
|
|
20
|
-
};
|
|
21
|
-
/** State of each stage relative to the current one. */
|
|
22
|
-
export const resolveStageStates = (stages, currentId) => {
|
|
23
|
-
const index = stages.findIndex(stage => stage.id === currentId);
|
|
24
|
-
// An unknown current id means nothing is complete — better than silently
|
|
25
|
-
// marking the whole path done because the host sent a stale value.
|
|
26
|
-
return stages.map((_, position) => index === -1
|
|
27
|
-
? "upcoming"
|
|
28
|
-
: position < index
|
|
29
|
-
? "complete"
|
|
30
|
-
: position === index
|
|
31
|
-
? "current"
|
|
32
|
-
: "upcoming");
|
|
33
|
-
};
|
|
34
|
-
const elementStyles = `
|
|
35
|
-
:host {
|
|
36
|
-
display: block;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
[part="path"] {
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-wrap: wrap;
|
|
42
|
-
margin: 0;
|
|
43
|
-
padding: 0;
|
|
44
|
-
list-style: none;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
[part="stage"] {
|
|
48
|
-
position: relative;
|
|
49
|
-
flex: 1 1 0;
|
|
50
|
-
min-inline-size: 6rem;
|
|
51
|
-
padding: 0.45rem 0.7rem 0.45rem 1.35rem;
|
|
52
|
-
background: var(--boe-token-surface-surface-hover, #f4f4f4);
|
|
53
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
54
|
-
font-size: 0.82rem;
|
|
55
|
-
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
[part="stage"]:first-child {
|
|
59
|
-
padding-inline-start: 0.8rem;
|
|
60
|
-
border-start-start-radius: 999px;
|
|
61
|
-
border-end-start-radius: 999px;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
[part="stage"]:last-child {
|
|
65
|
-
border-start-end-radius: 999px;
|
|
66
|
-
border-end-end-radius: 999px;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* The chevron: a notch cut from the following stage plus an arrow on
|
|
70
|
-
this one, so the path reads as a directed sequence rather than a row
|
|
71
|
-
of pills. Drawn with clip-path so no extra elements are needed. */
|
|
72
|
-
[part="stage"]:not(:last-child) {
|
|
73
|
-
clip-path: polygon(0 0, calc(100% - 0.7rem) 0, 100% 50%, calc(100% - 0.7rem) 100%, 0 100%, 0.7rem 50%);
|
|
74
|
-
margin-inline-end: -0.55rem;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
[part="stage"]:first-child:not(:last-child) {
|
|
78
|
-
clip-path: polygon(0 0, calc(100% - 0.7rem) 0, 100% 50%, calc(100% - 0.7rem) 100%, 0 100%);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
[part="stage"]:last-child:not(:first-child) {
|
|
82
|
-
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0.7rem 50%);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
[part="stage"][data-state="complete"] {
|
|
86
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 18%, transparent);
|
|
87
|
-
color: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26a27b) 72%, black 28%);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
[part="stage"][data-state="current"] {
|
|
91
|
-
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
92
|
-
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
93
|
-
font-weight: 700;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
[part="stage-label"] {
|
|
97
|
-
display: block;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
[part="stage-description"] {
|
|
101
|
-
display: block;
|
|
102
|
-
margin-block-start: 0.1rem;
|
|
103
|
-
font-size: 0.72rem;
|
|
104
|
-
font-weight: 400;
|
|
105
|
-
opacity: 0.85;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
[part="stage-marker"] {
|
|
109
|
-
margin-inline-end: 0.3rem;
|
|
110
|
-
font-weight: 700;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@media (prefers-reduced-motion: reduce) {
|
|
114
|
-
[part="stage"] {
|
|
115
|
-
transition: none;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
@media (max-width: 34rem) {
|
|
120
|
-
/* Chevrons collapse below the width where they stop being legible;
|
|
121
|
-
the states still read from tone and the completed marker. */
|
|
122
|
-
[part="stage"],
|
|
123
|
-
[part="stage"]:first-child:not(:last-child),
|
|
124
|
-
[part="stage"]:last-child:not(:first-child) {
|
|
125
|
-
clip-path: none;
|
|
126
|
-
margin-inline-end: 0;
|
|
127
|
-
padding-inline-start: 0.8rem;
|
|
128
|
-
border-radius: 0;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
`;
|
|
132
|
-
/**
|
|
133
|
-
* Horizontal chevron lifecycle tracker for record headers — Draft → In
|
|
134
|
-
* Review → Approved → Executed.
|
|
135
|
-
*
|
|
136
|
-
* Distinct from `box-progress-steps`, which is a vertical setup rail for a
|
|
137
|
-
* task the reader is working through: this states where a *record* sits in
|
|
138
|
-
* its lifecycle, is read-only, and belongs in a header rather than a form.
|
|
139
|
-
*
|
|
140
|
-
* Rendered as an ordered list with the current stage marked `aria-current`,
|
|
141
|
-
* so the sequence and the position are both available without relying on the
|
|
142
|
-
* chevron geometry — which is decoration, and collapses on narrow viewports.
|
|
143
|
-
*/
|
|
144
|
-
export class StagePath extends BaseElement {
|
|
145
|
-
static tagName = DEFAULT_TAG_NAME;
|
|
146
|
-
static get observedAttributes() {
|
|
147
|
-
return ["current", "label", "stages"];
|
|
148
|
-
}
|
|
149
|
-
pathEl;
|
|
150
|
-
stagesRaw = null;
|
|
151
|
-
stagesCache = [];
|
|
152
|
-
get label() {
|
|
153
|
-
return this.getAttribute("label") ?? "Lifecycle";
|
|
154
|
-
}
|
|
155
|
-
set label(value) {
|
|
156
|
-
this.setAttribute("label", value);
|
|
157
|
-
}
|
|
158
|
-
/** Id of the stage the record is at. An unknown id leaves every stage upcoming. */
|
|
159
|
-
get current() {
|
|
160
|
-
return this.getAttribute("current") ?? "";
|
|
161
|
-
}
|
|
162
|
-
set current(value) {
|
|
163
|
-
this.setAttribute("current", value);
|
|
164
|
-
}
|
|
165
|
-
get stages() {
|
|
166
|
-
const raw = this.getAttribute("stages");
|
|
167
|
-
if (!raw) {
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
if (raw !== this.stagesRaw) {
|
|
171
|
-
this.stagesRaw = raw;
|
|
172
|
-
try {
|
|
173
|
-
const parsed = JSON.parse(raw);
|
|
174
|
-
this.stagesCache =
|
|
175
|
-
Array.isArray(parsed) && parsed.every(isStagePathStageRecord)
|
|
176
|
-
? parsed
|
|
177
|
-
: [];
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
this.stagesCache = [];
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return [...this.stagesCache];
|
|
184
|
-
}
|
|
185
|
-
set stages(value) {
|
|
186
|
-
if (value.length) {
|
|
187
|
-
this.setAttribute("stages", JSON.stringify(value));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
this.removeAttribute("stages");
|
|
191
|
-
}
|
|
192
|
-
/** State of each stage, in order. */
|
|
193
|
-
get states() {
|
|
194
|
-
return resolveStageStates(this.stages, this.current);
|
|
195
|
-
}
|
|
196
|
-
renderTemplate() {
|
|
197
|
-
if (!this.shadowRoot) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
this.shadowRoot.innerHTML = `<style>${elementStyles}</style><ol part="path"></ol>`;
|
|
201
|
-
this.pathEl = this.shadowRoot.querySelector('[part="path"]');
|
|
202
|
-
}
|
|
203
|
-
setupListeners() {
|
|
204
|
-
// Read-only: a record's lifecycle position is not something a header edits.
|
|
205
|
-
}
|
|
206
|
-
update() {
|
|
207
|
-
if (!this.pathEl) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const stages = this.stages;
|
|
211
|
-
const states = resolveStageStates(stages, this.current);
|
|
212
|
-
this.pathEl.setAttribute("aria-label", this.label);
|
|
213
|
-
this.pathEl.innerHTML = stages
|
|
214
|
-
.map((stage, index) => {
|
|
215
|
-
const state = states[index];
|
|
216
|
-
// The completed marker is a second, non-colour signal for "done".
|
|
217
|
-
const marker = state === "complete" ? `<span part="stage-marker" aria-hidden="true">✓</span>` : "";
|
|
218
|
-
return `
|
|
219
|
-
<li
|
|
220
|
-
part="stage"
|
|
221
|
-
data-stage-id="${escapeHtml(stage.id)}"
|
|
222
|
-
data-state="${state}"
|
|
223
|
-
${state === "current" ? 'aria-current="step"' : ""}
|
|
224
|
-
>
|
|
225
|
-
<span part="stage-label">${marker}${escapeHtml(stage.label)}</span>
|
|
226
|
-
${state === "current" && stage.description
|
|
227
|
-
? `<span part="stage-description">${escapeHtml(stage.description)}</span>`
|
|
228
|
-
: ""}
|
|
229
|
-
</li>
|
|
230
|
-
`;
|
|
231
|
-
})
|
|
232
|
-
.join("");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
StagePath.register();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "../components/feedback/stage-path.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "../components/feedback/stage-path.js";
|