@siyahi/tools 0.1.7-next.2 → 0.1.7-next.3
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 +5 -1
- package/package.json +1 -1
- package/shots.mjs +34 -8
- package/types/shots.d.mts +1 -0
package/README.md
CHANGED
|
@@ -85,7 +85,11 @@ and the fonts are ready, so content a page renders after loading its data is
|
|
|
85
85
|
in the shot. A mode fails when nothing matches within 15 seconds; pass a
|
|
86
86
|
selector that only the finished page has, such as `--wait-for '[data-ready]'`. It fails when visible text or a
|
|
87
87
|
card comes closer than `--gutter` pixels to either edge, when the page scrolls
|
|
88
|
-
sideways,
|
|
88
|
+
sideways, when text or a control (a heading, paragraph, cell, link, button or
|
|
89
|
+
field) is cut off at the side by a box that hides its overflow, such as a card,
|
|
90
|
+
or when the body font is not a loaded `--font` family. The cut-off check names
|
|
91
|
+
each element and the pixels it loses, `button "Monthly return" by 56px`; the
|
|
92
|
+
page never scrolls sideways in that case, so the scroll check cannot see it. Pass
|
|
89
93
|
`--report` for a static HTML report on `report.css`, which loads no font file
|
|
90
94
|
by design: the body must still name `--font` first, but the viewer's installed
|
|
91
95
|
font or the system fallback may draw it, and the run prints
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siyahi/tools",
|
|
3
|
-
"version": "0.1.7-next.
|
|
3
|
+
"version": "0.1.7-next.3",
|
|
4
4
|
"description": "Checks and build helpers for Siyahi pages: a lint plugin, a font preload plugin, a theme contrast check, and screenshot and layout shift scripts.",
|
|
5
5
|
"homepage": "https://siyahi.jatinbansal.com",
|
|
6
6
|
"license": "MIT",
|
package/shots.mjs
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Checks: a gutter of at least --gutter px on both sides of visible text and
|
|
17
17
|
// cards, no sideways document scroll (also with a classic scrollbar taking its
|
|
18
|
-
// width),
|
|
18
|
+
// width), no text or control cut off at the side by a box that hides its
|
|
19
|
+
// overflow (a card does; the document scroll check cannot see what it cuts),
|
|
20
|
+
// and --font as the loaded body font. --report is for static HTML
|
|
19
21
|
// reports, which load no font file by design: the body must still name --font
|
|
20
22
|
// first, but the viewer's installed font or the system fallback may draw it.
|
|
21
23
|
// Exempt from the gutter: content
|
|
@@ -35,7 +37,7 @@ import { parseArgs } from "node:util";
|
|
|
35
37
|
import { chromium, errors } from "playwright";
|
|
36
38
|
|
|
37
39
|
/** @typedef {{ name: string, width: number, height: number, dark: boolean }} Mode */
|
|
38
|
-
/** @typedef {{ gutterLeft: number | null, gutterRight: number | null, overflow: number, overflowWithScrollbar: number, font: string, fontLoaded: boolean, docHeight: number, h1: string | null }} Probe */
|
|
40
|
+
/** @typedef {{ gutterLeft: number | null, gutterRight: number | null, overflow: number, overflowWithScrollbar: number, clipped: string[], font: string, fontLoaded: boolean, docHeight: number, h1: string | null }} Probe */
|
|
39
41
|
|
|
40
42
|
/** @type {Mode[]} */
|
|
41
43
|
const MODES = [
|
|
@@ -89,14 +91,35 @@ const PROBE = `((chrome) => {
|
|
|
89
91
|
}
|
|
90
92
|
return false;
|
|
91
93
|
};
|
|
94
|
+
const shown = (el) => {
|
|
95
|
+
const r = el.getBoundingClientRect();
|
|
96
|
+
return r.width > 0 && r.height > 0 && !el.closest(chrome) && getComputedStyle(el).visibility !== "hidden";
|
|
97
|
+
};
|
|
98
|
+
// Pixels of el cut off at the side by an ancestor that hides its overflow,
|
|
99
|
+
// and how it hides it, up to the nearest one that scrolls, which shows the
|
|
100
|
+
// rest on scroll.
|
|
101
|
+
const cutOff = (el) => {
|
|
102
|
+
const r = el.getBoundingClientRect();
|
|
103
|
+
for (let a = el.parentElement; a; a = a.parentElement) {
|
|
104
|
+
const o = getComputedStyle(a).overflowX;
|
|
105
|
+
if (o === "auto" || o === "scroll") return 0;
|
|
106
|
+
if (o !== "hidden" && o !== "clip") continue;
|
|
107
|
+
const left = a.getBoundingClientRect().left + a.clientLeft;
|
|
108
|
+
const cut = Math.max(left - r.left, r.right - (left + a.clientWidth));
|
|
109
|
+
if (cut >= 1) return { px: Math.round(cut), overflow: o };
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
};
|
|
92
113
|
const visible = [...document.querySelectorAll('h1,h2,h3,h4,p,li,td,th,[data-slot="card"]')].filter((el) => {
|
|
93
114
|
const r = el.getBoundingClientRect();
|
|
94
|
-
|
|
95
|
-
if (r.right <= 0 || r.left >= de.clientWidth) return false;
|
|
96
|
-
if (el.closest(chrome)) return false;
|
|
97
|
-
const s = getComputedStyle(el);
|
|
98
|
-
return s.visibility !== "hidden" && !scrollsSideways(el);
|
|
115
|
+
return shown(el) && r.right > 0 && r.left < de.clientWidth && !scrollsSideways(el);
|
|
99
116
|
});
|
|
117
|
+
// Badges, card titles and descriptions are spans and divs, which the tag list misses.
|
|
118
|
+
const clipped = [...document.querySelectorAll('h1,h2,h3,h4,p,li,td,th,a,button,input,select,textarea,label,[data-slot="badge"],[data-slot="card-title"],[data-slot="card-description"]')]
|
|
119
|
+
.filter(shown)
|
|
120
|
+
.map((el) => ({ el, cut: cutOff(el) }))
|
|
121
|
+
.filter(({ cut }) => cut)
|
|
122
|
+
.map(({ el, cut }) => el.tagName.toLowerCase() + " " + JSON.stringify(el.textContent.trim().slice(0, 40)) + " by " + cut.px + "px is cut off by an overflow: " + cut.overflow + " box");
|
|
100
123
|
const lefts = visible.map((el) => el.getBoundingClientRect().left);
|
|
101
124
|
const rights = visible.map((el) => de.clientWidth - el.getBoundingClientRect().right);
|
|
102
125
|
const overflow = de.scrollWidth - de.clientWidth;
|
|
@@ -113,6 +136,7 @@ const PROBE = `((chrome) => {
|
|
|
113
136
|
gutterRight: rights.length ? Math.round(Math.min(...rights)) : null,
|
|
114
137
|
overflow,
|
|
115
138
|
overflowWithScrollbar,
|
|
139
|
+
clipped,
|
|
116
140
|
font: first,
|
|
117
141
|
fontLoaded: document.fonts.check('16px "' + first + '"') && [...document.fonts].some((f) => f.family.replace(/["']/g, "") === first && f.status === "loaded"),
|
|
118
142
|
docHeight: de.scrollHeight,
|
|
@@ -194,6 +218,8 @@ function problemsOf(p, ready) {
|
|
|
194
218
|
if (p.overflow > 0) problems.push(`document scrolls sideways by ${p.overflow}px`);
|
|
195
219
|
if (p.overflowWithScrollbar > 0)
|
|
196
220
|
problems.push(`scrolls sideways by ${p.overflowWithScrollbar}px once a scrollbar takes its width`);
|
|
221
|
+
for (const what of p.clipped.slice(0, 5)) problems.push(what);
|
|
222
|
+
if (p.clipped.length > 5) problems.push(`and ${p.clipped.length - 5} more cut off`);
|
|
197
223
|
if (!p.font.startsWith(values.font)) problems.push(`body font is ${p.font}, not ${values.font}`);
|
|
198
224
|
else if (!p.fontLoaded && !values.report) problems.push(`${p.font} is named but never loaded`);
|
|
199
225
|
return problems;
|
|
@@ -209,7 +235,7 @@ for (const url of positionals) {
|
|
|
209
235
|
const font = values.report && !probe.fontLoaded ? `${probe.font} (report, system fallback allowed)` : probe.font;
|
|
210
236
|
console.log(
|
|
211
237
|
`${problems.length ? "FAIL" : "ok "} ${mode.name.padEnd(10)} gutter=${probe.gutterLeft}/${probe.gutterRight} ` +
|
|
212
|
-
`overflow=${probe.overflow}/${probe.overflowWithScrollbar} font=${font} h1=${JSON.stringify(probe.h1)} ` +
|
|
238
|
+
`overflow=${probe.overflow}/${probe.overflowWithScrollbar} clipped=${probe.clipped.length} font=${font} h1=${JSON.stringify(probe.h1)} ` +
|
|
213
239
|
`height=${height} ${files.join(" ")}`,
|
|
214
240
|
);
|
|
215
241
|
for (const problem of problems) console.log(` ${problem}`);
|