@unofficialbox/box-open-elements 0.10.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/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/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,5 +1,6 @@
|
|
|
1
1
|
import { isLineageNodeRecord, resolveLineageDeviation } from "./types.js";
|
|
2
2
|
import { computeVersionGraphLayout } from "../versions/graph-layout.js";
|
|
3
|
+
import { hasEndArrow, hasStartArrow, resolveGraphArrows } from "../versions/graph-arrows.js";
|
|
3
4
|
import { formatItemDate } from "../content-explorer/adapters/item-summary.js";
|
|
4
5
|
import { BaseElement } from "../../core/index.js";
|
|
5
6
|
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
@@ -14,6 +15,14 @@ const escapeHtml = (value) => value
|
|
|
14
15
|
const LANE_WIDTH = 26;
|
|
15
16
|
const ROW_HEIGHT = 56;
|
|
16
17
|
const EDGE_PAD = 8;
|
|
18
|
+
/**
|
|
19
|
+
* How far short of a node's centre an edge stops.
|
|
20
|
+
*
|
|
21
|
+
* The arrowhead marks the path's end vertex, so an edge running to the centre
|
|
22
|
+
* would bury its head under the node disc. The largest node here is ~1.05rem
|
|
23
|
+
* across, so backing off 9px leaves the head just clear of the biggest one.
|
|
24
|
+
*/
|
|
25
|
+
const NODE_EDGE_OFFSET = 9;
|
|
17
26
|
const DEVIATION_LABELS = {
|
|
18
27
|
none: "in sync",
|
|
19
28
|
minor: "minor deviation",
|
|
@@ -59,6 +68,21 @@ const elementStyles = `
|
|
|
59
68
|
pointer-events: none;
|
|
60
69
|
}
|
|
61
70
|
|
|
71
|
+
/* The arrowhead. Same colour as the stroke it terminates, stated
|
|
72
|
+
rather than inherited: context-stroke is not supported everywhere. */
|
|
73
|
+
[part="edge-arrow"] {
|
|
74
|
+
fill: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 45%, transparent);
|
|
75
|
+
stroke: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[part="edge-arrow"][data-deviation="minor"] {
|
|
79
|
+
fill: color-mix(in srgb, var(--boe-token-surface-status-surface-warning, #f5b31b) 75%, transparent);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[part="edge-arrow"][data-deviation="major"] {
|
|
83
|
+
fill: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 70%, transparent);
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
[part="edge"] {
|
|
63
87
|
fill: none;
|
|
64
88
|
stroke: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 45%, transparent);
|
|
@@ -196,7 +220,17 @@ const elementStyles = `
|
|
|
196
220
|
export class LineageGraph extends BaseElement {
|
|
197
221
|
static tagName = DEFAULT_TAG_NAME;
|
|
198
222
|
static get observedAttributes() {
|
|
199
|
-
return ["heading", "nodes"];
|
|
223
|
+
return ["arrows", "heading", "nodes"];
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Which ends of each edge carry an arrowhead. Defaults to `end`, the head on
|
|
227
|
+
* the derived node, because the layout builds edges source → derived.
|
|
228
|
+
*/
|
|
229
|
+
get arrows() {
|
|
230
|
+
return resolveGraphArrows(this.getAttribute("arrows"));
|
|
231
|
+
}
|
|
232
|
+
set arrows(value) {
|
|
233
|
+
this.setAttribute("arrows", value);
|
|
200
234
|
}
|
|
201
235
|
nodesValue = [];
|
|
202
236
|
rovingId = null;
|
|
@@ -352,16 +386,43 @@ export class LineageGraph extends BaseElement {
|
|
|
352
386
|
const graphHeight = layout.rowCount * ROW_HEIGHT;
|
|
353
387
|
const centerX = (lane) => EDGE_PAD + lane * LANE_WIDTH + LANE_WIDTH / 2;
|
|
354
388
|
const centerY = (row) => row * ROW_HEIGHT + ROW_HEIGHT / 2;
|
|
389
|
+
const arrows = this.arrows;
|
|
355
390
|
const edgePaths = layout.edges
|
|
356
391
|
.map(edge => {
|
|
357
392
|
const x1 = centerX(edge.fromLane);
|
|
358
393
|
const y1 = centerY(edge.fromRow);
|
|
359
394
|
const x2 = centerX(edge.toLane);
|
|
360
395
|
const y2 = centerY(edge.toRow);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
396
|
+
// One curve rule for every edge, rather than a straight line for
|
|
397
|
+
// same-lane edges and a cubic for lane changes: two rules made the
|
|
398
|
+
// graph read as two different diagrams. A cubic whose ends share an x
|
|
399
|
+
// degenerates to a straight vertical on its own, so same-lane edges
|
|
400
|
+
// still render straight without being a special case.
|
|
401
|
+
//
|
|
402
|
+
// Control points are pulled *along* the direction of travel — half the
|
|
403
|
+
// vertical distance each — so the bend always leaves the parent
|
|
404
|
+
// downward and enters the child from above. The previous version
|
|
405
|
+
// offset them by a fixed half-row in the wrong direction (up from the
|
|
406
|
+
// start, down from the end on a downward edge), so an edge spanning
|
|
407
|
+
// one row bowed backwards into an S while a longer one looked almost
|
|
408
|
+
// straight.
|
|
409
|
+
// Stop at each node's edge rather than its centre. A marker sits on
|
|
410
|
+
// the path's end vertex, so a vertex inside the node disc buries it —
|
|
411
|
+
// and an edge that starts and ends at the rim reads as connecting two
|
|
412
|
+
// nodes rather than as passing through them, which is what React Flow
|
|
413
|
+
// gets from anchoring edges to handles.
|
|
414
|
+
const yStart = y1 + Math.sign(y2 - y1) * NODE_EDGE_OFFSET;
|
|
415
|
+
const yEnd = y2 + Math.sign(y1 - y2) * NODE_EDGE_OFFSET;
|
|
416
|
+
const dy = (yEnd - yStart) / 2;
|
|
417
|
+
const d = `M ${x1} ${yStart} C ${x1} ${yStart + dy}, ${x2} ${yEnd - dy}, ${x2} ${yEnd}`;
|
|
418
|
+
const deviation = deviationFor(edge.fromId, edge.toId);
|
|
419
|
+
// One marker per deviation rather than context-stroke, which is not
|
|
420
|
+
// supported everywhere: an arrowhead in the wrong colour reads as a
|
|
421
|
+
// different edge.
|
|
422
|
+
const marker = deviation === "minor" || deviation === "major" ? `boe-graph-arrow-${deviation}` : "boe-graph-arrow";
|
|
423
|
+
const startMarker = hasStartArrow(arrows) ? ` marker-start="url(#${marker})"` : "";
|
|
424
|
+
const endMarker = hasEndArrow(arrows) ? ` marker-end="url(#${marker})"` : "";
|
|
425
|
+
return `<path part="edge" data-deviation="${deviation}" d="${d}"${startMarker}${endMarker}></path>`;
|
|
365
426
|
})
|
|
366
427
|
.join("");
|
|
367
428
|
const rovingId = this.rovingId && byId.has(this.rovingId)
|
|
@@ -415,7 +476,7 @@ export class LineageGraph extends BaseElement {
|
|
|
415
476
|
${layout.placements.length > 0
|
|
416
477
|
? `
|
|
417
478
|
<div part="graph">
|
|
418
|
-
<svg part="edges" width="${String(graphWidth)}" height="${String(graphHeight)}" viewBox="0 0 ${String(graphWidth)} ${String(graphHeight)}" aria-hidden="true">${edgePaths}</svg>
|
|
479
|
+
<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><marker id="boe-graph-arrow-minor" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" data-deviation="minor" d="M 0 0 L 8 4 L 0 8 z"></path></marker><marker id="boe-graph-arrow-major" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" data-deviation="major" d="M 0 0 L 8 4 L 0 8 z"></path></marker></defs>${edgePaths}</svg>
|
|
419
480
|
<ol part="rows" role="list">${rows}</ol>
|
|
420
481
|
</div>
|
|
421
482
|
`
|
|
@@ -21,37 +21,100 @@ const elementStyles = `
|
|
|
21
21
|
font: inherit;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Read as a flow diagram rather than a breadcrumb. The anatomy is
|
|
25
|
+
React Flow's, taken from its stylesheet: nodes are bordered boxes
|
|
26
|
+
with a small radius (not pills), ports are 6px circles sitting on
|
|
27
|
+
the node's edge, and the connection between them is a drawn edge
|
|
28
|
+
with an arrowhead rather than a text glyph.
|
|
29
|
+
|
|
30
|
+
The canvas dot grid is the other half of the signal — a node graph
|
|
31
|
+
reads as a canvas, not as a line of text. */
|
|
24
32
|
[part="strip"] {
|
|
25
33
|
display: flex;
|
|
26
|
-
|
|
34
|
+
/* A canvas scrolls; it does not reflow. Wrapping put an edge at the
|
|
35
|
+
start of the second line with its arrow pointing into nothing,
|
|
36
|
+
which reads as broken rather than as a continuation. */
|
|
37
|
+
flex-wrap: nowrap;
|
|
38
|
+
overflow-x: auto;
|
|
27
39
|
align-items: center;
|
|
28
|
-
gap: 0
|
|
40
|
+
gap: 0;
|
|
29
41
|
margin: 0;
|
|
30
|
-
padding: 0;
|
|
42
|
+
padding: 0.75rem;
|
|
31
43
|
list-style: none;
|
|
44
|
+
border-radius: ${boeRadius.large};
|
|
45
|
+
background-image: radial-gradient(
|
|
46
|
+
color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 85%, transparent) 1px,
|
|
47
|
+
transparent 1px
|
|
48
|
+
);
|
|
49
|
+
background-size: 12px 12px;
|
|
50
|
+
background-position: -1px -1px;
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
[part="item"] {
|
|
35
54
|
display: inline-flex;
|
|
36
55
|
align-items: center;
|
|
37
|
-
|
|
56
|
+
/* No shrinking: with nowrap a long chain must scroll, not compress
|
|
57
|
+
the nodes until their labels ellipsize. */
|
|
58
|
+
flex: 0 0 auto;
|
|
38
59
|
}
|
|
39
60
|
|
|
61
|
+
/* The node. React Flow's default: 10px padding, a small radius, a 1px
|
|
62
|
+
border and a centred 12px label. Width is left to the content —
|
|
63
|
+
React Flow fixes it at 150px because it lays out on a free canvas,
|
|
64
|
+
whereas this strip is a row in a header and the labels are short. */
|
|
40
65
|
[part="chip"] {
|
|
41
66
|
appearance: none;
|
|
67
|
+
position: relative;
|
|
42
68
|
display: inline-flex;
|
|
43
69
|
align-items: center;
|
|
44
|
-
gap: 0.
|
|
70
|
+
gap: 0.35rem;
|
|
45
71
|
font: inherit;
|
|
46
|
-
font-size: 0.
|
|
72
|
+
font-size: 0.78rem;
|
|
47
73
|
font-weight: 600;
|
|
48
|
-
padding: 0.
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
padding: 0.4rem 0.6rem;
|
|
75
|
+
/* 4px, the nearest token to React Flow's 3px. The control radius is
|
|
76
|
+
20px here, which rendered the nodes as stadium pills — the shape
|
|
77
|
+
the strip was moving away from. */
|
|
78
|
+
border-radius: ${boeRadius.size};
|
|
79
|
+
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 90%, var(--boe-token-text-text, #222222));
|
|
51
80
|
background: var(--boe-token-surface-surface, #ffffff);
|
|
52
|
-
color: var(--boe-token-text-text
|
|
81
|
+
color: var(--boe-token-text-text, #222222);
|
|
82
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 6%);
|
|
53
83
|
cursor: pointer;
|
|
54
|
-
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}
|
|
84
|
+
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard},
|
|
85
|
+
border-color ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Ports. 6px circles straddling the node's edge, ringed in the surface
|
|
89
|
+
colour so they read as attached to the node rather than floating —
|
|
90
|
+
the same construction React Flow uses for its handles. */
|
|
91
|
+
[part="chip"]::before,
|
|
92
|
+
[part="chip"]::after {
|
|
93
|
+
content: "";
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset-block-start: 50%;
|
|
96
|
+
inline-size: 6px;
|
|
97
|
+
block-size: 6px;
|
|
98
|
+
border-radius: 100%;
|
|
99
|
+
background: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
100
|
+
border: 1px solid var(--boe-token-surface-surface, #ffffff);
|
|
101
|
+
transform: translateY(-50%);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[part="chip"]::before {
|
|
105
|
+
inset-inline-start: -4px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[part="chip"]::after {
|
|
109
|
+
inset-inline-end: -4px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* The first node has nothing feeding it and the last nothing leaving
|
|
113
|
+
it, so they carry one port each — React Flow's input and output
|
|
114
|
+
nodes do the same. */
|
|
115
|
+
[part="item"]:first-child [part="chip"]::before,
|
|
116
|
+
[part="item"]:last-child [part="chip"]::after {
|
|
117
|
+
display: none;
|
|
55
118
|
}
|
|
56
119
|
|
|
57
120
|
[part="chip"]:hover {
|
|
@@ -59,15 +122,19 @@ const elementStyles = `
|
|
|
59
122
|
}
|
|
60
123
|
|
|
61
124
|
[part="chip"][aria-current="true"] {
|
|
62
|
-
border-color:
|
|
125
|
+
border-color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
63
126
|
color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
64
127
|
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 8%, var(--boe-token-surface-surface, #ffffff));
|
|
65
128
|
}
|
|
66
129
|
|
|
130
|
+
[part="chip"][aria-current="true"]::before,
|
|
131
|
+
[part="chip"][aria-current="true"]::after {
|
|
132
|
+
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
133
|
+
}
|
|
134
|
+
|
|
67
135
|
[part="chip"]:focus-visible {
|
|
68
136
|
outline: none;
|
|
69
137
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
|
|
70
|
-
border-radius: ${boeRadius.large};
|
|
71
138
|
}
|
|
72
139
|
|
|
73
140
|
[part="kind-dot"] {
|
|
@@ -85,10 +152,34 @@ const elementStyles = `
|
|
|
85
152
|
border-radius: 2px;
|
|
86
153
|
}
|
|
87
154
|
|
|
155
|
+
/* The edge: a drawn line between two ports with an arrowhead, in place
|
|
156
|
+
of the "→" character the strip used to print. Two co-linear handles
|
|
157
|
+
are exactly the case where React Flow's bezier degenerates to a
|
|
158
|
+
straight line, so a straight edge is the faithful shape here rather
|
|
159
|
+
than a simplification. */
|
|
88
160
|
[part="separator"] {
|
|
89
|
-
|
|
161
|
+
position: relative;
|
|
162
|
+
display: inline-flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
inline-size: 1.6rem;
|
|
165
|
+
block-size: 2px;
|
|
166
|
+
margin-inline: 4px;
|
|
167
|
+
background: color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 90%, var(--boe-token-text-text, #222222));
|
|
90
168
|
user-select: none;
|
|
91
169
|
}
|
|
170
|
+
|
|
171
|
+
[part="separator"]::after {
|
|
172
|
+
content: "";
|
|
173
|
+
position: absolute;
|
|
174
|
+
inset-inline-end: -1px;
|
|
175
|
+
inset-block-start: 50%;
|
|
176
|
+
inline-size: 0;
|
|
177
|
+
block-size: 0;
|
|
178
|
+
border-block: 4px solid transparent;
|
|
179
|
+
border-inline-start: 6px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 90%, var(--boe-token-text-text, #222222));
|
|
180
|
+
transform: translateY(-50%);
|
|
181
|
+
}
|
|
182
|
+
|
|
92
183
|
`;
|
|
93
184
|
/**
|
|
94
185
|
* The cheap, high-frequency lineage sibling: a linear ancestry strip
|
|
@@ -175,7 +266,7 @@ export class ProvenanceStrip extends BaseElement {
|
|
|
175
266
|
const kind = node.kind ?? "";
|
|
176
267
|
return `
|
|
177
268
|
<li part="item"${kind ? ` data-kind="${escapeHtml(kind)}"` : ""}>
|
|
178
|
-
${index > 0 ? `<span part="separator" aria-hidden="true"
|
|
269
|
+
${index > 0 ? `<span part="separator" aria-hidden="true"></span>` : ""}
|
|
179
270
|
<button
|
|
180
271
|
type="button"
|
|
181
272
|
part="chip"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { countUnreadNotifications, groupNotifications, isNotificationItemRecord, resolveNotificationFilter,
|
|
1
|
+
import { countUnreadNotifications, groupNotifications, isNotificationItemRecord, resolveNotificationFilter, } from "./types.js";
|
|
2
2
|
import { BaseElement } from "../../core/index.js";
|
|
3
3
|
import { isSafeHref } from "../internal/safe-href.js";
|
|
4
4
|
import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
|
|
@@ -10,20 +10,6 @@ const escapeHtml = (value) => value
|
|
|
10
10
|
.replaceAll(">", ">")
|
|
11
11
|
.replaceAll('"', """)
|
|
12
12
|
.replaceAll("'", "'");
|
|
13
|
-
const toneColor = (tone) => {
|
|
14
|
-
switch (tone) {
|
|
15
|
-
case "brand":
|
|
16
|
-
return "var(--boe-token-surface-surface-brand, #0061d5)";
|
|
17
|
-
case "success":
|
|
18
|
-
return "var(--boe-token-surface-status-surface-success, #26a27b)";
|
|
19
|
-
case "warning":
|
|
20
|
-
return "var(--boe-token-surface-status-surface-warning, #f5b31b)";
|
|
21
|
-
case "error":
|
|
22
|
-
return "var(--boe-token-surface-status-surface-error, #ed3757)";
|
|
23
|
-
default:
|
|
24
|
-
return "var(--boe-token-text-text-secondary, #6f6f6f)";
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
13
|
const elementStyles = `
|
|
28
14
|
[hidden] {
|
|
29
15
|
display: none !important;
|
|
@@ -160,7 +146,6 @@ const elementStyles = `
|
|
|
160
146
|
align-items: start;
|
|
161
147
|
padding: 0.5rem 0.55rem;
|
|
162
148
|
border-radius: ${boeRadius.med};
|
|
163
|
-
border-left: 3px solid var(--notification-tone, transparent);
|
|
164
149
|
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 6%, transparent);
|
|
165
150
|
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
166
151
|
}
|
|
@@ -381,7 +366,6 @@ export class NotificationInbox extends BaseElement {
|
|
|
381
366
|
this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }));
|
|
382
367
|
}
|
|
383
368
|
itemHtml(item) {
|
|
384
|
-
const tone = resolveNotificationTone(item.tone);
|
|
385
369
|
const read = Boolean(item.read);
|
|
386
370
|
const entity = item.entityRef;
|
|
387
371
|
const entityHtml = entity
|
|
@@ -390,7 +374,7 @@ export class NotificationInbox extends BaseElement {
|
|
|
390
374
|
: `<span part="entity">${escapeHtml(entity.label)}</span>`
|
|
391
375
|
: "";
|
|
392
376
|
return `
|
|
393
|
-
<li part="item" data-item-id="${escapeHtml(item.id)}" data-read="${String(read)}" data-type="${escapeHtml(item.type)}"
|
|
377
|
+
<li part="item" data-item-id="${escapeHtml(item.id)}" data-read="${String(read)}" data-type="${escapeHtml(item.type)}">
|
|
394
378
|
<span part="unread-dot" aria-hidden="true"></span>
|
|
395
379
|
<span part="item-body">
|
|
396
380
|
<button type="button" part="item-title" data-item-id="${escapeHtml(item.id)}">${escapeHtml(item.title)}</button>
|
|
@@ -60,6 +60,14 @@ const elementStyles = `
|
|
|
60
60
|
margin: 0;
|
|
61
61
|
padding: 0;
|
|
62
62
|
display: grid;
|
|
63
|
+
|
|
64
|
+
/* Named so the spine can derive how far it has to reach rather than
|
|
65
|
+
restating the numbers. The line has to cross this entry's bottom
|
|
66
|
+
padding, the next entry's top padding and the marker's own offset
|
|
67
|
+
to arrive at the next marker; spelling that out as arithmetic
|
|
68
|
+
keeps it correct if any of the three changes. */
|
|
69
|
+
--timeline-event-pad: 0.55rem;
|
|
70
|
+
--timeline-marker-offset: 0.3rem;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
[part="event"] {
|
|
@@ -67,29 +75,53 @@ const elementStyles = `
|
|
|
67
75
|
display: grid;
|
|
68
76
|
grid-template-columns: auto 1fr;
|
|
69
77
|
gap: 0.65rem;
|
|
70
|
-
padding:
|
|
78
|
+
padding: var(--timeline-event-pad) 0 var(--timeline-event-pad) 0.15rem;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
/* The
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
/* The marker column, built the way box-path's base variant builds its
|
|
82
|
+
rail: the marker is a fixed box and the spine is a flex sibling that
|
|
83
|
+
takes whatever height is left, so the line runs from the marker to
|
|
84
|
+
the bottom of the entry however tall the entry's content turns out
|
|
85
|
+
to be.
|
|
86
|
+
|
|
87
|
+
The previous version positioned the spine with hand-tuned constants
|
|
88
|
+
(top: 1.9rem, bottom: -0.35rem) that guessed where the marker ended.
|
|
89
|
+
They were only ever right for one font size and one content height,
|
|
90
|
+
which is why the line stopped short of the next marker and dangled. */
|
|
91
|
+
[part="marker-rail"] {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
align-items: center;
|
|
95
|
+
align-self: stretch;
|
|
96
|
+
margin-top: var(--timeline-marker-offset);
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
[part="marker"] {
|
|
100
|
+
flex: 0 0 auto;
|
|
85
101
|
inline-size: 1.1rem;
|
|
86
102
|
block-size: 1.1rem;
|
|
87
|
-
margin-top: 0.3rem;
|
|
88
103
|
border-radius: 999px;
|
|
89
104
|
border: 2px solid var(--timeline-tone, var(--boe-token-text-text-secondary, #6f6f6f));
|
|
90
105
|
background: color-mix(in srgb, var(--timeline-tone, var(--boe-token-text-text-secondary, #6f6f6f)) 14%, var(--boe-token-surface-surface, #ffffff) 86%);
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
/* The spine. Grows to fill the column, and the negative bottom margin
|
|
109
|
+
carries it across the gap into the next entry's marker so the two
|
|
110
|
+
actually meet. Hidden on the last entry, which leads nowhere. */
|
|
111
|
+
[part="marker-rail"]::after {
|
|
112
|
+
content: "";
|
|
113
|
+
flex: 1 1 auto;
|
|
114
|
+
inline-size: 2px;
|
|
115
|
+
margin-block: 0.25rem
|
|
116
|
+
calc(-1 * (var(--timeline-event-pad) * 2 + var(--timeline-marker-offset)));
|
|
117
|
+
border-radius: 999px;
|
|
118
|
+
background: var(--boe-token-stroke-stroke, #e8e8e8);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[part="event"]:last-child [part="marker-rail"]::after {
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
93
125
|
[part="event-body"] {
|
|
94
126
|
display: grid;
|
|
95
127
|
gap: 0.25rem;
|
|
@@ -328,7 +360,7 @@ export class Timeline extends BaseElement {
|
|
|
328
360
|
const timestamp = event.timestamp ? formatItemDate(event.timestamp) : "";
|
|
329
361
|
return `
|
|
330
362
|
<li part="event" data-event-id="${escapeHtml(event.id)}" style="--timeline-tone:${toneColor(tone)};">
|
|
331
|
-
<span part="marker" aria-hidden="true"></span>
|
|
363
|
+
<span part="marker-rail" aria-hidden="true"><span part="marker"></span></span>
|
|
332
364
|
<div part="event-body">
|
|
333
365
|
<div part="event-topline">
|
|
334
366
|
${event.actor ? `<span part="actor">${escapeHtml(event.actor.name)}</span>` : ""}
|
|
@@ -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.11.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",
|