@fasl-work/caos-app-shell 0.14.3 → 0.15.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 +1 -1
- package/gate/inpage.mjs +69 -0
- package/gate/run.mjs +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ const config: ShellConfig = {
|
|
|
110
110
|
(G1) or mode (G2), console errors and 4xx/5xx (G3), broken deep links or HTML for an artifact (G4), anything a
|
|
111
111
|
reader cannot scroll to, see whole or click (G5), blank or underfilled drawings and an instrument under half the
|
|
112
112
|
viewport (G6), a view that never settles (G7), loops at rest (G8), controls that change nothing (G9), labels cut or
|
|
113
|
-
overlapping in a drawing, also in a wider fallback font (G10), a decimal point on a Spanish page (G11), a vertical
|
|
113
|
+
overlapping in a drawing, also in a wider fallback font (G10), a decimal point on a Spanish page (G11), a number or percentage broken across lines (G19), a vertical
|
|
114
114
|
sub-tab list that scrolls away (G12), text under WCAG AA contrast (G13, every language on a documentation page), and a documentation page without the
|
|
115
115
|
required areas of its kind (`ADR-0017.2`: executive and technical Introduction, Methodology, Implementation,
|
|
116
116
|
Experiments, Benchmark; declared with `<DocSection area="...">`, waived only with a reason of 12 letters or digits in two
|
package/gate/inpage.mjs
CHANGED
|
@@ -1281,6 +1281,75 @@ export function installLib() {
|
|
|
1281
1281
|
return [...new Set(out)].slice(0, 12);
|
|
1282
1282
|
};
|
|
1283
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* G19, values whole (BL-065): a short value written in the page's text sits on one line. Shell 0.14.2 fixed a share
|
|
1286
|
+
* "35%" that broke into "3", "5", "%" in a squeezed flex row; no other check sees it, because the pieces are neither
|
|
1287
|
+
* clipped nor outside their box. A short value is a run of characters without breaking whitespace (U+0020, tab, CR,
|
|
1288
|
+
* LF; U+00A0 and U+202F do not end a run) inside one text node, 2 to 16 characters long once the punctuation of the
|
|
1289
|
+
* sentence is stripped from its ends, with a digit, only digits, signs (the hyphens and dashes included), separators,
|
|
1290
|
+
* currency signs, % per mille x and degree signs and at most 4 letters (35%, 0,96, US$1.2bn, 2026-10-15, -3,2pp, 1e-6,
|
|
1291
|
+
* ADR-0071). A value is cut into segments right after a hyphen (U+002D, U+2010, U+2011), an en or em dash and a slash;
|
|
1292
|
+
* a break at those places is a normal line break, so the pieces between them are what must stay whole. A leading sign
|
|
1293
|
+
* stays in the first segment (a lone "-" apart from "3,2" is a broken value). The lines of a segment of 2 or more
|
|
1294
|
+
* characters are the groups of its Range's client rects by top (2px); a segment on more than one line fails the value,
|
|
1295
|
+
* also when it is longer than its container: a segment is never cut into pieces. Two values apart by a regular space
|
|
1296
|
+
* ("12 %") are two runs and may wrap between them. Limits: text nodes only (a value split across elements, <b>35</b>%,
|
|
1297
|
+
* is not read); SVG text is G10's
|
|
1298
|
+
* subject and canvas text cannot be read; code, formulas and anything marked translate="no" are skipped.
|
|
1299
|
+
*/
|
|
1300
|
+
G.brokenValues = () => {
|
|
1301
|
+
const out = [];
|
|
1302
|
+
const skip = 'code, pre, kbd, samp, .katex, svg, canvas, textarea, [translate="no"], [aria-hidden="true"], script, style';
|
|
1303
|
+
const runRe = /[^ \t\r\n]+/g;
|
|
1304
|
+
const BREAK_AFTER = '-\u2010\u2011\u2013\u2014/';
|
|
1305
|
+
const strip = /^[()[\]"',;:.]+|[()[\]"',;:.]+$/g;
|
|
1306
|
+
const allowed = /^[\p{Nd}+\-\u2010\u2011\u2013\u2014\u2212\u00b1.,:/'\u00a0\u202f$\u20ac\u00a3\u00a5%\u2030\u00d7\u00b0\p{L}]+$/u;
|
|
1307
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
1308
|
+
const range = document.createRange();
|
|
1309
|
+
const seen = new Set();
|
|
1310
|
+
for (let n = walker.nextNode(); n && out.length < 12; n = walker.nextNode()) {
|
|
1311
|
+
const text = n.nodeValue || '';
|
|
1312
|
+
if (!/\d/.test(text)) continue;
|
|
1313
|
+
const el = n.parentElement;
|
|
1314
|
+
if (!el || el.closest(skip) || !shown(el)) continue;
|
|
1315
|
+
for (const m of text.matchAll(runRe)) {
|
|
1316
|
+
const run = m[0];
|
|
1317
|
+
if (!/\d/.test(run)) continue;
|
|
1318
|
+
const lead = run.length - run.replace(/^[()[\]"',;:.]+/, '').length;
|
|
1319
|
+
const value = run.replace(strip, '');
|
|
1320
|
+
if (value.length < 2 || value.length > 16 || !/\d/.test(value) || !allowed.test(value)) continue;
|
|
1321
|
+
if ((value.match(/\p{L}/gu) || []).length > 4) continue;
|
|
1322
|
+
const start = m.index + lead;
|
|
1323
|
+
// a break right after a hyphen, a dash or a slash is a normal line break; the segments between them must stay whole.
|
|
1324
|
+
// A leading sign belongs to the first segment (a "-" apart from "3,2" is a broken value).
|
|
1325
|
+
const cuts = [0];
|
|
1326
|
+
for (let i = 1; i < value.length - 1; i += 1) if (BREAK_AFTER.includes(value[i])) cuts.push(i + 1);
|
|
1327
|
+
cuts.push(value.length);
|
|
1328
|
+
let worst = 1;
|
|
1329
|
+
for (let k = 0; k + 1 < cuts.length; k += 1) {
|
|
1330
|
+
if (cuts[k + 1] - cuts[k] < 2) continue;
|
|
1331
|
+
range.setStart(n, start + cuts[k]);
|
|
1332
|
+
range.setEnd(n, start + cuts[k + 1]);
|
|
1333
|
+
const tops = [];
|
|
1334
|
+
for (const r of range.getClientRects()) {
|
|
1335
|
+
if (r.width <= 0.5) continue;
|
|
1336
|
+
if (!tops.some((t) => Math.abs(t - r.top) <= 2)) tops.push(r.top);
|
|
1337
|
+
}
|
|
1338
|
+
worst = Math.max(worst, tops.length);
|
|
1339
|
+
}
|
|
1340
|
+
if (worst > 1) {
|
|
1341
|
+
const msg = `"${value}" breaks across ${worst} lines (${describe(el)})`;
|
|
1342
|
+
if (!seen.has(msg)) {
|
|
1343
|
+
seen.add(msg);
|
|
1344
|
+
out.push(msg);
|
|
1345
|
+
if (out.length >= 12) break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return out;
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1284
1353
|
/** The scroll container of an element: the nearest ancestor that scrolls vertically, or the document. */
|
|
1285
1354
|
const scroller = (el) => {
|
|
1286
1355
|
for (let a = el.parentElement; a && a !== document.body; a = a.parentElement) {
|
package/gate/run.mjs
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// G18 Focus mode the App's Focus control opens the case's open group in the full viewport and returns to the same case and
|
|
30
30
|
// group (by the exit control and by Escape); no page scroll, the rail without scroll, one tab row; no stage
|
|
31
31
|
// control under the Focus chrome (label, HUD, actions); no Focus control inside the Focus frame
|
|
32
|
+
// G19 values whole a short value (number, percentage, amount, unit) sits on one line in every state
|
|
32
33
|
// plus the ADR-0071 and ADR-0017 measures (viewport, one-row tabs, rail without scroll, centred prose, captions,
|
|
33
34
|
// references).
|
|
34
35
|
|
|
@@ -465,6 +466,7 @@ export async function runGate(o) {
|
|
|
465
466
|
if (big) for (const c of f.fillStages ?? []) if (c.h < opt.stageFloor) fail('G6', where, `the drawing of ${c.label} keeps ${Math.round(c.h)} px under its note (floor ${opt.stageFloor} px)`);
|
|
466
467
|
for (const m of await page.evaluate(() => window.__caosGate.drawingText())) fail('G10', where, m);
|
|
467
468
|
if (s.lang === 'es') for (const m of await page.evaluate(() => window.__caosGate.decimals())) fail('G11', where, m);
|
|
469
|
+
for (const m of await page.evaluate(() => window.__caosGate.brokenValues())) fail('G19', where, m);
|
|
468
470
|
if (s.w > 760) for (const m of await page.evaluate(() => window.__caosGate.sticky())) fail('G12', where, m);
|
|
469
471
|
if (s.contrast) for (const m of await page.evaluate(() => window.__caosGate.contrast())) fail('G13', where, m);
|
|
470
472
|
if (f.sectionsWithoutRefs) fail('ADR-0017.4', where, `${f.sectionsWithoutRefs} section(s) end without references or a stated reason`);
|
|
@@ -893,6 +895,7 @@ export async function runGate(o) {
|
|
|
893
895
|
for (const c of f.covered) fail('G18', at, `${label}: ${c.chrome} covers the control "${c.control}" (the stage must clear the Focus chrome)`);
|
|
894
896
|
if (f.enterInside > 0) fail('G18', at, `${label}: the Focus control shows inside Focus mode (it opens the mode the reader is already in)`);
|
|
895
897
|
for (const m of await page.evaluate(() => window.__caosGate.drawingText())) fail('G10', at, m);
|
|
898
|
+
for (const m of await page.evaluate(() => window.__caosGate.brokenValues())) fail('G19', at, m);
|
|
896
899
|
if (s.contrast) for (const m of await page.evaluate(() => window.__caosGate.contrast())) fail('G13', at, m);
|
|
897
900
|
if (index === 0) await saveShot(page, `focus_${slug(s.route)}_${s.w}x${s.h}_${s.theme}_${s.lang}.png`);
|
|
898
901
|
// leave: the exit control for one case, Escape for the next, so both ways are walked
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|