@fasl-work/caos-app-shell 0.8.0 → 0.9.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/README.md +3 -2
- package/chart.css +3 -0
- package/dist/chart.d.ts +22 -4
- package/dist/chart.js +88 -17
- package/dist/{chunk-ZLJCB7DD.js → chunk-IDUNUMCR.js} +2 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.js +13 -6
- package/dist/{tokens-DkyEXX5g.d.ts → tokens-BTOzar-U.d.ts} +3 -0
- package/gate/inpage.mjs +31 -4
- package/gate/run.mjs +5 -3
- package/package.json +1 -1
- package/reserved-classes.json +5 -1
- package/styles.css +3 -0
package/README.md
CHANGED
|
@@ -121,8 +121,9 @@ const config: ShellConfig = {
|
|
|
121
121
|
`ChipGroup`, `Stage`, `useStageSize`, `makeStateKey`, `isStale`, `useWorkbenchState`,
|
|
122
122
|
`WorkbenchStateContext`, `pick`; since 0.8.0 `SurfacePage`, `useUrlView`, `viewParamOf`, `replaceQueryParam`,
|
|
123
123
|
`BREAKPOINTS`, `BarChart`, `textWidth`, `widestLabel`, `fitLabel`, `niceTicks`, `niceStep`, `fontFamily`,
|
|
124
|
-
`formatTicks`, `NBSP`, `SCIENTIFIC_BELOW`, the type `ShellColorToken`. Separate entries:
|
|
125
|
-
`
|
|
124
|
+
`formatTicks`, `NBSP`, `SCIENTIFIC_BELOW`, the type `ShellColorToken`; since 0.9.0 `ViewsRow`. Separate entries:
|
|
125
|
+
`/chart` (`UPlotChart` with `x.log`, `parity`, `onPick`; `drawMarks`, `tickSteps`, `repeatedLabels`, `seriesStyle`,
|
|
126
|
+
`sortOrder`, `parityRange`), `/keys` (the storage keys, no React), `/styles.css`, `/chart.css`,
|
|
126
127
|
`/reserved-classes.json`. Bin: `caos-shell-gate`.
|
|
127
128
|
|
|
128
129
|
### Authenticated workbenches
|
package/chart.css
CHANGED
|
@@ -16,4 +16,7 @@
|
|
|
16
16
|
.caos-chart-swatch { flex: none; width: 16px; height: 0; border-top: 2px solid var(--swatch); }
|
|
17
17
|
.caos-chart-key[data-dash] .caos-chart-swatch { border-top-style: dashed; }
|
|
18
18
|
.caos-chart-key[data-mode="points"] .caos-chart-swatch { width: 7px; height: 7px; border: 0; border-radius: 50%; background: var(--swatch); }
|
|
19
|
+
/* A parity plot is square and centred in its box; a chart whose points can be picked shows a pointer. */
|
|
20
|
+
.caos-chart[data-parity] .uplot { margin-inline: auto; }
|
|
21
|
+
.caos-chart[data-pick] .u-over { cursor: pointer; }
|
|
19
22
|
.caos-chart .u-cursor-x, .caos-chart .u-cursor-y { border-color: var(--color-fg-faint) !important; }
|
package/dist/chart.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import uPlot from 'uplot';
|
|
3
|
-
import { F as FormatOptions, S as ShellColorToken } from './tokens-
|
|
3
|
+
import { F as FormatOptions, S as ShellColorToken } from './tokens-BTOzar-U.js';
|
|
4
4
|
import { B as BiText } from './text-BlY47nIt.js';
|
|
5
5
|
import 'zustand';
|
|
6
6
|
|
|
@@ -26,6 +26,7 @@ interface UPlotChartProps {
|
|
|
26
26
|
x: ChartAxis & {
|
|
27
27
|
values: number[];
|
|
28
28
|
time?: boolean;
|
|
29
|
+
log?: boolean;
|
|
29
30
|
};
|
|
30
31
|
y: ChartAxis & {
|
|
31
32
|
log?: boolean;
|
|
@@ -40,9 +41,26 @@ interface UPlotChartProps {
|
|
|
40
41
|
/** Height in pixels, or `fill` to take the container's height (inside `PlotCard fill`); the width is always the
|
|
41
42
|
* container's. */
|
|
42
43
|
height?: number | 'fill';
|
|
43
|
-
/** Called with the index under the cursor (or null), for linked views
|
|
44
|
+
/** Called with the index under the cursor (or null), for linked views; the index is the caller's, before any
|
|
45
|
+
* sorting by x. */
|
|
44
46
|
onCursor?: (index: number | null) => void;
|
|
47
|
+
/** Called with the caller's index of the point under the cursor when the plot is clicked (select a case, a blast). */
|
|
48
|
+
onPick?: (index: number) => void;
|
|
49
|
+
/** A parity plot: predicted (the series) against observed (x) on one shared range, in a square box, with the
|
|
50
|
+
* identity line. Unsorted x is allowed (the chart sorts it and maps every index back). */
|
|
51
|
+
parity?: boolean;
|
|
45
52
|
}
|
|
53
|
+
/** The colour and dash of a series: its own, or the rotation's; when the rotation comes round (a seventh series
|
|
54
|
+
* repeats the first colour) the repeat is dashed, so two series never look the same (CAOS_Fragmenta drew this for
|
|
55
|
+
* itself until 0.9.0). */
|
|
56
|
+
declare function seriesStyle(s: ChartSeries, i: number): {
|
|
57
|
+
color: ShellColorToken;
|
|
58
|
+
dash?: number[];
|
|
59
|
+
};
|
|
60
|
+
/** The order that sorts x ascending (uPlot draws aligned data by increasing x), or null when x is already sorted. */
|
|
61
|
+
declare function sortOrder(xs: readonly number[]): number[] | null;
|
|
62
|
+
/** One range for both axes of a parity plot, padded by 5% of the span on each side. */
|
|
63
|
+
declare function parityRange(...arrays: readonly (readonly (number | null)[])[]): [number, number];
|
|
46
64
|
/**
|
|
47
65
|
* The tick steps an axis may use. With fixed `decimals` (scaled by 100 for a percent) only multiples of the smallest
|
|
48
66
|
* difference its labels can show, so no two ticks read the same: an integer axis ticks at integers, never at 1.5,
|
|
@@ -78,6 +96,6 @@ declare function drawMarks(u: uPlot, marks: {
|
|
|
78
96
|
* (known shell defect 17). An axis with fixed decimals ticks only where its labels differ (`tickSteps`). The host
|
|
79
97
|
* declares `data-series`, `data-axis-titles`, `data-ticks-cut`, `data-ticks-repeat` and `data-drawn` for the gate.
|
|
80
98
|
*/
|
|
81
|
-
declare function UPlotChart({ x, y, series, marks, height, onCursor }: UPlotChartProps): react.JSX.Element;
|
|
99
|
+
declare function UPlotChart({ x, y, series, marks, height, onCursor, onPick, parity }: UPlotChartProps): react.JSX.Element;
|
|
82
100
|
|
|
83
|
-
export { type ChartAxis, type ChartSeries, UPlotChart, type UPlotChartProps, drawMarks, repeatedLabels, tickSteps };
|
|
101
|
+
export { type ChartAxis, type ChartSeries, UPlotChart, type UPlotChartProps, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
|
package/dist/chart.js
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
import { useShellLang, useThemeStore, useStageSize, pick, resolveToken, NBSP, formatNumber, formatTicks } from './chunk-
|
|
1
|
+
import { useShellLang, useThemeStore, useStageSize, pick, resolveToken, NBSP, formatNumber, formatTicks } from './chunk-IDUNUMCR.js';
|
|
2
2
|
import './chunk-X6Y5CDUD.js';
|
|
3
3
|
import { useRef, useCallback, useState, useMemo, useEffect } from 'react';
|
|
4
4
|
import uPlot from 'uplot';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
var ROTATION = ["--color-accent", "--color-magenta", "--color-accent-2", "--color-good", "--color-warn", "--color-bad"];
|
|
8
|
+
function seriesStyle(s, i) {
|
|
9
|
+
const color = s.color ?? ROTATION[i % ROTATION.length];
|
|
10
|
+
const dash = s.dash ?? (!s.color && i >= ROTATION.length ? [6, 4] : void 0);
|
|
11
|
+
return { color, dash };
|
|
12
|
+
}
|
|
13
|
+
function sortOrder(xs) {
|
|
14
|
+
for (let i = 1; i < xs.length; i++) {
|
|
15
|
+
if (xs[i] < xs[i - 1]) return xs.map((_, j) => j).sort((a, b) => xs[a] - xs[b] || a - b);
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function parityRange(...arrays) {
|
|
20
|
+
let lo = Infinity;
|
|
21
|
+
let hi = -Infinity;
|
|
22
|
+
for (const a of arrays) {
|
|
23
|
+
for (const v of a) {
|
|
24
|
+
if (v === null || !Number.isFinite(v)) continue;
|
|
25
|
+
lo = Math.min(lo, v);
|
|
26
|
+
hi = Math.max(hi, v);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isFinite(lo)) return [0, 1];
|
|
30
|
+
const pad = (hi - lo || Math.abs(hi) || 1) * 0.05;
|
|
31
|
+
return [lo - pad, hi + pad];
|
|
32
|
+
}
|
|
8
33
|
var STEPS = [];
|
|
9
34
|
for (let e = -16; e <= 16; e++) for (const m of [1, 2, 2.5, 5]) STEPS.push(Number((m * 10 ** e).toPrecision(3)));
|
|
10
35
|
function tickSteps(fmt) {
|
|
@@ -57,7 +82,7 @@ function drawMarks(u, marks, style) {
|
|
|
57
82
|
}
|
|
58
83
|
ctx.restore();
|
|
59
84
|
}
|
|
60
|
-
function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
85
|
+
function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parity }) {
|
|
61
86
|
const lang = useShellLang();
|
|
62
87
|
const theme = useThemeStore((s) => s.theme);
|
|
63
88
|
const fill = height === "fill";
|
|
@@ -70,20 +95,30 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
70
95
|
},
|
|
71
96
|
[measureRef]
|
|
72
97
|
);
|
|
73
|
-
const width = box.width;
|
|
74
|
-
const h = fill ? box.height : height;
|
|
98
|
+
const width = parity ? Math.min(box.width, fill ? box.height : height) : box.width;
|
|
99
|
+
const h = parity ? width : fill ? box.height : height;
|
|
75
100
|
const uRef = useRef(null);
|
|
76
101
|
const hostRef = useRef(null);
|
|
77
102
|
const [cursor, setCursor] = useState(null);
|
|
78
103
|
const onCursorRef = useRef(onCursor);
|
|
79
104
|
onCursorRef.current = onCursor;
|
|
105
|
+
const onPickRef = useRef(onPick);
|
|
106
|
+
onPickRef.current = onPick;
|
|
107
|
+
const order = useMemo(() => sortOrder(x.values), [x.values]);
|
|
108
|
+
const orderRef = useRef(order);
|
|
109
|
+
orderRef.current = order;
|
|
110
|
+
const xs = useMemo(() => order ? order.map((i) => x.values[i]) : x.values, [order, x.values]);
|
|
111
|
+
const ys = useMemo(() => series.map((s) => order ? order.map((i) => s.values[i] ?? null) : s.values), [order, series]);
|
|
112
|
+
const range = useMemo(() => parity ? parityRange(xs, ...ys) : null, [parity, xs, ys]);
|
|
80
113
|
const xLabel = pick(x.label, lang);
|
|
81
114
|
const yLabel = pick(y.label, lang);
|
|
82
115
|
const xUnit = x.unit ? pick(x.unit, lang) : "";
|
|
83
116
|
const yUnit = y.unit ? pick(y.unit, lang) : "";
|
|
84
117
|
const structure = JSON.stringify({
|
|
85
|
-
s: series.map((s, i) => [pick(s.label, lang), s
|
|
86
|
-
x: [xLabel, xUnit, Boolean(x.time), x.format ?? null],
|
|
118
|
+
s: series.map((s, i) => [pick(s.label, lang), seriesStyle(s, i), s.width ?? 2, s.mode ?? "line"]),
|
|
119
|
+
x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null],
|
|
120
|
+
p: range,
|
|
121
|
+
pick: Boolean(onPick),
|
|
87
122
|
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null],
|
|
88
123
|
m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
|
|
89
124
|
theme,
|
|
@@ -91,7 +126,7 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
91
126
|
h,
|
|
92
127
|
w: width
|
|
93
128
|
});
|
|
94
|
-
const data = useMemo(() => [
|
|
129
|
+
const data = useMemo(() => [xs, ...ys], [xs, ys]);
|
|
95
130
|
useEffect(() => {
|
|
96
131
|
const el = plotRef.current;
|
|
97
132
|
if (!el || width <= 0 || h <= 0) return;
|
|
@@ -107,13 +142,23 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
107
142
|
hostRef.current?.setAttribute("data-ticks-repeat", String(repeats.x + repeats.y));
|
|
108
143
|
return labels;
|
|
109
144
|
};
|
|
110
|
-
const xSteps = x.time ? void 0 : tickSteps(x.format);
|
|
145
|
+
const xSteps = x.time || x.log ? void 0 : tickSteps(x.format);
|
|
111
146
|
const ySteps = y.log ? void 0 : tickSteps(y.format);
|
|
112
147
|
const opts = {
|
|
113
148
|
width,
|
|
114
149
|
height: h,
|
|
115
150
|
legend: { show: false },
|
|
116
|
-
|
|
151
|
+
// a log axis labels the ticks uPlot keeps and passes null for the rest, which formatTicks leaves blank
|
|
152
|
+
scales: {
|
|
153
|
+
// a log x axis spans the data, not the decades around it (uPlot rounds out to the next decade, which left an
|
|
154
|
+
// axis to 1,000 for data that ends at 120)
|
|
155
|
+
x: {
|
|
156
|
+
time: Boolean(x.time),
|
|
157
|
+
distr: x.log ? 3 : 1,
|
|
158
|
+
...range ? { range } : x.log ? { range: (_u, min, max) => [min, max] } : {}
|
|
159
|
+
},
|
|
160
|
+
y: { distr: y.log ? 3 : 1, ...range ? { range } : y.range ? { range: y.range } : {} }
|
|
161
|
+
},
|
|
117
162
|
axes: [
|
|
118
163
|
{
|
|
119
164
|
stroke: fg,
|
|
@@ -149,8 +194,9 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
149
194
|
series: [
|
|
150
195
|
{},
|
|
151
196
|
...series.map((s, i) => {
|
|
152
|
-
const
|
|
153
|
-
|
|
197
|
+
const style = seriesStyle(s, i);
|
|
198
|
+
const stroke = resolveToken(style.color, "#4a8");
|
|
199
|
+
return s.mode === "points" ? { label: pick(s.label, lang), stroke, width: 1.5, paths: () => null, points: { show: true, space: 0, size: 9, fill: stroke } } : { label: pick(s.label, lang), stroke, width: s.width ?? 2, dash: style.dash, spanGaps: false, points: { show: false } };
|
|
154
200
|
})
|
|
155
201
|
],
|
|
156
202
|
hooks: {
|
|
@@ -158,11 +204,33 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
158
204
|
(u) => {
|
|
159
205
|
const idx = u.cursor.idx ?? null;
|
|
160
206
|
setCursor(idx);
|
|
161
|
-
onCursorRef.current?.(idx);
|
|
207
|
+
onCursorRef.current?.(idx === null ? null : orderRef.current ? orderRef.current[idx] : idx);
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
ready: [
|
|
211
|
+
(u) => {
|
|
212
|
+
u.over.addEventListener("click", () => {
|
|
213
|
+
const idx = u.cursor.idx;
|
|
214
|
+
if (idx === null || idx === void 0 || !onPickRef.current) return;
|
|
215
|
+
onPickRef.current(orderRef.current ? orderRef.current[idx] : idx);
|
|
216
|
+
});
|
|
162
217
|
}
|
|
163
218
|
],
|
|
164
219
|
draw: [
|
|
165
220
|
(u) => {
|
|
221
|
+
if (range) {
|
|
222
|
+
const ctx = u.ctx;
|
|
223
|
+
const pr = uPlot.pxRatio || 1;
|
|
224
|
+
ctx.save();
|
|
225
|
+
ctx.strokeStyle = resolveToken("--color-fg-faint", "#888");
|
|
226
|
+
ctx.lineWidth = Math.max(1, pr);
|
|
227
|
+
ctx.setLineDash([5 * pr, 4 * pr]);
|
|
228
|
+
ctx.beginPath();
|
|
229
|
+
ctx.moveTo(u.valToPos(range[0], "x", true), u.valToPos(range[0], "y", true));
|
|
230
|
+
ctx.lineTo(u.valToPos(range[1], "x", true), u.valToPos(range[1], "y", true));
|
|
231
|
+
ctx.stroke();
|
|
232
|
+
ctx.restore();
|
|
233
|
+
}
|
|
166
234
|
if (!marks?.length) return;
|
|
167
235
|
drawMarks(u, marks.map((m) => ({ x: m.x, label: pick(m.label, lang) })), {
|
|
168
236
|
color: resolveToken("--color-warn", "#c80"),
|
|
@@ -184,9 +252,9 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
184
252
|
uRef.current?.setData(data);
|
|
185
253
|
}, [data]);
|
|
186
254
|
const read = cursor !== null ? [
|
|
187
|
-
`${xLabel} ${formatNumber(
|
|
255
|
+
`${xLabel} ${formatNumber(xs[cursor], lang, x.format ?? { digits: 4 })}${xUnit ? `${NBSP}${xUnit}` : ""}`,
|
|
188
256
|
...series.map(
|
|
189
|
-
(s) => `${pick(s.label, lang)} ${formatNumber(
|
|
257
|
+
(s, k) => `${pick(s.label, lang)} ${formatNumber(ys[k][cursor] ?? null, lang, y.format ?? { digits: 4 })}${yUnit ? `${NBSP}${yUnit}` : ""}`
|
|
190
258
|
)
|
|
191
259
|
].join(" \xB7 ") : lang === "es" ? "Pase el cursor sobre el gr\xE1fico para leer los valores" : "Hover the chart to read the values";
|
|
192
260
|
return /* @__PURE__ */ jsxs(
|
|
@@ -199,10 +267,13 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
199
267
|
"data-ticks-cut": "0",
|
|
200
268
|
"data-ticks-repeat": "0",
|
|
201
269
|
"data-drawn": width > 0 && h > 0 ? "1" : "0",
|
|
270
|
+
"data-parity": parity ? "1" : void 0,
|
|
271
|
+
"data-log-x": x.log ? "1" : void 0,
|
|
272
|
+
"data-pick": onPick ? "1" : void 0,
|
|
202
273
|
children: [
|
|
203
274
|
/* @__PURE__ */ jsx("div", { ref: setPlot, className: "caos-chart-plot" }),
|
|
204
|
-
series.length > 1 && /* @__PURE__ */ jsx("ul", { className: "caos-chart-legend", "aria-label": lang === "es" ? "Series del gr\xE1fico" : "Series of the chart", children: series.map((s, i) => /* @__PURE__ */ jsxs("li", { className: "caos-chart-key", "data-mode": s.mode ?? "line", "data-dash": s.dash ? "1" : void 0, children: [
|
|
205
|
-
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${s
|
|
275
|
+
series.length > 1 && /* @__PURE__ */ jsx("ul", { className: "caos-chart-legend", "aria-label": lang === "es" ? "Series del gr\xE1fico" : "Series of the chart", children: series.map((s, i) => /* @__PURE__ */ jsxs("li", { className: "caos-chart-key", "data-mode": s.mode ?? "line", "data-dash": seriesStyle(s, i).dash ? "1" : void 0, children: [
|
|
276
|
+
/* @__PURE__ */ jsx("span", { className: "caos-chart-swatch", style: { "--swatch": `var(${seriesStyle(s, i).color})` }, "aria-hidden": "true" }),
|
|
206
277
|
pick(s.label, lang)
|
|
207
278
|
] }, i)) }),
|
|
208
279
|
/* @__PURE__ */ jsx("p", { className: "caos-chart-readout", "aria-live": "polite", title: read, children: read })
|
|
@@ -211,4 +282,4 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor }) {
|
|
|
211
282
|
);
|
|
212
283
|
}
|
|
213
284
|
|
|
214
|
-
export { UPlotChart, drawMarks, repeatedLabels, tickSteps };
|
|
285
|
+
export { UPlotChart, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
|
|
@@ -148,7 +148,8 @@ function formatNumber(value, lang, opts = {}) {
|
|
|
148
148
|
const v = opts.percent ? value * 100 : value;
|
|
149
149
|
const digits = opts.digits ?? 4;
|
|
150
150
|
const scientific = v !== 0 && (opts.notation === "scientific" || Math.abs(v) < SCIENTIFIC_BELOW);
|
|
151
|
-
const
|
|
151
|
+
const grouping = opts.grouping === false ? { useGrouping: false } : {};
|
|
152
|
+
const nf = opts.decimals !== void 0 ? new Intl.NumberFormat(LOCALE[lang], { minimumFractionDigits: opts.decimals, maximumFractionDigits: opts.decimals, ...grouping }) : scientific ? new Intl.NumberFormat(LOCALE[lang], { notation: "scientific", maximumSignificantDigits: digits }) : Math.abs(v) >= 10 ** (digits - 1) ? new Intl.NumberFormat(LOCALE[lang], { maximumFractionDigits: 0, ...grouping }) : new Intl.NumberFormat(LOCALE[lang], { maximumSignificantDigits: digits, ...grouping });
|
|
152
153
|
return opts.percent ? `${nf.format(v)}${NBSP}%` : nf.format(v);
|
|
153
154
|
}
|
|
154
155
|
function formatTicks(values, lang, opts = {}) {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { A as AppShell, a as ArchTab, b as ArchitectureConfig, c as Architecture
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, Component, ErrorInfo } from 'react';
|
|
4
4
|
import * as zustand from 'zustand';
|
|
5
|
-
import { F as FormatOptions } from './tokens-
|
|
6
|
-
export { N as NBSP, a as NOT_AVAILABLE, b as SCIENTIFIC_BELOW, c as SHELL_TOKENS, S as ShellColorToken, d as ShellToken, f as formatNumber, e as formatTicks, r as resolveToken, u as useFormat, g as useThemeTokens } from './tokens-
|
|
5
|
+
import { F as FormatOptions } from './tokens-BTOzar-U.js';
|
|
6
|
+
export { N as NBSP, a as NOT_AVAILABLE, b as SCIENTIFIC_BELOW, c as SHELL_TOKENS, S as ShellColorToken, d as ShellToken, f as formatNumber, e as formatTicks, r as resolveToken, u as useFormat, g as useThemeTokens } from './tokens-BTOzar-U.js';
|
|
7
7
|
import { L as Lang, B as BiText } from './text-BlY47nIt.js';
|
|
8
8
|
export { p as pick, u as useLangStore, a as useShellLang } from './text-BlY47nIt.js';
|
|
9
9
|
|
|
@@ -823,4 +823,17 @@ declare function Stage({ label, children, className }: {
|
|
|
823
823
|
className?: string;
|
|
824
824
|
}): react.JSX.Element;
|
|
825
825
|
|
|
826
|
-
|
|
826
|
+
interface ViewsRowProps {
|
|
827
|
+
/** The views side by side: `PlotCard`s, or components that render one. */
|
|
828
|
+
children: ReactNode;
|
|
829
|
+
/** The share of the row each view takes, in order (`[3, 2]`: the first takes three fifths); equal when absent. */
|
|
830
|
+
shares?: number[];
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Views side by side in an instrument panel, each in a column the shell owns, so a filling `PlotCard` keeps its height
|
|
834
|
+
* chain and the row splits by `shares` (CAOS_Fragmenta's design row, three fifths for the response surface). Before
|
|
835
|
+
* 0.9.0 a product wrote this column itself (`.fr-viewcol`). Below 900 px the views stack.
|
|
836
|
+
*/
|
|
837
|
+
declare function ViewsRow({ children, shares }: ViewsRowProps): react.JSX.Element;
|
|
838
|
+
|
|
839
|
+
export { BREAKPOINTS, BarChart, type BarChartProps, type BarDatum, BiText, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, CaseWorkbench, type CaseWorkbenchProps, ChipGroup, type ChipGroupProps, type ChipOption, type Citation, CitationsProvider, Cite, DocPage, DocSection, type DocSectionProps, Equation, Figure, type FittedLabel, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, InlineMath, Knob, type KnobProps, type Lane, LaneBadge, Lang, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Stage, type StageSize, type SubTabDef, SubTabs, type SubTabsProps, SurfacePage, type SurfacePageProps, type TabDef, type TabGroupDef, TabGroups, type TabGroupsProps, Tabs, type TabsProps, type Theme, ThemeToggle, type Tone, type UsePausedVizOptions, VariantBar, type VariantBarProps, type VariantDef, Verdict, type VerdictProps, ViewsRow, type ViewsRowProps, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchGroup, WorkbenchLayout, type WorkbenchLayoutProps, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, type WorkbenchState, WorkbenchStateContext, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, readCaseParam, readTheme, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useStageSize, useThemeStore, useUrlView, useWorkbenchState, viewParamOf, widestLabel, withCaseParam };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, NOT_AVAILABLE, formatTicks, formatNumber, NBSP } from './chunk-
|
|
2
|
-
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, formatNumber, formatTicks, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-
|
|
1
|
+
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, NOT_AVAILABLE, formatTicks, formatNumber, NBSP } from './chunk-IDUNUMCR.js';
|
|
2
|
+
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, formatNumber, formatTicks, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-IDUNUMCR.js';
|
|
3
3
|
export { LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-X6Y5CDUD.js';
|
|
4
|
-
import { createContext, Component, useState, useRef, useId, useEffect, useCallback, useContext, useMemo } from 'react';
|
|
4
|
+
import { createContext, Component, useState, useRef, useId, useEffect, useCallback, useContext, useMemo, Children } from 'react';
|
|
5
5
|
import { useInRouterContext, NavLink, useLocation } from 'react-router';
|
|
6
6
|
import { Sun, Moon, Languages, Boxes, CodeXml, Globe, Briefcase, Info } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -1264,11 +1264,11 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
|
|
|
1264
1264
|
const t1 = ticks[ticks.length - 1];
|
|
1265
1265
|
const tickLabels = formatTicks(ticks, lang, fmt);
|
|
1266
1266
|
const sx = (v) => x0 + (v - t0) / (t1 - t0 || 1) * (x1 - x0);
|
|
1267
|
-
const rowH = fill && box.height > 0 ? Math.max(need,
|
|
1267
|
+
const rowH = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / data.length) : need;
|
|
1268
1268
|
const top = 4;
|
|
1269
1269
|
const plotBottom = top + data.length * rowH;
|
|
1270
1270
|
drawnHeight = fill && box.height > 0 ? Math.max(box.height, plotBottom + axisH) : plotBottom + axisH;
|
|
1271
|
-
const barH = Math.min(rowH * 0.
|
|
1271
|
+
const barH = Math.min(rowH * 0.5, 40);
|
|
1272
1272
|
drawing = /* @__PURE__ */ jsxs("svg", { width, height: drawnHeight, role: "img", "aria-label": name, "data-chart": "", "data-chart-kind": "bars", children: [
|
|
1273
1273
|
/* @__PURE__ */ jsx("title", { children: name }),
|
|
1274
1274
|
ticks.map((t, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
@@ -1740,5 +1740,12 @@ function ChipGroup({ id, label, options, value, onChange }) {
|
|
|
1740
1740
|
)) })
|
|
1741
1741
|
] });
|
|
1742
1742
|
}
|
|
1743
|
+
function ViewsRow({ children, shares }) {
|
|
1744
|
+
const items = Children.toArray(children).filter(Boolean);
|
|
1745
|
+
return /* @__PURE__ */ jsx("div", { className: "caos-views-row", "data-views": items.length, children: items.map((child, i) => {
|
|
1746
|
+
const share = shares?.[i];
|
|
1747
|
+
return /* @__PURE__ */ jsx("div", { className: "caos-views-col", "data-share": share ?? 1, style: share && share > 0 ? { flexGrow: share } : void 0, children: child }, i);
|
|
1748
|
+
}) });
|
|
1749
|
+
}
|
|
1743
1750
|
|
|
1744
|
-
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DocPage, DocSection, Equation, Figure, FocusShell, Gauge, InlineMath, Knob, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Readout, ReferenceList, Refs, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, readCaseParam, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, viewParamOf, widestLabel, withCaseParam };
|
|
1751
|
+
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DocPage, DocSection, Equation, Figure, FocusShell, Gauge, InlineMath, Knob, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Readout, ReferenceList, Refs, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, readCaseParam, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, viewParamOf, widestLabel, withCaseParam };
|
|
@@ -12,6 +12,9 @@ interface FormatOptions {
|
|
|
12
12
|
/** `scientific` writes every non-zero value in scientific notation with `digits` significant digits; the default
|
|
13
13
|
* chooses by magnitude. An axis uses it so all its ticks share one notation (`formatTicks`). */
|
|
14
14
|
notation?: 'auto' | 'scientific';
|
|
15
|
+
/** `false` writes the integer part without a group separator, in both languages: a calendar year on an axis, in a
|
|
16
|
+
* readout or in a table reads 2021, never 2,021 or 2.021 (known shell defect 30). Default true. */
|
|
17
|
+
grouping?: boolean;
|
|
15
18
|
}
|
|
16
19
|
/** The no-break space (U+00A0) that joins a value to its unit or sign. */
|
|
17
20
|
declare const NBSP = "\u00A0";
|
package/gate/inpage.mjs
CHANGED
|
@@ -17,6 +17,11 @@ export function installLib() {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** The ancestors of an element up to (not including) `stop`. */
|
|
21
|
+
function* ancestorsUntil(el, stop) {
|
|
22
|
+
for (let a = el.parentElement; a && a !== stop && a !== document.body; a = a.parentElement) yield a;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
const visible = (el) => {
|
|
21
26
|
if (!el || el.nodeType !== 1) return false;
|
|
22
27
|
const r = el.getBoundingClientRect();
|
|
@@ -233,7 +238,9 @@ export function installLib() {
|
|
|
233
238
|
y,
|
|
234
239
|
w: x2 - x,
|
|
235
240
|
h: y2 - y,
|
|
236
|
-
|
|
241
|
+
// a table or drawing inside a box that SCROLLS sideways (a wide table on a phone, ADR-0071 rule 3) ends where
|
|
242
|
+
// its box ends: a reader scrolls it; one cut by a box that hides its overflow still extends past the page
|
|
243
|
+
right: Math.min(r.right, ...[...ancestorsUntil(el, inst)].filter((a) => ['auto', 'scroll'].includes(getComputedStyle(a).overflowX)).map((a) => a.getBoundingClientRect().right)),
|
|
237
244
|
stageKey: path(stageEl),
|
|
238
245
|
stage: clipRect(stageEl.getBoundingClientRect()),
|
|
239
246
|
viewKey: path(viewEl),
|
|
@@ -523,6 +530,25 @@ export function installLib() {
|
|
|
523
530
|
|
|
524
531
|
// ---- 0.8.0 measures ----------------------------------------------------------------------------------------
|
|
525
532
|
|
|
533
|
+
/** The part of the page the ancestors of an element let it show when they CUT their content (overflow hidden or
|
|
534
|
+
* clip). A scroll container does not cut (a reader scrolls it), and neither does the viewport. */
|
|
535
|
+
const cutArea = (el) => {
|
|
536
|
+
const area = { left: -Infinity, top: -Infinity, right: Infinity, bottom: Infinity };
|
|
537
|
+
for (let a = el.parentElement; a && a !== document.body && a !== document.documentElement; a = a.parentElement) {
|
|
538
|
+
const s = getComputedStyle(a);
|
|
539
|
+
const ar = a.getBoundingClientRect();
|
|
540
|
+
if (s.overflowX === 'hidden' || s.overflowX === 'clip') {
|
|
541
|
+
area.left = Math.max(area.left, ar.left);
|
|
542
|
+
area.right = Math.min(area.right, ar.right);
|
|
543
|
+
}
|
|
544
|
+
if (s.overflowY === 'hidden' || s.overflowY === 'clip') {
|
|
545
|
+
area.top = Math.max(area.top, ar.top);
|
|
546
|
+
area.bottom = Math.min(area.bottom, ar.bottom);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return area;
|
|
550
|
+
};
|
|
551
|
+
|
|
526
552
|
/** The boxes a text element occupies on screen: one per line (tspan) when it has several. */
|
|
527
553
|
const textBoxes = (t) => {
|
|
528
554
|
const spans = [...t.querySelectorAll('tspan')].filter((s) => (s.textContent || '').trim());
|
|
@@ -550,7 +576,7 @@ export function installLib() {
|
|
|
550
576
|
if (svg.classList.contains('lucide') || !visible(svg)) continue;
|
|
551
577
|
const sr = svg.getBoundingClientRect();
|
|
552
578
|
if (sr.width < 48 || sr.height < 24) continue;
|
|
553
|
-
const area =
|
|
579
|
+
const area = cutArea(svg);
|
|
554
580
|
const box = { left: Math.max(sr.left, area.left), top: Math.max(sr.top, area.top), right: Math.min(sr.right, area.right), bottom: Math.min(sr.bottom, area.bottom) };
|
|
555
581
|
const name = describe(svg.closest('[data-plot]') || svg);
|
|
556
582
|
const texts = [...svg.querySelectorAll('text')].filter((t) => (t.textContent || '').trim() && visible(t));
|
|
@@ -559,7 +585,7 @@ export function installLib() {
|
|
|
559
585
|
for (const b of textBoxes(t)) {
|
|
560
586
|
// a label outside the visible part of the drawing: cut, or drawn where no one sees it
|
|
561
587
|
if (b.top >= box.bottom || b.bottom <= box.top) {
|
|
562
|
-
|
|
588
|
+
out.push(`${name}: the label "${(t.textContent || '').trim().slice(0, 40)}" lies outside the drawing`);
|
|
563
589
|
continue;
|
|
564
590
|
}
|
|
565
591
|
const cutX = Math.max(0, box.left - b.left) + Math.max(0, b.right - box.right);
|
|
@@ -595,7 +621,8 @@ export function installLib() {
|
|
|
595
621
|
const out = [];
|
|
596
622
|
const skip = 'code, pre, kbd, samp, .katex, [data-version], [data-build], [translate="no"], script, style, [aria-hidden="true"]';
|
|
597
623
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
598
|
-
|
|
624
|
+
// an exponent belongs to the number: 2.7e-08 on a Spanish page writes its decimal with a point too
|
|
625
|
+
const re = /(?<![\w.,/:@#])(\d+)\.(\d+)(?:[eE][-+]?\d+)?(?![\w/@]|\.\d)/g;
|
|
599
626
|
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
600
627
|
const text = n.nodeValue || '';
|
|
601
628
|
if (!/\d\.\d/.test(text)) continue;
|
package/gate/run.mjs
CHANGED
|
@@ -318,7 +318,7 @@ export async function runGate(o) {
|
|
|
318
318
|
if (primary) {
|
|
319
319
|
// ADR-0071 rule 9 / G5: every header route reached by a real pointer click from the App route.
|
|
320
320
|
for (const route of routes.filter((r) => r !== '/')) {
|
|
321
|
-
await page.goto(urlFor('/'), { waitUntil: 'load', timeout: 30000 });
|
|
321
|
+
await page.goto(urlFor('/'), { waitUntil: 'load', timeout: 30000 }).catch(() => undefined);
|
|
322
322
|
await settle(page);
|
|
323
323
|
const link = page.locator('header nav a[href]');
|
|
324
324
|
const hrefs = await link.evaluateAll((as) => as.map((a) => a.href));
|
|
@@ -326,8 +326,10 @@ export async function runGate(o) {
|
|
|
326
326
|
let ok = false;
|
|
327
327
|
if (idx >= 0) {
|
|
328
328
|
// A reader scrolls a narrow nav row to the link first; so does the gate, then clicks with the pointer.
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
// A link that does not answer in time is a failure of this route, never an exception that ends the
|
|
330
|
+
// whole run (a locator waited its default 30 s under load and aborted the gate, 0.9.0 self-test).
|
|
331
|
+
await link.nth(idx).evaluate((el) => window.__caosGate.bringEl(el), undefined, { timeout: 10000 }).catch(() => undefined);
|
|
332
|
+
const box = await link.nth(idx).boundingBox({ timeout: 10000 }).catch(() => null);
|
|
331
333
|
if (box) {
|
|
332
334
|
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
|
333
335
|
await page.waitForURL((u) => routeOf(u.href) === route, { timeout: 3000 }).catch(() => undefined);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Shared web-app shell, workbench primitives, content primitives, design system and measured gate for the CAOS / Faena public apps (ADR-0016, ADR-0017, ADR-0071).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Felipe Santibáñez-Leal",
|
package/reserved-classes.json
CHANGED
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"caos-verdict",
|
|
95
95
|
"caos-verdict-text",
|
|
96
96
|
"caos-verdict-title",
|
|
97
|
+
"caos-views-col",
|
|
97
98
|
"caos-views-row",
|
|
98
99
|
"caos-wb",
|
|
99
100
|
"caos-wb-main",
|
|
@@ -210,7 +211,8 @@
|
|
|
210
211
|
"tone-good",
|
|
211
212
|
"tone-neutral",
|
|
212
213
|
"tone-warn",
|
|
213
|
-
"two-col"
|
|
214
|
+
"two-col",
|
|
215
|
+
"uplot"
|
|
214
216
|
],
|
|
215
217
|
"modifiers": [
|
|
216
218
|
"accent",
|
|
@@ -325,6 +327,7 @@
|
|
|
325
327
|
"caos-verdict",
|
|
326
328
|
"caos-verdict-text",
|
|
327
329
|
"caos-verdict-title",
|
|
330
|
+
"caos-views-col",
|
|
328
331
|
"caos-views-row",
|
|
329
332
|
"caos-wb",
|
|
330
333
|
"caos-wb-main",
|
|
@@ -454,6 +457,7 @@
|
|
|
454
457
|
"tone-neutral",
|
|
455
458
|
"tone-warn",
|
|
456
459
|
"two-col",
|
|
460
|
+
"uplot",
|
|
457
461
|
"warn",
|
|
458
462
|
"wide"
|
|
459
463
|
]
|
package/styles.css
CHANGED
|
@@ -579,6 +579,9 @@ svg.lucide { flex: none; }
|
|
|
579
579
|
defect 23, #54: `.caos-plot.fill` outranked the row's zero basis and took the row). */
|
|
580
580
|
.caos-views-row { display: flex; gap: 0.9rem; flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
581
581
|
.caos-views-row > *, .caos-views-row > .caos-plot.fill { flex: 1 1 0; min-width: 0; }
|
|
582
|
+
/* ViewsRow's column: a view inside it keeps its height chain (a filling card fills the column); its share of the row
|
|
583
|
+
is its flex-grow. */
|
|
584
|
+
.caos-views-col { display: flex; flex-direction: column; min-width: 0; min-height: 0; gap: 0.6rem; }
|
|
582
585
|
|
|
583
586
|
/* Staleness: a view showing data for an earlier selection says so. */
|
|
584
587
|
.caos-stale .caos-plot-body > :not(.caos-stale-overlay), .caos-readout.caos-stale dl { opacity: 0.45; }
|