@fasl-work/caos-app-shell 0.9.0 → 0.9.2
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/chart.d.ts +12 -1
- package/dist/chart.js +33 -4
- package/gate/inpage.mjs +41 -7
- package/package.json +1 -1
- package/styles.css +2 -2
package/dist/chart.d.ts
CHANGED
|
@@ -68,6 +68,17 @@ declare function parityRange(...arrays: readonly (readonly (number | null)[])[])
|
|
|
68
68
|
* by significant digits has no fixed resolution, and `data-ticks-repeat` reports what it does.
|
|
69
69
|
*/
|
|
70
70
|
declare function tickSteps(fmt: FormatOptions | undefined): number[] | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* uPlot ticks a log axis at 1 to 9 times every power of ten, from a precision table that ends near 1e-22: a log axis
|
|
73
|
+
* that reaches below it throws "Invalid array length" after stalling the page, and the chart is never drawn (known
|
|
74
|
+
* shell defect 31, p-values of 1e-124 in CAOS_Contraste, 2026-10-07). Below this floor the axis ticks at powers of ten
|
|
75
|
+
* only (`logDecades`), each one labelled.
|
|
76
|
+
*/
|
|
77
|
+
declare const LOG_TABLE_FLOOR = 1e-22;
|
|
78
|
+
/** The powers of ten within [min, max], thinned to at most `most` (every k-th decade, the top one kept). */
|
|
79
|
+
declare function logDecades(min: number, max: number, most?: number): number[];
|
|
80
|
+
/** The smallest positive value among columns of values and the lower end of a fixed range; Infinity if none. */
|
|
81
|
+
declare function smallestPositive(columns: readonly (readonly (number | null)[])[], range?: readonly [number, number] | null): number;
|
|
71
82
|
/** How many tick labels repeat the label before them (empty labels apart); the gate fails a chart with any (G6). */
|
|
72
83
|
declare function repeatedLabels(labels: readonly (string | null | undefined)[]): number;
|
|
73
84
|
/**
|
|
@@ -98,4 +109,4 @@ declare function drawMarks(u: uPlot, marks: {
|
|
|
98
109
|
*/
|
|
99
110
|
declare function UPlotChart({ x, y, series, marks, height, onCursor, onPick, parity }: UPlotChartProps): react.JSX.Element;
|
|
100
111
|
|
|
101
|
-
export { type ChartAxis, type ChartSeries, UPlotChart, type UPlotChartProps, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
|
|
112
|
+
export { type ChartAxis, type ChartSeries, LOG_TABLE_FLOOR, UPlotChart, type UPlotChartProps, drawMarks, logDecades, parityRange, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
package/dist/chart.js
CHANGED
|
@@ -37,6 +37,26 @@ function tickSteps(fmt) {
|
|
|
37
37
|
const res = 10 ** -fmt.decimals / (fmt.percent ? 100 : 1);
|
|
38
38
|
return STEPS.filter((s) => s >= res * (1 - 1e-9) && Math.abs(s / res - Math.round(s / res)) < 1e-6);
|
|
39
39
|
}
|
|
40
|
+
var LOG_TABLE_FLOOR = 1e-22;
|
|
41
|
+
function logDecades(min, max, most = 8) {
|
|
42
|
+
if (!(min > 0) || !(max > 0) || !Number.isFinite(min) || !Number.isFinite(max) || max < min) return [];
|
|
43
|
+
const a = Math.ceil(Math.log10(min) - 1e-9);
|
|
44
|
+
const b = Math.floor(Math.log10(max) + 1e-9);
|
|
45
|
+
if (b < a) return [];
|
|
46
|
+
const step = Math.max(1, Math.ceil((b - a + 1) / most));
|
|
47
|
+
const out = [];
|
|
48
|
+
for (let k = b; k >= a; k -= step) out.unshift(Number(`1e${k}`));
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function smallestPositive(columns, range) {
|
|
52
|
+
let lo = range && range[0] > 0 ? range[0] : Infinity;
|
|
53
|
+
for (const c of columns) for (const v of c) if (v !== null && Number.isFinite(v) && v > 0 && v < lo) lo = v;
|
|
54
|
+
return lo;
|
|
55
|
+
}
|
|
56
|
+
var DECADE_AXIS = {
|
|
57
|
+
splits: (_u, _i, min, max) => logDecades(min, max),
|
|
58
|
+
filter: (_u, splits) => splits
|
|
59
|
+
};
|
|
40
60
|
function repeatedLabels(labels) {
|
|
41
61
|
let n = 0;
|
|
42
62
|
for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
|
|
@@ -110,16 +130,23 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
|
|
|
110
130
|
const xs = useMemo(() => order ? order.map((i) => x.values[i]) : x.values, [order, x.values]);
|
|
111
131
|
const ys = useMemo(() => series.map((s) => order ? order.map((i) => s.values[i] ?? null) : s.values), [order, series]);
|
|
112
132
|
const range = useMemo(() => parity ? parityRange(xs, ...ys) : null, [parity, xs, ys]);
|
|
133
|
+
const tiny = useMemo(
|
|
134
|
+
() => ({
|
|
135
|
+
x: Boolean(x.log) && smallestPositive([xs], range) < LOG_TABLE_FLOOR,
|
|
136
|
+
y: Boolean(y.log) && smallestPositive(ys, range ?? y.range) < LOG_TABLE_FLOOR
|
|
137
|
+
}),
|
|
138
|
+
[x.log, y.log, xs, ys, range, y.range]
|
|
139
|
+
);
|
|
113
140
|
const xLabel = pick(x.label, lang);
|
|
114
141
|
const yLabel = pick(y.label, lang);
|
|
115
142
|
const xUnit = x.unit ? pick(x.unit, lang) : "";
|
|
116
143
|
const yUnit = y.unit ? pick(y.unit, lang) : "";
|
|
117
144
|
const structure = JSON.stringify({
|
|
118
145
|
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],
|
|
146
|
+
x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null, tiny.x],
|
|
120
147
|
p: range,
|
|
121
148
|
pick: Boolean(onPick),
|
|
122
|
-
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null],
|
|
149
|
+
y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null, tiny.y],
|
|
123
150
|
m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
|
|
124
151
|
theme,
|
|
125
152
|
lang,
|
|
@@ -168,7 +195,8 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
|
|
|
168
195
|
label: xUnit ? `${xLabel} (${xUnit})` : xLabel,
|
|
169
196
|
labelFont: font,
|
|
170
197
|
values: x.time ? void 0 : tick(x.format, "x"),
|
|
171
|
-
...xSteps ? { incrs: xSteps } : {}
|
|
198
|
+
...xSteps ? { incrs: xSteps } : {},
|
|
199
|
+
...tiny.x ? DECADE_AXIS : {}
|
|
172
200
|
},
|
|
173
201
|
{
|
|
174
202
|
stroke: fg,
|
|
@@ -179,6 +207,7 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
|
|
|
179
207
|
labelFont: font,
|
|
180
208
|
values: tick(y.format, "y"),
|
|
181
209
|
...ySteps ? { incrs: ySteps } : {},
|
|
210
|
+
...tiny.y ? DECADE_AXIS : {},
|
|
182
211
|
// Sized to the longest tick label. uPlot draws in device pixels with a font scaled by its pixel ratio, so the
|
|
183
212
|
// label is measured in that font and divided back (measuring the unscaled font and dividing by the ratio
|
|
184
213
|
// made the axis too narrow on every high-density screen; the gate runs at ratio 1 and never saw it).
|
|
@@ -282,4 +311,4 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
|
|
|
282
311
|
);
|
|
283
312
|
}
|
|
284
313
|
|
|
285
|
-
export { UPlotChart, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
|
|
314
|
+
export { LOG_TABLE_FLOOR, UPlotChart, drawMarks, logDecades, parityRange, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
|
package/gate/inpage.mjs
CHANGED
|
@@ -531,20 +531,27 @@ export function installLib() {
|
|
|
531
531
|
// ---- 0.8.0 measures ----------------------------------------------------------------------------------------
|
|
532
532
|
|
|
533
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
|
|
534
|
+
* clip). A scroll container does not cut (a reader scrolls it), and neither does the viewport; and what lies beyond
|
|
535
|
+
* the first scroll container on an axis is a matter of scroll position, so the walk stops there on that axis (0.9.1:
|
|
536
|
+
* the contained `.app-shell`, viewport-high with its overflow hidden, read every documentation figure below the fold
|
|
537
|
+
* as "outside the drawing"). */
|
|
535
538
|
const cutArea = (el) => {
|
|
536
539
|
const area = { left: -Infinity, top: -Infinity, right: Infinity, bottom: Infinity };
|
|
537
|
-
|
|
540
|
+
let x = true;
|
|
541
|
+
let y = true;
|
|
542
|
+
for (let a = el.parentElement; a && a !== document.body && a !== document.documentElement && (x || y); a = a.parentElement) {
|
|
538
543
|
const s = getComputedStyle(a);
|
|
539
544
|
const ar = a.getBoundingClientRect();
|
|
540
|
-
if (s.overflowX === 'hidden' || s.overflowX === 'clip') {
|
|
545
|
+
if (x && (s.overflowX === 'hidden' || s.overflowX === 'clip')) {
|
|
541
546
|
area.left = Math.max(area.left, ar.left);
|
|
542
547
|
area.right = Math.min(area.right, ar.right);
|
|
543
548
|
}
|
|
544
|
-
if (s.overflowY === 'hidden' || s.overflowY === 'clip') {
|
|
549
|
+
if (y && (s.overflowY === 'hidden' || s.overflowY === 'clip')) {
|
|
545
550
|
area.top = Math.max(area.top, ar.top);
|
|
546
551
|
area.bottom = Math.min(area.bottom, ar.bottom);
|
|
547
552
|
}
|
|
553
|
+
if (s.overflowX === 'auto' || s.overflowX === 'scroll') x = false;
|
|
554
|
+
if (s.overflowY === 'auto' || s.overflowY === 'scroll') y = false;
|
|
548
555
|
}
|
|
549
556
|
return area;
|
|
550
557
|
};
|
|
@@ -719,6 +726,29 @@ export function installLib() {
|
|
|
719
726
|
for (let i = layers.length - 1; i >= 0; i -= 1) base = over(layers[i], base);
|
|
720
727
|
return base;
|
|
721
728
|
};
|
|
729
|
+
/** The colour an SVG text element is painted on: the last filled shape painted before it whose box holds its centre. */
|
|
730
|
+
const svgBackdrop = (text, page) => {
|
|
731
|
+
const svg = text.closest('svg');
|
|
732
|
+
if (!svg) return page;
|
|
733
|
+
const r = text.getBoundingClientRect();
|
|
734
|
+
const cx = r.left + r.width / 2;
|
|
735
|
+
const cy = r.top + r.height / 2;
|
|
736
|
+
let found = null;
|
|
737
|
+
for (const shape of svg.querySelectorAll('rect, circle, ellipse, polygon, path')) {
|
|
738
|
+
if (!(shape.compareDocumentPosition(text) & Node.DOCUMENT_POSITION_FOLLOWING)) continue;
|
|
739
|
+
const ss = getComputedStyle(shape);
|
|
740
|
+
if (ss.display === 'none' || ss.visibility === 'hidden') continue;
|
|
741
|
+
const fill = parseColor(ss.fill);
|
|
742
|
+
if (!fill || fill.a === 0) continue;
|
|
743
|
+
const b = shape.getBoundingClientRect();
|
|
744
|
+
if (cx < b.left || cx > b.right || cy < b.top || cy > b.bottom) continue;
|
|
745
|
+
const a = fill.a * Number(ss.fillOpacity || 1) * opacityOf(shape);
|
|
746
|
+
if (a <= 0.05) continue;
|
|
747
|
+
found = { ...fill, a };
|
|
748
|
+
}
|
|
749
|
+
return found ? over(found, page) : page;
|
|
750
|
+
};
|
|
751
|
+
|
|
722
752
|
const opacityOf = (el) => {
|
|
723
753
|
let o = 1;
|
|
724
754
|
for (let a = el; a; a = a.parentElement) o *= Number(getComputedStyle(a).opacity || 1);
|
|
@@ -753,8 +783,12 @@ export function installLib() {
|
|
|
753
783
|
const fg = parseColor(isSvg ? s.fill : s.color);
|
|
754
784
|
if (!fg) continue;
|
|
755
785
|
const host = isSvg ? el.closest('svg')?.parentElement ?? el : el;
|
|
756
|
-
const
|
|
757
|
-
if (!
|
|
786
|
+
const page = backdrop(host);
|
|
787
|
+
if (!page) continue;
|
|
788
|
+
// SVG text sits on what the drawing painted under it (a label on a bar): the last filled shape before it whose
|
|
789
|
+
// box holds the text's centre, composited over the page (0.9.1: a marker in the page colour on a red bar read
|
|
790
|
+
// 1.06:1 against the page)
|
|
791
|
+
const bg = isSvg ? svgBackdrop(el, page) : page;
|
|
758
792
|
const alpha = fg.a * opacityOf(el);
|
|
759
793
|
const shownColor = over({ ...fg, a: alpha }, bg);
|
|
760
794
|
const l1 = lum(shownColor);
|
|
@@ -763,7 +797,7 @@ export function installLib() {
|
|
|
763
797
|
const px = parseFloat(s.fontSize) || 16;
|
|
764
798
|
const bold = Number(s.fontWeight) >= 700;
|
|
765
799
|
const need = px >= 24 || (bold && px >= 18.66) ? 3 : 4.5;
|
|
766
|
-
if (ratio + 1e-6 < need) out.push(`${describe(el)}: contrast ${ratio.toFixed(2)}:1, below ${need}:1 (${s.
|
|
800
|
+
if (ratio + 1e-6 < need) out.push(`${describe(el)}: contrast ${ratio.toFixed(2)}:1, below ${need}:1 (${isSvg ? s.fill : s.color} on rgb(${Math.round(bg.r)}, ${Math.round(bg.g)}, ${Math.round(bg.b)}))`);
|
|
767
801
|
}
|
|
768
802
|
return out.slice(0, 10);
|
|
769
803
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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/styles.css
CHANGED
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
--color-accent-fg: #ffffff;
|
|
57
57
|
--color-accent-soft: #ddf4ff;
|
|
58
58
|
--color-accent-2: #0a7886;
|
|
59
|
-
--color-magenta: #
|
|
60
|
-
--color-good: #
|
|
59
|
+
--color-magenta: #b93785;
|
|
60
|
+
--color-good: #197c36;
|
|
61
61
|
--color-warn: #946300;
|
|
62
62
|
--color-bad: #cf222e;
|
|
63
63
|
--color-shadow: 0 1px 2px rgba(31, 35, 40, 0.06), 0 4px 10px rgba(31, 35, 40, 0.04);
|