@leonidstern/census 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +189 -2
- package/cli/cdp.mjs +199 -0
- package/cli/census.mjs +227 -0
- package/cli/playwright.mjs +63 -0
- package/cli/read.mjs +59 -0
- package/cli/sourcemap.mjs +110 -0
- package/dist/browser.d.ts +1 -0
- package/dist/census.d.ts +193 -0
- package/dist/census.js +1990 -0
- package/dist/census.mjs +2111 -0
- package/dist/color.d.ts +9 -0
- package/dist/diff.d.ts +15 -0
- package/dist/findings.d.ts +79 -0
- package/dist/index.d.ts +6 -0
- package/package.json +63 -4
package/dist/census.mjs
ADDED
|
@@ -0,0 +1,2111 @@
|
|
|
1
|
+
// src/color.ts
|
|
2
|
+
function parseColor(css) {
|
|
3
|
+
let m = css.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/);
|
|
4
|
+
if (m) return [+m[1], +m[2], +m[3], m[4] === void 0 ? 1 : m[4].endsWith("%") ? parseFloat(m[4]) / 100 : +m[4]];
|
|
5
|
+
m = css.match(/^color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6
|
+
if (m) return [+m[1] * 255, +m[2] * 255, +m[3] * 255, m[4] === void 0 ? 1 : m[4].endsWith("%") ? parseFloat(m[4]) / 100 : +m[4]];
|
|
7
|
+
m = css.match(/^#([0-9a-f]{3,8})$/i);
|
|
8
|
+
if (m && [3, 4, 6, 8].includes(m[1].length)) {
|
|
9
|
+
const h = m[1].length <= 4 ? [...m[1]].map((x) => x + x).join("") : m[1];
|
|
10
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1];
|
|
11
|
+
}
|
|
12
|
+
return painted(css);
|
|
13
|
+
}
|
|
14
|
+
var paints = /* @__PURE__ */ new Map();
|
|
15
|
+
var probeHost = null;
|
|
16
|
+
var kept = null;
|
|
17
|
+
function colorProbe(host) {
|
|
18
|
+
probeHost = host;
|
|
19
|
+
kept = null;
|
|
20
|
+
}
|
|
21
|
+
var paintCtx;
|
|
22
|
+
function painted(css) {
|
|
23
|
+
if (!/^[a-z(]/i.test(css) || css.length > 240) return null;
|
|
24
|
+
const had = paints.get(css);
|
|
25
|
+
if (had !== void 0) return had;
|
|
26
|
+
if (paintCtx === void 0) {
|
|
27
|
+
try {
|
|
28
|
+
const c = typeof document === "undefined" ? null : document.createElement("canvas");
|
|
29
|
+
if (c) {
|
|
30
|
+
c.width = c.height = 1;
|
|
31
|
+
}
|
|
32
|
+
paintCtx = c?.getContext("2d", { willReadFrequently: true }) ?? null;
|
|
33
|
+
} catch {
|
|
34
|
+
paintCtx = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
let out = null;
|
|
38
|
+
try {
|
|
39
|
+
if (typeof document !== "undefined" && document.body) {
|
|
40
|
+
const probe = kept ?? document.createElement("i");
|
|
41
|
+
probe.style.color = "";
|
|
42
|
+
probe.style.color = `color-mix(in srgb, ${css} 100%, transparent)`;
|
|
43
|
+
if (probe.style.color) {
|
|
44
|
+
if (!probeHost) {
|
|
45
|
+
probe.style.display = "none";
|
|
46
|
+
document.body.append(probe);
|
|
47
|
+
} else if (!kept) {
|
|
48
|
+
probeHost.append(probe);
|
|
49
|
+
kept = probe;
|
|
50
|
+
}
|
|
51
|
+
const said = getComputedStyle(probe).color;
|
|
52
|
+
if (!probeHost) probe.remove();
|
|
53
|
+
const m = said.match(/^color\(srgb\s+([-\d.e]+)\s+([-\d.e]+)\s+([-\d.e]+)(?:\s*\/\s*([\d.e]+))?\s*\)$/);
|
|
54
|
+
if (m) out = [Math.min(255, Math.max(0, +m[1] * 255)), Math.min(255, Math.max(0, +m[2] * 255)), Math.min(255, Math.max(0, +m[3] * 255)), m[4] === void 0 ? 1 : +m[4]];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
out = null;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
if (!out && paintCtx) {
|
|
62
|
+
paintCtx.clearRect(0, 0, 1, 1);
|
|
63
|
+
paintCtx.fillStyle = "#000";
|
|
64
|
+
paintCtx.fillStyle = css;
|
|
65
|
+
if (paintCtx.fillStyle !== "#000000" || /^(#0{3,8}|black|rgba?\(0[,\s]+0[,\s]+0\)?)/i.test(css)) {
|
|
66
|
+
paintCtx.fillRect(0, 0, 1, 1);
|
|
67
|
+
const d = paintCtx.getImageData(0, 0, 1, 1).data;
|
|
68
|
+
out = [d[0], d[1], d[2], d[3] / 255];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
out = null;
|
|
73
|
+
}
|
|
74
|
+
if (paints.size < 200) paints.set(css, out);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function over(top, under) {
|
|
78
|
+
const a = top[3];
|
|
79
|
+
return [top[0] * a + under[0] * (1 - a), top[1] * a + under[1] * (1 - a), top[2] * a + under[2] * (1 - a), 1];
|
|
80
|
+
}
|
|
81
|
+
var lum = (c) => {
|
|
82
|
+
const ch = (v) => {
|
|
83
|
+
const x = v / 255;
|
|
84
|
+
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
85
|
+
};
|
|
86
|
+
return 0.2126 * ch(c[0]) + 0.7152 * ch(c[1]) + 0.0722 * ch(c[2]);
|
|
87
|
+
};
|
|
88
|
+
function contrast(a, b) {
|
|
89
|
+
const x = lum(a), y = lum(b);
|
|
90
|
+
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/findings.ts
|
|
94
|
+
var CHECKS = {
|
|
95
|
+
off: { level: "rule", what: "a value off rules.json (colors, type, radii, shadows)" },
|
|
96
|
+
"no-part": { level: "rule", what: "a control none of rules.parts drew" },
|
|
97
|
+
contrast: { level: "accessibility", preset: "recommended@1", what: "text contrast, WCAG 1.4.3" },
|
|
98
|
+
focus: { level: "accessibility", preset: "recommended@1", what: "focus visible, WCAG 2.4.7" },
|
|
99
|
+
target: { level: "accessibility", preset: "recommended@1", what: "target size, WCAG 2.5.8" },
|
|
100
|
+
max: { level: "max", preset: "best-practice@1", what: "more distinct values in a group than best practice" }
|
|
101
|
+
};
|
|
102
|
+
var PRESETS = ["recommended@1", "best-practice@1"];
|
|
103
|
+
var MAX_DEFAULTS = {
|
|
104
|
+
font: 2,
|
|
105
|
+
type: 8,
|
|
106
|
+
text: 6,
|
|
107
|
+
fill: 12,
|
|
108
|
+
line: 4,
|
|
109
|
+
radius: 4,
|
|
110
|
+
shadow: 5,
|
|
111
|
+
height: 6,
|
|
112
|
+
layer: 3
|
|
113
|
+
};
|
|
114
|
+
var COLOR_GROUPS = ["fill", "text", "line"];
|
|
115
|
+
var presetsOf = (rules) => (typeof rules.extends === "string" ? [rules.extends] : rules.extends ?? []).filter((e) => PRESETS.includes(e));
|
|
116
|
+
var hasValues = (rules) => !!(rules.colors || rules.type || rules.radii || rules.shadows || rules.parts);
|
|
117
|
+
function checkSetting(rules, check) {
|
|
118
|
+
const c = CHECKS[check];
|
|
119
|
+
if (!c) return { level: null };
|
|
120
|
+
const s = rules.checks?.[check];
|
|
121
|
+
const byPreset = !!c.preset && presetsOf(rules).includes(c.preset);
|
|
122
|
+
const byDefault = check === "max" && (!!rules.max || !hasValues(rules));
|
|
123
|
+
const base = c.level === "rule" || byPreset || byDefault ? c.level : null;
|
|
124
|
+
if (s === void 0) return { level: base };
|
|
125
|
+
if (s === "off") return { level: null };
|
|
126
|
+
if (s === "rule") return { level: "rule" };
|
|
127
|
+
if (typeof s === "number") return { level: base ?? c.level, n: s };
|
|
128
|
+
if (Array.isArray(s)) return { level: "rule", n: s[1] };
|
|
129
|
+
return { level: base };
|
|
130
|
+
}
|
|
131
|
+
var asksChecks = (rules) => !!(rules.extends || rules.checks && Object.values(rules.checks).some((v) => v !== "off") || checkSetting(rules, "max").level);
|
|
132
|
+
function maxFindings(groups, rules, name = 3) {
|
|
133
|
+
const want = checkSetting(rules, "max");
|
|
134
|
+
if (!want.level) return [];
|
|
135
|
+
const level = want.level;
|
|
136
|
+
const capOf = (group) => rules.max?.[group] ?? MAX_DEFAULTS[group];
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const g of groups) {
|
|
139
|
+
const cap2 = capOf(g.name);
|
|
140
|
+
if (cap2 === void 0 || cap2 === "off") continue;
|
|
141
|
+
const n = g.rows.length + (g.more ?? 0);
|
|
142
|
+
if (n <= cap2) continue;
|
|
143
|
+
const over2 = n - cap2;
|
|
144
|
+
const tail = [...g.rows].sort((a, b) => a.n - b.n).slice(0, Math.min(name, over2)).map((r) => `${r.key}${(r.note ?? "").replace(/ \((from|via) .*$/, "")}${r.n ? ` \xD7${r.n}` : ""}${r.at[0] ? `: ${r.at[0]}` : ""}`);
|
|
145
|
+
out.push({ id: `max|${g.name}`, check: "max", level, key: `${g.name} ${n} values, ${cap2} the max: drop ${over2}, rarest first`, n, at: [...tail, ...over2 > tail.length ? [`... ${over2 - tail.length} more`] : []] });
|
|
146
|
+
}
|
|
147
|
+
const palette = Object.keys(rules.colors ?? {}).length;
|
|
148
|
+
const paletteCap = COLOR_GROUPS.reduce((sum, g) => {
|
|
149
|
+
const c = capOf(g);
|
|
150
|
+
return typeof c === "number" ? sum + c : sum;
|
|
151
|
+
}, 0);
|
|
152
|
+
if (palette && paletteCap && palette > paletteCap) out.push({ id: "max|rules.json|colors", check: "max", level, key: `rules.json lists ${palette} colors, ${paletteCap} the max`, n: palette, at: [] });
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
function findingLines(c, max = 12) {
|
|
156
|
+
const out = [];
|
|
157
|
+
const fs = (c.findings ?? []).filter((f) => f.level === "accessibility");
|
|
158
|
+
if (fs.length) {
|
|
159
|
+
const lines = [...fs].sort((a, b) => Number(!!a.unsure) - Number(!!b.unsure) || b.n - a.n).slice(0, max).map((f) => ` ${f.key}${f.n > 1 ? ` \xD7${f.n}` : ""}${f.unsure ? " (unsure)" : ""}${f.note ?? ""}${f.at.length ? `: ${f.at.join("; ")}` : ""}`);
|
|
160
|
+
out.push(`accessibility (${fs.length}, WCAG 2.2 AA; unsure: computed styles can't decide)`, ...lines, ...fs.length > max ? [` ... ${fs.length - max} more`] : []);
|
|
161
|
+
}
|
|
162
|
+
const ms = (c.findings ?? []).filter((f) => f.level === "max");
|
|
163
|
+
if (ms.length) {
|
|
164
|
+
out.push(`max (${ms.length} over, best practice)`);
|
|
165
|
+
for (const f of ms) {
|
|
166
|
+
out.push(` ${f.key}${f.note ?? ""}`);
|
|
167
|
+
for (const a of f.at) out.push(` ${a}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
function mergeFindings(pages) {
|
|
173
|
+
const names = Object.keys(pages);
|
|
174
|
+
if (!names.some((p) => pages[p].findings)) return void 0;
|
|
175
|
+
const by = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const p of names) for (const f of pages[p].findings ?? []) {
|
|
177
|
+
if (f.level === "max") continue;
|
|
178
|
+
const had = by.get(f.id), at = f.at.slice(0, 1).map((x) => `[${p}] ${x}`);
|
|
179
|
+
if (!had) {
|
|
180
|
+
by.set(f.id, { f: { ...f, at, values: f.values ? [...f.values] : void 0 }, pages: [p] });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
had.f.n += f.n;
|
|
184
|
+
had.pages.push(p);
|
|
185
|
+
if (had.f.at.length < 3) had.f.at.push(...at);
|
|
186
|
+
if (f.values) had.f.values = [.../* @__PURE__ */ new Set([...had.f.values ?? [], ...f.values])];
|
|
187
|
+
had.f.unsure = had.f.unsure && f.unsure;
|
|
188
|
+
}
|
|
189
|
+
return [...by.values()].map(({ f, pages: ps }) => ({ ...f, note: `${f.note ?? ""} (${ps.length === names.length && names.length > 1 ? "all" : ps.join("+")})` }));
|
|
190
|
+
}
|
|
191
|
+
function diffFindings(before, after, max = 40) {
|
|
192
|
+
const a = new Map((before.findings ?? []).map((f) => [f.id, f])), b = new Map((after.findings ?? []).map((f) => [f.id, f]));
|
|
193
|
+
const line = (f, extra = "") => ` ${f.key}${extra}${f.n > 1 ? ` \xD7${f.n}` : ""}${f.unsure ? " (unsure)" : ""}${f.at[0] ? `: ${f.at[0]}` : ""}`;
|
|
194
|
+
const capped = (l) => l.length > max ? [...l.slice(0, max), ` ... ${l.length - max} more`] : l;
|
|
195
|
+
const newRules = [], newAccessibility = [], maxWorse = [], nw = [], fixed = [], worse = [], better = [];
|
|
196
|
+
for (const [id, f] of b) {
|
|
197
|
+
const was = a.get(id);
|
|
198
|
+
if (f.level === "max") {
|
|
199
|
+
if (!was) {
|
|
200
|
+
maxWorse.push(f);
|
|
201
|
+
worse.push(` ${f.key}`);
|
|
202
|
+
} else if (f.n > was.n) {
|
|
203
|
+
maxWorse.push(f);
|
|
204
|
+
worse.push(` ${f.key} (was ${was.n})`);
|
|
205
|
+
} else if (f.n < was.n) better.push(` ${f.key} (was ${was.n})`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const more = was ? (f.values ?? []).filter((v) => !(was.values ?? []).includes(v)) : [];
|
|
209
|
+
if (was && !more.length) continue;
|
|
210
|
+
(f.level === "rule" ? newRules : newAccessibility).push(f);
|
|
211
|
+
if (f.level === "accessibility") nw.push(line(f, more.length ? ` (now also ${more.slice(0, 6).join(", ")})` : ""));
|
|
212
|
+
}
|
|
213
|
+
for (const [id, f] of a) {
|
|
214
|
+
if (b.has(id)) continue;
|
|
215
|
+
if (f.level === "accessibility") fixed.push(line(f));
|
|
216
|
+
else if (f.level === "max") better.push(` ${f.key.replace(/:.*$/, "")} \u2014 now under`);
|
|
217
|
+
}
|
|
218
|
+
const lines = [
|
|
219
|
+
...nw.length ? [`accessibility new (${nw.length})`, ...capped(nw)] : [],
|
|
220
|
+
...fixed.length ? [`accessibility fixed (${fixed.length})`, ...capped(fixed)] : [],
|
|
221
|
+
...worse.length ? [`max worse (${worse.length})`, ...capped(worse)] : [],
|
|
222
|
+
...better.length ? [`max better (${better.length})`, ...capped(better)] : []
|
|
223
|
+
];
|
|
224
|
+
return { lines, newRules, newAccessibility, maxWorse };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/census.ts
|
|
228
|
+
var POPUP = "[role=menu],[role=listbox],[role=dialog],[role=alertdialog]";
|
|
229
|
+
var PRESSABLE = "button,a[href],[role=button],[role=tab],[role=option],[role=menuitem],[role=switch],[role=checkbox],[role=radio],[role=treeitem],[role=row],[data-item],[data-section]";
|
|
230
|
+
var MADE = /^(react-aria|_r_|:r|«r|radix-|base-ui-|headlessui-|mui-)/;
|
|
231
|
+
var ownId = (el) => el.id && !MADE.test(el.id) ? el.id : "";
|
|
232
|
+
var PROBLEM = /^(outside|cut-text|overlap|wrapped|no-edge|faint|faded|squeezed|covered|faint-drawn|hidden-drawn) /;
|
|
233
|
+
var REGION = "[role=dialog],[role=alertdialog],[role=menu],[role=listbox],[role=toolbar],[role=tablist],[role=region],header,nav,aside,main";
|
|
234
|
+
var box = (r) => `${Math.round(r.left)},${Math.round(r.top)} ${Math.round(r.width)}x${Math.round(r.height)}`;
|
|
235
|
+
var shown = (el, r) => r.width > 0 && r.height > 0 && r.bottom > 0 && r.right > 0 && r.top < innerHeight && r.left < innerWidth && (typeof el.checkVisibility !== "function" || el.checkVisibility({ opacityProperty: true, visibilityProperty: true }));
|
|
236
|
+
var ownText = (el) => [...el.childNodes].filter((n) => n.nodeType === 3).map((n) => n.textContent ?? "").join("").trim().replace(/\s+/g, " ");
|
|
237
|
+
var WORDY = "button,a,label,h1,h2,h3,h4,h5,h6,[role=button],[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio],[role=option],[role=tab],[role=heading],[role=switch],[role=checkbox],[role=radio]";
|
|
238
|
+
function nameOf(el) {
|
|
239
|
+
if (ownId(el)) return `#${el.id}`;
|
|
240
|
+
const role = el.getAttribute("role"), label = el.getAttribute("aria-label");
|
|
241
|
+
const own = el.closest(WORDY) && !el.closest("[data-census-private]") ? ownText(el) : null;
|
|
242
|
+
const words2 = (label ?? own ?? "").slice(0, 40);
|
|
243
|
+
return `${role ?? el.tagName.toLowerCase()}${words2 ? ` ${JSON.stringify(words2)}` : ""}`;
|
|
244
|
+
}
|
|
245
|
+
var inside = (el) => {
|
|
246
|
+
for (let p = el.parentElement; p; p = p.parentElement) if (ownId(p)) return ` in #${p.id}`;
|
|
247
|
+
return "";
|
|
248
|
+
};
|
|
249
|
+
function layoutLines(root = document.body, cap2 = 160, skip, faint = FAINT_TEXT) {
|
|
250
|
+
const regions = [], wrong = [];
|
|
251
|
+
const texts = [];
|
|
252
|
+
const controls = [];
|
|
253
|
+
const seeing = looking();
|
|
254
|
+
const clipOf = /* @__PURE__ */ new Map();
|
|
255
|
+
const room = (el) => {
|
|
256
|
+
if (!el || el === document.documentElement) return new DOMRect(0, 0, innerWidth, innerHeight);
|
|
257
|
+
if (clipOf.has(el)) return clipOf.get(el);
|
|
258
|
+
const up = room(el.parentElement);
|
|
259
|
+
let out = up;
|
|
260
|
+
if (up && el instanceof HTMLElement && !/visible/.test(getComputedStyle(el).overflow)) out = cross(up, el.getBoundingClientRect());
|
|
261
|
+
clipOf.set(el, out);
|
|
262
|
+
return out;
|
|
263
|
+
};
|
|
264
|
+
const all = root.querySelectorAll("*");
|
|
265
|
+
for (let i = 0; i < all.length && i < 8e3; i++) {
|
|
266
|
+
const el = all[i];
|
|
267
|
+
if (el.closest("svg") && el.tagName.toLowerCase() !== "svg") continue;
|
|
268
|
+
if (skip?.(el)) continue;
|
|
269
|
+
const full = el.getBoundingClientRect();
|
|
270
|
+
if (el instanceof HTMLElement && full.width > 0 && full.height < 24 && el.scrollHeight > 40 && el.children.length && wrong.length < 60 && /auto|scroll/.test(getComputedStyle(el).overflowY) && (typeof el.checkVisibility !== "function" || el.checkVisibility({ visibilityProperty: true }))) {
|
|
271
|
+
wrong.push(`squeezed ${nameOf(el)}${inside(el)} ${box(full)}: shows ${Math.round(full.height)}px of ${el.scrollHeight}px`);
|
|
272
|
+
}
|
|
273
|
+
if (!shown(el, full)) continue;
|
|
274
|
+
const space = room(el.parentElement);
|
|
275
|
+
const r = space && cross(space, full);
|
|
276
|
+
if (!r) continue;
|
|
277
|
+
if (el instanceof HTMLElement && wrong.length < 60) {
|
|
278
|
+
const cs = getComputedStyle(el);
|
|
279
|
+
if (/flex/.test(cs.display) && cs.flexWrap === "wrap") {
|
|
280
|
+
const kids = [...el.children].map((c) => ({ c, r: c.getBoundingClientRect() })).filter((k) => k.r.width > 0 && k.r.height > 0);
|
|
281
|
+
const top = Math.min(...kids.map((k) => k.r.top));
|
|
282
|
+
const down = kids.filter((k) => k.r.top >= top + Math.max(4, kids[0]?.r.height ?? 0) * 0.75 && k.c.matches(`${WORDY},[id],[aria-label]`));
|
|
283
|
+
if (kids.length > 1 && down.length) wrong.push(`wrapped ${nameOf(el)}${inside(el)} ${box(full)}: ${down.slice(0, 3).map((k) => nameOf(k.c)).join(", ")} dropped to the next line`);
|
|
284
|
+
}
|
|
285
|
+
if (el.matches(PRESSABLE) && !el.closest(OFF)) {
|
|
286
|
+
const op = seeing.opacity(el);
|
|
287
|
+
if (op < 0.35) wrong.push(`faded ${nameOf(el)}${inside(el)} ${box(full)}: opacity ${op.toFixed(2)} but not disabled`);
|
|
288
|
+
else if (r.width >= 4 && r.height >= 4 && (ownId(el) || el.getAttribute("aria-label") || ownText(el))) controls.push({ el, r });
|
|
289
|
+
const svg = !el.textContent?.trim() ? el.querySelector("svg") : null;
|
|
290
|
+
if (svg && op >= 0.35) {
|
|
291
|
+
const ratio = seeing.ratio(el, getComputedStyle(svg).color, op);
|
|
292
|
+
if (ratio !== null && ratio < FAINT_ICON) wrong.push(`faint ${nameOf(el)}${inside(el)} ${box(full)}: icon contrast ${ratio.toFixed(1)}:1 ${seeing.say(el, getComputedStyle(svg).color)}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (el.matches("button,[role=button]")) {
|
|
296
|
+
const bg = cs.backgroundColor;
|
|
297
|
+
if (!/^(transparent|rgba\([^)]*, 0\))$/.test(bg) && !(parseFloat(cs.borderTopWidth) > 0 && cs.borderTopColor !== bg)) {
|
|
298
|
+
let up = el.parentElement, under = "";
|
|
299
|
+
while (up && !under) {
|
|
300
|
+
const b = getComputedStyle(up).backgroundColor;
|
|
301
|
+
if (!/^(transparent|rgba\([^)]*, 0\))$/.test(b)) under = b;
|
|
302
|
+
else up = up.parentElement;
|
|
303
|
+
}
|
|
304
|
+
if (under === bg && !(cs.outlineStyle !== "none" && parseFloat(cs.outlineWidth) > 0) && !shadowLayers(cs.boxShadow).rings.length) wrong.push(`no-edge ${nameOf(el)}${inside(el)} ${box(full)}: same color as ${up ? nameOf(up) : "the page"} behind it (${((c) => c ? `rgb(${c.slice(0, 3).map(Math.round).join(",")})` : bg)(parseColor(bg))})`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if ((el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.placeholder && !el.value && wrong.length < 60 && full.width >= 24) {
|
|
309
|
+
const cs = getComputedStyle(el), ps = getComputedStyle(el, "::placeholder");
|
|
310
|
+
const ctx = measure ??= document.createElement("canvas").getContext("2d");
|
|
311
|
+
if (ctx) {
|
|
312
|
+
ctx.font = `${ps.fontStyle} ${ps.fontWeight} ${ps.fontSize} ${ps.fontFamily}`;
|
|
313
|
+
const said = ps.textTransform === "uppercase" ? el.placeholder.toUpperCase() : ps.textTransform === "lowercase" ? el.placeholder.toLowerCase() : el.placeholder;
|
|
314
|
+
const need = ctx.measureText(said).width + (parseFloat(ps.letterSpacing) || 0) * said.length;
|
|
315
|
+
const lines = el instanceof HTMLTextAreaElement ? Math.max(1, Math.floor((el.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)) / (parseFloat(ps.lineHeight) || parseFloat(ps.fontSize) * 1.2 || 1))) : 1;
|
|
316
|
+
const has = (el.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight) - (parseFloat(cs.textIndent) || 0)) * lines;
|
|
317
|
+
if (need > has + 1) wrong.push(`cut-text placeholder of ${nameOf(el)}${inside(el)} ${box(full)} needs ${Math.ceil(need)}px, has ${Math.floor(has)}px${cs.textOverflow === "ellipsis" ? " (ends in \u2026)" : ""}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if ((ownId(el) || el.matches(REGION)) && full.width >= 24 && full.height >= 12) {
|
|
321
|
+
regions.push(`${nameOf(el)} ${box(full)}`);
|
|
322
|
+
const W = innerWidth, H = innerHeight;
|
|
323
|
+
const scrolls = (axis) => {
|
|
324
|
+
for (let p = el.parentElement; p; p = p.parentElement) if (/auto|scroll/.test(getComputedStyle(p)[axis])) return true;
|
|
325
|
+
return false;
|
|
326
|
+
};
|
|
327
|
+
if ((full.top < -1 && space.top <= 1 || full.bottom > H + 1 && space.bottom >= H - 1) && !scrolls("overflowY") || (full.left < -1 && space.left <= 1 || full.right > W + 1 && space.right >= W - 1) && !scrolls("overflowX")) wrong.push(`outside ${nameOf(el)} ${box(full)} window ${W}x${H}`);
|
|
328
|
+
}
|
|
329
|
+
if (!ownText(el)) continue;
|
|
330
|
+
const range = document.createRange();
|
|
331
|
+
const nodes = [...el.childNodes].filter((n2) => n2.nodeType === 3 && n2.textContent?.trim());
|
|
332
|
+
range.setStartBefore(nodes[0]);
|
|
333
|
+
range.setEndAfter(nodes[nodes.length - 1]);
|
|
334
|
+
const words2 = cross(r, range.getBoundingClientRect());
|
|
335
|
+
if (words2) texts.push({ el, r: words2, layer: el.closest(POPUP) });
|
|
336
|
+
if (el instanceof HTMLElement) {
|
|
337
|
+
const cs = getComputedStyle(el);
|
|
338
|
+
if (words2 && wrong.length < 60 && !el.closest(OFF)) {
|
|
339
|
+
const ratio = seeing.ratio(el, cs.color, seeing.opacity(el));
|
|
340
|
+
if (ratio !== null && ratio < faint) wrong.push(`faint ${nameOf(el)}${inside(el)} ${box(full)}: contrast ${ratio.toFixed(1)}:1 ${seeing.say(el, cs.color)}`);
|
|
341
|
+
}
|
|
342
|
+
const clips = cs.textOverflow === "ellipsis" || /hidden|clip/.test(cs.overflowX + cs.overflowY);
|
|
343
|
+
if (clips && full.width > 2 && full.height > 2 && (el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1)) {
|
|
344
|
+
wrong.push(`cut-text ${nameOf(el)}${inside(el)} ${box(full)} needs ${el.scrollWidth}x${el.scrollHeight}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const showing = (el, r) => {
|
|
349
|
+
for (const c of document.elementsFromPoint(r.left + r.width / 2, r.top + r.height / 2)) {
|
|
350
|
+
if (c === el || el.contains(c) || c.contains(el)) return true;
|
|
351
|
+
if (!/^(transparent|rgba\([^)]*, 0\))$/.test(getComputedStyle(c).backgroundColor)) return false;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
};
|
|
355
|
+
const peek = new CSSStyleSheet();
|
|
356
|
+
peek.replaceSync("* { pointer-events: auto !important; }");
|
|
357
|
+
const sheets = [...document.adoptedStyleSheets];
|
|
358
|
+
document.adoptedStyleSheets = [...sheets, peek];
|
|
359
|
+
const inert = [...document.querySelectorAll("[inert]")];
|
|
360
|
+
for (const e of inert) e.inert = false;
|
|
361
|
+
const n = Math.min(texts.length, 700);
|
|
362
|
+
try {
|
|
363
|
+
for (let a = 0; a < n && wrong.length < cap2; a++) {
|
|
364
|
+
for (let b = a + 1; b < n; b++) {
|
|
365
|
+
const A = texts[a], B = texts[b];
|
|
366
|
+
const x = cross(A.r, B.r);
|
|
367
|
+
if (x && x.width > 3 && x.height > 3 && A.layer === B.layer && !A.el.contains(B.el) && !B.el.contains(A.el) && showing(A.el, A.r) && showing(B.el, B.r)) wrong.push(`overlap ${nameOf(A.el)} ${box(A.r)} / ${nameOf(B.el)} ${box(B.r)}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (const c of controls.slice(0, 300)) {
|
|
371
|
+
if (wrong.length >= cap2) break;
|
|
372
|
+
if (c.el.closest(POPUP)) continue;
|
|
373
|
+
const by = coverOf(c.el, c.r);
|
|
374
|
+
if (by) wrong.push(`covered ${nameOf(c.el)}${inside(c.el)} ${box(c.r)}: under ${nameOf(by)}${inside(by)}`);
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
document.adoptedStyleSheets = sheets;
|
|
378
|
+
for (const e of inert) e.inert = true;
|
|
379
|
+
}
|
|
380
|
+
const head = [`window ${innerWidth}x${innerHeight}`];
|
|
381
|
+
const rest = cap2 - head.length - Math.min(wrong.length, 60);
|
|
382
|
+
return [...head, ...wrong.slice(0, 60), ...regions.slice(0, Math.max(0, rest))];
|
|
383
|
+
}
|
|
384
|
+
var OFF = "[disabled],[aria-disabled=true],[inert],[data-census-dim]";
|
|
385
|
+
var measure;
|
|
386
|
+
var FAINT_TEXT = 2;
|
|
387
|
+
var FAINT_ICON = 1.5;
|
|
388
|
+
var clear = (c) => !c || c[3] < 0.02;
|
|
389
|
+
function looking() {
|
|
390
|
+
const bg = /* @__PURE__ */ new Map(), op = /* @__PURE__ */ new Map();
|
|
391
|
+
const base = () => {
|
|
392
|
+
for (const e of [document.body, document.documentElement]) {
|
|
393
|
+
const c = e && parseColor(getComputedStyle(e).backgroundColor);
|
|
394
|
+
if (c && !clear(c)) return over(c, [255, 255, 255, 1]);
|
|
395
|
+
}
|
|
396
|
+
return [255, 255, 255, 1];
|
|
397
|
+
};
|
|
398
|
+
const back = (el) => {
|
|
399
|
+
if (!el) return base();
|
|
400
|
+
const had = bg.get(el);
|
|
401
|
+
if (had) return had;
|
|
402
|
+
const c = parseColor(getComputedStyle(el).backgroundColor);
|
|
403
|
+
const out = clear(c) ? back(el.parentElement) : c[3] >= 0.995 ? c : over(c, back(el.parentElement));
|
|
404
|
+
bg.set(el, out);
|
|
405
|
+
return out;
|
|
406
|
+
};
|
|
407
|
+
const opacity = (el) => {
|
|
408
|
+
if (!el) return 1;
|
|
409
|
+
const had = op.get(el);
|
|
410
|
+
if (had !== void 0) return had;
|
|
411
|
+
const out = (parseFloat(getComputedStyle(el).opacity) || 0) * opacity(el.parentElement);
|
|
412
|
+
op.set(el, out);
|
|
413
|
+
return out;
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
opacity,
|
|
417
|
+
back,
|
|
418
|
+
/** The contrast of `color` drawn at `alpha` in el, against what's behind el. */
|
|
419
|
+
ratio(el, color, alpha) {
|
|
420
|
+
const fg = parseColor(color);
|
|
421
|
+
if (!fg) return null;
|
|
422
|
+
const under = back(el);
|
|
423
|
+
return contrast(over([fg[0], fg[1], fg[2], fg[3] * alpha], under), under);
|
|
424
|
+
},
|
|
425
|
+
say: (el, color) => {
|
|
426
|
+
const rgb = (c) => `rgb(${c.slice(0, 3).map(Math.round).join(",")})`, fg = parseColor(color);
|
|
427
|
+
return `(${fg ? rgb(fg) : color.replace(/\s+/g, "")} on ${rgb(back(el))})`;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function behind(el) {
|
|
432
|
+
if (!el) return [255, 255, 255, 1];
|
|
433
|
+
const c = parseColor(getComputedStyle(el).backgroundColor);
|
|
434
|
+
return clear(c) ? behind(el.parentElement) : c[3] >= 0.995 ? c : over(c, behind(el.parentElement));
|
|
435
|
+
}
|
|
436
|
+
function coverOf(el, r) {
|
|
437
|
+
for (const c of document.elementsFromPoint(r.left + r.width / 2, r.top + r.height / 2)) {
|
|
438
|
+
if (c === el || el.contains(c) || c.contains(el)) return null;
|
|
439
|
+
if (c.closest(POPUP)) return null;
|
|
440
|
+
const cr = c.getBoundingClientRect();
|
|
441
|
+
if (cr.width * cr.height > innerWidth * innerHeight * 0.8) continue;
|
|
442
|
+
const bg = parseColor(getComputedStyle(c).backgroundColor);
|
|
443
|
+
if (bg && bg[3] > 0.5 || c.tagName === "CANVAS" || c.tagName === "IMG" || c.tagName === "VIDEO") return c;
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
function sayAlign(a, dir) {
|
|
448
|
+
if (a === "center" || a === "-webkit-center") return "center";
|
|
449
|
+
if (a === "justify") return "justify";
|
|
450
|
+
const rtl = dir === "rtl";
|
|
451
|
+
return a === "end" || a === "right" && !rtl || a === "left" && rtl ? "end" : "start";
|
|
452
|
+
}
|
|
453
|
+
var SAY_JUSTIFY = { center: "center", "flex-end": "end", end: "end", right: "end", "space-between": "between", "space-around": "around", "space-evenly": "evenly" };
|
|
454
|
+
var SAY_ITEMS = { center: "center", "flex-start": "start", start: "start", "flex-end": "end", end: "end", baseline: "baseline" };
|
|
455
|
+
var sayGaps = (g) => {
|
|
456
|
+
const lo = Math.min(...g), hi = Math.max(...g);
|
|
457
|
+
return lo === hi ? `${lo}` : g.length <= 6 ? g.join(" ") : `${lo}-${hi}`;
|
|
458
|
+
};
|
|
459
|
+
function layoutOf(el, cs, r, filled) {
|
|
460
|
+
if (r.width < 4 || r.height < 4) return null;
|
|
461
|
+
const flex = /flex/.test(cs.display), grid = /grid/.test(cs.display);
|
|
462
|
+
const pad = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map((v) => Math.round(parseFloat(v) || 0));
|
|
463
|
+
const padded = pad.some(Boolean);
|
|
464
|
+
if (!flex && !grid && !(filled && padded)) return null;
|
|
465
|
+
const kids = [...el.children].map((c) => ({ c, b: c.getBoundingClientRect() })).filter(({ c, b }) => b.width > 0 && b.height > 0 && !/^(absolute|fixed)$/.test(getComputedStyle(c).position));
|
|
466
|
+
if (!kids.length || (flex || grid) && kids.length < 2 && !(filled && padded)) return null;
|
|
467
|
+
const parts = [];
|
|
468
|
+
if (grid) {
|
|
469
|
+
const cols = cs.gridTemplateColumns.split(/\s+(?![^(]*\))/).filter(Boolean).length;
|
|
470
|
+
const rg = Math.round(parseFloat(cs.rowGap) || 0), cg = Math.round(parseFloat(cs.columnGap) || 0);
|
|
471
|
+
parts.push(`grid ${cols} col${cols > 1 ? "s" : ""}`);
|
|
472
|
+
if (rg || cg) parts.push(rg === cg ? `gap ${rg}` : `gap ${rg}/${cg}`);
|
|
473
|
+
} else if (flex) {
|
|
474
|
+
const col = /column/.test(cs.flexDirection);
|
|
475
|
+
parts.push(`${col ? "column" : "row"}${cs.flexWrap === "wrap" ? " wrap" : ""}`);
|
|
476
|
+
const sorted = [...kids].sort((a, b) => col ? a.b.top - b.b.top : a.b.left - b.b.left);
|
|
477
|
+
const set = Math.round(parseFloat(col ? cs.rowGap : cs.columnGap) || 0), gaps = [], from = /* @__PURE__ */ new Set();
|
|
478
|
+
const m = (e, side) => parseFloat(getComputedStyle(e).getPropertyValue(`margin-${side}`)) || 0;
|
|
479
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
480
|
+
const a = sorted[i - 1], b = sorted[i];
|
|
481
|
+
if (!col && Math.min(a.b.bottom, b.b.bottom) - Math.max(a.b.top, b.b.top) <= 0) continue;
|
|
482
|
+
const g = Math.round(col ? b.b.top - a.b.bottom : b.b.left - a.b.right), mg = Math.round(col ? m(a.c, "bottom") + m(b.c, "top") : m(a.c, "right") + m(b.c, "left"));
|
|
483
|
+
gaps.push(g);
|
|
484
|
+
if (set) from.add("gap");
|
|
485
|
+
if (mg > 0) from.add("margins");
|
|
486
|
+
if (g - set - mg > 1) from.add("free space");
|
|
487
|
+
}
|
|
488
|
+
if (gaps.length) {
|
|
489
|
+
if (Math.min(...gaps) < 0) parts.push("overlapping");
|
|
490
|
+
else if (Math.max(...gaps) > 0) parts.push(`${new Set(gaps).size > 1 ? "gaps" : "gap"} ${sayGaps(gaps)}${from.size && !(from.size === 1 && from.has("gap")) ? ` (${[...from].map((f) => f === "gap" ? `gap ${set}` : f).join(" and ")})` : ""}`);
|
|
491
|
+
}
|
|
492
|
+
} else parts.push("box");
|
|
493
|
+
if (padded) parts.push(`pad ${pad.every((p) => p === pad[0]) ? pad[0] : pad[0] === pad[2] && pad[1] === pad[3] ? `${pad[0]} ${pad[1]}` : pad.join(" ")}`);
|
|
494
|
+
if (flex) {
|
|
495
|
+
const j = SAY_JUSTIFY[cs.justifyContent], it = SAY_ITEMS[cs.alignItems];
|
|
496
|
+
if (j) parts.push(`justify ${j}`);
|
|
497
|
+
if (it) parts.push(`items ${it}`);
|
|
498
|
+
const grow = kids.filter((k) => parseFloat(getComputedStyle(k.c).flexGrow) > 0).length;
|
|
499
|
+
if (grow) parts.push(grow === kids.length ? "children grow" : `${grow} of ${kids.length} grow`);
|
|
500
|
+
}
|
|
501
|
+
return { key: parts.join(" "), kids: `${kids.length}: ${kids.slice(0, 3).map((k) => nameOf(k.c)).join(", ")}${kids.length > 3 ? ", \u2026" : ""}` };
|
|
502
|
+
}
|
|
503
|
+
function cross(a, b) {
|
|
504
|
+
const l = Math.max(a.left, b.left), t = Math.max(a.top, b.top), r = Math.min(a.right, b.right), btm = Math.min(a.bottom, b.bottom);
|
|
505
|
+
return r > l && btm > t ? new DOMRect(l, t, r - l, btm - t) : null;
|
|
506
|
+
}
|
|
507
|
+
var hex = (c) => `#${c.slice(0, 3).map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}${c[3] < 0.995 ? Math.round(c[3] * 255).toString(16).padStart(2, "0") : ""}`;
|
|
508
|
+
var num = (v) => String(Math.round(v * 10) / 10);
|
|
509
|
+
var splitTop = (s) => {
|
|
510
|
+
const parts = [];
|
|
511
|
+
let depth = 0, from = 0;
|
|
512
|
+
for (let i = 0; i < s.length; i++) {
|
|
513
|
+
const c = s[i];
|
|
514
|
+
if (c === "(" || c === "[") depth++;
|
|
515
|
+
else if (c === ")" || c === "]") depth--;
|
|
516
|
+
else if (c === "," && !depth) {
|
|
517
|
+
parts.push(s.slice(from, i));
|
|
518
|
+
from = i + 1;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
parts.push(s.slice(from));
|
|
522
|
+
return parts.map((x) => x.trim());
|
|
523
|
+
};
|
|
524
|
+
function shadowLayers(css) {
|
|
525
|
+
const out = { rings: [], rest: [] };
|
|
526
|
+
if (!css || css === "none") return out;
|
|
527
|
+
for (const layer of splitTop(css)) {
|
|
528
|
+
let color = "", inset = false;
|
|
529
|
+
const len = [];
|
|
530
|
+
const words2 = layer.replace(/[a-z-]+\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\)|#[0-9a-f]{3,8}\b/i, (m) => {
|
|
531
|
+
color = m;
|
|
532
|
+
return " ";
|
|
533
|
+
});
|
|
534
|
+
for (const w of words2.trim().split(/\s+/)) {
|
|
535
|
+
if (w === "inset") inset = true;
|
|
536
|
+
else if (/^-?[\d.]+(e[+-]?\d+)?(px)?$/.test(w)) len.push(parseFloat(w));
|
|
537
|
+
else if (w) color ||= w;
|
|
538
|
+
}
|
|
539
|
+
const [x = 0, y = 0, blur = 0, spread = 0] = len;
|
|
540
|
+
const c = color ? parseColor(color) : null;
|
|
541
|
+
if (c && c[3] < 0.02 || !x && !y && !blur && !spread) continue;
|
|
542
|
+
const say = c ? hex(c) : color || "currentcolor";
|
|
543
|
+
if (!x && !y && !blur) out.rings.push(`${say} ${num(Math.abs(spread))}px (ring${inset ? " inset" : ""})`);
|
|
544
|
+
else out.rest.push(`${[x, y, blur, spread].map((v) => v ? `${num(v)}px` : "0").join(" ")} ${say}${inset ? " inset" : ""}`);
|
|
545
|
+
}
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
function stateOf(el) {
|
|
549
|
+
const a = (k) => el.getAttribute(k);
|
|
550
|
+
const out = [];
|
|
551
|
+
if (el.disabled === true || a("aria-disabled") === "true" || a("data-disabled") !== null) out.push("disabled");
|
|
552
|
+
if (a("aria-selected") === "true" || a("aria-checked") === "true" || a("aria-pressed") === "true" || (a("aria-current") ?? "false") !== "false" || /^(on|active|checked|selected)$/.test(a("data-state") ?? "") || a("data-selected") !== null) out.push("selected");
|
|
553
|
+
if (a("aria-expanded") === "true" || a("data-state") === "open") out.push("open");
|
|
554
|
+
return out.join(" ");
|
|
555
|
+
}
|
|
556
|
+
function fontDiff(stack, first) {
|
|
557
|
+
const fam = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
558
|
+
const a = fam(stack), b = fam(first);
|
|
559
|
+
const plus = a.filter((x) => !b.includes(x)), minus = b.filter((x) => !a.includes(x));
|
|
560
|
+
if (!plus.length && !minus.length) return `the first stack in another order: ${a.join(", ")}`;
|
|
561
|
+
if (plus.length + minus.length > a.length + b.length - plus.length - minus.length) return a.join(", ");
|
|
562
|
+
return `the first stack${plus.length ? ` plus ${plus.map((x) => `${x}${a[0] === x ? " (in front)" : ""}`).join(", ")}` : ""}${minus.length ? ` minus ${minus.join(", ")}` : ""}`;
|
|
563
|
+
}
|
|
564
|
+
var PART_PROPS = ["variant", "size", "tone", "intent", "kind", "appearance", "shape", "density"];
|
|
565
|
+
var MORE_PROPS = ["color", "colorScheme", "colorPalette", "status", "severity", "level"];
|
|
566
|
+
var compName = (t, depth = 0) => {
|
|
567
|
+
if (depth > 3) return "";
|
|
568
|
+
if (typeof t === "function") return t.displayName ?? t.name ?? "";
|
|
569
|
+
if (t && typeof t === "object") {
|
|
570
|
+
const o = t;
|
|
571
|
+
return o.displayName ?? compName(o.render ?? o.type, depth + 1);
|
|
572
|
+
}
|
|
573
|
+
return "";
|
|
574
|
+
};
|
|
575
|
+
var PLAIN = /^[A-Z][A-Za-z0-9]{2,}$/;
|
|
576
|
+
var PLUMBING = /(Provider|Context|Consumer|Boundary|Slot|Portal|Fragment|FocusManager|FocusScope|FocusTrap|Presence)$/;
|
|
577
|
+
var fiberKey = "";
|
|
578
|
+
var fiberOf = (el) => {
|
|
579
|
+
const bag = el;
|
|
580
|
+
if (!fiberKey || !(fiberKey in bag)) {
|
|
581
|
+
const k = Object.keys(el).find((x) => x.startsWith("__reactFiber$"));
|
|
582
|
+
if (!k) return null;
|
|
583
|
+
fiberKey = k;
|
|
584
|
+
}
|
|
585
|
+
return bag[fiberKey] ?? null;
|
|
586
|
+
};
|
|
587
|
+
var propsOf = (f, keys = PART_PROPS) => keys.flatMap((k) => {
|
|
588
|
+
const v = f.memoizedProps?.[k];
|
|
589
|
+
return (typeof v === "string" || typeof v === "number") && /^[\w.-]{1,20}$/.test(String(v)) ? [`${k}=${v}`] : [];
|
|
590
|
+
}).join(" ");
|
|
591
|
+
var named = (f) => {
|
|
592
|
+
const n = compName(f.type);
|
|
593
|
+
return PLAIN.test(n) && !PLUMBING.test(n) ? n : "";
|
|
594
|
+
};
|
|
595
|
+
var family = (name) => name.match(/^[A-Za-z][a-z0-9]*/)?.[0] ?? name;
|
|
596
|
+
var obj = (v) => v && typeof v === "object" ? v : null;
|
|
597
|
+
var propsIn = (o, keys = PART_PROPS) => o ? keys.flatMap((k) => {
|
|
598
|
+
let v = o[k];
|
|
599
|
+
if (typeof v === "function") {
|
|
600
|
+
try {
|
|
601
|
+
v = /^\[(?:Input )?Signal: (.*)\]$/.exec(String(v))?.[1];
|
|
602
|
+
} catch {
|
|
603
|
+
v = void 0;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return (typeof v === "string" || typeof v === "number") && /^[\w.-]{1,20}$/.test(String(v)) ? [`${k}=${v}`] : [];
|
|
607
|
+
}).join(" ") : "";
|
|
608
|
+
var baseName = (file) => typeof file === "string" ? file.replace(/^.*[\\/]/, "").replace(/\.(vue|svelte|[jt]sx?)$/, "") : "";
|
|
609
|
+
function otherChain(el) {
|
|
610
|
+
const e = el, out = [];
|
|
611
|
+
const add = (name, props, src) => {
|
|
612
|
+
if (PLAIN.test(name) && !PLUMBING.test(name)) out.push({ name, props, fiber: null, gap: false, src });
|
|
613
|
+
};
|
|
614
|
+
for (let inst = obj(e.__vueParentComponent), i = 0; inst && i < 12; inst = obj(inst.parent), i++) {
|
|
615
|
+
if (obj(inst.subTree)?.el !== el && obj(inst.vnode)?.el !== el) break;
|
|
616
|
+
const type = obj(inst.type) ?? {};
|
|
617
|
+
add(String(type.name ?? type.__name ?? "") || baseName(type.__file), propsIn(obj(inst.props)), String(obj(obj(inst.parent)?.type)?.__file ?? "") || void 0);
|
|
618
|
+
}
|
|
619
|
+
if (out.length) return out;
|
|
620
|
+
for (let vm = obj(e.__vue__), i = 0; vm && vm.$el === el && i < 12; vm = obj(vm.$parent), i++) {
|
|
621
|
+
const o = obj(vm.$options) ?? {};
|
|
622
|
+
add(String(o.name ?? o._componentTag ?? "") || baseName(o.__file), propsIn(obj(vm.$props)), String(obj(obj(vm.$parent)?.$options)?.__file ?? "") || void 0);
|
|
623
|
+
}
|
|
624
|
+
if (out.length) return out;
|
|
625
|
+
const meta = obj(e.__svelte_meta), file = obj(meta?.loc)?.file;
|
|
626
|
+
if (typeof file === "string") {
|
|
627
|
+
const up = obj(el.parentElement?.__svelte_meta);
|
|
628
|
+
if (obj(up?.loc)?.file !== file) {
|
|
629
|
+
const name = baseName(file);
|
|
630
|
+
let src;
|
|
631
|
+
for (let p = obj(meta.parent), i = 0; p && i < 20; p = obj(p.parent), i++) if (p.type === "component" && (!p.componentTag || p.componentTag === name) && typeof p.file === "string") {
|
|
632
|
+
src = `${p.file}:${p.line ?? 0}`;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
add(name, "", src);
|
|
636
|
+
}
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
const ng = obj(globalThis.ng);
|
|
640
|
+
if (ng && typeof ng.getComponent === "function") {
|
|
641
|
+
const get = ng.getComponent;
|
|
642
|
+
for (const host of [el, el.parentElement && el.parentElement.children.length === 1 ? el.parentElement : null]) {
|
|
643
|
+
if (!host) continue;
|
|
644
|
+
let c = null;
|
|
645
|
+
try {
|
|
646
|
+
c = obj(get(host));
|
|
647
|
+
} catch {
|
|
648
|
+
c = null;
|
|
649
|
+
}
|
|
650
|
+
if (c) {
|
|
651
|
+
add(String(c.constructor?.name ?? "").replace(/Component$/, ""), propsIn(c));
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
var NOT_APP = /node_modules|react-dom|react-jsx|jsx-dev-runtime|react-server|scheduler|\(native\)|<anonymous>/;
|
|
659
|
+
function fiberSrc(f) {
|
|
660
|
+
const b = obj(f);
|
|
661
|
+
if (!b) return "";
|
|
662
|
+
const ds = obj(b._debugSource);
|
|
663
|
+
if (ds && typeof ds.fileName === "string") return `${ds.fileName}:${ds.lineNumber ?? 0}`;
|
|
664
|
+
const st = obj(b._debugStack)?.stack;
|
|
665
|
+
if (typeof st !== "string") return "";
|
|
666
|
+
const frames = [];
|
|
667
|
+
for (const line of st.split("\n").slice(1)) {
|
|
668
|
+
const m = /((?:https?|file|webpack|turbopack|webpack-internal):\/\/[^\s()]+?):(\d+):(\d+)\)?\s*$/.exec(line);
|
|
669
|
+
if (m && !NOT_APP.test(m[1])) frames.push(`${m[1]}:${m[2]}:${m[3]}`);
|
|
670
|
+
if (frames.length === 3) break;
|
|
671
|
+
}
|
|
672
|
+
return frames.length ? `{${frames.join(" ")}}` : "";
|
|
673
|
+
}
|
|
674
|
+
function partInfo(el, parts) {
|
|
675
|
+
const tag = el.getAttribute("data-part");
|
|
676
|
+
if (tag) {
|
|
677
|
+
const props = (e) => ["variant", "size", "tone"].flatMap((k) => {
|
|
678
|
+
const v = e.getAttribute(`data-${k}`);
|
|
679
|
+
return v ? [`${k}=${v.slice(0, 20)}`] : [];
|
|
680
|
+
}).join(" ");
|
|
681
|
+
let say2 = [tag, props(el)].filter(Boolean).join(" ");
|
|
682
|
+
if (!props(el)) for (let p = el.parentElement, i = 0; p && i < 6; p = p.parentElement, i++) {
|
|
683
|
+
const t = p.getAttribute("data-part");
|
|
684
|
+
if (t && family(t) === family(tag) && props(p)) {
|
|
685
|
+
say2 = `${tag} of ${t} ${props(p)}`;
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return { say: say2, key: say2, fiber: null, listed: !parts?.length || parts.includes(tag), cand: true, own: false };
|
|
690
|
+
}
|
|
691
|
+
const start = fiberOf(el);
|
|
692
|
+
let chain = [];
|
|
693
|
+
let gap = false;
|
|
694
|
+
if (!start) chain = otherChain(el);
|
|
695
|
+
else for (let f = start.return, i = 0; f && typeof f.type !== "string" && chain.length < 16 && i < 80; f = f.return, i++) {
|
|
696
|
+
const raw = compName(f.type);
|
|
697
|
+
if (/(Provider|Context|Consumer|Boundary)$/.test(raw)) gap = chain.length > 0;
|
|
698
|
+
else if (PLAIN.test(raw) && !PLUMBING.test(raw)) {
|
|
699
|
+
chain.push({ name: raw, props: propsOf(f), fiber: f, gap });
|
|
700
|
+
gap = false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (!chain.length) return null;
|
|
704
|
+
const text = (c) => c.props ? `${c.name} ${c.props}` : c.name;
|
|
705
|
+
const inner = chain.find((c) => c.props);
|
|
706
|
+
let main = chain.find((c) => parts?.includes(c.name));
|
|
707
|
+
const listed = !!main;
|
|
708
|
+
const reach = (from, ok) => {
|
|
709
|
+
let to = from;
|
|
710
|
+
for (const c of chain.slice(chain.indexOf(from) + 1)) {
|
|
711
|
+
if (c.gap) break;
|
|
712
|
+
if (ok(c)) to = c;
|
|
713
|
+
}
|
|
714
|
+
return to;
|
|
715
|
+
};
|
|
716
|
+
if (main) main = reach(main, (c) => !!parts?.includes(c.name) && family(c.name) === family(main.name));
|
|
717
|
+
else if (inner) main = reach(inner, (c) => !!c.props);
|
|
718
|
+
else main = chain[0];
|
|
719
|
+
let key = text(main);
|
|
720
|
+
if (!main.props && start) {
|
|
721
|
+
let hosts = 0;
|
|
722
|
+
for (let f = start.return, i = 0; f && i < 60 && hosts < 6; f = f.return, i++) {
|
|
723
|
+
if (typeof f.type === "string") {
|
|
724
|
+
hosts++;
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const name = hosts ? named(f) : "";
|
|
728
|
+
if (name && family(name) === family(main.name) && propsOf(f)) {
|
|
729
|
+
key = `${main.name} of ${name} ${propsOf(f)}`;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const say = inner && inner !== main && inner.name !== main.name && chain.indexOf(inner) < chain.indexOf(main) ? `${key} < ${text(inner)}` : key;
|
|
735
|
+
return { say, key, fiber: main.fiber, listed, cand: listed || key.includes("="), own: listed || !!main.props, src: start ? void 0 : main.src };
|
|
736
|
+
}
|
|
737
|
+
function partOf(el, parts) {
|
|
738
|
+
return partInfo(el, parts)?.say ?? "";
|
|
739
|
+
}
|
|
740
|
+
var identEnd = (s, i) => {
|
|
741
|
+
while (i < s.length) {
|
|
742
|
+
if (s[i] === "\\") i += 2;
|
|
743
|
+
else if (/[\w-]/.test(s[i]) || s.charCodeAt(i) > 127) i++;
|
|
744
|
+
else break;
|
|
745
|
+
}
|
|
746
|
+
return i;
|
|
747
|
+
};
|
|
748
|
+
var closeOf = (s, i) => {
|
|
749
|
+
let d = 0;
|
|
750
|
+
for (; i < s.length; i++) {
|
|
751
|
+
if (s[i] === "\\") i++;
|
|
752
|
+
else if (s[i] === "(" || s[i] === "[") d++;
|
|
753
|
+
else if ((s[i] === ")" || s[i] === "]") && !--d) return i + 1;
|
|
754
|
+
}
|
|
755
|
+
return s.length;
|
|
756
|
+
};
|
|
757
|
+
function specificity(sel) {
|
|
758
|
+
let n = 0;
|
|
759
|
+
for (let i = 0; i < sel.length; ) {
|
|
760
|
+
const c = sel[i];
|
|
761
|
+
if (c === "\\") i += 2;
|
|
762
|
+
else if (c === "#") {
|
|
763
|
+
n += 1e6;
|
|
764
|
+
i = identEnd(sel, i + 1);
|
|
765
|
+
} else if (c === ".") {
|
|
766
|
+
n += 1e3;
|
|
767
|
+
i = identEnd(sel, i + 1);
|
|
768
|
+
} else if (c === "[") {
|
|
769
|
+
n += 1e3;
|
|
770
|
+
i = closeOf(sel, i);
|
|
771
|
+
} else if (c === ":") {
|
|
772
|
+
const el = sel[i + 1] === ":", from = i + (el ? 2 : 1), to = identEnd(sel, from), name = sel.slice(from, to).toLowerCase();
|
|
773
|
+
const end = sel[to] === "(" ? closeOf(sel, to) : to, arg = end > to ? sel.slice(to + 1, end - 1) : "";
|
|
774
|
+
if (el || /^(before|after|first-line|first-letter)$/.test(name)) n += 1;
|
|
775
|
+
else if (/^(is|not|has|matches|-webkit-any|-moz-any)$/.test(name)) n += Math.max(0, ...splitTop(arg).map(specificity));
|
|
776
|
+
else if (name !== "where") n += 1e3 + (/^nth-(last-)?child$/.test(name) && / of /.test(arg) ? Math.max(0, ...splitTop(arg.slice(arg.indexOf(" of ") + 4)).map(specificity)) : 0);
|
|
777
|
+
i = end;
|
|
778
|
+
} else if (/[a-zA-Z_]/.test(c) || sel.charCodeAt(i) > 127) {
|
|
779
|
+
n += 1;
|
|
780
|
+
i = identEnd(sel, i);
|
|
781
|
+
} else i++;
|
|
782
|
+
}
|
|
783
|
+
return n;
|
|
784
|
+
}
|
|
785
|
+
var ON_STATE = /\[(?:data-state=["']?(?:on|active|checked|selected)["']?|aria-(?:pressed|checked|selected)=["']?true["']?|data-(?:selected|checked|on)(?:=[^\]]*)?)\]/;
|
|
786
|
+
var PRESSED_ATTR = /\[data-pressed(?:=[^\]]*)?\]/;
|
|
787
|
+
var pressedIsOnIn = (sel, list) => {
|
|
788
|
+
const alt = (arms) => arms.some((x) => PRESSED_ATTR.test(x)) && arms.some((x) => ON_STATE.test(x) && !PRESSED_ATTR.test(x));
|
|
789
|
+
if (alt(list)) return true;
|
|
790
|
+
for (const m of sel.matchAll(/:(?:is|where|matches|-webkit-any)\(/g)) {
|
|
791
|
+
const open = m.index + m[0].length - 1;
|
|
792
|
+
if (alt(splitTop(sel.slice(open + 1, closeOf(sel, open) - 1)))) return true;
|
|
793
|
+
}
|
|
794
|
+
return false;
|
|
795
|
+
};
|
|
796
|
+
function readSheets() {
|
|
797
|
+
const layers = /* @__PURE__ */ new Map();
|
|
798
|
+
const layerCmp = (a, b) => {
|
|
799
|
+
if (a === b) return 0;
|
|
800
|
+
if (!a || !b) return a ? -1 : 1;
|
|
801
|
+
const x = a.split("."), y = b.split(".");
|
|
802
|
+
for (let i = 0; ; i++) {
|
|
803
|
+
if (i >= x.length || i >= y.length) return i >= x.length ? 1 : -1;
|
|
804
|
+
if (x[i] !== y[i]) return (layers.get(x.slice(0, i + 1).join(".")) ?? 0) - (layers.get(y.slice(0, i + 1).join(".")) ?? 0);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const spec = (r) => r.spec ??= specificity(r.sel);
|
|
808
|
+
const out = { states: [], plain: [], unreadable: 0, beats: (a, aImp, b, bImp) => {
|
|
809
|
+
if (aImp !== bImp) return aImp;
|
|
810
|
+
const l = layerCmp(a.layer, b.layer) * (aImp ? -1 : 1);
|
|
811
|
+
return l ? l > 0 : spec(a) !== spec(b) ? spec(a) > spec(b) : a.order > b.order;
|
|
812
|
+
} };
|
|
813
|
+
let order = 0, anon = 0;
|
|
814
|
+
const enter = (parent, name) => {
|
|
815
|
+
let full = parent;
|
|
816
|
+
for (const seg of name.split(".")) {
|
|
817
|
+
full = full ? `${full}.${seg}` : seg;
|
|
818
|
+
if (!layers.has(full)) layers.set(full, layers.size);
|
|
819
|
+
}
|
|
820
|
+
return full;
|
|
821
|
+
};
|
|
822
|
+
const re = (pseudo, attrs) => new RegExp([`(?<!\\\\)${pseudo}(?![\\w-])`, ...attrs.map((n) => `\\[${n}(?:=[^\\]]*)?\\]`)].join("|"), "g");
|
|
823
|
+
const STATE = [["hover", re(":hover", ["data-hovered", "data-highlighted"])], ["pressed", re(":active", ["data-pressed"])], ["focus", re(":focus-visible", ["data-focus-visible"])]];
|
|
824
|
+
const walk = (list, parent, layer) => {
|
|
825
|
+
for (const rule of list) {
|
|
826
|
+
if (out.plain.length > 4e4) return;
|
|
827
|
+
const kind = rule.constructor.name;
|
|
828
|
+
if (rule instanceof CSSImportRule) {
|
|
829
|
+
const ln = rule.layerName;
|
|
830
|
+
try {
|
|
831
|
+
if (rule.styleSheet && (!rule.media.mediaText || matchMedia(rule.media.mediaText).matches)) walk(rule.styleSheet.cssRules, "", ln == null ? layer : enter(layer, ln || `(${anon++})`));
|
|
832
|
+
} catch {
|
|
833
|
+
out.unreadable++;
|
|
834
|
+
}
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (kind === "CSSLayerStatementRule") {
|
|
838
|
+
for (const n of rule.nameList ?? []) enter(layer, n);
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (rule instanceof CSSMediaRule && !matchMedia(rule.conditionText).matches) continue;
|
|
842
|
+
if (rule instanceof CSSSupportsRule && !CSS.supports(rule.conditionText)) continue;
|
|
843
|
+
if (kind === "CSSStartingStyleRule") continue;
|
|
844
|
+
const within = kind === "CSSLayerBlockRule" ? enter(layer, rule.name || `(${anon++})`) : layer;
|
|
845
|
+
let sels = null;
|
|
846
|
+
if (rule instanceof CSSStyleRule) sels = splitTop(rule.selectorText).map((x) => parent ? x.includes("&") ? x.replaceAll("&", parent) : `${parent} ${x}` : x);
|
|
847
|
+
else if (parent && kind === "CSSNestedDeclarations") sels = [parent];
|
|
848
|
+
const style = sels ? rule.style : null;
|
|
849
|
+
if (sels && style?.length) {
|
|
850
|
+
order++;
|
|
851
|
+
for (const sel of sels) {
|
|
852
|
+
const plain = { sel, style, layer: within, order, has: [] };
|
|
853
|
+
out.plain.push(plain);
|
|
854
|
+
for (const [state, r] of STATE) {
|
|
855
|
+
r.lastIndex = 0;
|
|
856
|
+
if (!r.test(sel)) continue;
|
|
857
|
+
plain.has.push(state);
|
|
858
|
+
r.lastIndex = 0;
|
|
859
|
+
let base = sel.replace(r, ":where(*)");
|
|
860
|
+
const pm = /::?(before|after)$/.exec(base);
|
|
861
|
+
if (pm) base = base.slice(0, pm.index) || "*";
|
|
862
|
+
const on = state === "pressed" && !/(?<!\\):active(?![\w-])/.test(sel);
|
|
863
|
+
if (!base.includes("::")) out.states.push({ state, base, pseudo: pm ? pm[1] : "", style, sel, layer: within, order, on, toggle: on && pressedIsOnIn(sel, sels) });
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const kids = rule.cssRules;
|
|
868
|
+
if (kids?.length) walk(kids, rule instanceof CSSStyleRule ? sels.length > 1 ? `:is(${sels.join(",")})` : sels[0] : parent, within);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
for (const sheet of [...document.styleSheets, ...document.adoptedStyleSheets ?? []]) {
|
|
872
|
+
if (sheet.disabled) continue;
|
|
873
|
+
try {
|
|
874
|
+
if (!sheet.media.mediaText || matchMedia(sheet.media.mediaText).matches) walk(sheet.cssRules, "", "");
|
|
875
|
+
} catch {
|
|
876
|
+
out.unreadable++;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return out;
|
|
880
|
+
}
|
|
881
|
+
var pseudoOf = (x) => {
|
|
882
|
+
if (x.pseudo === void 0) {
|
|
883
|
+
const m = /::?(before|after)$/.exec(x.sel);
|
|
884
|
+
x.base = m ? x.sel.slice(0, m.index) || "*" : x.sel;
|
|
885
|
+
x.pseudo = m ? m[1] : x.sel.includes("::") ? "other" : "";
|
|
886
|
+
}
|
|
887
|
+
return x.pseudo;
|
|
888
|
+
};
|
|
889
|
+
function resolveVars(v, el, first = []) {
|
|
890
|
+
const cs = getComputedStyle(el);
|
|
891
|
+
const get = (name) => {
|
|
892
|
+
for (const s of first) {
|
|
893
|
+
const x = s.getPropertyValue(name).trim();
|
|
894
|
+
if (x) return x;
|
|
895
|
+
}
|
|
896
|
+
return cs.getPropertyValue(name).trim();
|
|
897
|
+
};
|
|
898
|
+
for (let i = 0; i < 6 && v.includes("var("); i++) v = v.replace(/var\((--[\w-]+)(?:,\s*([^()]*(?:\([^()]*\))?[^()]*))?\)/g, (_, name, fb) => get(name) || (fb ?? "").trim());
|
|
899
|
+
return v.trim();
|
|
900
|
+
}
|
|
901
|
+
function censusLines(c) {
|
|
902
|
+
const out = [c.head];
|
|
903
|
+
for (const g of c.groups) {
|
|
904
|
+
if (!g.rows.length) continue;
|
|
905
|
+
const n = g.rows.length + (g.more ?? 0);
|
|
906
|
+
out.push(`${g.name} (${n}${g.what === void 0 ? " values" : g.what ? `, ${g.what}` : ""})`);
|
|
907
|
+
for (const r of g.rows) out.push(` ${rowText(r)}`);
|
|
908
|
+
if (g.more) out.push(` ... ${g.more} more`);
|
|
909
|
+
}
|
|
910
|
+
if (c.report) out.push(...findingLines(c));
|
|
911
|
+
return out;
|
|
912
|
+
}
|
|
913
|
+
var looksOf = (r) => {
|
|
914
|
+
if (!r.flag || !r.looks) return 1;
|
|
915
|
+
const skins = /* @__PURE__ */ new Map();
|
|
916
|
+
for (const l of r.looks) skins.set(l.state, (skins.get(l.state) ?? /* @__PURE__ */ new Set()).add(l.skin));
|
|
917
|
+
return Math.max(new Set(r.looks.map((l) => l.shape)).size, ...[...skins.values()].map((s) => s.size));
|
|
918
|
+
};
|
|
919
|
+
function rowText(r) {
|
|
920
|
+
if (!r.looks) return `${r.key}${r.n ? ` \xD7${r.n}` : ""}${r.note ?? ""}${r.at.length ? `: ${r.at.join("; ")}` : ""}`;
|
|
921
|
+
const multi = r.looks.length > 1, n = looksOf(r);
|
|
922
|
+
const by = /* @__PURE__ */ new Map();
|
|
923
|
+
for (const l of [...r.looks].sort((a, b) => b.n - a.n)) {
|
|
924
|
+
const say = l.say.replace("h=content", l.lo === l.hi ? `h${l.lo} (its content's)` : `h${l.lo}-${l.hi} (its content's)`);
|
|
925
|
+
by.set(l.state, [...by.get(l.state) ?? [], `${say} \xD7${l.n}${multi && l.at.length ? ` (${l.at.join("; ")})` : ""}`]);
|
|
926
|
+
}
|
|
927
|
+
return `${r.key}${n > 1 ? ` IN ${n} LOOKS` : ""}${r.note ?? ""}: ${[...by].sort((a, b) => a[0].localeCompare(b[0])).map(([st, v]) => `${st ? `${st}: ` : ""}${v.join(" | ")}`).join(" ; ")}`;
|
|
928
|
+
}
|
|
929
|
+
function mergeCensus(pages, rules = {}) {
|
|
930
|
+
const names = Object.keys(pages);
|
|
931
|
+
const groups = [];
|
|
932
|
+
const accs = /* @__PURE__ */ new Map();
|
|
933
|
+
for (const page of names) for (const g of pages[page].groups) {
|
|
934
|
+
let to = groups.find((x) => x.name === g.name);
|
|
935
|
+
if (!to) {
|
|
936
|
+
to = { name: g.name, what: g.what, rows: [], more: 0 };
|
|
937
|
+
groups.push(to);
|
|
938
|
+
accs.set(to, /* @__PURE__ */ new Map());
|
|
939
|
+
}
|
|
940
|
+
to.more = Math.max(to.more ?? 0, g.more ?? 0);
|
|
941
|
+
const rows = accs.get(to);
|
|
942
|
+
for (const r of g.rows) {
|
|
943
|
+
const k = `${r.key}
|
|
944
|
+
${r.note ?? ""}`;
|
|
945
|
+
let a = rows.get(k);
|
|
946
|
+
if (!a) {
|
|
947
|
+
a = { row: { key: r.key, n: 0, note: r.note, at: [], looks: r.looks ? [] : void 0 }, pages: [], at: /* @__PURE__ */ new Map() };
|
|
948
|
+
rows.set(k, a);
|
|
949
|
+
to.rows.push(a.row);
|
|
950
|
+
}
|
|
951
|
+
a.row.n += r.n;
|
|
952
|
+
a.pages.push(page);
|
|
953
|
+
a.at.set(page, r.at);
|
|
954
|
+
if (r.flag) a.row.flag = true;
|
|
955
|
+
for (const l of r.looks ?? []) {
|
|
956
|
+
const had = a.row.looks.find((x) => x.state === l.state && x.say === l.say), at = l.at.slice(0, 1).map((p) => `[${page}] ${p}`);
|
|
957
|
+
if (!had) a.row.looks.push({ ...l, at });
|
|
958
|
+
else {
|
|
959
|
+
had.n += l.n;
|
|
960
|
+
had.lo = Math.min(had.lo ?? 1e9, l.lo ?? 1e9);
|
|
961
|
+
had.hi = Math.max(had.hi ?? 0, l.hi ?? 0);
|
|
962
|
+
if (had.at.length < 2) had.at.push(...at);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
for (const rows of accs.values()) for (const a of rows.values()) {
|
|
968
|
+
a.row.note = `${a.row.note ?? ""} (${a.pages.length === names.length && names.length > 1 ? "all" : a.pages.join("+")})`;
|
|
969
|
+
const said = /* @__PURE__ */ new Set();
|
|
970
|
+
for (let i = 0; i < 3; i++) for (const p of a.pages) {
|
|
971
|
+
const x = a.at.get(p)?.[i];
|
|
972
|
+
if (x && a.row.at.length < 3 && !said.has(x)) {
|
|
973
|
+
said.add(x);
|
|
974
|
+
a.row.at.push(`[${p}] ${x}`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
for (const g of groups) if (g.name !== "layout problems") g.rows.sort((a, b) => looksOf(b) - looksOf(a) || b.n - a.n);
|
|
979
|
+
const merged = mergeFindings(pages), overs = maxFindings(groups, rules), findings = merged || overs.length ? [...merged ?? [], ...overs] : void 0;
|
|
980
|
+
const report = names.some((p) => pages[p].report);
|
|
981
|
+
return { head: `${pages[names[0]]?.head ?? "styles on screen"}; pages: ${names.join(", ")}`, groups: groups.map((g) => ({ ...g, more: g.more || void 0 })), ...findings ? { findings } : {}, ...report ? { report } : {} };
|
|
982
|
+
}
|
|
983
|
+
var shares = (sizes, budget) => {
|
|
984
|
+
const out = sizes.map(() => 0);
|
|
985
|
+
let left = budget, n = sizes.length;
|
|
986
|
+
for (const i of sizes.map((_, j) => j).sort((a, b) => sizes[a] - sizes[b])) {
|
|
987
|
+
out[i] = Math.min(sizes[i], Math.floor(left / n));
|
|
988
|
+
left -= out[i];
|
|
989
|
+
n--;
|
|
990
|
+
}
|
|
991
|
+
return out;
|
|
992
|
+
};
|
|
993
|
+
function styleLines(rules = {}, root = document.body, cap2 = 220) {
|
|
994
|
+
return censusLines(styleData(rules, root, cap2));
|
|
995
|
+
}
|
|
996
|
+
function styleData(rules = {}, root = document.body, cap2 = 220) {
|
|
997
|
+
const host = document.createElement("div");
|
|
998
|
+
host.setAttribute("data-census-private", "");
|
|
999
|
+
host.style.display = "none";
|
|
1000
|
+
document.body.append(host);
|
|
1001
|
+
colorProbe(host);
|
|
1002
|
+
try {
|
|
1003
|
+
return census(rules, root, cap2, host);
|
|
1004
|
+
} finally {
|
|
1005
|
+
colorProbe(null);
|
|
1006
|
+
host.remove();
|
|
1007
|
+
forced = /* @__PURE__ */ new WeakMap();
|
|
1008
|
+
rings = /* @__PURE__ */ new WeakMap();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
var textLines = (el) => {
|
|
1012
|
+
const tops = [], walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT), range = document.createRange();
|
|
1013
|
+
for (let n = walk.nextNode(), i = 0; n && i < 200; n = walk.nextNode(), i++) {
|
|
1014
|
+
if (!n.nodeValue?.trim()) continue;
|
|
1015
|
+
range.selectNodeContents(n);
|
|
1016
|
+
for (const b of range.getClientRects()) if (b.width > 0 && !tops.some((t) => Math.abs(t - b.top) < b.height / 2)) tops.push(b.top);
|
|
1017
|
+
}
|
|
1018
|
+
return tops.length;
|
|
1019
|
+
};
|
|
1020
|
+
var CONTROL = "button,[role=button],[role=tab],[role=radio],[role=checkbox],[role=switch],[role=option],[role=menuitem],input,select,textarea,a[href]";
|
|
1021
|
+
var NATIVE = "a[href],button,input,select,textarea";
|
|
1022
|
+
var forced = /* @__PURE__ */ new WeakMap();
|
|
1023
|
+
var rings = /* @__PURE__ */ new WeakMap();
|
|
1024
|
+
var LAYER = /^(dialog|alertdialog|menu|listbox|tooltip)$/;
|
|
1025
|
+
var LOOK = ["height", "min-height", "border-top-left-radius", "background-color", "color", "font-size", "font-weight", "line-height", "letter-spacing", "font-family", "box-shadow", "border-top-color"];
|
|
1026
|
+
var STYLE_KEYS = { height: "height", minHeight: "min-height", borderRadius: "border-top-left-radius", background: "background-color", backgroundColor: "background-color", color: "color", fontSize: "font-size", fontWeight: "font-weight", lineHeight: "line-height", letterSpacing: "letter-spacing", fontFamily: "font-family", boxShadow: "box-shadow", borderColor: "border-top-color" };
|
|
1027
|
+
function census(rules, root, cap2, host) {
|
|
1028
|
+
const G = () => /* @__PURE__ */ new Map();
|
|
1029
|
+
const groups = { font: G(), type: G(), text: G(), fill: G(), line: G(), radius: G(), shadow: G(), field: G(), height: G(), layout: G(), align: G(), states: G(), used: G(), layer: G(), rules: G() };
|
|
1030
|
+
const infos = /* @__PURE__ */ new Map();
|
|
1031
|
+
const part = (el) => {
|
|
1032
|
+
let p = infos.get(el);
|
|
1033
|
+
if (p === void 0) {
|
|
1034
|
+
p = partInfo(el, rules.parts);
|
|
1035
|
+
infos.set(el, p);
|
|
1036
|
+
}
|
|
1037
|
+
return p;
|
|
1038
|
+
};
|
|
1039
|
+
const srcs = /* @__PURE__ */ new Map();
|
|
1040
|
+
const srcOf = (p) => {
|
|
1041
|
+
if (!rules.sources || !p) return "";
|
|
1042
|
+
let s = p.src ?? "";
|
|
1043
|
+
if (!s && p.fiber) {
|
|
1044
|
+
s = srcs.get(p.fiber) ?? fiberSrc(p.fiber);
|
|
1045
|
+
srcs.set(p.fiber, s);
|
|
1046
|
+
}
|
|
1047
|
+
return s ? ` @ ${s}` : "";
|
|
1048
|
+
};
|
|
1049
|
+
const hostSrc = (el) => {
|
|
1050
|
+
if (!rules.sources) return "";
|
|
1051
|
+
const loc = obj(obj(el.__svelte_meta)?.loc);
|
|
1052
|
+
const s = fiberSrc(fiberOf(el)) || (loc && typeof loc.file === "string" ? `${loc.file}:${loc.line ?? 0}` : "");
|
|
1053
|
+
return s ? ` @ ${s}` : "";
|
|
1054
|
+
};
|
|
1055
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1056
|
+
const ownerOf = (el) => {
|
|
1057
|
+
let o = owners.get(el);
|
|
1058
|
+
if (o !== void 0) return o;
|
|
1059
|
+
o = null;
|
|
1060
|
+
const makers = /* @__PURE__ */ new Set(), f = fiberOf(el);
|
|
1061
|
+
let told = false;
|
|
1062
|
+
if (f && "_debugOwner" in f) {
|
|
1063
|
+
told = true;
|
|
1064
|
+
for (let m = f._debugOwner, i = 0; m && i < 12; m = m._debugOwner ?? m.owner ?? null, i++) makers.add(compName(m.type) || m.name || "");
|
|
1065
|
+
}
|
|
1066
|
+
for (let a = el.parentElement, i = 0; a && a !== document.body && i < 4; a = a.parentElement, i++) {
|
|
1067
|
+
if (a.matches(CONTROL)) break;
|
|
1068
|
+
const lone = told || a.querySelectorAll(CONTROL).length === 1, ap = part(a);
|
|
1069
|
+
if (ap?.listed && lone && (!told || !ap.fiber || makers.has(compName(ap.fiber.type)))) {
|
|
1070
|
+
o = ap;
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
if (!lone) break;
|
|
1074
|
+
}
|
|
1075
|
+
owners.set(el, o);
|
|
1076
|
+
return o;
|
|
1077
|
+
};
|
|
1078
|
+
const inPart = (el, p) => rules.parts?.length && !p?.listed && el.matches(NATIVE) ? ownerOf(el) : null;
|
|
1079
|
+
const stray = (el, p) => !!rules.parts?.length && !p?.listed && el.matches(NATIVE) && !ownerOf(el);
|
|
1080
|
+
const where = (el) => {
|
|
1081
|
+
for (let p = el.parentElement; p; p = p.parentElement) {
|
|
1082
|
+
const t = p.tagName.toLowerCase(), role = p.getAttribute("role");
|
|
1083
|
+
if (/^(header|footer|nav|aside)$/.test(t)) return ` in ${t}`;
|
|
1084
|
+
if (role && /^(dialog|alertdialog|menu|listbox|tooltip|toolbar)$/.test(role)) return ` in ${role}`;
|
|
1085
|
+
}
|
|
1086
|
+
return inside(el);
|
|
1087
|
+
};
|
|
1088
|
+
const place = (el, r, name = el) => {
|
|
1089
|
+
const p = part(name), st = stateOf(name), no = stray(name, p), own = no ? null : inPart(name, p), tail = no ? "no DS part" : own ? `inside ${own.say}` : "";
|
|
1090
|
+
return `${nameOf(name)}${st ? ` (${st})` : ""}${p ? ` [${p.say}${tail ? `, ${tail}` : ""}]` : tail ? ` [${tail}]` : ""}${where(el)} ${box(r)}`;
|
|
1091
|
+
};
|
|
1092
|
+
const snap = rules.items ? /* @__PURE__ */ new Map() : null;
|
|
1093
|
+
const groupName = new Map(Object.entries(groups).map(([k, v]) => [v, k]));
|
|
1094
|
+
const add = (g, k, el, r, name = el) => {
|
|
1095
|
+
if (snap && snap.size < 4e3) {
|
|
1096
|
+
let it = snap.get(name);
|
|
1097
|
+
if (!it) snap.set(name, it = { el, r, look: /* @__PURE__ */ new Map() });
|
|
1098
|
+
const gn = groupName.get(g), had = it.look.get(gn) ?? [];
|
|
1099
|
+
if (!had.includes(k)) it.look.set(gn, [...had, k]);
|
|
1100
|
+
}
|
|
1101
|
+
let s = g.get(k);
|
|
1102
|
+
if (!s) g.set(k, s = { n: 0, at: [], el: name, els: [] });
|
|
1103
|
+
s.n++;
|
|
1104
|
+
if (s.at.length < 3) {
|
|
1105
|
+
s.at.push(place(el, r, name));
|
|
1106
|
+
s.els?.push(name);
|
|
1107
|
+
}
|
|
1108
|
+
return s;
|
|
1109
|
+
};
|
|
1110
|
+
const looks = /* @__PURE__ */ new Map();
|
|
1111
|
+
const seenLooks = [];
|
|
1112
|
+
const colorOf = (css) => {
|
|
1113
|
+
const c = parseColor(css);
|
|
1114
|
+
return c && c[3] >= 0.02 ? hex(c) : null;
|
|
1115
|
+
};
|
|
1116
|
+
const px = (v) => Math.round(parseFloat(v) * 10) / 10;
|
|
1117
|
+
const lineOf = (cs) => {
|
|
1118
|
+
for (const side of ["top", "right", "bottom", "left"]) {
|
|
1119
|
+
const w = parseFloat(cs.getPropertyValue(`border-${side}-width`));
|
|
1120
|
+
const c = w > 0 && cs.getPropertyValue(`border-${side}-style`) !== "none" ? colorOf(cs.getPropertyValue(`border-${side}-color`)) : null;
|
|
1121
|
+
if (c) return `${c} ${num(w)}px`;
|
|
1122
|
+
}
|
|
1123
|
+
return "";
|
|
1124
|
+
};
|
|
1125
|
+
const cornerOf = (cs) => {
|
|
1126
|
+
const one2 = (v) => {
|
|
1127
|
+
const n = parseFloat(v) || 0;
|
|
1128
|
+
return (v.includes("%") ? n >= 50 : n >= 999) ? "full" : num(n);
|
|
1129
|
+
};
|
|
1130
|
+
const c = [cs.borderTopLeftRadius, cs.borderTopRightRadius, cs.borderBottomRightRadius, cs.borderBottomLeftRadius].map(one2);
|
|
1131
|
+
return c.every((x) => x === c[0]) ? c[0] : c.join(" ");
|
|
1132
|
+
};
|
|
1133
|
+
const sayCorner = (c) => c.split(" ").map((x) => x === "full" || x === "0" ? x : `${x}px`).join(" ");
|
|
1134
|
+
const seeing = looking();
|
|
1135
|
+
const ruleColors = Object.entries(rules.colors ?? {}).map(([k, v]) => [k, parseColor(v)]).filter((x) => !!x[1]);
|
|
1136
|
+
const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2], (a[3] - b[3]) * 255);
|
|
1137
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
1138
|
+
const tokenOf = (h) => {
|
|
1139
|
+
let t = tokens.get(h);
|
|
1140
|
+
if (t) return t;
|
|
1141
|
+
t = {};
|
|
1142
|
+
const c = ruleColors.length ? parseColor(h) : null;
|
|
1143
|
+
if (c) {
|
|
1144
|
+
let best = ruleColors[0], d = Infinity;
|
|
1145
|
+
for (const x of ruleColors) {
|
|
1146
|
+
const e = dist(c, x[1]);
|
|
1147
|
+
if (e < d) {
|
|
1148
|
+
d = e;
|
|
1149
|
+
best = x;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const solid = d <= 2 || c[3] >= 0.995 ? void 0 : ruleColors.find((x) => x[1][3] >= 0.995 && Math.hypot(c[0] - x[1][0], c[1] - x[1][1], c[2] - x[1][2]) <= 3);
|
|
1153
|
+
t = d <= 2 ? { name: best[0] } : solid ? { name: `${solid[0]}/${Math.round(c[3] * 100)}` } : { off: ` OFF (nearest ${best[0]}, ${Math.round(d)} away)` };
|
|
1154
|
+
}
|
|
1155
|
+
tokens.set(h, t);
|
|
1156
|
+
return t;
|
|
1157
|
+
};
|
|
1158
|
+
const sayColors = (s) => s.replace(/(?<![\dx] )#[0-9a-f]{6,8}\b/g, (h) => {
|
|
1159
|
+
const t = tokenOf(h);
|
|
1160
|
+
return t.name ?? `${h}${t.off ?? ""}`;
|
|
1161
|
+
});
|
|
1162
|
+
const shadowNames = Object.entries(rules.shadows ?? {}).map(([k, v]) => [k, shadowLayers(v).rest.join(", ")]);
|
|
1163
|
+
const sayShadow = (k) => !shadowNames.length || k === "none" ? k : shadowNames.find((x) => x[1] === k)?.[0] ?? `${k} OFF`;
|
|
1164
|
+
const nameOf$ = (h) => h ? tokenOf(h).name ?? null : null;
|
|
1165
|
+
const base$ = (n) => n.replace(/\/\d+$/, "");
|
|
1166
|
+
const legacyRe = rules.legacy?.length ? new RegExp(`(?:^|[ <])(${rules.legacy.map((x) => x.replace(/[^\w-]/g, "")).join("|")})(?= |$)`) : null;
|
|
1167
|
+
const roleCheck = (who, fill, shadow, corner, el, r) => {
|
|
1168
|
+
const want2 = rules.roles?.[who];
|
|
1169
|
+
if (!want2) return;
|
|
1170
|
+
if (want2.fill && base$(nameOf$(fill) ?? "none") !== want2.fill) add(groups.rules, `${who} fill ${fill ?? "none"}: its role takes ${want2.fill}`, el, r);
|
|
1171
|
+
if (want2.shadow && shadow !== want2.shadow) add(groups.rules, `${who} shadow ${shadow}: its role takes ${want2.shadow}`, el, r);
|
|
1172
|
+
if (want2.radius !== void 0 && corner !== String(want2.radius)) add(groups.rules, `${who} corner ${sayCorner(corner)}: its role takes ${want2.radius}px`, el, r);
|
|
1173
|
+
};
|
|
1174
|
+
let skipped = [];
|
|
1175
|
+
try {
|
|
1176
|
+
if (rules.ignore) skipped = [rules.ignore];
|
|
1177
|
+
root.querySelector(rules.ignore || "*");
|
|
1178
|
+
} catch {
|
|
1179
|
+
skipped = [];
|
|
1180
|
+
}
|
|
1181
|
+
const out = new Set(root.querySelectorAll(["[data-census-private]", ...skipped].join(",")));
|
|
1182
|
+
let react = rules.foreign !== false && [...document.body.children].some((c) => fiberOf(c) || [...c.children].some(fiberOf));
|
|
1183
|
+
if (react) {
|
|
1184
|
+
const cs = [...document.querySelectorAll("button,[role=button],input,a[href]")].filter((c) => !c.closest("[data-census-private]"));
|
|
1185
|
+
if (cs.filter((c) => !fiberOf(c)).length * 3 > cs.length) react = false;
|
|
1186
|
+
}
|
|
1187
|
+
const foreign = /* @__PURE__ */ new Map(), embedded = /* @__PURE__ */ new Map(), roots = /* @__PURE__ */ new Map();
|
|
1188
|
+
const isRoot = (el) => {
|
|
1189
|
+
let r = roots.get(el);
|
|
1190
|
+
if (r === void 0) {
|
|
1191
|
+
r = Object.keys(el).some((k) => k.startsWith("__reactContainer$"));
|
|
1192
|
+
roots.set(el, r);
|
|
1193
|
+
}
|
|
1194
|
+
return r;
|
|
1195
|
+
};
|
|
1196
|
+
const foreignTop = (el) => {
|
|
1197
|
+
let top = el;
|
|
1198
|
+
while (top.parentElement && top.parentElement !== document.body && !fiberOf(top.parentElement) && !isRoot(top.parentElement)) top = top.parentElement;
|
|
1199
|
+
let f = foreign.get(top);
|
|
1200
|
+
if (f === void 0) {
|
|
1201
|
+
const par = top.parentElement, pf = par ? fiberOf(par) : null;
|
|
1202
|
+
f = false;
|
|
1203
|
+
if (!pf?.memoizedProps?.dangerouslySetInnerHTML && !isRoot(top) && ![...top.querySelectorAll("*")].some((c) => fiberOf(c) || isRoot(c))) {
|
|
1204
|
+
const tr = top.getBoundingClientRect(), flow = par === document.body && /^(static|relative)$/.test(getComputedStyle(top).position) && tr.width > 0 && tr.height > 0;
|
|
1205
|
+
f = !flow && (top.matches("button,[role=button],input,a[href]") ? [top] : [...top.querySelectorAll("button,[role=button],input,a[href]")]).some((c) => !fiberOf(c));
|
|
1206
|
+
}
|
|
1207
|
+
foreign.set(top, f);
|
|
1208
|
+
}
|
|
1209
|
+
return f ? top : null;
|
|
1210
|
+
};
|
|
1211
|
+
const sheets = readSheets();
|
|
1212
|
+
const pressedIsOn = !!document.querySelector("[data-pressed]:not(:active)");
|
|
1213
|
+
const saysOn = (el) => el.hasAttribute("aria-pressed") || el.hasAttribute("aria-checked") || el.hasAttribute("aria-selected");
|
|
1214
|
+
const lit = /* @__PURE__ */ new Set(), litText = /* @__PURE__ */ new Set(), litBy = /* @__PURE__ */ new Map();
|
|
1215
|
+
for (const st of sheets.states) {
|
|
1216
|
+
if (st.pseudo || st.on && (st.toggle || pressedIsOn)) continue;
|
|
1217
|
+
const tint = !!st.style.getPropertyValue("color");
|
|
1218
|
+
if (!tint && !["background-color", "background", "border-color", "box-shadow", "opacity"].some((k) => st.style.getPropertyValue(k))) continue;
|
|
1219
|
+
let els = litBy.get(st.sel);
|
|
1220
|
+
if (!els) {
|
|
1221
|
+
try {
|
|
1222
|
+
els = [...document.querySelectorAll(st.sel)];
|
|
1223
|
+
} catch {
|
|
1224
|
+
els = [];
|
|
1225
|
+
}
|
|
1226
|
+
litBy.set(st.sel, els);
|
|
1227
|
+
}
|
|
1228
|
+
for (const e of els) {
|
|
1229
|
+
if (st.on && saysOn(e)) continue;
|
|
1230
|
+
lit.add(e);
|
|
1231
|
+
if (tint) litText.add(e);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
const sizing = /* @__PURE__ */ new Map(), sizers = /* @__PURE__ */ new Map();
|
|
1235
|
+
const sized = (el) => {
|
|
1236
|
+
let v = sizing.get(el);
|
|
1237
|
+
if (v !== void 0) return v;
|
|
1238
|
+
const real = (x) => !!x && !/^(auto|0(px)?|fit-content|min-content|max-content|inherit|initial|unset|revert|revert-layer)$/.test(x.trim());
|
|
1239
|
+
v = ["height", "min-height", "block-size", "min-block-size", "aspect-ratio"].some((k) => {
|
|
1240
|
+
const own = el.style?.getPropertyValue(k) ?? "";
|
|
1241
|
+
if (own) return real(own);
|
|
1242
|
+
let list = sizers.get(k), top = null;
|
|
1243
|
+
if (!list) sizers.set(k, list = sheets.plain.filter((x) => x.style.getPropertyValue(k)));
|
|
1244
|
+
const imp = (x) => x.style.getPropertyPriority(k) === "important";
|
|
1245
|
+
for (const x of list) {
|
|
1246
|
+
try {
|
|
1247
|
+
if ((!top || sheets.beats(x, imp(x), top, imp(top))) && el.matches(x.sel)) top = x;
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return !!top && real(top.style.getPropertyValue(k));
|
|
1252
|
+
});
|
|
1253
|
+
sizing.set(el, v);
|
|
1254
|
+
return v;
|
|
1255
|
+
};
|
|
1256
|
+
const want = (check) => checkSetting(rules, check);
|
|
1257
|
+
const W = { contrast: want("contrast") };
|
|
1258
|
+
const hits = /* @__PURE__ */ new Map();
|
|
1259
|
+
const hit = (id, el, r, more = {}) => {
|
|
1260
|
+
let h = hits.get(id);
|
|
1261
|
+
if (!h) hits.set(id, h = { n: 0, at: [], el, r, ...more });
|
|
1262
|
+
h.n++;
|
|
1263
|
+
if (h.at.length < 3) h.at.push(place(el, r));
|
|
1264
|
+
const nm = nameOf(el);
|
|
1265
|
+
if ((h.names ??= []).length < 20 && !h.names.includes(nm)) h.names.push(nm);
|
|
1266
|
+
if (more.min !== void 0) h.min = Math.min(h.min ?? 99, more.min);
|
|
1267
|
+
if (more.unsure === false) h.unsure = false;
|
|
1268
|
+
return h;
|
|
1269
|
+
};
|
|
1270
|
+
const unsureText = (el, cs, r) => {
|
|
1271
|
+
if (cs.backgroundClip === "text" || cs.getPropertyValue("-webkit-background-clip") === "text" || cs.getPropertyValue("-webkit-text-fill-color") && colorOf(cs.getPropertyValue("-webkit-text-fill-color")) !== colorOf(cs.color) || cs.mixBlendMode !== "normal" || cs.filter !== "none" || cs.textShadow !== "none") return true;
|
|
1272
|
+
for (let e = el; e; e = e.parentElement) {
|
|
1273
|
+
const s = getComputedStyle(e);
|
|
1274
|
+
if (s.backgroundImage !== "none" || s.backdropFilter && s.backdropFilter !== "none") return true;
|
|
1275
|
+
const c = parseColor(s.backgroundColor);
|
|
1276
|
+
if (c && c[3] >= 0.995) break;
|
|
1277
|
+
}
|
|
1278
|
+
for (const c of document.elementsFromPoint(r.left + r.width / 2, r.top + r.height / 2)) {
|
|
1279
|
+
if (c === el || c.contains(el) || el.contains(c)) return false;
|
|
1280
|
+
const s = getComputedStyle(c), bgc = parseColor(s.backgroundColor);
|
|
1281
|
+
if (bgc && bgc[3] > 0.02 || s.backgroundImage !== "none" || /^(IMG|VIDEO|CANVAS)$/.test(c.tagName)) return true;
|
|
1282
|
+
}
|
|
1283
|
+
return false;
|
|
1284
|
+
};
|
|
1285
|
+
const turnedOff = (el) => !!el.closest(`${OFF},fieldset[disabled],[data-disabled]`) || el.disabled === true;
|
|
1286
|
+
const contrastOf = (el, color, cs, r, what) => {
|
|
1287
|
+
const ratio = seeing.ratio(el, color, seeing.opacity(el));
|
|
1288
|
+
if (ratio === null) return;
|
|
1289
|
+
const size = parseFloat(cs.fontSize), weight = parseFloat(cs.fontWeight) || 400;
|
|
1290
|
+
const large = size >= 24 || size >= 18.66 && weight >= 700, team = W.contrast.n;
|
|
1291
|
+
const floor = team !== void 0 ? large ? Math.min(3, team) : team : large ? 3 : 4.5;
|
|
1292
|
+
if (ratio >= floor) return;
|
|
1293
|
+
const fg = colorOf(color) ?? color, bg = hex(seeing.back(el)), id = `contrast|${what}${fg} on ${bg}|${large ? "large" : ""}`;
|
|
1294
|
+
const had = hits.get(id);
|
|
1295
|
+
hit(id, el, r, { min: ratio, big: large, fg, bg, what, ...had && (had.n >= 3 || !had.unsure) ? {} : { unsure: unsureText(el, cs, r) } });
|
|
1296
|
+
};
|
|
1297
|
+
const placeholders = /* @__PURE__ */ new Map(), icons = /* @__PURE__ */ new Map(), tracking = /* @__PURE__ */ new Map();
|
|
1298
|
+
let moving = 0, hot = 0, warm = null;
|
|
1299
|
+
const controls = [];
|
|
1300
|
+
const uses = [];
|
|
1301
|
+
const all = [root, ...root.querySelectorAll("*")];
|
|
1302
|
+
let skip = root.closest("[data-census-private]") ? root : null;
|
|
1303
|
+
for (const el of all) {
|
|
1304
|
+
if (skip) {
|
|
1305
|
+
if (skip.contains(el)) continue;
|
|
1306
|
+
skip = null;
|
|
1307
|
+
}
|
|
1308
|
+
if (out.has(el)) {
|
|
1309
|
+
skip = el;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (!(el instanceof HTMLElement || el instanceof SVGElement)) continue;
|
|
1313
|
+
if (react && !fiberOf(el)) {
|
|
1314
|
+
const top = foreignTop(el);
|
|
1315
|
+
if (top) {
|
|
1316
|
+
embedded.set(top, (embedded.get(top) ?? 0) + 1);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const r = el.getBoundingClientRect();
|
|
1321
|
+
if (!shown(el, r)) continue;
|
|
1322
|
+
const cs = getComputedStyle(el);
|
|
1323
|
+
if (el instanceof SVGElement) {
|
|
1324
|
+
if (/^(path|rect|circle|ellipse|line|polyline|polygon|use)$/.test(el.tagName)) {
|
|
1325
|
+
const svg = el.closest("svg") ?? el;
|
|
1326
|
+
const had = icons.get(svg) ?? /* @__PURE__ */ new Set();
|
|
1327
|
+
icons.set(svg, had);
|
|
1328
|
+
for (const v of [cs.fill, cs.stroke]) {
|
|
1329
|
+
const c = v && v !== "none" ? colorOf(v) : null;
|
|
1330
|
+
if (c && !had.has(c)) {
|
|
1331
|
+
had.add(c);
|
|
1332
|
+
add(groups.text, `${c} (icon)`, svg, svg.getBoundingClientRect());
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (warm && !warm.contains(el)) warm = null;
|
|
1339
|
+
if (!warm && litText.has(el)) warm = el;
|
|
1340
|
+
const own = ownText(el);
|
|
1341
|
+
if (!own && !el.children.length && cs.animationName !== "none" && /infinite/.test(cs.animationIterationCount)) {
|
|
1342
|
+
const f = colorOf(cs.backgroundColor) ?? "none";
|
|
1343
|
+
placeholders.set(f, (placeholders.get(f) ?? 0) + 1);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (own && r.width > 2 && r.height > 2) {
|
|
1347
|
+
const lh = cs.lineHeight === "normal" ? "normal" : String(px(cs.lineHeight)), ls = parseFloat(cs.letterSpacing) ? px(cs.letterSpacing) : 0;
|
|
1348
|
+
add(groups.type, `${px(cs.fontSize)}/${cs.fontWeight}/${lh}${ls ? ` ${ls}ls` : ""}`, el, r);
|
|
1349
|
+
add(groups.font, cs.fontFamily.replace(/["']/g, "").replace(/\s*,\s*/g, ", "), el, r);
|
|
1350
|
+
const t = colorOf(cs.color);
|
|
1351
|
+
if (t && !warm) {
|
|
1352
|
+
add(groups.text, t, el, r);
|
|
1353
|
+
const tn = nameOf$(t);
|
|
1354
|
+
if (rules.text?.length && tn && !rules.text.includes(tn) && !rules.text.includes(base$(tn))) add(groups.rules, `text ${t} is not a text color (text takes ${rules.text.join(", ")})`, el, r);
|
|
1355
|
+
if (rules.pairs && tn) {
|
|
1356
|
+
const under = hex(behind(el)), un = nameOf$(under), takes = un ? rules.pairs[un] ?? rules.pairs[base$(un)] : void 0;
|
|
1357
|
+
if (takes && !takes.includes(tn) && !takes.includes(base$(tn))) add(groups.rules, `text ${t} on ${under}: that fill takes text ${takes.join(", ")}`, el, r);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const ta = sayAlign(cs.textAlign, cs.direction);
|
|
1361
|
+
if (ta && ta !== "start" && !(ta === "center" && el.closest(CONTROL))) add(groups.align, `text ${ta}`, el, r);
|
|
1362
|
+
const per = tracking.get(px(cs.fontSize)) ?? /* @__PURE__ */ new Map();
|
|
1363
|
+
tracking.set(px(cs.fontSize), per);
|
|
1364
|
+
const had = per.get(ls);
|
|
1365
|
+
if (had) had.n++;
|
|
1366
|
+
else per.set(ls, { n: 1, at: [place(el, r)], el });
|
|
1367
|
+
if (W.contrast.level && !warm && !turnedOff(el) && !el.closest("svg,[role=img]")) contrastOf(el, cs.color, cs, r, "");
|
|
1368
|
+
}
|
|
1369
|
+
if (W.contrast.level && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) && el.placeholder && !el.value && !turnedOff(el)) {
|
|
1370
|
+
const ps = getComputedStyle(el, "::placeholder");
|
|
1371
|
+
contrastOf(el, ps.color, ps, r, "placeholder ");
|
|
1372
|
+
}
|
|
1373
|
+
const big = r.width >= 4 && r.height >= 4;
|
|
1374
|
+
let bg = colorOf(cs.backgroundColor), line = lineOf(cs), corner = cornerOf(cs);
|
|
1375
|
+
const ctl = big && el.matches(CONTROL);
|
|
1376
|
+
const still = !(bg || ctl) || !el.getAnimations?.().some((a) => a.playState === "running");
|
|
1377
|
+
if (!still) {
|
|
1378
|
+
moving++;
|
|
1379
|
+
bg = null;
|
|
1380
|
+
}
|
|
1381
|
+
const live = lit.has(el);
|
|
1382
|
+
if (live) {
|
|
1383
|
+
if (bg || line || ctl) hot++;
|
|
1384
|
+
bg = null;
|
|
1385
|
+
line = "";
|
|
1386
|
+
}
|
|
1387
|
+
if (bg && big) add(groups.fill, bg, el, r);
|
|
1388
|
+
if (line) add(groups.line, line, el, r);
|
|
1389
|
+
const sh = !live && cs.boxShadow && cs.boxShadow !== "none" ? shadowLayers(cs.boxShadow) : null;
|
|
1390
|
+
for (const ring of sh?.rings ?? []) add(groups.line, ring, el, r);
|
|
1391
|
+
if (corner !== "0" && (bg || line)) add(groups.radius, sayCorner(corner), el, r);
|
|
1392
|
+
if (sh?.rest.length) add(groups.shadow, sh.rest.join(", "), el, r);
|
|
1393
|
+
const lay = layoutOf(el, cs, r, !!(bg || line));
|
|
1394
|
+
if (lay) {
|
|
1395
|
+
const s = add(groups.layout, lay.key, el, r);
|
|
1396
|
+
if (s.at.length && !s.at[s.at.length - 1].includes(" holding ")) s.at[s.at.length - 1] += ` holding ${lay.kids}`;
|
|
1397
|
+
}
|
|
1398
|
+
const role = el.getAttribute("role");
|
|
1399
|
+
if (role && LAYER.test(role) || el === root && root !== document.body) {
|
|
1400
|
+
let lb = bg, ll = line, lc = corner, ls = sh;
|
|
1401
|
+
const inner = !lb && !ls?.rest.length && !!el.parentElement?.closest("[role=dialog],[role=alertdialog],[role=menu],[role=listbox],[role=tooltip]");
|
|
1402
|
+
for (let p2 = el.parentElement, i = 0; !inner && !lb && !ls?.rest.length && p2 && i < 4; p2 = p2.parentElement, i++) {
|
|
1403
|
+
const pcs = getComputedStyle(p2), pb = colorOf(pcs.backgroundColor), ps = shadowLayers(pcs.boxShadow);
|
|
1404
|
+
if (pb || ps.rest.length) {
|
|
1405
|
+
lb = pb;
|
|
1406
|
+
ll = lineOf(pcs);
|
|
1407
|
+
lc = cornerOf(pcs);
|
|
1408
|
+
ls = ps;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (!inner) {
|
|
1412
|
+
add(groups.layer, `${role ?? "layer"} corner ${sayCorner(lc)} fill ${lb ?? "none"}${ll ? ` line ${ll}` : ""} shadow ${ls?.rest.length ? ls.rest.join(", ") : "none"}`, el, r);
|
|
1413
|
+
if (role) roleCheck(role, lb, ls?.rest.length ? sayShadow(ls.rest.join(", ")) : "none", lc, el, r);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
const p = part(el);
|
|
1417
|
+
if (p && big) {
|
|
1418
|
+
const old = legacyRe?.exec(p.say)?.[1];
|
|
1419
|
+
if (old) add(groups.rules, `legacy part ${old} on screen`, el, r);
|
|
1420
|
+
const pn = p.key.split(" ")[0];
|
|
1421
|
+
if (rules.roles?.[pn] && !live && still && !stateOf(el)) roleCheck(pn, bg, sh?.rest.length ? sayShadow(sh.rest.join(", ")) : "none", corner, el, r);
|
|
1422
|
+
}
|
|
1423
|
+
let boxR = r, hug = false;
|
|
1424
|
+
if (ctl) {
|
|
1425
|
+
if (!live && (el instanceof HTMLInputElement && /^(text|search|email|number|password|tel|url)$/.test(el.type) || el instanceof HTMLTextAreaElement)) {
|
|
1426
|
+
for (let w = el.parentElement, i = 0; !bg && !line && w && i < 6; w = w.parentElement, i++) {
|
|
1427
|
+
const wr = w.getBoundingClientRect();
|
|
1428
|
+
if (wr.height > r.height * 3 + 8) break;
|
|
1429
|
+
const wcs = getComputedStyle(w), wb = colorOf(wcs.backgroundColor), wl = lineOf(wcs);
|
|
1430
|
+
if (wb || wl) {
|
|
1431
|
+
bg = wb;
|
|
1432
|
+
line = wl;
|
|
1433
|
+
corner = cornerOf(wcs);
|
|
1434
|
+
boxR = wr;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
add(groups.field, `${Math.round(boxR.height)}px corner ${sayCorner(corner)} fill ${bg ?? "none"}${line ? ` line ${line}` : ""}`, el, boxR);
|
|
1438
|
+
}
|
|
1439
|
+
const inner = Math.max(0, ...[...el.children].map((c) => c.getBoundingClientRect().height), own && cs.lineHeight !== "normal" ? parseFloat(cs.lineHeight) : 0);
|
|
1440
|
+
hug = !bg && !line && !parseFloat(cs.paddingTop) && !parseFloat(cs.paddingBottom) && inner >= r.height - 1;
|
|
1441
|
+
const kind = role ?? (el.tagName === "INPUT" ? `input ${el.type}` : el.tagName.toLowerCase());
|
|
1442
|
+
if (!hug) {
|
|
1443
|
+
const s = add(groups.height, `${kind} ${Math.round(boxR.height)}px${boxR === r ? "" : " (field box)"}`, el, boxR);
|
|
1444
|
+
if (p) (s.parts ??= /* @__PURE__ */ new Set()).add(p.key);
|
|
1445
|
+
}
|
|
1446
|
+
controls.push({ el, r: boxR });
|
|
1447
|
+
}
|
|
1448
|
+
const no = stray(el, p), within = no ? null : inPart(el, p);
|
|
1449
|
+
if (big && (p || no || within) && (bg || ctl)) {
|
|
1450
|
+
if (still && !live) {
|
|
1451
|
+
const st = stateOf(el), tc = ctl ? colorOf(cs.color) : null, op = seeing.opacity(el);
|
|
1452
|
+
let h = Math.round(boxR.height);
|
|
1453
|
+
if (ctl && !hug && boxR === r) {
|
|
1454
|
+
const lh = parseFloat(cs.lineHeight), inner = r.height - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom) - parseFloat(cs.borderTopWidth) - parseFloat(cs.borderBottomWidth), n = lh > 0 ? Math.round(inner / lh) : 0;
|
|
1455
|
+
if (n >= 2 && Math.abs(inner - n * lh) <= 1 && (el instanceof HTMLTextAreaElement || textLines(el) === n) && !sized(el)) h = Math.round(r.height - (n - 1) * lh);
|
|
1456
|
+
}
|
|
1457
|
+
const shape = `${ctl ? hug ? "h=content " : `h${h} ` : ""}r${corner.replaceAll(" ", "/")}`;
|
|
1458
|
+
const skin = `${shape} fill ${bg ?? "none"}${op < 0.995 ? ` at ${Math.round(op * 100)}%` : ""}`, say = `${skin}${tc ? ` text ${tc}` : ""}`;
|
|
1459
|
+
const key = no ? `${p?.key ?? el.tagName.toLowerCase()} (no DS part)` : within ? `${within.key} ${el.tagName.toLowerCase()}` : p.key;
|
|
1460
|
+
seenLooks.push({ key, flag: no ? false : within ? within.cand : p.cand, st, shape, skin, say, h, hug, at: `${nameOf(el)}${where(el)} ${box(boxR)}${srcOf(within ?? p)}`, fiber: (within ?? p)?.fiber ?? null });
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
const props = p?.own && big ? p.fiber?.memoizedProps : null;
|
|
1464
|
+
if (props && uses.length < 600) {
|
|
1465
|
+
const cls = typeof props.className === "string" ? props.className.split(/\s+/).filter(Boolean).slice(0, 40) : [];
|
|
1466
|
+
const style = props.style && typeof props.style === "object" ? Object.keys(props.style).flatMap((k) => STYLE_KEYS[k] ? [STYLE_KEYS[k]] : []) : [];
|
|
1467
|
+
if (cls.length || style.length) uses.push({ el, r, key: p.key, cls, style, ctl });
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
const perKey = /* @__PURE__ */ new Map();
|
|
1471
|
+
for (const x of seenLooks) perKey.set(x.key, [...perKey.get(x.key) ?? [], x]);
|
|
1472
|
+
for (const [key, list] of perKey) {
|
|
1473
|
+
if (!list[0].flag || new Set(list.map((x) => x.shape)).size < 2 && new Set(list.map((x) => `${x.st}|${x.skin}`)).size === new Set(list.map((x) => x.st)).size) continue;
|
|
1474
|
+
const more = list.map((x) => x.fiber ? propsOf(x.fiber, MORE_PROPS) : "");
|
|
1475
|
+
if (new Set(more).size > 1) list.forEach((x, i) => {
|
|
1476
|
+
if (more[i]) x.key = `${key} ${more[i]}`;
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
for (const x of seenLooks) {
|
|
1480
|
+
const of = looks.get(x.key) ?? { flag: x.flag, m: /* @__PURE__ */ new Map() };
|
|
1481
|
+
looks.set(x.key, of);
|
|
1482
|
+
let l = of.m.get(`${x.st}|${x.say}`);
|
|
1483
|
+
if (!l) of.m.set(`${x.st}|${x.say}`, l = { state: x.st, shape: x.shape, skin: x.skin, say: x.say, n: 0, at: [] });
|
|
1484
|
+
l.n++;
|
|
1485
|
+
if (x.hug) {
|
|
1486
|
+
l.lo = Math.min(l.lo ?? 1e9, x.h);
|
|
1487
|
+
l.hi = Math.max(l.hi ?? 0, x.h);
|
|
1488
|
+
}
|
|
1489
|
+
if (l.at.length < 2) l.at.push(x.at);
|
|
1490
|
+
}
|
|
1491
|
+
const shut = /* @__PURE__ */ new Map();
|
|
1492
|
+
let cut = false;
|
|
1493
|
+
if (rules.closed) {
|
|
1494
|
+
let top = all.map(fiberOf).find(Boolean) ?? null;
|
|
1495
|
+
while (top?.return) top = top.return;
|
|
1496
|
+
const mounted = /* @__PURE__ */ new Set(), fibers = [], stack = top ? [top] : [];
|
|
1497
|
+
const worn = /* @__PURE__ */ new Map();
|
|
1498
|
+
while (stack.length && fibers.length < 6e4) {
|
|
1499
|
+
const f = stack.pop();
|
|
1500
|
+
fibers.push(f);
|
|
1501
|
+
mounted.add(f.memoizedProps);
|
|
1502
|
+
if (f.alternate?.memoizedProps) mounted.add(f.alternate.memoizedProps);
|
|
1503
|
+
const cn = typeof f.type === "string" ? null : f.memoizedProps?.className;
|
|
1504
|
+
if (typeof cn === "string" && cn) for (const t of /* @__PURE__ */ new Set([f.type, f.elementType ?? f.type])) {
|
|
1505
|
+
const l = worn.get(t) ?? [];
|
|
1506
|
+
if (l.length < 400) l.push({ props: propsOf(f), cls: new Set(cn.split(/\s+/)) });
|
|
1507
|
+
worn.set(t, l);
|
|
1508
|
+
}
|
|
1509
|
+
if (f.sibling) stack.push(f.sibling);
|
|
1510
|
+
if (f.child) stack.push(f.child);
|
|
1511
|
+
}
|
|
1512
|
+
if (stack.length) cut = true;
|
|
1513
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1514
|
+
let budget = 3e4;
|
|
1515
|
+
const scan = (v, depth) => {
|
|
1516
|
+
if (!v || typeof v !== "object" || depth > 12) return;
|
|
1517
|
+
if (Array.isArray(v)) {
|
|
1518
|
+
if (seen.has(v)) return;
|
|
1519
|
+
seen.add(v);
|
|
1520
|
+
for (let i = 0; i < v.length && i < 200; i++) {
|
|
1521
|
+
const x = v[i];
|
|
1522
|
+
if (x && typeof x === "object" && !Array.isArray(x) && !x.$$typeof) return;
|
|
1523
|
+
scan(x, depth + 1);
|
|
1524
|
+
}
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const e = v;
|
|
1528
|
+
if (!e.$$typeof || !e.props || typeof e.props !== "object" || seen.has(v)) return;
|
|
1529
|
+
if (budget-- < 0) {
|
|
1530
|
+
cut = true;
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
seen.add(v);
|
|
1534
|
+
const name = compName(e.type);
|
|
1535
|
+
if (!mounted.has(e.props) && typeof e.props.className === "string" && e.props.className && PLAIN.test(name) && !PLUMBING.test(name)) {
|
|
1536
|
+
const props = propsOf({ type: e.type, return: null, memoizedProps: e.props }), k = `${name} ${props}
|
|
1537
|
+
${e.props.className}`, had = shut.get(k), cls = e.props.className.split(/\s+/).filter(Boolean);
|
|
1538
|
+
if (had) had.n++;
|
|
1539
|
+
else if (worn.get(e.type)?.some((w) => w.props === props && cls.every((t) => w.cls.has(t)))) {
|
|
1540
|
+
} else if (shut.size < 300) shut.set(k, { name, props, cls: e.props.className, n: 1 });
|
|
1541
|
+
else cut = true;
|
|
1542
|
+
}
|
|
1543
|
+
for (const k of Object.keys(e.props)) scan(e.props[k], depth + 1);
|
|
1544
|
+
};
|
|
1545
|
+
for (const f of fibers) {
|
|
1546
|
+
const p = f.memoizedProps;
|
|
1547
|
+
if (p && typeof p === "object") for (const k of Object.keys(p)) scan(p[k], 0);
|
|
1548
|
+
}
|
|
1549
|
+
for (const x of shut.values()) {
|
|
1550
|
+
x.el = document.createElement("div");
|
|
1551
|
+
x.el.className = x.cls;
|
|
1552
|
+
host.append(x.el);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
const ghosts = /* @__PURE__ */ new Map();
|
|
1556
|
+
for (const cls of [...uses.map((u) => u.cls), ...[...shut.values()].filter((x) => x.props).map((x) => x.cls.split(/\s+/).filter(Boolean))]) for (const t of cls) if (!ghosts.has(t) && ghosts.size < 500) {
|
|
1557
|
+
const g = document.createElement("div");
|
|
1558
|
+
g.className = t;
|
|
1559
|
+
host.append(g);
|
|
1560
|
+
ghosts.set(t, { el: g, sets: /* @__PURE__ */ new Map() });
|
|
1561
|
+
}
|
|
1562
|
+
const targets = /* @__PURE__ */ new Set([...controls.map((c) => c.el), ...uses.map((u) => u.el), ...[...ghosts.values()].map((g) => g.el)]);
|
|
1563
|
+
const matched = /* @__PURE__ */ new Map(), found = /* @__PURE__ */ new Map();
|
|
1564
|
+
for (const st of sheets.states) {
|
|
1565
|
+
if (st.on && (st.toggle || pressedIsOn)) continue;
|
|
1566
|
+
let els = found.get(st.base);
|
|
1567
|
+
if (!els) {
|
|
1568
|
+
try {
|
|
1569
|
+
els = [...document.querySelectorAll(st.base)].filter((e) => targets.has(e));
|
|
1570
|
+
} catch {
|
|
1571
|
+
els = [];
|
|
1572
|
+
}
|
|
1573
|
+
found.set(st.base, els);
|
|
1574
|
+
}
|
|
1575
|
+
for (const el of els) {
|
|
1576
|
+
const l = matched.get(el);
|
|
1577
|
+
if (l) l.push(st);
|
|
1578
|
+
else matched.set(el, [st]);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const stateSaid = /* @__PURE__ */ new Map();
|
|
1582
|
+
const setters = /* @__PURE__ */ new Map();
|
|
1583
|
+
const settersOf = (prop) => {
|
|
1584
|
+
let l = setters.get(prop);
|
|
1585
|
+
if (!l) setters.set(prop, l = sheets.plain.filter((x) => x.style.getPropertyValue(prop)));
|
|
1586
|
+
return l;
|
|
1587
|
+
};
|
|
1588
|
+
const statesOf = (el) => {
|
|
1589
|
+
let said = stateSaid.get(el);
|
|
1590
|
+
if (said) return said;
|
|
1591
|
+
said = [];
|
|
1592
|
+
const real = forced.get(el), toggles = saysOn(el);
|
|
1593
|
+
if (real && real.size > 1) {
|
|
1594
|
+
const rest = real.get("rest"), cs = getComputedStyle(el), off2 = el.matches(":disabled");
|
|
1595
|
+
const attrPress = (matched.get(el) ?? []).some((st) => st.state === "pressed" && st.on) && !toggles;
|
|
1596
|
+
for (const [name, look] of real) {
|
|
1597
|
+
if (name === (attrPress ? "pressed" : "pressed-attr")) continue;
|
|
1598
|
+
const state = name === "pressed-attr" ? "pressed" : name;
|
|
1599
|
+
if (state === "rest" || state !== "focus" && cs.pointerEvents === "none" || state !== "hover" && off2) continue;
|
|
1600
|
+
for (const [k, v] of Object.entries(look)) {
|
|
1601
|
+
const c = v ? colorOf(v) : null;
|
|
1602
|
+
const was = (x) => x ? colorOf(x) : null;
|
|
1603
|
+
if (c && c !== was(rest[k]) && !(state === "pressed" && c === was(real.get("hover")?.[k]))) said.push(`${state} ${k.startsWith("overlay") ? "overlay fill" : k} ${c}`);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
stateSaid.set(el, said);
|
|
1607
|
+
return said;
|
|
1608
|
+
}
|
|
1609
|
+
const win = /* @__PURE__ */ new Map();
|
|
1610
|
+
const mine = matched.get(el) ?? [];
|
|
1611
|
+
for (const st of mine) for (const [prop, word] of [["background-color", "fill"], ["color", "text"], ["border-color", "line"]]) {
|
|
1612
|
+
const raw = st.on && toggles ? "" : st.style.getPropertyValue(prop);
|
|
1613
|
+
if (!raw) continue;
|
|
1614
|
+
const k = `${st.state}${st.pseudo ? " overlay" : ""} ${word}`, imp = st.style.getPropertyPriority(prop) === "important", had = win.get(k);
|
|
1615
|
+
if (!had || sheets.beats(st, imp, had.st, had.imp)) win.set(k, { raw, imp, st, prop });
|
|
1616
|
+
}
|
|
1617
|
+
const ecs = win.size ? getComputedStyle(el) : null, off = el.matches(":disabled");
|
|
1618
|
+
for (const [k, w] of win) {
|
|
1619
|
+
const state = w.st.state, long = w.prop === "border-color" ? "border-top-color" : w.prop;
|
|
1620
|
+
if (state !== "focus" && ecs.pointerEvents === "none" || state !== "hover" && off) continue;
|
|
1621
|
+
const inline = w.st.pseudo ? "" : el.style?.getPropertyValue(long) ?? "";
|
|
1622
|
+
if (inline && !(w.imp && el.style.getPropertyPriority(long) !== "important")) continue;
|
|
1623
|
+
if (settersOf(long).some((x) => {
|
|
1624
|
+
if (x === w.st || x.has.includes(state) || pseudoOf(x) !== w.st.pseudo || !sheets.beats(x, x.style.getPropertyPriority(long) === "important", w.st, w.imp)) return false;
|
|
1625
|
+
try {
|
|
1626
|
+
return el.matches(x.base);
|
|
1627
|
+
} catch {
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
})) continue;
|
|
1631
|
+
const rest = getComputedStyle(el, w.st.pseudo ? `::${w.st.pseudo}` : void 0);
|
|
1632
|
+
if (w.st.pseudo && /^(none|normal)$/.test(rest.content) && !w.st.style.getPropertyValue("content")) continue;
|
|
1633
|
+
if (w.prop === "border-color" && !(parseFloat(rest.borderTopWidth) > 0)) continue;
|
|
1634
|
+
const same = [w.st.style, ...mine.filter((x) => x !== w.st && x.state === state && x.pseudo === w.st.pseudo).reverse().map((x) => x.style)];
|
|
1635
|
+
let value = resolveVars(w.raw, el, same);
|
|
1636
|
+
if (w.prop !== "color" && /currentcolor/i.test(value)) {
|
|
1637
|
+
const tw = win.get(`${state}${w.st.pseudo ? " overlay" : ""} text`) ?? win.get(`${state} text`);
|
|
1638
|
+
const text = tw ? resolveVars(tw.raw, el, [tw.st.style]) : "";
|
|
1639
|
+
value = value.replace(/currentcolor/gi, text && !/currentcolor|inherit/i.test(text) ? text : rest.color);
|
|
1640
|
+
}
|
|
1641
|
+
const c = colorOf(value);
|
|
1642
|
+
if (c && c !== colorOf(rest.getPropertyValue(long))) said.push(`${k} ${c}`);
|
|
1643
|
+
}
|
|
1644
|
+
stateSaid.set(el, said);
|
|
1645
|
+
return said;
|
|
1646
|
+
};
|
|
1647
|
+
const stateRule = (st) => {
|
|
1648
|
+
const w = rules.states?.[st];
|
|
1649
|
+
if (!w) return null;
|
|
1650
|
+
const m = /^wash\s+(\S+)$/.exec(w.trim()), name = m ? m[1] : w.trim(), c = m && rules.colors?.[name] ? parseColor(rules.colors[name]) : null;
|
|
1651
|
+
return m ? c ? { wash: c, name } : null : { name };
|
|
1652
|
+
};
|
|
1653
|
+
for (const c of controls) for (const s of statesOf(c.el)) {
|
|
1654
|
+
add(groups.states, s, c.el, c.r);
|
|
1655
|
+
const m = /^(\w+)( overlay)? fill (#[0-9a-f]{6,8})$/.exec(s), want2 = m ? stateRule(m[1]) : null;
|
|
1656
|
+
if (!m || !want2) continue;
|
|
1657
|
+
const got = parseColor(m[3]);
|
|
1658
|
+
if (want2.wash) {
|
|
1659
|
+
const rest = parseColor(getComputedStyle(c.el).backgroundColor), back = behind(c.el.parentElement), under = rest && rest[3] >= 0.02 ? rest[3] >= 0.995 ? rest : over(rest, back) : back;
|
|
1660
|
+
if (Math.max(under[0], under[1], under[2]) - Math.min(under[0], under[1], under[2]) > 16) continue;
|
|
1661
|
+
const ok = m[2] ? dist(got, want2.wash) <= 2 : dist(got[3] >= 0.995 ? got : over(got, back), over(want2.wash, under)) <= 3;
|
|
1662
|
+
if (!ok) add(groups.rules, `${m[1]} fill ${m[3]} is not a ${want2.name} wash${m[2] ? "" : ` over ${hex(under)}`}`, c.el, c.r);
|
|
1663
|
+
} else if (base$(nameOf$(m[3]) ?? "") !== want2.name) add(groups.rules, `${m[1]} fill ${m[3]}: ${m[1]} takes ${want2.name}`, c.el, c.r);
|
|
1664
|
+
}
|
|
1665
|
+
const focusW = want("focus");
|
|
1666
|
+
if (focusW.level) {
|
|
1667
|
+
const within = sheets.plain.filter((x) => /:focus-within/.test(x.sel)).map((x) => x.sel.replace(/:focus-within/g, ""));
|
|
1668
|
+
for (const c of controls) {
|
|
1669
|
+
const real = forced.get(c.el), ring = real && rings.get(real);
|
|
1670
|
+
if (!real?.has("focus") || !ring?.focus || turnedOff(c.el) || c.el.closest("[inert]") || c.el.getAttribute("tabindex") === "-1") continue;
|
|
1671
|
+
if (JSON.stringify(real.get("focus")) !== JSON.stringify(real.get("rest")) || ring.focus !== ring.rest) continue;
|
|
1672
|
+
let unsure = false;
|
|
1673
|
+
for (let a = c.el.parentElement; a && !unsure; a = a.parentElement) unsure = within.some((s) => {
|
|
1674
|
+
try {
|
|
1675
|
+
return a.matches(s);
|
|
1676
|
+
} catch {
|
|
1677
|
+
return false;
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
hit(`focus|${part(c.el)?.key ?? c.el.tagName.toLowerCase()}`, c.el, c.r, { unsure });
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const targetW = want("target");
|
|
1684
|
+
if (targetW.level) {
|
|
1685
|
+
const min = targetW.n ?? 24;
|
|
1686
|
+
const boxes = controls.filter((c) => !c.el.closest("[inert]")).map((c) => ({ el: c.el, r: c.el.getBoundingClientRect() }));
|
|
1687
|
+
const exempt = (el) => {
|
|
1688
|
+
if (turnedOff(el)) return true;
|
|
1689
|
+
const cs = getComputedStyle(el);
|
|
1690
|
+
if (el.matches("a[href]") && cs.display === "inline" && [...el.parentElement?.childNodes ?? []].some((n) => n !== el && n.nodeType === 3 && n.textContent?.trim())) return true;
|
|
1691
|
+
return el instanceof HTMLInputElement && /^(checkbox|radio|range|color|file|date|time)$/.test(el.type) && cs.appearance !== "none";
|
|
1692
|
+
};
|
|
1693
|
+
const small = boxes.filter((b) => (b.r.width < min - 0.5 || b.r.height < min - 0.5) && !exempt(b.el)).map((b) => {
|
|
1694
|
+
let r = b.r;
|
|
1695
|
+
for (const l of b.el.labels ?? []) {
|
|
1696
|
+
const lr = l.getBoundingClientRect();
|
|
1697
|
+
const L = Math.min(r.left, lr.left), T = Math.min(r.top, lr.top);
|
|
1698
|
+
r = new DOMRect(L, T, Math.max(r.right, lr.right) - L, Math.max(r.bottom, lr.bottom) - T);
|
|
1699
|
+
}
|
|
1700
|
+
return { ...b, hit: r };
|
|
1701
|
+
}).filter((b) => b.hit.width < min - 0.5 || b.hit.height < min - 0.5);
|
|
1702
|
+
const mid = (r) => [r.left + r.width / 2, r.top + r.height / 2];
|
|
1703
|
+
const reaches = (x, y, r) => Math.hypot(x - Math.max(r.left, Math.min(x, r.right)), y - Math.max(r.top, Math.min(y, r.bottom))) < min / 2;
|
|
1704
|
+
for (const s of small) {
|
|
1705
|
+
const [x, y] = mid(s.hit);
|
|
1706
|
+
const near = boxes.some((o) => o.el !== s.el && !o.el.contains(s.el) && !s.el.contains(o.el) && reaches(x, y, o.r)) || small.some((o) => o !== s && !o.el.contains(s.el) && !s.el.contains(o.el) && Math.hypot(x - mid(o.hit)[0], y - mid(o.hit)[1]) < min);
|
|
1707
|
+
if (near) hit(`target|${part(s.el)?.key ?? s.el.getAttribute("role") ?? s.el.tagName.toLowerCase()}|${Math.round(s.hit.width)}x${Math.round(s.hit.height)}`, s.el, s.r);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
const closed = [];
|
|
1711
|
+
if (uses.length || shut.size) {
|
|
1712
|
+
const bare = host.appendChild(document.createElement("div")), bcs = getComputedStyle(bare);
|
|
1713
|
+
const base = new Map(LOOK.map((k) => [k, bcs.getPropertyValue(k)]));
|
|
1714
|
+
for (const g of ghosts.values()) {
|
|
1715
|
+
const gcs = getComputedStyle(g.el);
|
|
1716
|
+
for (const k of LOOK) {
|
|
1717
|
+
const v = gcs.getPropertyValue(k);
|
|
1718
|
+
if (v !== base.get(k)) g.sets.set(k, v);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
for (const u of uses) {
|
|
1722
|
+
const cs = getComputedStyle(u.el), set = new Set(u.style), hover = /* @__PURE__ */ new Set();
|
|
1723
|
+
for (const t of u.cls) {
|
|
1724
|
+
const g = ghosts.get(t);
|
|
1725
|
+
if (!g) continue;
|
|
1726
|
+
for (const [k, v] of g.sets) if (cs.getPropertyValue(k) === v) set.add(k);
|
|
1727
|
+
for (const s of statesOf(g.el)) hover.add(s);
|
|
1728
|
+
}
|
|
1729
|
+
const items2 = [];
|
|
1730
|
+
for (const k of LOOK) {
|
|
1731
|
+
if (!set.has(k)) continue;
|
|
1732
|
+
const v = cs.getPropertyValue(k), c = /color$/.test(k) ? colorOf(v) : null;
|
|
1733
|
+
if (k === "height" || k === "min-height") {
|
|
1734
|
+
if (u.ctl) items2.push(`${k === "height" ? "height" : "min height"} ${num(parseFloat(v))}px`);
|
|
1735
|
+
} else if (k === "border-top-left-radius") items2.push(`corner ${sayCorner(cornerOf(cs))}`);
|
|
1736
|
+
else if (k === "background-color") items2.push(`fill ${c ?? "none"}`);
|
|
1737
|
+
else if (k === "color") {
|
|
1738
|
+
if (c) items2.push(`text ${c}`);
|
|
1739
|
+
} else if (k === "border-top-color") {
|
|
1740
|
+
if (c && parseFloat(cs.borderTopWidth) > 0 && c !== colorOf(cs.color)) items2.push(`line ${c}`);
|
|
1741
|
+
} else if (k === "font-size" || k === "line-height") items2.push(`${k === "font-size" ? "size" : "line height"} ${num(parseFloat(v))}px`);
|
|
1742
|
+
else if (k === "font-weight") items2.push(`weight ${v}`);
|
|
1743
|
+
else if (k === "letter-spacing") items2.push(`tracking ${num(parseFloat(v) || 0)}px`);
|
|
1744
|
+
else if (k === "font-family") items2.push(`font ${v.replace(/["']/g, "").split(",")[0]}\u2026`);
|
|
1745
|
+
else if (k === "box-shadow") {
|
|
1746
|
+
const s = shadowLayers(v);
|
|
1747
|
+
items2.push(`shadow ${s.rest.length ? sayShadow(s.rest.join(", ")) : s.rings.length ? s.rings.join(", ") : "none"}`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const mine = statesOf(u.el);
|
|
1751
|
+
items2.push(...[...hover].filter((s) => mine.includes(s)));
|
|
1752
|
+
if (!items2.length || items2.every((i) => i.startsWith("text "))) continue;
|
|
1753
|
+
add(groups.used, `${u.key}: ${items2.join("; ")}`, u.el, u.r);
|
|
1754
|
+
}
|
|
1755
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1756
|
+
for (const x of shut.values()) {
|
|
1757
|
+
const cs = getComputedStyle(x.el), fill = colorOf(cs.backgroundColor), sh = shadowLayers(cs.boxShadow), line = lineOf(cs), say = x.props ? `${x.name} ${x.props}` : x.name;
|
|
1758
|
+
let row = "";
|
|
1759
|
+
if (fill && (sh.rest.length || cornerOf(cs) !== "0" || line)) row = `${say}: corner ${sayCorner(cornerOf(cs))} fill ${fill}${line ? ` line ${line}` : ""} shadow ${sh.rest.length ? sayShadow(sh.rest.join(", ")) : "none"}`;
|
|
1760
|
+
else if (x.props && (!rules.parts?.length || rules.parts.includes(x.name))) {
|
|
1761
|
+
const set = /* @__PURE__ */ new Map(), hover = /* @__PURE__ */ new Set();
|
|
1762
|
+
for (const t of x.cls.split(/\s+/)) {
|
|
1763
|
+
const g = ghosts.get(t);
|
|
1764
|
+
if (!g) continue;
|
|
1765
|
+
for (const [k, v] of g.sets) set.set(k, v);
|
|
1766
|
+
for (const st of statesOf(g.el)) hover.add(st);
|
|
1767
|
+
}
|
|
1768
|
+
const items2 = [set.has("height") ? `height ${num(parseFloat(set.get("height")))}px` : "", set.has("border-top-left-radius") ? `corner ${sayCorner(cornerOf(cs))}` : "", fill ? `fill ${fill}` : "", ...hover].filter((i) => i && !/NaN/.test(i));
|
|
1769
|
+
if (items2.length) row = `${say}: set where used: ${items2.join("; ")}`;
|
|
1770
|
+
}
|
|
1771
|
+
if (row) rows.set(row, (rows.get(row) ?? 0) + x.n);
|
|
1772
|
+
}
|
|
1773
|
+
for (const [k, n] of [...rows].slice(0, 30)) closed.push({ key: sayColors(k), n, at: [] });
|
|
1774
|
+
}
|
|
1775
|
+
const SHORT = { "border-top-left-radius": ["border-radius"], "background-color": ["background"], "font-family": ["font"], "line-height": ["font"] };
|
|
1776
|
+
const PASS = /^(inherit|unset|revert|revert-layer)$/;
|
|
1777
|
+
const declared = (style, prop) => style.getPropertyValue(prop).trim() || (SHORT[prop] ?? []).map((k) => style.getPropertyValue(k).trim()).find((v) => v.includes("var(")) || "";
|
|
1778
|
+
const declaring = /* @__PURE__ */ new Map();
|
|
1779
|
+
const whence = (el, prop, inherited) => {
|
|
1780
|
+
let list = declaring.get(prop);
|
|
1781
|
+
if (!list) declaring.set(prop, list = sheets.plain.filter((x) => declared(x.style, prop)));
|
|
1782
|
+
for (let e = el, i = 0; e && i < (inherited ? 16 : 1); e = e.parentElement, i++) {
|
|
1783
|
+
const own = e.style ? declared(e.style, prop) : "";
|
|
1784
|
+
if (own && !PASS.test(own)) return " (from its style attribute)";
|
|
1785
|
+
const hits2 = /* @__PURE__ */ new Map(), pass = [];
|
|
1786
|
+
for (const x of list) {
|
|
1787
|
+
try {
|
|
1788
|
+
if (e.matches(x.sel)) {
|
|
1789
|
+
if (PASS.test(declared(x.style, prop))) pass.push(x);
|
|
1790
|
+
else hits2.set(x.style, x);
|
|
1791
|
+
}
|
|
1792
|
+
} catch {
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (hits2.size > 1) return "";
|
|
1796
|
+
for (const [style, x] of hits2) {
|
|
1797
|
+
const imp = (r) => r.style.getPropertyPriority(prop) === "important";
|
|
1798
|
+
if (own || pass.some((y) => sheets.beats(y, imp(y), x, imp(x)))) break;
|
|
1799
|
+
const text = declared(style, prop), whole = text.match(/^var\((--[\w-]+)\)$/), vars = style.getPropertyValue(prop) ? [] : [...text.matchAll(/var\((--[\w-]+)/g)];
|
|
1800
|
+
const v = whole?.[1] ?? (vars.length === 1 ? vars[0][1] : "");
|
|
1801
|
+
return ` (from ${x.sel.length > 40 ? `${x.sel.slice(0, 39)}\u2026` : x.sel}, ${v ? `via var(${v})` : "written out"})`;
|
|
1802
|
+
}
|
|
1803
|
+
if (!inherited || !e.parentElement || getComputedStyle(e).getPropertyValue(prop) !== getComputedStyle(e.parentElement).getPropertyValue(prop)) return "";
|
|
1804
|
+
}
|
|
1805
|
+
return "";
|
|
1806
|
+
};
|
|
1807
|
+
const typeNote = (k) => !rules.type?.length || rules.type.includes(k.split(" ")[0]) ? "" : " OFF";
|
|
1808
|
+
const radiusNote = (k) => {
|
|
1809
|
+
if (!rules.radii?.length) return "";
|
|
1810
|
+
const off = k.split(" ").map(parseFloat).filter((v) => v > 0 && !rules.radii.includes(v));
|
|
1811
|
+
return off.length ? ` OFF (nearest ${rules.radii.reduce((a, b) => Math.abs(b - off[0]) < Math.abs(a - off[0]) ? b : a)}px)` : "";
|
|
1812
|
+
};
|
|
1813
|
+
const nearOf = (g, k, list) => {
|
|
1814
|
+
const me = list.find((x) => x[0] === k);
|
|
1815
|
+
for (const [o, s] of list) {
|
|
1816
|
+
if (o === k || s.n < me[1].n) continue;
|
|
1817
|
+
if (g === "text" || g === "fill" || g === "line") {
|
|
1818
|
+
const a = parseColor(k.split(" ")[0]), b = parseColor(o.split(" ")[0]);
|
|
1819
|
+
if (a && b && dist(a, b) > 0 && dist(a, b) <= 12 && k.split(" ").slice(1).join(" ") === o.split(" ").slice(1).join(" ")) return ` near ${sayColors(o.split(" ")[0])}`;
|
|
1820
|
+
} else if (g === "radius" && !/full| /.test(k + o) && Math.abs(parseFloat(k) - parseFloat(o)) <= 1.5) return ` near ${o}`;
|
|
1821
|
+
else if (g === "type") {
|
|
1822
|
+
const [a, b] = [k.split(/[/ ]/), o.split(/[/ ]/)];
|
|
1823
|
+
if (a[0] !== b[0] && Math.abs(parseFloat(a[0]) - parseFloat(b[0])) <= 1 && a[1] === b[1]) return ` near ${o.split(" ")[0]}`;
|
|
1824
|
+
if (a[0] === b[0] && a[1] === b[1] && a[2] !== b[2]) return ` near ${o.split(" ")[0]} (line height)`;
|
|
1825
|
+
} else if (g === "height") {
|
|
1826
|
+
const [ka, kh] = [k.replace(/ \d+px.*$/, ""), parseFloat(k.match(/(\d+)px/)[1])], [oa, oh] = [o.replace(/ \d+px.*$/, ""), parseFloat(o.match(/(\d+)px/)[1])];
|
|
1827
|
+
if (ka === oa && kh !== oh && Math.abs(kh - oh) <= 4 && [...me[1].parts ?? []].some((x) => s.parts?.has(x))) return ` near ${oh}px (same part)`;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return "";
|
|
1831
|
+
};
|
|
1832
|
+
const WHENCE = { text: ["color", true], fill: ["background-color", false], radius: ["border-top-left-radius", false] };
|
|
1833
|
+
const made = [];
|
|
1834
|
+
for (const [g, map] of Object.entries(groups)) {
|
|
1835
|
+
const list = [...map].sort((a, b) => b[1].n - a[1].n);
|
|
1836
|
+
if (!list.length && !(g === "align" && groups.type.size)) continue;
|
|
1837
|
+
const rows = list.map(([k, s]) => {
|
|
1838
|
+
let key = k, note = "";
|
|
1839
|
+
if (g === "font") {
|
|
1840
|
+
key = k === list[0][0] ? k : fontDiff(k, list[0][0]);
|
|
1841
|
+
note = whence(s.el, "font-family", true);
|
|
1842
|
+
} else if (g === "type") note = typeNote(k);
|
|
1843
|
+
else if (g === "radius") note = radiusNote(k);
|
|
1844
|
+
else if (g === "shadow") key = sayShadow(k);
|
|
1845
|
+
else if (g === "layer") key = `${sayColors(k.slice(0, k.indexOf(" shadow ")))} shadow ${sayShadow(k.slice(k.indexOf(" shadow ") + 8))}`;
|
|
1846
|
+
else if (g === "text" || g === "fill" || g === "line") {
|
|
1847
|
+
const [h, ...rest] = k.split(" "), t = tokenOf(h), near = nearOf(g, k, list);
|
|
1848
|
+
key = [t.name && near !== ` near ${t.name}` ? t.name : h, ...rest].join(" ");
|
|
1849
|
+
note = t.off ?? "";
|
|
1850
|
+
} else key = sayColors(k);
|
|
1851
|
+
note += nearOf(g, k, list);
|
|
1852
|
+
if (note && WHENCE[g] && !/\(icon\)/.test(k)) note += whence(s.el, ...WHENCE[g]);
|
|
1853
|
+
else if (g === "type" && / \(line height\)$/.test(note)) note += whence(s.el, "line-height", true);
|
|
1854
|
+
const at = rules.sources && (g === "used" || g === "rules" || / OFF| near /.test(`${key}${note}`)) ? s.at.map((a, i) => {
|
|
1855
|
+
const e = s.els?.[i];
|
|
1856
|
+
return e ? `${a}${srcOf(part(e) ?? ownerOf(e)) || hostSrc(e)}` : a;
|
|
1857
|
+
}) : s.at;
|
|
1858
|
+
return { key, n: s.n, note: note || void 0, at };
|
|
1859
|
+
});
|
|
1860
|
+
if (g === "align") rows.push({ key: "every other text at the start of its box", n: 0, at: [] });
|
|
1861
|
+
if (g === "type") {
|
|
1862
|
+
for (const [size, per] of tracking) {
|
|
1863
|
+
const signs = [...per].sort((a, b) => b[1].n - a[1].n);
|
|
1864
|
+
const odd = signs.find(([ls]) => ls * signs[0][0] < 0);
|
|
1865
|
+
if (odd) rows.push({ key: `tracking at ${size}px: ${signs.map(([ls, s]) => `${ls > 0 ? "+" : ""}${ls} \xD7${s.n}`).join(", ")}`, n: 0, note: whence(odd[1].el, "letter-spacing", true) || void 0, at: odd[1].at });
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
const WHAT = { used: "what a part's use site changed about its look with a class or style of its own", layer: "pop-ups and their boxes", layout: "rows, columns, grids and filled boxes: the gaps between their children as drawn, their padding, how the children sit", align: "words set other than to the start of their box", rules: "the rules file's roles, text, states, pairs and legacy, each line one rule broken" };
|
|
1869
|
+
made.push({ name: g === "used" ? "set where used" : g === "rules" ? "rules broken" : g, what: WHAT[g], rows });
|
|
1870
|
+
}
|
|
1871
|
+
const partRows = [...looks].map(([key, l]) => ({ key, n: [...l.m.values()].reduce((n, x) => n + x.n, 0), at: [], flag: l.flag, looks: [...l.m.values()].map((x) => ({ ...x, skin: sayColors(x.skin), say: sayColors(x.say) })) }));
|
|
1872
|
+
partRows.sort((a, b) => looksOf(b) - looksOf(a) || b.n - a.n);
|
|
1873
|
+
if (closed.length) made.push({ name: "closed", what: "would look; not on screen: pop-ups and their contents the page holds but hasn't opened", rows: closed });
|
|
1874
|
+
if (partRows.length) made.push({ name: "parts", what: "by the component that drew them; looks are height (controls), corner, fill and text color, by state", rows: partRows });
|
|
1875
|
+
const findings = [];
|
|
1876
|
+
const push = (f, level) => {
|
|
1877
|
+
if (level) findings.push({ ...f, level });
|
|
1878
|
+
};
|
|
1879
|
+
const plain = (note) => (note ?? "").replace(/ \((from|via) .*$/, "");
|
|
1880
|
+
const offW = want("off");
|
|
1881
|
+
for (const g of made) {
|
|
1882
|
+
if (/^(parts|closed|left out|layout problems)$/.test(g.name)) continue;
|
|
1883
|
+
for (const r of g.rows) {
|
|
1884
|
+
const said = `${r.key}${r.note ?? ""}`;
|
|
1885
|
+
if (/ OFF/.test(said)) push({ id: `off|${g.name}|${r.key}`, check: "off", key: `${g.name} ${r.key}${plain(r.note)}`, n: r.n, at: r.at.slice(0, 1) }, offW.level);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
for (const r of partRows) if (/\(no DS part\)/.test(r.key)) push({ id: `no-part|${r.key}`, check: "no-part", key: r.key, n: r.n, at: (r.looks ?? []).flatMap((l) => l.at).slice(0, 2) }, want("no-part").level);
|
|
1889
|
+
for (const [id, h] of hits) {
|
|
1890
|
+
const [check, a, b] = id.split("|");
|
|
1891
|
+
if (check === "contrast") {
|
|
1892
|
+
const team = W.contrast.n, floor = team !== void 0 ? h.big ? Math.min(3, team) : team : h.big ? 3 : 4.5, wcag = h.big ? 3 : 4.5;
|
|
1893
|
+
const crit = team !== void 0 && team < wcag ? ` (team floor; WCAG 1.4.3 asks ${wcag})` : " (WCAG 1.4.3)";
|
|
1894
|
+
const nm = (x) => tokenOf(x).name ?? x;
|
|
1895
|
+
push({ id, check, key: `contrast ${h.min.toFixed(2)}:1 ${h.what}${nm(h.fg)} on ${nm(h.bg)}${h.big ? " (large text)" : ""} below ${floor}${crit}`, n: h.n, at: h.at, unsure: h.unsure, values: h.names }, W.contrast.level);
|
|
1896
|
+
} else if (check === "focus") push({ id, check, key: `focus not visible (2.4.7): ${a} changes nothing focused`, n: h.n, at: h.at, unsure: h.unsure, values: h.names }, focusW.level);
|
|
1897
|
+
else if (check === "target") push({ id, check, key: `target ${b} under ${targetW.n ?? 24}px, too close to another (2.5.8): ${a}`, n: h.n, at: h.at, values: h.names }, targetW.level);
|
|
1898
|
+
}
|
|
1899
|
+
findings.push(...maxFindings(made, rules));
|
|
1900
|
+
const give = shares(made.map((g) => g.rows.length), cap2);
|
|
1901
|
+
made.forEach((g, i) => {
|
|
1902
|
+
if (g.rows.length > give[i]) {
|
|
1903
|
+
g.more = g.rows.length - give[i];
|
|
1904
|
+
g.rows = g.rows.slice(0, give[i]);
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
const left = [];
|
|
1908
|
+
if (placeholders.size) left.push({ key: "loading placeholders (empty boxes that animate forever)", n: [...placeholders.values()].reduce((a, b) => a + b, 0), at: [[...placeholders].map(([f, n]) => `fill ${sayColors(f)} \xD7${n}`).join(", ")] });
|
|
1909
|
+
if (moving) left.push({ key: "mid-animation when read (a flash or a fade: its fill and look aren't counted)", n: moving, at: [] });
|
|
1910
|
+
if (hot) left.push({ key: "under the pointer, pressed or focused when read (it wears that state's colors: its fill, line and look aren't counted)", n: hot, at: [] });
|
|
1911
|
+
if (cut) left.push({ key: "closed pop-ups: the scan stopped early, the closed list is incomplete", n: 0, at: [] });
|
|
1912
|
+
if (embedded.size) left.push({ key: "not the app's components (drawn by another script: a dev tool, an embedded widget)", n: [...embedded.values()].reduce((a, b) => a + b, 0), at: [...embedded.keys()].slice(0, 4).map((e) => {
|
|
1913
|
+
const b = e.matches("button,a[href]") ? e : e.querySelector("button,[role=button],a[href]") ?? e;
|
|
1914
|
+
return `${nameOf(b)} ${box(b.getBoundingClientRect())}`;
|
|
1915
|
+
}) });
|
|
1916
|
+
if (sheets.unreadable) left.push({ key: `${sheets.unreadable} sheets unreadable (another site's: their hover and pressed colors aren't in states)`, n: 0, at: [] });
|
|
1917
|
+
if (left.length) made.push({ name: "left out", what: "", rows: left });
|
|
1918
|
+
const gone = [...embedded.keys(), ...out];
|
|
1919
|
+
const problems = layoutLines(root, 160, gone.length ? (el) => gone.some((x) => x.contains(el)) : void 0, rules.contrast ?? FAINT_TEXT).filter((l) => PROBLEM.test(l));
|
|
1920
|
+
if (problems.length) made.push({ name: "layout problems", what: "", rows: problems.slice(0, 30).map((l) => ({ key: l, n: 0, at: [] })), more: Math.max(0, problems.length - 30) || void 0 });
|
|
1921
|
+
let items;
|
|
1922
|
+
if (snap) {
|
|
1923
|
+
items = [];
|
|
1924
|
+
for (const [name, it] of snap) {
|
|
1925
|
+
const p = part(name), st = stateOf(name), look = {};
|
|
1926
|
+
for (const [g, ks] of it.look) look[g === "used" ? "set where used" : g === "rules" ? "rules broken" : g] = ks.map((k) => g === "shadow" ? sayShadow(k) : sayColors(k)).join("; ");
|
|
1927
|
+
if (st) look.state = st;
|
|
1928
|
+
const tag = name.getAttribute("role") ?? name.tagName.toLowerCase();
|
|
1929
|
+
items.push({ id: `${nameOf(name)}${p ? ` [${p.say}]` : ""}${where(it.el)}`, loose: `${tag}${p ? ` [${p.key}]` : ""}${where(it.el)}`, at: place(it.el, it.r, name), box: [it.r.left, it.r.top, it.r.width, it.r.height].map(Math.round), look });
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
const report = asksChecks(rules);
|
|
1933
|
+
const against = rules.colors || rules.type || rules.radii ? ", checked against the design system's values" : checkSetting(rules, "max").level ? ", against best practice" : "";
|
|
1934
|
+
return { head: `styles on screen, window ${innerWidth}x${innerHeight}${against}`, groups: made, items, findings, ...report ? { report } : {} };
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/diff.ts
|
|
1938
|
+
var BOX = /-?\d+,-?\d+ \d+x\d+/g;
|
|
1939
|
+
var LOOK_ORDER = ["state", "height", "field", "layout", "radius", "fill", "line", "text", "align", "type", "font", "shadow", "states", "set where used", "layer"];
|
|
1940
|
+
function one(a, b) {
|
|
1941
|
+
const x = a.split(" "), y = b.split(" ");
|
|
1942
|
+
let i = 0;
|
|
1943
|
+
while (i < x.length - 1 && i < y.length - 1 && x[i] === y[i]) i++;
|
|
1944
|
+
return `${x.slice(0, i).join(" ")}${i ? " " : ""}${x.slice(i).join(" ")}\u2192${y.slice(i).join(" ")}`;
|
|
1945
|
+
}
|
|
1946
|
+
function change(a, b) {
|
|
1947
|
+
if (a === void 0) return `+${b}`;
|
|
1948
|
+
if (b === void 0) return `-${a}`;
|
|
1949
|
+
const x = a.split("; "), y = b.split("; ");
|
|
1950
|
+
if (x.length === 1 && y.length === 1) return one(a, b);
|
|
1951
|
+
const come = y.filter((v) => !x.includes(v)), out = [];
|
|
1952
|
+
const head = (v) => v.split(" ").slice(0, -1).join(" ");
|
|
1953
|
+
for (const g of x.filter((v) => !y.includes(v))) {
|
|
1954
|
+
const j = come.findIndex((c) => head(c) === head(g) && head(g) !== "");
|
|
1955
|
+
if (j < 0) {
|
|
1956
|
+
out.push(`-${g}`);
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
out.push(one(g, come[j]));
|
|
1960
|
+
come.splice(j, 1);
|
|
1961
|
+
}
|
|
1962
|
+
return [...out, ...come.map((c) => `+${c}`)].join(", ");
|
|
1963
|
+
}
|
|
1964
|
+
var words = (x) => x.at.match(/^[^"[]*"((?:[^"\\]|\\.)*)"/)?.[1];
|
|
1965
|
+
function pairUp(a, b) {
|
|
1966
|
+
const pairs = [];
|
|
1967
|
+
let left = [...a], right = [...b];
|
|
1968
|
+
for (const key of ["id", "loose"]) {
|
|
1969
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1970
|
+
for (const x of right) byKey.set(x[key], [...byKey.get(x[key]) ?? [], x]);
|
|
1971
|
+
const rest = [];
|
|
1972
|
+
for (const x of left) {
|
|
1973
|
+
const list = byKey.get(x[key]);
|
|
1974
|
+
if (!list?.length) {
|
|
1975
|
+
rest.push(x);
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
let best = 0, d = Infinity;
|
|
1979
|
+
list.forEach((y, i) => {
|
|
1980
|
+
const e = Math.abs(y.box[0] - x.box[0]) + Math.abs(y.box[1] - x.box[1]);
|
|
1981
|
+
if (e < d) {
|
|
1982
|
+
d = e;
|
|
1983
|
+
best = i;
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
pairs.push([x, list.splice(best, 1)[0]]);
|
|
1987
|
+
}
|
|
1988
|
+
left = rest;
|
|
1989
|
+
right = [...byKey.values()].flat();
|
|
1990
|
+
}
|
|
1991
|
+
return { pairs, gone: left, added: right };
|
|
1992
|
+
}
|
|
1993
|
+
var differs = ([x, y], skip) => words(x) !== words(y) || LOOK_ORDER.some((k) => k !== skip && x.look[k] !== y.look[k]) || Math.abs(x.box[2] - y.box[2]) > 2 || Math.abs(x.box[3] - y.box[3]) > 2;
|
|
1994
|
+
function changesOnItsOwn(a, b) {
|
|
1995
|
+
const q = pairUp(a, b);
|
|
1996
|
+
return [...q.gone, ...q.added, ...q.pairs.filter((p) => differs(p, "states")).flatMap(([x, y]) => [x, y])];
|
|
1997
|
+
}
|
|
1998
|
+
var CONTROL_AT = /^(button|a|link|tab|combobox|checkbox|radio|switch|menuitem|option|input|select|textarea|textbox|searchbox|slider)\b/;
|
|
1999
|
+
var regionOf = (x) => x.at.match(/\] in ([a-z][\w -]*?) -?\d+,-?\d+ \d+x\d+$| in ([a-z][\w -]*?) -?\d+,-?\d+ \d+x\d+$/)?.slice(1).find(Boolean) ?? "";
|
|
2000
|
+
var nameAt = (x) => x.at.replace(/ \[[^\]]*\]/g, "").replace(/ in [a-z][\w -]*? (?=-?\d+,)/, " ").replace(/ -?\d+,-?\d+ \d+x\d+$/, "");
|
|
2001
|
+
var way = (dx, dy) => [dx ? `${Math.abs(dx)}px ${dx < 0 ? "left" : "right"}` : "", dy ? `${Math.abs(dy)}px ${dy < 0 ? "up" : "down"}` : ""].filter(Boolean).join(" and ");
|
|
2002
|
+
var lookText = (x) => LOOK_ORDER.filter((k) => x.look[k]).map((k) => `${k} ${x.look[k]}`).join("; ");
|
|
2003
|
+
var cap = (lines, n) => lines.length > n ? [...lines.slice(0, n), ` ... ${lines.length - n} more`] : lines;
|
|
2004
|
+
function diffCensus(before, after, max = 40, live) {
|
|
2005
|
+
const out = [];
|
|
2006
|
+
const a = before.items ?? [], b = after.items ?? [];
|
|
2007
|
+
const all = pairUp(a, b);
|
|
2008
|
+
const hot = [];
|
|
2009
|
+
if (live?.items) hot.push(...changesOnItsOwn(a, live.items));
|
|
2010
|
+
if (before.again) hot.push(...changesOnItsOwn(a, before.again.items ?? []));
|
|
2011
|
+
if (after.again) hot.push(...changesOnItsOwn(b, after.again.items ?? []));
|
|
2012
|
+
const watched = !!(live?.items || before.again || after.again);
|
|
2013
|
+
const isHot = (x) => hot.some((h) => h.loose === x.loose && Math.abs(h.box[1] - x.box[1]) <= 4 && h.box[0] < x.box[0] + x.box[2] + 4 && x.box[0] < h.box[0] + h.box[2] + 4);
|
|
2014
|
+
let skipped = 0;
|
|
2015
|
+
const keep = (x) => hot.length && isHot(x) ? (skipped++, false) : true;
|
|
2016
|
+
const pairs = all.pairs.filter((p) => !differs(p) || keep(p[1]));
|
|
2017
|
+
const gone = all.gone.filter(keep), added = all.added.filter(keep);
|
|
2018
|
+
const changed = [];
|
|
2019
|
+
const moves = /* @__PURE__ */ new Map();
|
|
2020
|
+
let moved = 0;
|
|
2021
|
+
for (const [x, y] of pairs) {
|
|
2022
|
+
const keys = LOOK_ORDER.filter((k) => x.look[k] !== y.look[k]);
|
|
2023
|
+
const sized = x.id === y.id && !!(y.look.height || y.look.text) && !keys.includes("height") && (Math.abs(x.box[2] - y.box[2]) > 2 || Math.abs(x.box[3] - y.box[3]) > 2);
|
|
2024
|
+
const said = words(x) !== words(y) ? [`words "${words(x) ?? ""}"\u2192"${words(y) ?? ""}"`] : [];
|
|
2025
|
+
if (said.length || keys.length || sized) changed.push(` ${y.at}: ${[...said, ...keys.map((k) => `${k} ${change(x.look[k], y.look[k])}`), ...sized ? [`size ${x.box[2]}x${x.box[3]}\u2192${y.box[2]}x${y.box[3]}`] : []].join("; ")}`);
|
|
2026
|
+
else if (Math.abs(x.box[0] - y.box[0]) > 2 || Math.abs(x.box[1] - y.box[1]) > 2) {
|
|
2027
|
+
if (!keep(y)) continue;
|
|
2028
|
+
moved++;
|
|
2029
|
+
const region = regionOf(y), dx = y.box[0] - x.box[0], dy = y.box[1] - x.box[1], k = `${region}|${dx}|${dy}`;
|
|
2030
|
+
const m = moves.get(k) ?? { region, dx, dy, els: [] };
|
|
2031
|
+
m.els.push(y);
|
|
2032
|
+
moves.set(k, m);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
const moveLines = [...moves.values()].sort((p, q) => q.els.length - p.els.length || Math.abs(q.dx) + Math.abs(q.dy) - Math.abs(p.dx) - Math.abs(p.dy)).map((m) => {
|
|
2036
|
+
const x0 = Math.min(...m.els.map((e) => e.box[0])), y0 = Math.min(...m.els.map((e) => e.box[1])), x1 = Math.max(...m.els.map((e) => e.box[0] + e.box[2])), y1 = Math.max(...m.els.map((e) => e.box[1] + e.box[3]));
|
|
2037
|
+
const named2 = [...m.els].sort((p, q) => Number(!words(p)) - Number(!words(q))).slice(0, 4).map(nameAt);
|
|
2038
|
+
return ` ${m.region ? `in ${m.region}, ` : ""}${m.els.length} moved ${way(m.dx, m.dy)} (area ${x0},${y0} ${x1 - x0}x${y1 - y0}): ${named2.join("; ")}${m.els.length > 4 ? "; \u2026" : ""}`;
|
|
2039
|
+
});
|
|
2040
|
+
if (!before.items || !after.items) out.push("(a census without items: take both with the capture, or rules.items, to compare elements)");
|
|
2041
|
+
const lost = gone.filter((x) => CONTROL_AT.test(x.at)).length;
|
|
2042
|
+
out.push(`census diff: ${changed.length} changed, ${added.length} new, ${gone.length} gone${lost ? ` (${lost} of them controls, marked !)` : ""}, ${moved} only moved${watched ? `; ${skipped} live (they change on their own, not listed)` : ""}`);
|
|
2043
|
+
if (changed.length) out.push(`changed (${changed.length})`, ...cap(changed, max));
|
|
2044
|
+
if (added.length) out.push(`new (${added.length})`, ...cap(added.map((y) => ` ${y.at}: ${lookText(y)}`), max));
|
|
2045
|
+
if (gone.length) out.push(`gone (${gone.length})`, ...cap(gone.map((x) => ` ${CONTROL_AT.test(x.at) ? "! " : ""}${x.at}: ${lookText(x)}`).sort((p, q) => Number(q.startsWith(" !")) - Number(p.startsWith(" !"))), max));
|
|
2046
|
+
if (moveLines.length) out.push(`moved (${moved}, in ${moveLines.length} groups)`, ...cap(moveLines, Math.min(max, 12)));
|
|
2047
|
+
const rows = (c) => {
|
|
2048
|
+
const m = /* @__PURE__ */ new Map();
|
|
2049
|
+
for (const g of c.groups) {
|
|
2050
|
+
if (g.name === "parts" || g.name === "layout problems" || g.name === "left out") continue;
|
|
2051
|
+
for (const r of g.rows) m.set(`${g.name} ${r.key}`, `${g.name} ${r.key}${r.note ? r.note.replace(/ \((?:from|via) (?:[^()]|\([^()]*\))*\)/g, "") : ""} \xD7${r.n}${r.at[0] ? `: ${r.at[0]}` : ""}`);
|
|
2052
|
+
}
|
|
2053
|
+
return m;
|
|
2054
|
+
};
|
|
2055
|
+
const ra = rows(before), rb = rows(after);
|
|
2056
|
+
const nv = [...rb].filter(([k]) => !ra.has(k)).map(([, v]) => v).sort((p, q) => Number(/ OFF/.test(q)) - Number(/ OFF/.test(p)));
|
|
2057
|
+
const gv = [...ra].filter(([k]) => !rb.has(k)).map(([, v]) => v);
|
|
2058
|
+
if (nv.length) out.push(`values new on the page (${nv.length})`, ...cap(nv.map((v) => ` ${v}`), max));
|
|
2059
|
+
if (gv.length) out.push(`values no longer on the page (${gv.length})`, ...cap(gv.map((v) => ` ${v}`), max));
|
|
2060
|
+
const looks = (c) => {
|
|
2061
|
+
const m = /* @__PURE__ */ new Map();
|
|
2062
|
+
for (const r of c.groups.find((g) => g.name === "parts")?.rows ?? []) m.set(r.key, new Set((r.looks ?? []).map((l) => `${l.state ? `${l.state}: ` : ""}${l.say}`)));
|
|
2063
|
+
return m;
|
|
2064
|
+
};
|
|
2065
|
+
const la = looks(before), lb = looks(after), pl = [];
|
|
2066
|
+
for (const [k, s] of lb) {
|
|
2067
|
+
const was = la.get(k);
|
|
2068
|
+
for (const l of s) if (was && !was.has(l)) pl.push(` ${k}: new look ${l}`);
|
|
2069
|
+
}
|
|
2070
|
+
for (const [k, s] of la) {
|
|
2071
|
+
const now = lb.get(k);
|
|
2072
|
+
for (const l of s) if (now && !now.has(l)) pl.push(` ${k}: lost look ${l}`);
|
|
2073
|
+
}
|
|
2074
|
+
if (pl.length) out.push(`parts (${pl.length})`, ...cap(pl, max));
|
|
2075
|
+
const probs = (c) => new Map((c.groups.find((g) => g.name === "layout problems")?.rows ?? []).map((r) => [r.key.replace(BOX, "#"), r.key]));
|
|
2076
|
+
const pa = probs(before), pb = probs(after);
|
|
2077
|
+
const np = [...pb].filter(([k]) => !pa.has(k)).map(([, v]) => ` ${v}`), fp = [...pa].filter(([k]) => !pb.has(k)).map(([, v]) => ` ${v}`);
|
|
2078
|
+
if (np.length) out.push(`layout problems new (${np.length})`, ...cap(np, max));
|
|
2079
|
+
if (fp.length) out.push(`layout problems gone (${fp.length})`, ...cap(fp, max));
|
|
2080
|
+
if (before.report || after.report) out.push(...diffFindings(before, after, max).lines);
|
|
2081
|
+
return out;
|
|
2082
|
+
}
|
|
2083
|
+
export {
|
|
2084
|
+
CHECKS,
|
|
2085
|
+
MAX_DEFAULTS,
|
|
2086
|
+
PRESETS,
|
|
2087
|
+
asksChecks,
|
|
2088
|
+
behind,
|
|
2089
|
+
censusLines,
|
|
2090
|
+
changesOnItsOwn,
|
|
2091
|
+
checkSetting,
|
|
2092
|
+
contrast,
|
|
2093
|
+
diffCensus,
|
|
2094
|
+
diffFindings,
|
|
2095
|
+
findingLines,
|
|
2096
|
+
fontDiff,
|
|
2097
|
+
layoutLines,
|
|
2098
|
+
maxFindings,
|
|
2099
|
+
mergeCensus,
|
|
2100
|
+
mergeFindings,
|
|
2101
|
+
nameOf,
|
|
2102
|
+
over,
|
|
2103
|
+
ownId,
|
|
2104
|
+
parseColor,
|
|
2105
|
+
partOf,
|
|
2106
|
+
shadowLayers,
|
|
2107
|
+
specificity,
|
|
2108
|
+
stateOf,
|
|
2109
|
+
styleData,
|
|
2110
|
+
styleLines
|
|
2111
|
+
};
|