@unofficialbox/box-open-elements 0.28.7 → 0.29.1
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/README.md +13 -13
- package/dist/element-maps.d.ts +15 -1
- package/dist/entries/box-plot.d.ts +1 -0
- package/dist/entries/box-plot.js +1 -0
- package/dist/entries/trace-waterfall.d.ts +1 -0
- package/dist/entries/trace-waterfall.js +1 -0
- package/dist/native-types.d.ts +2491 -0
- package/dist/native-types.js +1 -0
- package/dist/patterns/form-wizard/form-wizard.d.ts +9 -1
- package/dist/patterns/insights/box-plot.d.ts +55 -0
- package/dist/patterns/insights/box-plot.js +201 -0
- package/dist/patterns/insights/index.d.ts +1 -0
- package/dist/patterns/insights/index.js +1 -0
- package/dist/patterns/process-modeler/design.d.ts +1 -1
- package/dist/patterns/process-modeler/design.js +7 -1
- package/dist/patterns/process-modeler/process-modeler.d.ts +9 -0
- package/dist/patterns/process-modeler/process-modeler.js +175 -43
- package/dist/patterns/run/index.d.ts +1 -0
- package/dist/patterns/run/index.js +1 -0
- package/dist/patterns/run/run-trace.d.ts +3 -0
- package/dist/patterns/run/run-trace.js +52 -13
- package/dist/patterns/run/trace-waterfall.d.ts +80 -0
- package/dist/patterns/run/trace-waterfall.js +360 -0
- package/dist/react-jsx.d.ts +21 -0
- package/dist/react-jsx.js +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
export type TraceSpanStatus = "ok" | "failed" | "skipped" | "running";
|
|
3
|
+
export interface TraceMarker {
|
|
4
|
+
atMs: number;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TraceSpan {
|
|
8
|
+
id: string;
|
|
9
|
+
parentId?: string;
|
|
10
|
+
label: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
startMs: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
status: TraceSpanStatus;
|
|
15
|
+
system?: boolean;
|
|
16
|
+
markers?: TraceMarker[];
|
|
17
|
+
detail?: {
|
|
18
|
+
input?: unknown;
|
|
19
|
+
response?: unknown;
|
|
20
|
+
attributes?: unknown;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface TraceRow {
|
|
24
|
+
span: TraceSpan;
|
|
25
|
+
comparisonOnly: boolean;
|
|
26
|
+
parentId?: string;
|
|
27
|
+
depth: number;
|
|
28
|
+
hasChildren: boolean;
|
|
29
|
+
expanded: boolean;
|
|
30
|
+
position: number;
|
|
31
|
+
siblings: number;
|
|
32
|
+
}
|
|
33
|
+
export interface TraceLayout {
|
|
34
|
+
rows: TraceRow[];
|
|
35
|
+
hiddenCount: number;
|
|
36
|
+
endMs: number;
|
|
37
|
+
}
|
|
38
|
+
/** Invalid/duplicate spans are omitted; invalid parent relationships become roots. */
|
|
39
|
+
export declare function validTraceSpans(spans: TraceSpan[]): TraceSpan[];
|
|
40
|
+
export declare function traceSpanDuration(span: TraceSpan, nowMs: number): number;
|
|
41
|
+
/** Stable report layout: input sibling order, one zero-based axis, no browser dependency. */
|
|
42
|
+
export declare function traceWaterfallLayout(spans: TraceSpan[], options?: {
|
|
43
|
+
showSystem?: boolean;
|
|
44
|
+
search?: string;
|
|
45
|
+
collapsed?: ReadonlySet<string>;
|
|
46
|
+
nowMs?: number;
|
|
47
|
+
comparisonSpans?: TraceSpan[];
|
|
48
|
+
}): TraceLayout;
|
|
49
|
+
/** Host-driven live or completed trace; requests selection, never owns host sessions. */
|
|
50
|
+
export declare class TraceWaterfall extends BaseElement {
|
|
51
|
+
static readonly tagName = "box-trace-waterfall";
|
|
52
|
+
static get observedAttributes(): string[];
|
|
53
|
+
private _spans;
|
|
54
|
+
private _comparisonSpans;
|
|
55
|
+
private collapsed;
|
|
56
|
+
private search;
|
|
57
|
+
private table;
|
|
58
|
+
private code;
|
|
59
|
+
private fullScreen;
|
|
60
|
+
private focusedId;
|
|
61
|
+
get heading(): string;
|
|
62
|
+
set heading(value: string);
|
|
63
|
+
get spans(): TraceSpan[];
|
|
64
|
+
set spans(value: TraceSpan[]);
|
|
65
|
+
get comparisonSpans(): TraceSpan[];
|
|
66
|
+
set comparisonSpans(value: TraceSpan[]);
|
|
67
|
+
get showSystem(): boolean;
|
|
68
|
+
set showSystem(value: boolean);
|
|
69
|
+
get selectedSpanId(): string;
|
|
70
|
+
set selectedSpanId(value: string);
|
|
71
|
+
get nowMs(): number;
|
|
72
|
+
set nowMs(value: number);
|
|
73
|
+
get layout(): TraceLayout;
|
|
74
|
+
protected renderTemplate(): void;
|
|
75
|
+
private select;
|
|
76
|
+
private focusRow;
|
|
77
|
+
private toggle;
|
|
78
|
+
protected setupListeners(): void;
|
|
79
|
+
protected update(): void;
|
|
80
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
3
|
+
const escape = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4
|
+
const statuses = ["ok", "failed", "skipped", "running"];
|
|
5
|
+
const statusLabels = { ok: "Succeeded", failed: "Failed", skipped: "Skipped", running: "Running" };
|
|
6
|
+
const statusShapes = { ok: "✓", failed: "×", skipped: "−", running: "◷" };
|
|
7
|
+
const validMarkers = (span) => Array.isArray(span.markers)
|
|
8
|
+
? span.markers.filter(marker => marker && finite(marker.atMs) && marker.atMs >= 0 && typeof marker.label === "string")
|
|
9
|
+
: [];
|
|
10
|
+
const duration = (value) => value >= 1000 ? `${(value / 1000).toLocaleString(undefined, { maximumFractionDigits: 2 })} s` : `${value.toLocaleString(undefined, { maximumFractionDigits: 2 })} ms`;
|
|
11
|
+
/** Invalid/duplicate spans are omitted; invalid parent relationships become roots. */
|
|
12
|
+
export function validTraceSpans(spans) {
|
|
13
|
+
const ids = new Set();
|
|
14
|
+
return spans.filter(span => {
|
|
15
|
+
if (!span || typeof span.id !== "string" || !span.id.trim() || ids.has(span.id) || typeof span.label !== "string" || !span.label.trim() || typeof span.kind !== "string" || !finite(span.startMs) || span.startMs < 0 || !finite(span.durationMs) || span.durationMs < 0 || !finite(span.startMs + span.durationMs) || !statuses.includes(span.status))
|
|
16
|
+
return false;
|
|
17
|
+
ids.add(span.id);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function traceSpanDuration(span, nowMs) {
|
|
22
|
+
return span.status === "running" && finite(nowMs) ? Math.max(span.durationMs, nowMs - span.startMs, 0) : span.durationMs;
|
|
23
|
+
}
|
|
24
|
+
/** Stable report layout: input sibling order, one zero-based axis, no browser dependency. */
|
|
25
|
+
export function traceWaterfallLayout(spans, options = {}) {
|
|
26
|
+
const current = validTraceSpans(spans);
|
|
27
|
+
const currentIds = new Set(current.map(span => span.id));
|
|
28
|
+
const earlier = validTraceSpans(options.comparisonSpans ?? []);
|
|
29
|
+
const valid = [...current, ...earlier.filter(span => !currentIds.has(span.id))];
|
|
30
|
+
const byId = new Map(valid.map(span => [span.id, span]));
|
|
31
|
+
const parents = new Map();
|
|
32
|
+
for (const span of valid) {
|
|
33
|
+
const seen = new Set([span.id]);
|
|
34
|
+
let next = span.parentId;
|
|
35
|
+
let cyclic = false;
|
|
36
|
+
while (next && byId.has(next)) {
|
|
37
|
+
if (seen.has(next)) {
|
|
38
|
+
cyclic = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
seen.add(next);
|
|
42
|
+
next = byId.get(next)?.parentId;
|
|
43
|
+
}
|
|
44
|
+
if (!cyclic && span.parentId && byId.has(span.parentId))
|
|
45
|
+
parents.set(span.id, span.parentId);
|
|
46
|
+
}
|
|
47
|
+
const visible = valid.filter(span => options.showSystem || !span.system);
|
|
48
|
+
const visibleIds = new Set(visible.map(span => span.id));
|
|
49
|
+
const visibleParents = new Map();
|
|
50
|
+
for (const span of visible) {
|
|
51
|
+
let parent = parents.get(span.id);
|
|
52
|
+
while (parent && !visibleIds.has(parent))
|
|
53
|
+
parent = parents.get(parent);
|
|
54
|
+
if (parent)
|
|
55
|
+
visibleParents.set(span.id, parent);
|
|
56
|
+
}
|
|
57
|
+
const query = options.search?.trim().toLocaleLowerCase() ?? "";
|
|
58
|
+
const keep = new Set();
|
|
59
|
+
for (const span of visible) {
|
|
60
|
+
if (!query || `${span.label} ${span.kind} ${statusLabels[span.status]}`.toLocaleLowerCase().includes(query)) {
|
|
61
|
+
keep.add(span.id);
|
|
62
|
+
let parent = visibleParents.get(span.id);
|
|
63
|
+
while (parent) {
|
|
64
|
+
keep.add(parent);
|
|
65
|
+
parent = visibleParents.get(parent);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const children = new Map();
|
|
70
|
+
for (const span of visible) {
|
|
71
|
+
if (!keep.has(span.id))
|
|
72
|
+
continue;
|
|
73
|
+
const parent = visibleParents.get(span.id);
|
|
74
|
+
children.set(parent, [...(children.get(parent) ?? []), span]);
|
|
75
|
+
}
|
|
76
|
+
const rows = [];
|
|
77
|
+
const visit = (parentId, depth) => {
|
|
78
|
+
const siblings = children.get(parentId) ?? [];
|
|
79
|
+
siblings.forEach((span, index) => {
|
|
80
|
+
const hasChildren = Boolean(children.get(span.id)?.length);
|
|
81
|
+
const expanded = hasChildren && (Boolean(query) || !options.collapsed?.has(span.id));
|
|
82
|
+
rows.push({ span, comparisonOnly: !currentIds.has(span.id), parentId, depth, hasChildren, expanded, position: index + 1, siblings: siblings.length });
|
|
83
|
+
if (expanded)
|
|
84
|
+
visit(span.id, depth + 1);
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
visit(undefined, 0);
|
|
88
|
+
const all = [...current, ...earlier];
|
|
89
|
+
let endMs = 1;
|
|
90
|
+
for (const span of all) {
|
|
91
|
+
endMs = Math.max(endMs, span.startMs + traceSpanDuration(span, options.nowMs ?? 0));
|
|
92
|
+
for (const marker of validMarkers(span))
|
|
93
|
+
endMs = Math.max(endMs, marker.atMs);
|
|
94
|
+
}
|
|
95
|
+
return { rows, hiddenCount: valid.filter(span => span.system).length, endMs };
|
|
96
|
+
}
|
|
97
|
+
const styles = `
|
|
98
|
+
:host { display:block; min-width:0; container-type:inline-size; color:var(--boe-token-text-text,#1f1e1b); font:inherit }
|
|
99
|
+
:host([hidden]) { display:none!important }
|
|
100
|
+
[part=panel] { border:1px solid var(--boe-token-stroke-stroke,#dedfe4); border-radius:.75rem; padding:1rem; background:var(--boe-token-surface-surface,#fff); min-width:0 }
|
|
101
|
+
[part=header], [part=controls], [part=detail-controls] { display:flex; gap:.5rem; flex-wrap:wrap; align-items:center }
|
|
102
|
+
[part=header] { justify-content:space-between; margin-bottom:.75rem } h2,h3 { font:inherit; font-weight:700; margin:0; min-width:0; max-width:100%; overflow-wrap:anywhere }
|
|
103
|
+
[part=controls] { margin-bottom:.75rem } input { font:inherit; width:12rem; max-width:100%; box-sizing:border-box; color:inherit; background:inherit; border:1px solid var(--boe-token-stroke-stroke,#dedfe4); border-radius:.3rem; padding:.4rem }
|
|
104
|
+
button { font:inherit; color:inherit; background:inherit; border:1px solid var(--boe-token-stroke-stroke,#dedfe4); border-radius:.3rem; padding:.35rem .5rem; cursor:pointer; min-height:24px }
|
|
105
|
+
button:disabled { opacity:.6; cursor:default } :is(button,input,[role=row]):focus-visible { outline:2px solid var(--boe-token-surface-surface-brand,#0061d5); outline-offset:2px }
|
|
106
|
+
[part=body] { display:grid; grid-template-columns:minmax(0,1fr); gap:1rem; min-width:0 } [part=body][data-details] { grid-template-columns:minmax(0,2fr) minmax(12rem,1fr) }
|
|
107
|
+
[part=treegrid] { min-width:0 } [part=columns], [part=row] { display:grid; grid-template-columns:minmax(10rem,1fr) minmax(0,2fr); gap:.75rem; align-items:center; min-width:0 }
|
|
108
|
+
[part=columns] { font-size:.75rem; font-weight:600; padding:.5rem; color:var(--boe-token-text-text-secondary,#626b7d) }
|
|
109
|
+
[part=row] { padding:.45rem .5rem; border-radius:.25rem; cursor:pointer; border:1px solid transparent }
|
|
110
|
+
[part=row]:hover { background:var(--boe-token-surface-surface-secondary,#f5f7fb) } [part=row][aria-selected=true] { background:var(--boe-trace-selected,var(--boe-token-surface-surface-secondary,#f5f7fb)); border-color:var(--boe-token-surface-surface-brand,#0061d5) }
|
|
111
|
+
[part=label-cell] { display:flex; gap:.3rem; align-items:center; min-width:0; padding-left:var(--indent) } [part=label] { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0 }
|
|
112
|
+
[part=kind], [part=status], [part=duration] { font-size:.75rem } [part=kind] { min-width:0; max-width:35%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--boe-token-text-text-secondary,#626b7d) } [part=expand] { width:24px; height:24px; min-width:24px; border:0; padding:0 } [part=expand-space] { width:24px; min-width:24px }
|
|
113
|
+
[part=timing] { min-width:0 } [part=timing-label] { display:flex; gap:.4rem; flex-wrap:wrap; margin-bottom:.2rem }
|
|
114
|
+
[part=track] { position:relative; height:1.1rem; background:var(--boe-trace-track,var(--boe-token-surface-surface-secondary,#f5f7fb)); border-radius:.15rem }
|
|
115
|
+
[part=bar] { position:absolute; top:.2rem; height:.7rem; min-width:2px; background:var(--boe-trace-bar,var(--boe-token-surface-surface-brand,#0061d5)); border-radius:.1rem }
|
|
116
|
+
[data-parent] [part=bar] { background:var(--boe-trace-parent-bar,var(--boe-token-text-text,#1f1e1b)); height:.9rem; top:.1rem }
|
|
117
|
+
[data-status=failed] [part=bar] { background:var(--boe-trace-failed,var(--boe-token-surface-surface-brand,#0061d5)); border:2px dashed var(--boe-token-text-text,#1f1e1b); box-sizing:border-box }
|
|
118
|
+
[data-status=skipped] [part=bar] { background:transparent; border:1px dashed var(--boe-token-text-text-secondary,#626b7d); box-sizing:border-box }
|
|
119
|
+
[part=comparison-bar] { position:absolute; top:.65rem; height:.35rem; min-width:2px; border:1px solid var(--boe-trace-bar,var(--boe-token-surface-surface-brand,#0061d5)); background:var(--boe-trace-track,var(--boe-token-surface-surface-secondary,#f5f7fb)); box-sizing:border-box }
|
|
120
|
+
[part=marker], [part=comparison-marker] { position:absolute; top:0; height:100%; width:0; border-left:2px solid var(--boe-token-text-text,#1f1e1b) }
|
|
121
|
+
[part=comparison-marker] { border-left-style:dashed }
|
|
122
|
+
[part=axis] { display:flex; justify-content:space-between; gap:.5rem; font-size:.75rem }
|
|
123
|
+
[part=details] { border-left:1px solid var(--boe-token-stroke-stroke,#dedfe4); padding-left:1rem; min-width:0; overflow-wrap:anywhere } [part=detail-section] { margin-top:.8rem } pre { font:inherit; font-family:monospace; font-size:.8rem; white-space:pre-wrap; overflow-wrap:anywhere; margin:.35rem 0 } [part=detail-text] { white-space:pre-wrap; font-size:.8rem }
|
|
124
|
+
[part=note], [part=empty] { font-size:.8rem; margin:.6rem 0; color:var(--boe-token-text-text-secondary,#626b7d) }
|
|
125
|
+
table { border-collapse:collapse; width:100%; table-layout:fixed; font-size:.8rem } caption { text-align:left; font-weight:600; padding:.5rem 0 } th,td { text-align:left; vertical-align:top; padding:.4rem; border-bottom:1px solid var(--boe-token-stroke-stroke,#dedfe4); overflow-wrap:anywhere } th:first-child { width:35% }
|
|
126
|
+
[part=fullscreen] { position:fixed; inset:0; width:100%; height:100%; max-width:none; max-height:none; margin:0; border:0; color:inherit; z-index:1000; overflow:auto; background:var(--boe-token-surface-surface,#fff); padding:1rem; box-sizing:border-box }
|
|
127
|
+
@container (max-width:650px) { [part=panel] { padding:.75rem } [part=body][data-details] { grid-template-columns:minmax(0,1fr) } [part=columns], [part=row] { grid-template-columns:minmax(0,1fr) } [part=columns] [part=axis] { display:none } [part=timing] { padding-left:1.5rem } [part=details] { border-left:0; border-top:1px solid var(--boe-token-stroke-stroke,#dedfe4); padding:1rem 0 0 } [part=label-cell] { padding-left:min(var(--indent),4rem) } }
|
|
128
|
+
@media(prefers-reduced-motion:reduce) { *,*::before,*::after { animation:none!important; transition:none!important } }
|
|
129
|
+
`;
|
|
130
|
+
/** Host-driven live or completed trace; requests selection, never owns host sessions. */
|
|
131
|
+
export class TraceWaterfall extends BaseElement {
|
|
132
|
+
static tagName = "box-trace-waterfall";
|
|
133
|
+
static get observedAttributes() { return ["heading", "spans", "show-system", "selected-span-id", "now-ms"]; }
|
|
134
|
+
_spans = null;
|
|
135
|
+
_comparisonSpans = [];
|
|
136
|
+
collapsed = new Set();
|
|
137
|
+
search = "";
|
|
138
|
+
table = false;
|
|
139
|
+
code = false;
|
|
140
|
+
fullScreen = false;
|
|
141
|
+
focusedId = null;
|
|
142
|
+
get heading() { return this.getAttribute("heading")?.trim() || "Trace"; }
|
|
143
|
+
set heading(value) { this.setAttribute("heading", value); }
|
|
144
|
+
get spans() { if (this._spans)
|
|
145
|
+
return this._spans; try {
|
|
146
|
+
const parsed = JSON.parse(this.getAttribute("spans") ?? "[]");
|
|
147
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return [];
|
|
151
|
+
} }
|
|
152
|
+
set spans(value) { this._spans = value; this.update(); }
|
|
153
|
+
get comparisonSpans() { return this._comparisonSpans; }
|
|
154
|
+
set comparisonSpans(value) { this._comparisonSpans = value; this.update(); }
|
|
155
|
+
get showSystem() { return this.hasAttribute("show-system"); }
|
|
156
|
+
set showSystem(value) { this.toggleAttribute("show-system", value); }
|
|
157
|
+
get selectedSpanId() { return this.getAttribute("selected-span-id") ?? ""; }
|
|
158
|
+
set selectedSpanId(value) { if (value)
|
|
159
|
+
this.setAttribute("selected-span-id", value);
|
|
160
|
+
else
|
|
161
|
+
this.removeAttribute("selected-span-id"); }
|
|
162
|
+
get nowMs() { const value = Number(this.getAttribute("now-ms") ?? 0); return finite(value) && value >= 0 ? value : 0; }
|
|
163
|
+
set nowMs(value) { this.setAttribute("now-ms", String(value)); }
|
|
164
|
+
get layout() { return traceWaterfallLayout(this.spans, { showSystem: this.showSystem, search: this.search, collapsed: this.collapsed, nowMs: this.nowMs, comparisonSpans: this.comparisonSpans }); }
|
|
165
|
+
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles}</style><div part="content-host"></div>`; }
|
|
166
|
+
select(id) {
|
|
167
|
+
this.selectedSpanId = id;
|
|
168
|
+
const row = this.layout.rows.find(row => row.span.id === id);
|
|
169
|
+
const span = row?.span;
|
|
170
|
+
this.dispatchEvent(new CustomEvent("span-selected", { detail: { spanId: id, span, comparisonOnly: row?.comparisonOnly ?? false }, bubbles: true, composed: true }));
|
|
171
|
+
}
|
|
172
|
+
focusRow(id) {
|
|
173
|
+
this.focusedId = id;
|
|
174
|
+
const rows = this.shadowRoot.querySelectorAll('[part="row"]');
|
|
175
|
+
rows.forEach(row => { row.tabIndex = row.dataset.spanId === id ? 0 : -1; if (row.tabIndex === 0)
|
|
176
|
+
row.focus(); });
|
|
177
|
+
if (!rows.length)
|
|
178
|
+
this.shadowRoot.querySelectorAll("[data-select-id]").forEach(button => { if (button.dataset.selectId === id)
|
|
179
|
+
button.focus(); });
|
|
180
|
+
}
|
|
181
|
+
toggle(row) {
|
|
182
|
+
if (!row.hasChildren || this.search.trim())
|
|
183
|
+
return;
|
|
184
|
+
if (this.collapsed.has(row.span.id))
|
|
185
|
+
this.collapsed.delete(row.span.id);
|
|
186
|
+
else
|
|
187
|
+
this.collapsed.add(row.span.id);
|
|
188
|
+
this.update();
|
|
189
|
+
this.focusRow(row.span.id);
|
|
190
|
+
this.dispatchEvent(new CustomEvent("span-toggled", { detail: { spanId: row.span.id, expanded: !this.collapsed.has(row.span.id) }, bubbles: true, composed: true }));
|
|
191
|
+
}
|
|
192
|
+
setupListeners() {
|
|
193
|
+
this.shadowRoot.addEventListener("keydown", event => {
|
|
194
|
+
if (!(event instanceof KeyboardEvent))
|
|
195
|
+
return;
|
|
196
|
+
const target = event.target;
|
|
197
|
+
if (event.key === "Escape") {
|
|
198
|
+
if (this.selectedSpanId) {
|
|
199
|
+
const id = this.selectedSpanId;
|
|
200
|
+
this.selectedSpanId = "";
|
|
201
|
+
this.focusRow(id);
|
|
202
|
+
}
|
|
203
|
+
else if (this.fullScreen) {
|
|
204
|
+
this.fullScreen = false;
|
|
205
|
+
this.update();
|
|
206
|
+
this.shadowRoot.querySelector('[part="fullscreen-toggle"]')?.focus();
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
return;
|
|
210
|
+
event.preventDefault();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const element = target.closest('[part="row"]');
|
|
214
|
+
if (!element || target !== element)
|
|
215
|
+
return;
|
|
216
|
+
const rows = this.layout.rows;
|
|
217
|
+
const index = rows.findIndex(row => row.span.id === element.dataset.spanId);
|
|
218
|
+
const row = rows[index];
|
|
219
|
+
if (!row)
|
|
220
|
+
return;
|
|
221
|
+
let next;
|
|
222
|
+
switch (event.key) {
|
|
223
|
+
case "ArrowDown":
|
|
224
|
+
next = rows[index + 1]?.span.id;
|
|
225
|
+
break;
|
|
226
|
+
case "ArrowUp":
|
|
227
|
+
next = rows[index - 1]?.span.id;
|
|
228
|
+
break;
|
|
229
|
+
case "Home":
|
|
230
|
+
next = rows[0]?.span.id;
|
|
231
|
+
break;
|
|
232
|
+
case "End":
|
|
233
|
+
next = rows.at(-1)?.span.id;
|
|
234
|
+
break;
|
|
235
|
+
case "ArrowRight":
|
|
236
|
+
if (row.hasChildren && !row.expanded)
|
|
237
|
+
this.toggle(row);
|
|
238
|
+
else if (row.hasChildren)
|
|
239
|
+
next = rows[index + 1]?.span.id;
|
|
240
|
+
break;
|
|
241
|
+
case "ArrowLeft":
|
|
242
|
+
if (row.hasChildren && row.expanded && !this.search.trim())
|
|
243
|
+
this.toggle(row);
|
|
244
|
+
else
|
|
245
|
+
next = row.parentId;
|
|
246
|
+
break;
|
|
247
|
+
case "Enter":
|
|
248
|
+
case " ":
|
|
249
|
+
this.select(row.span.id);
|
|
250
|
+
break;
|
|
251
|
+
default: return;
|
|
252
|
+
}
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
if (next)
|
|
255
|
+
this.focusRow(next);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
update() {
|
|
259
|
+
const host = this.shadowRoot?.querySelector('[part="content-host"]');
|
|
260
|
+
if (!host)
|
|
261
|
+
return;
|
|
262
|
+
const active = this.shadowRoot?.activeElement;
|
|
263
|
+
const activePart = active?.getAttribute("part");
|
|
264
|
+
const wasRow = activePart === "row";
|
|
265
|
+
const activeSelectId = active?.getAttribute("data-select-id");
|
|
266
|
+
const activeRowId = active?.closest('[part="row"]')?.dataset.spanId;
|
|
267
|
+
const menuOpen = this.shadowRoot?.querySelector('[part="menu"]')?.open ?? false;
|
|
268
|
+
const searchSelection = active instanceof HTMLInputElement ? [active.selectionStart, active.selectionEnd] : null;
|
|
269
|
+
const { rows, hiddenCount, endMs } = this.layout;
|
|
270
|
+
if (!rows.some(row => row.span.id === this.focusedId))
|
|
271
|
+
this.focusedId = rows[0]?.span.id ?? null;
|
|
272
|
+
const allIds = new Set([...validTraceSpans(this.spans), ...validTraceSpans(this.comparisonSpans)].map(span => span.id));
|
|
273
|
+
this.collapsed.forEach(id => { if (!allIds.has(id))
|
|
274
|
+
this.collapsed.delete(id); });
|
|
275
|
+
const comparison = new Map(validTraceSpans(this.comparisonSpans).map(span => [span.id, span]));
|
|
276
|
+
const position = (ms) => Math.max(0, Math.min(100, ms / endMs * 100));
|
|
277
|
+
const geometry = (span) => `left:${position(span.startMs)}%;width:${position(span.startMs + traceSpanDuration(span, this.nowMs)) - position(span.startMs)}%`;
|
|
278
|
+
const markerList = validMarkers;
|
|
279
|
+
const summary = (span, comparisonOnly = false) => {
|
|
280
|
+
const previous = comparison.get(span.id);
|
|
281
|
+
const current = comparisonOnly ? `${span.label}, Not in current trace` : `${span.label}, ${statusLabels[span.status]}, starts at ${duration(span.startMs)}, takes ${duration(traceSpanDuration(span, this.nowMs))}${markerList(span).map(marker => `, ${marker.label} at ${duration(marker.atMs)}`).join("")}`;
|
|
282
|
+
return current + (!comparison.size ? "" : previous ? `; comparison ${statusLabels[previous.status]}, starts at ${duration(previous.startMs)}, takes ${duration(traceSpanDuration(previous, this.nowMs))}${markerList(previous).map(marker => `, comparison marker ${marker.label} at ${duration(marker.atMs)}`).join("")}` : "; not in comparison trace");
|
|
283
|
+
};
|
|
284
|
+
const rowHtml = rows.map(row => {
|
|
285
|
+
const span = row.span;
|
|
286
|
+
const previous = comparison.get(span.id);
|
|
287
|
+
return `<div part="row" role="row" data-span-id="${escape(span.id)}" data-status="${span.status}"${row.hasChildren ? " data-parent" : ""} tabindex="${span.id === this.focusedId ? 0 : -1}" aria-level="${row.depth + 1}" aria-posinset="${row.position}" aria-setsize="${row.siblings}"${row.hasChildren ? ` aria-expanded="${row.expanded}"` : ""} aria-selected="${span.id === this.selectedSpanId}" aria-label="${escape(summary(span, row.comparisonOnly))}"><div role="gridcell" part="label-cell" style="--indent:${row.depth * 1.15}rem">${row.hasChildren ? `<button part="expand" type="button" tabindex="-1" aria-label="${row.expanded ? "Collapse" : "Expand"} ${escape(span.label)}"${this.search.trim() ? " disabled" : ""}>${row.expanded ? "▾" : "▸"}</button>` : '<span part="expand-space"></span>'}<span part="label" title="${escape(span.label)}">${escape(span.label)}</span><span part="kind">${escape(span.kind)}</span></div><div role="gridcell" part="timing"><div part="timing-label"><span part="status">${row.comparisonOnly ? "Comparison only · " : ""}${statusShapes[span.status]} ${statusLabels[span.status]}</span><span part="duration">${duration(traceSpanDuration(span, this.nowMs))}</span></div><div part="track" aria-hidden="true">${row.comparisonOnly ? "" : `<span part="bar" style="${geometry(span)}"></span>`}${previous ? `<span part="comparison-bar" style="${geometry(previous)}"></span>` : ""}${(row.comparisonOnly ? [] : markerList(span)).map(marker => `<span part="marker" title="${escape(marker.label)}: ${duration(marker.atMs)}" style="left:${position(marker.atMs)}%"></span>`).join("")}${previous ? markerList(previous).map(marker => `<span part="comparison-marker" title="Comparison ${escape(marker.label)}: ${duration(marker.atMs)}" style="left:${position(marker.atMs)}%"></span>`).join("") : ""}</div></div></div>`;
|
|
288
|
+
}).join("");
|
|
289
|
+
const tableRows = rows.map(row => `<tr><th scope="row"><button type="button" data-select-id="${escape(row.span.id)}">${escape(row.span.label)}</button><div>${escape(row.span.kind)}</div></th><td>${row.comparisonOnly ? "Comparison only · " : ""}${statusLabels[row.span.status]}</td><td>${row.comparisonOnly ? "Not in current trace" : `${duration(row.span.startMs)}<br>${duration(traceSpanDuration(row.span, this.nowMs))}${markerList(row.span).map(marker => `<br>${escape(marker.label)}: ${duration(marker.atMs)}`).join("")}`}${comparison.has(row.span.id) ? `<br>Comparison: ${duration(comparison.get(row.span.id).startMs)} / ${duration(traceSpanDuration(comparison.get(row.span.id), this.nowMs))}${markerList(comparison.get(row.span.id)).map(marker => `<br>Comparison marker ${escape(marker.label)}: ${duration(marker.atMs)}`).join("")}` : comparison.size ? "<br>Not in comparison trace" : ""}</td></tr>`).join("");
|
|
290
|
+
const selected = rows.find(row => row.span.id === this.selectedSpanId)?.span;
|
|
291
|
+
const selectedIndex = rows.findIndex(row => row.span.id === this.selectedSpanId);
|
|
292
|
+
const detailValue = (value) => { if (typeof value === "string")
|
|
293
|
+
return value; try {
|
|
294
|
+
return JSON.stringify(value, null, this.code ? 2 : 0) ?? "";
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return "Details are unavailable.";
|
|
298
|
+
} };
|
|
299
|
+
const details = selected ? `<aside part="details" aria-label="Span details"><h3>${escape(selected.label)}</h3><p part="note">${escape(summary(selected, Boolean(rows[selectedIndex]?.comparisonOnly)))}</p><div part="detail-controls"><button part="previous-span" type="button"${selectedIndex <= 0 ? " disabled" : ""}>Previous span</button><button part="next-span" type="button"${selectedIndex === rows.length - 1 ? " disabled" : ""}>Next span</button><button part="detail-mode" type="button" aria-pressed="${this.code}">${this.code ? "Show text" : "Show code"}</button><button part="close-details" type="button">Close details</button></div><slot name="details">${["input", "response", "attributes"].map(key => `<section part="detail-section"><h3>${key[0].toUpperCase() + key.slice(1)}</h3>${this.code ? `<pre>${escape(detailValue(selected.detail?.[key]))}</pre>` : `<div part="detail-text">${escape(detailValue(selected.detail?.[key])) || "No details supplied."}</div>`}</section>`).join("")}</slot></aside>` : "";
|
|
300
|
+
host.innerHTML = `<${this.fullScreen ? 'dialog part="fullscreen" aria-label="Expanded trace"' : "div"}><section part="panel" aria-label="${escape(this.heading)}"><header part="header"><h2>${escape(this.heading)}</h2><slot name="session"></slot><button part="fullscreen-toggle" type="button" aria-pressed="${this.fullScreen}">${this.fullScreen ? "Exit full screen" : "Expand to full screen"}</button></header><div part="controls"><label>Search spans <input part="search" type="search" value="${escape(this.search)}"></label><button part="table-toggle" type="button" aria-pressed="${this.table}">${this.table ? "Show waterfall" : "Show as a table"}</button><details part="menu"><summary part="menu-summary">Trace options</summary><button part="expand-all" type="button">Expand all</button><button part="collapse-all" type="button">Collapse all</button></details></div>${hiddenCount ? `<p part="note">${this.showSystem ? "System events are shown." : "Some system events are hidden."} <button part="system-toggle" type="button">${this.showSystem ? "Hide system events" : `Show ${hiddenCount} hidden`}</button></p>` : ""}<p part="note">One shared axis: 0–${duration(endMs)}. ${this.comparisonSpans.length ? "Outlined bars show the comparison trace. " : ""}Arrow keys navigate spans and open or close parents; Enter selects details.</p><div part="body"${selected ? " data-details" : ""}>${this.table ? `<table part="table"><caption>${escape(this.heading)} spans</caption><thead><tr><th scope="col">Span / kind</th><th scope="col">Status</th><th scope="col">Start / duration / markers</th></tr></thead><tbody>${tableRows}</tbody></table>` : `<div part="treegrid" role="treegrid" aria-label="${escape(this.heading)} spans" aria-colcount="2"><div role="rowgroup"><div role="row" part="columns"><div role="columnheader">Span</div><div role="columnheader" part="axis"><span>0 ms</span><span>${duration(endMs)}</span></div></div></div><div role="rowgroup">${rowHtml}</div></div>`}${details}</div>${rows.length ? "" : '<p part="empty">No matching spans.</p>'}</section></${this.fullScreen ? "dialog" : "div"}>`;
|
|
301
|
+
const dialog = host.querySelector('dialog');
|
|
302
|
+
if (dialog) {
|
|
303
|
+
if (typeof dialog.showModal === "function")
|
|
304
|
+
dialog.showModal();
|
|
305
|
+
else
|
|
306
|
+
dialog.setAttribute("open", "");
|
|
307
|
+
dialog.addEventListener("cancel", event => { event.preventDefault(); this.fullScreen = false; this.update(); this.shadowRoot.querySelector('[part="fullscreen-toggle"]')?.focus(); });
|
|
308
|
+
}
|
|
309
|
+
const menu = host.querySelector('[part="menu"]');
|
|
310
|
+
if (menu)
|
|
311
|
+
menu.open = menuOpen;
|
|
312
|
+
host.querySelectorAll('[part="row"]').forEach(element => {
|
|
313
|
+
const row = rows.find(item => item.span.id === element.dataset.spanId);
|
|
314
|
+
element.addEventListener("click", event => { if (event.target.closest('[part="expand"]'))
|
|
315
|
+
return; this.focusedId = row.span.id; this.select(row.span.id); this.focusRow(row.span.id); });
|
|
316
|
+
element.addEventListener("focus", () => { this.focusedId = row.span.id; });
|
|
317
|
+
element.querySelector('[part="expand"]')?.addEventListener("click", () => this.toggle(row));
|
|
318
|
+
});
|
|
319
|
+
const button = (part, action) => { host.querySelector(`[part="${part}"]`)?.addEventListener("click", action); };
|
|
320
|
+
const refreshControl = (part, action) => { action(); this.update(); this.shadowRoot.querySelector(`[part="${part}"]`)?.focus(); };
|
|
321
|
+
button("table-toggle", () => refreshControl("table-toggle", () => { this.table = !this.table; }));
|
|
322
|
+
button("fullscreen-toggle", () => refreshControl("fullscreen-toggle", () => { this.fullScreen = !this.fullScreen; }));
|
|
323
|
+
button("system-toggle", () => { this.showSystem = !this.showSystem; this.shadowRoot.querySelector('[part="system-toggle"]')?.focus(); });
|
|
324
|
+
button("expand-all", () => refreshControl("expand-all", () => this.collapsed.clear()));
|
|
325
|
+
button("collapse-all", () => refreshControl("collapse-all", () => rows.filter(row => row.hasChildren).forEach(row => this.collapsed.add(row.span.id))));
|
|
326
|
+
button("detail-mode", () => refreshControl("detail-mode", () => { this.code = !this.code; }));
|
|
327
|
+
button("close-details", () => { const id = this.selectedSpanId; this.selectedSpanId = ""; this.focusRow(id); });
|
|
328
|
+
for (const [part, offset] of [["previous-span", -1], ["next-span", 1]])
|
|
329
|
+
button(part, () => {
|
|
330
|
+
const next = rows[selectedIndex + offset];
|
|
331
|
+
if (next) {
|
|
332
|
+
this.select(next.span.id);
|
|
333
|
+
const target = this.shadowRoot.querySelector(`[part="${part}"]`);
|
|
334
|
+
(target?.disabled ? this.shadowRoot.querySelector(`[part="${offset < 0 ? "next-span" : "previous-span"}"]`) : target)?.focus();
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
host.querySelectorAll("[data-select-id]").forEach(element => element.addEventListener("click", () => { this.select(element.dataset.selectId); this.shadowRoot.querySelector('[part="close-details"]')?.focus(); }));
|
|
338
|
+
host.querySelector('[part="search"]')?.addEventListener("input", event => { this.search = event.target.value; this.update(); });
|
|
339
|
+
if (wasRow && this.focusedId)
|
|
340
|
+
this.focusRow(this.focusedId);
|
|
341
|
+
else if (activeSelectId) {
|
|
342
|
+
const buttons = Array.from(this.shadowRoot.querySelectorAll("[data-select-id]"));
|
|
343
|
+
(buttons.find(button => button.dataset.selectId === activeSelectId) ?? buttons[0] ?? this.shadowRoot.querySelector('[part="search"]'))?.focus();
|
|
344
|
+
}
|
|
345
|
+
else if (activePart === "expand" && activeRowId) {
|
|
346
|
+
const row = Array.from(this.shadowRoot.querySelectorAll('[part="row"]')).find(row => row.dataset.spanId === activeRowId);
|
|
347
|
+
const target = row?.querySelector('[part="expand"]');
|
|
348
|
+
(target && !target.disabled ? target : row)?.focus();
|
|
349
|
+
}
|
|
350
|
+
else if (activePart) {
|
|
351
|
+
let restored = this.shadowRoot.querySelector(`[part="${activePart}"]`);
|
|
352
|
+
if (restored instanceof HTMLButtonElement && restored.disabled)
|
|
353
|
+
restored = this.shadowRoot.querySelector('[part="detail-mode"]');
|
|
354
|
+
restored?.focus();
|
|
355
|
+
if (restored instanceof HTMLInputElement && searchSelection)
|
|
356
|
+
restored.setSelectionRange(searchSelection[0], searchSelection[1]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
TraceWaterfall.register();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
* Regenerate with: bun run types:generate
|
|
4
|
+
* Native custom-element properties and CustomEvent details from component source.
|
|
5
|
+
* Type-only: importing this entry never registers or renders an element.
|
|
6
|
+
*/
|
|
7
|
+
import type * as React from "react";
|
|
8
|
+
import type { BoxElementTagName } from "./element-maps.js";
|
|
9
|
+
import type { BoxElementEventHandlerProps, BoxElementProperties, BoxElementPropertyKeys } from "./native-types.js";
|
|
10
|
+
type BoxReactProps<Tag extends BoxElementTagName> = Omit<React.HTMLAttributes<HTMLElementTagNameMap[Tag]>, BoxElementPropertyKeys[Tag] | keyof BoxElementEventHandlerProps<Tag>> & BoxElementProperties<Tag> & BoxElementEventHandlerProps<Tag> & React.RefAttributes<HTMLElementTagNameMap[Tag]>;
|
|
11
|
+
type BoxReactIntrinsicElements = {
|
|
12
|
+
[Tag in BoxElementTagName]: BoxReactProps<Tag>;
|
|
13
|
+
};
|
|
14
|
+
/** Opt in with a type-only import of the react-jsx subpath. */
|
|
15
|
+
declare module "react" {
|
|
16
|
+
namespace JSX {
|
|
17
|
+
interface IntrinsicElements extends BoxReactIntrinsicElements {
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unofficialbox/box-open-elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
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",
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
"main": "dist/index.js",
|
|
29
29
|
"types": "dist/index.d.ts",
|
|
30
30
|
"exports": {
|
|
31
|
+
"./native-types": {
|
|
32
|
+
"types": "./dist/native-types.d.ts",
|
|
33
|
+
"import": "./dist/native-types.js"
|
|
34
|
+
},
|
|
35
|
+
"./react-jsx": {
|
|
36
|
+
"types": "./dist/react-jsx.d.ts",
|
|
37
|
+
"import": "./dist/react-jsx.js"
|
|
38
|
+
},
|
|
31
39
|
"./patterns/process-modeler": {
|
|
32
40
|
"types": "./dist/patterns/process-modeler/index.d.ts",
|
|
33
41
|
"import": "./dist/patterns/process-modeler/index.js"
|
|
@@ -201,7 +209,8 @@
|
|
|
201
209
|
"test": "vitest run",
|
|
202
210
|
"test:watch": "vitest",
|
|
203
211
|
"test:coverage": "vitest run --coverage",
|
|
204
|
-
"verify": "bun run typecheck && bun run test:coverage && bun run build && bun run frameworks:validate",
|
|
212
|
+
"verify": "bun run maps:check && bun run types:check && bun run typecheck && bun run test:coverage && bun run build && bun run types:validate && bun run frameworks:validate",
|
|
213
|
+
"types:validate": "tsc -p test/types/tsconfig.json --noEmit && tsc -p test/types/react-jsx-only.tsconfig.json --noEmit",
|
|
205
214
|
"frameworks:react": "tsc --noEmit -p examples/frameworks/react/tsconfig.json && vite build --config examples/frameworks/react/vite.config.ts",
|
|
206
215
|
"frameworks:react:ssr": "next build examples/frameworks/react-ssr",
|
|
207
216
|
"frameworks:angular": "ngc -p examples/frameworks/angular/tsconfig.json && vite build --config examples/frameworks/angular/vite.config.ts",
|
|
@@ -221,6 +230,9 @@
|
|
|
221
230
|
"test:regression:pixel": "bash tools/preview/container-run.sh 'bun run test:regression -- --pixel'",
|
|
222
231
|
"baselines:regen": "bash tools/preview/container-run.sh 'bun run build && bun tools/preview/capture.ts && bun tools/preview/docs-site-shots.ts'",
|
|
223
232
|
"maps:generate": "bun tools/generate-element-maps.ts",
|
|
233
|
+
"maps:check": "bun tools/generate-element-maps.ts --check",
|
|
234
|
+
"types:generate": "bun tools/generate-native-types.ts",
|
|
235
|
+
"types:check": "bun tools/generate-native-types.ts --check",
|
|
224
236
|
"icons:generate": "bun tools/iconography/generate-box-iconography.ts",
|
|
225
237
|
"bue-conformance": "bun tools/bue-conformance/audit.ts",
|
|
226
238
|
"bue-conformance:color": "bun tools/bue-conformance/color-audit.ts",
|
|
@@ -247,6 +259,7 @@
|
|
|
247
259
|
"@vitejs/plugin-vue": "6.0.8",
|
|
248
260
|
"@vitest/coverage-v8": "^4.1.2",
|
|
249
261
|
"@vue/server-renderer": "3.5.40",
|
|
262
|
+
"axe-core": "^4.14.0",
|
|
250
263
|
"bun-types": "^1.3.14",
|
|
251
264
|
"jsdom": "^29.0.1",
|
|
252
265
|
"next": "16.2.12",
|