design-constraint-validator 1.0.0 → 1.1.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 -21
- package/README.md +215 -659
- package/adapters/README.md +46 -46
- package/adapters/css.ts +116 -116
- package/adapters/js.ts +14 -14
- package/adapters/json.ts +45 -45
- package/cli/build-css.ts +32 -32
- package/cli/commands/build.ts +65 -65
- package/cli/commands/graph.d.ts.map +1 -1
- package/cli/commands/graph.js +26 -10
- package/cli/commands/graph.ts +180 -137
- package/cli/commands/index.ts +7 -7
- package/cli/commands/patch-apply.ts +80 -80
- package/cli/commands/patch.ts +22 -22
- package/cli/commands/set.d.ts.map +1 -1
- package/cli/commands/set.js +12 -4
- package/cli/commands/set.ts +239 -225
- package/cli/commands/utils.ts +50 -50
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +86 -33
- package/cli/commands/validate.ts +176 -115
- package/cli/commands/why.d.ts.map +1 -1
- package/cli/commands/why.js +86 -20
- package/cli/commands/why.ts +158 -46
- package/cli/config-schema.ts +27 -27
- package/cli/config.ts +35 -35
- package/cli/constraint-registry.d.ts +101 -0
- package/cli/constraint-registry.d.ts.map +1 -0
- package/cli/constraint-registry.js +225 -0
- package/cli/constraint-registry.ts +304 -0
- package/cli/constraints-loader.d.ts +30 -0
- package/cli/constraints-loader.d.ts.map +1 -0
- package/cli/constraints-loader.js +58 -0
- package/cli/constraints-loader.ts +83 -0
- package/cli/cross-axis-loader.d.ts +91 -0
- package/cli/cross-axis-loader.d.ts.map +1 -0
- package/cli/cross-axis-loader.js +222 -0
- package/cli/cross-axis-loader.ts +289 -0
- package/cli/dcv.js +4 -0
- package/cli/dcv.ts +111 -107
- package/cli/engine-helpers.d.ts +33 -0
- package/cli/engine-helpers.d.ts.map +1 -1
- package/cli/engine-helpers.js +87 -22
- package/cli/engine-helpers.ts +133 -61
- package/cli/graph-poset.ts +74 -74
- package/cli/json-output.d.ts +64 -0
- package/cli/json-output.d.ts.map +1 -0
- package/cli/json-output.js +107 -0
- package/cli/json-output.ts +177 -0
- package/cli/result.ts +27 -27
- package/cli/run.ts +54 -54
- package/cli/smoke-test.ts +40 -40
- package/cli/types.d.ts +6 -0
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +84 -78
- package/core/breakpoints.ts +50 -50
- package/core/cli-format.ts +31 -31
- package/core/color.ts +148 -148
- package/core/constraints/cross-axis.ts +114 -114
- package/core/constraints/monotonic-lightness.ts +38 -38
- package/core/constraints/monotonic.ts +74 -74
- package/core/constraints/threshold.ts +43 -43
- package/core/constraints/wcag.ts +70 -70
- package/core/cross-axis-config.d.ts +29 -0
- package/core/cross-axis-config.d.ts.map +1 -1
- package/core/cross-axis-config.js +29 -0
- package/core/cross-axis-config.ts +181 -151
- package/core/engine.d.ts +95 -0
- package/core/engine.d.ts.map +1 -1
- package/core/engine.js +22 -0
- package/core/engine.ts +167 -65
- package/core/flatten.ts +116 -116
- package/core/image-export.ts +48 -48
- package/core/index.d.ts +9 -30
- package/core/index.d.ts.map +1 -1
- package/core/index.js +7 -54
- package/core/index.ts +10 -72
- package/core/patch.ts +134 -134
- package/core/poset.ts +311 -311
- package/core/why.ts +63 -63
- package/package.json +96 -90
- package/themes/color.lg.order.json +15 -15
- package/themes/color.md.order.json +15 -15
- package/themes/color.order.json +15 -15
- package/themes/color.sm.order.json +15 -15
- package/themes/cross-axis.rules.json +35 -35
- package/themes/cross-axis.sm.rules.json +12 -12
- package/themes/layout.lg.order.json +18 -18
- package/themes/layout.md.order.json +18 -18
- package/themes/layout.order.json +18 -18
- package/themes/layout.sm.order.json +18 -18
- package/themes/spacing.order.json +14 -14
- package/themes/typography.lg.order.json +15 -15
- package/themes/typography.md.order.json +15 -15
- package/themes/typography.order.json +15 -15
- package/themes/typography.sm.order.json +15 -15
- package/dist/test-overrides-removal.json +0 -4
- package/dist/tmp.patch.json +0 -35
- package/tokens/overrides/base.json +0 -22
- package/tokens/overrides/lg.json +0 -20
- package/tokens/overrides/md.json +0 -16
- package/tokens/overrides/sm.json +0 -16
- package/tokens/overrides/viol.color.json +0 -6
- package/tokens/overrides/viol.typography.json +0 -6
- package/tokens/tokens.demo-violations.json +0 -116
- package/tokens/tokens.example.json +0 -128
- package/tokens/tokens.json +0 -67
- package/tokens/tokens.multi-violations.json +0 -21
- package/tokens/tokens.schema.d.ts +0 -2298
- package/tokens/tokens.schema.d.ts.map +0 -1
- package/tokens/tokens.schema.js +0 -148
- package/tokens/tokens.schema.ts +0 -196
- package/tokens/tokens.test.json +0 -38
- package/tokens/tokens.touch-violation.json +0 -8
- package/tokens/typography.classes.css +0 -11
- package/tokens/typography.css +0 -20
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
// core/constraints/monotonic.ts
|
|
2
|
-
import type { ConstraintPlugin } from "../engine.js";
|
|
3
|
-
import type { Order } from "../poset.js";
|
|
4
|
-
|
|
5
|
-
export function MonotonicPlugin(
|
|
6
|
-
orders: Order[],
|
|
7
|
-
parse: (v: unknown) => number | null,
|
|
8
|
-
ruleId = "monotonic"
|
|
9
|
-
): ConstraintPlugin {
|
|
10
|
-
return {
|
|
11
|
-
id: ruleId,
|
|
12
|
-
evaluate(engine, candidates) {
|
|
13
|
-
const issues = [];
|
|
14
|
-
for (const [a, op, b] of orders) {
|
|
15
|
-
const va = parse(engine.get(a));
|
|
16
|
-
const vb = parse(engine.get(b));
|
|
17
|
-
if (va == null || vb == null) continue; // skip unparseable
|
|
18
|
-
const ok = op === ">=" ? va >= vb : va <= vb;
|
|
19
|
-
if (!ok && (candidates.has(a) || candidates.has(b))) {
|
|
20
|
-
issues.push({
|
|
21
|
-
id: `${a}|${b}`,
|
|
22
|
-
rule: "monotonic",
|
|
23
|
-
level: "error" as const,
|
|
24
|
-
message: `${a} ${op} ${b} violated: ${va} vs ${vb}`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return issues;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// a minimal parser for "rem"/"px" numbers
|
|
34
|
-
export const parseSize = (v: unknown): number | null => {
|
|
35
|
-
if (typeof v !== "string") return null;
|
|
36
|
-
const m = v.trim().match(/^([0-9.]+)(rem|px)?$/i);
|
|
37
|
-
if (!m) return null;
|
|
38
|
-
const num = parseFloat(m[1]);
|
|
39
|
-
const unit = (m[2] || "rem").toLowerCase();
|
|
40
|
-
return unit === "px" ? num : num * 16; // assume 1rem=16px for comparisons
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Parser for unitless numbers (like scale factors)
|
|
44
|
-
export const parseNumber = (v: unknown): number | null => {
|
|
45
|
-
if (typeof v === "number") return v;
|
|
46
|
-
if (typeof v === "string") {
|
|
47
|
-
const num = parseFloat(v.trim());
|
|
48
|
-
return isNaN(num) ? null : num;
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Parser for color lightness (OKLCH L channel)
|
|
54
|
-
export const parseLightness = (v: unknown): number | null => {
|
|
55
|
-
if (typeof v !== "string") return null;
|
|
56
|
-
|
|
57
|
-
// Match oklch(L C H / A) format
|
|
58
|
-
const oklchMatch = v.trim().match(/oklch\s*\(\s*([0-9.]+)\s+/i);
|
|
59
|
-
if (oklchMatch) {
|
|
60
|
-
return parseFloat(oklchMatch[1]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// For hex colors, rough approximation (you'd want a proper converter)
|
|
64
|
-
const hexMatch = v.trim().match(/^#([0-9a-f]{6})$/i);
|
|
65
|
-
if (hexMatch) {
|
|
66
|
-
const r = parseInt(hexMatch[1].slice(0, 2), 16) / 255;
|
|
67
|
-
const g = parseInt(hexMatch[1].slice(2, 4), 16) / 255;
|
|
68
|
-
const b = parseInt(hexMatch[1].slice(4, 6), 16) / 255;
|
|
69
|
-
// Simple luminance approximation
|
|
70
|
-
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return null;
|
|
74
|
-
};
|
|
1
|
+
// core/constraints/monotonic.ts
|
|
2
|
+
import type { ConstraintPlugin } from "../engine.js";
|
|
3
|
+
import type { Order } from "../poset.js";
|
|
4
|
+
|
|
5
|
+
export function MonotonicPlugin(
|
|
6
|
+
orders: Order[],
|
|
7
|
+
parse: (v: unknown) => number | null,
|
|
8
|
+
ruleId = "monotonic"
|
|
9
|
+
): ConstraintPlugin {
|
|
10
|
+
return {
|
|
11
|
+
id: ruleId,
|
|
12
|
+
evaluate(engine, candidates) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
for (const [a, op, b] of orders) {
|
|
15
|
+
const va = parse(engine.get(a));
|
|
16
|
+
const vb = parse(engine.get(b));
|
|
17
|
+
if (va == null || vb == null) continue; // skip unparseable
|
|
18
|
+
const ok = op === ">=" ? va >= vb : va <= vb;
|
|
19
|
+
if (!ok && (candidates.has(a) || candidates.has(b))) {
|
|
20
|
+
issues.push({
|
|
21
|
+
id: `${a}|${b}`,
|
|
22
|
+
rule: "monotonic",
|
|
23
|
+
level: "error" as const,
|
|
24
|
+
message: `${a} ${op} ${b} violated: ${va} vs ${vb}`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return issues;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// a minimal parser for "rem"/"px" numbers
|
|
34
|
+
export const parseSize = (v: unknown): number | null => {
|
|
35
|
+
if (typeof v !== "string") return null;
|
|
36
|
+
const m = v.trim().match(/^([0-9.]+)(rem|px)?$/i);
|
|
37
|
+
if (!m) return null;
|
|
38
|
+
const num = parseFloat(m[1]);
|
|
39
|
+
const unit = (m[2] || "rem").toLowerCase();
|
|
40
|
+
return unit === "px" ? num : num * 16; // assume 1rem=16px for comparisons
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Parser for unitless numbers (like scale factors)
|
|
44
|
+
export const parseNumber = (v: unknown): number | null => {
|
|
45
|
+
if (typeof v === "number") return v;
|
|
46
|
+
if (typeof v === "string") {
|
|
47
|
+
const num = parseFloat(v.trim());
|
|
48
|
+
return isNaN(num) ? null : num;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Parser for color lightness (OKLCH L channel)
|
|
54
|
+
export const parseLightness = (v: unknown): number | null => {
|
|
55
|
+
if (typeof v !== "string") return null;
|
|
56
|
+
|
|
57
|
+
// Match oklch(L C H / A) format
|
|
58
|
+
const oklchMatch = v.trim().match(/oklch\s*\(\s*([0-9.]+)\s+/i);
|
|
59
|
+
if (oklchMatch) {
|
|
60
|
+
return parseFloat(oklchMatch[1]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// For hex colors, rough approximation (you'd want a proper converter)
|
|
64
|
+
const hexMatch = v.trim().match(/^#([0-9a-f]{6})$/i);
|
|
65
|
+
if (hexMatch) {
|
|
66
|
+
const r = parseInt(hexMatch[1].slice(0, 2), 16) / 255;
|
|
67
|
+
const g = parseInt(hexMatch[1].slice(2, 4), 16) / 255;
|
|
68
|
+
const b = parseInt(hexMatch[1].slice(4, 6), 16) / 255;
|
|
69
|
+
// Simple luminance approximation
|
|
70
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import type { ConstraintPlugin } from "../engine.js";
|
|
2
|
-
|
|
3
|
-
export type ThresholdRule = {
|
|
4
|
-
id: string; // token id to check
|
|
5
|
-
op: ">=" | "<="; // comparison
|
|
6
|
-
valuePx: number; // threshold in px
|
|
7
|
-
where?: string; // optional context label
|
|
8
|
-
level?: "error" | "warn"; // default error
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const parseSizePx = (v: unknown): number | null => {
|
|
12
|
-
if (typeof v !== "string") return null;
|
|
13
|
-
const m = v.trim().match(/^([0-9.]+)\s*(px|rem)?$/i);
|
|
14
|
-
if (!m) return null;
|
|
15
|
-
const n = parseFloat(m[1]);
|
|
16
|
-
const unit = (m[2] || "px").toLowerCase();
|
|
17
|
-
return unit === "rem" ? n * 16 : n;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export function ThresholdPlugin(rules: ThresholdRule[], ruleId = "threshold"): ConstraintPlugin {
|
|
21
|
-
return {
|
|
22
|
-
id: ruleId,
|
|
23
|
-
evaluate(engine, candidates) {
|
|
24
|
-
const issues = [];
|
|
25
|
-
for (const r of rules) {
|
|
26
|
-
if (!candidates.has(r.id)) continue; // incremental
|
|
27
|
-
const px = parseSizePx(engine.get(r.id));
|
|
28
|
-
if (px == null) continue;
|
|
29
|
-
const ok = r.op === ">=" ? px >= r.valuePx : px <= r.valuePx;
|
|
30
|
-
if (!ok) {
|
|
31
|
-
issues.push({
|
|
32
|
-
id: r.id,
|
|
33
|
-
rule: ruleId,
|
|
34
|
-
level: r.level ?? "error",
|
|
35
|
-
where: r.where,
|
|
36
|
-
message: `${r.id} ${r.op} ${r.valuePx}px violated: ${px}px`
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return issues;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
}
|
|
1
|
+
import type { ConstraintPlugin } from "../engine.js";
|
|
2
|
+
|
|
3
|
+
export type ThresholdRule = {
|
|
4
|
+
id: string; // token id to check
|
|
5
|
+
op: ">=" | "<="; // comparison
|
|
6
|
+
valuePx: number; // threshold in px
|
|
7
|
+
where?: string; // optional context label
|
|
8
|
+
level?: "error" | "warn"; // default error
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const parseSizePx = (v: unknown): number | null => {
|
|
12
|
+
if (typeof v !== "string") return null;
|
|
13
|
+
const m = v.trim().match(/^([0-9.]+)\s*(px|rem)?$/i);
|
|
14
|
+
if (!m) return null;
|
|
15
|
+
const n = parseFloat(m[1]);
|
|
16
|
+
const unit = (m[2] || "px").toLowerCase();
|
|
17
|
+
return unit === "rem" ? n * 16 : n;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function ThresholdPlugin(rules: ThresholdRule[], ruleId = "threshold"): ConstraintPlugin {
|
|
21
|
+
return {
|
|
22
|
+
id: ruleId,
|
|
23
|
+
evaluate(engine, candidates) {
|
|
24
|
+
const issues = [];
|
|
25
|
+
for (const r of rules) {
|
|
26
|
+
if (!candidates.has(r.id)) continue; // incremental
|
|
27
|
+
const px = parseSizePx(engine.get(r.id));
|
|
28
|
+
if (px == null) continue;
|
|
29
|
+
const ok = r.op === ">=" ? px >= r.valuePx : px <= r.valuePx;
|
|
30
|
+
if (!ok) {
|
|
31
|
+
issues.push({
|
|
32
|
+
id: r.id,
|
|
33
|
+
rule: ruleId,
|
|
34
|
+
level: r.level ?? "error",
|
|
35
|
+
where: r.where,
|
|
36
|
+
message: `${r.id} ${r.op} ${r.valuePx}px violated: ${px}px`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return issues;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
package/core/constraints/wcag.ts
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import type { ConstraintPlugin } from "../engine.js";
|
|
2
|
-
import type { TokenId } from "../flatten.js";
|
|
3
|
-
import { parseCssColor, compositeOver, relativeLuminance, contrastRatio } from "../color.js";
|
|
4
|
-
|
|
5
|
-
export type ContrastPair = {
|
|
6
|
-
fg: TokenId;
|
|
7
|
-
bg: TokenId;
|
|
8
|
-
min: number; // e.g., 4.5
|
|
9
|
-
where?: string;
|
|
10
|
-
// Optional ultimate backdrop if bg is transparent; token id or literal color.
|
|
11
|
-
backdrop?: TokenId | string; // default: "#ffffff"
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function resolveColor(engineGet: (id: TokenId)=>unknown, x: TokenId | string): string | undefined {
|
|
15
|
-
if (typeof x !== "string") return undefined;
|
|
16
|
-
// If it's a CSS color literal, return as is; else treat as token id.
|
|
17
|
-
const isLiteral = /^#|^rgb|^hsl|^oklch|^oklab|^transparent/i.test(x);
|
|
18
|
-
return isLiteral ? x : String(engineGet(x) ?? "");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function WcagContrastPlugin(pairs: ContrastPair[]): ConstraintPlugin {
|
|
22
|
-
return {
|
|
23
|
-
id: "wcag-contrast",
|
|
24
|
-
evaluate(engine, candidates) {
|
|
25
|
-
const issues = [];
|
|
26
|
-
for (const p of pairs) {
|
|
27
|
-
if (!candidates.has(p.fg) && !candidates.has(p.bg)) continue;
|
|
28
|
-
|
|
29
|
-
const fgStr = String(engine.get(p.fg) ?? "");
|
|
30
|
-
const bgStr = String(engine.get(p.bg) ?? "");
|
|
31
|
-
const fg = parseCssColor(fgStr);
|
|
32
|
-
const bgRaw = parseCssColor(bgStr);
|
|
33
|
-
|
|
34
|
-
const backdropStr = resolveColor(engine.get.bind(engine), p.backdrop ?? "#ffffff");
|
|
35
|
-
const backdrop = parseCssColor(backdropStr || "#ffffff");
|
|
36
|
-
|
|
37
|
-
if (!fg || !bgRaw || !backdrop) {
|
|
38
|
-
issues.push({
|
|
39
|
-
id: `${p.fg}|${p.bg}`,
|
|
40
|
-
rule: "wcag-contrast",
|
|
41
|
-
level: "warn" as const,
|
|
42
|
-
where: p.where,
|
|
43
|
-
message: `Unparseable color(s): fg="${fgStr}" bg="${bgStr}" backdrop="${backdropStr}"`
|
|
44
|
-
});
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Effective background (handle bg alpha)
|
|
49
|
-
const effBg = bgRaw.a < 1 ? compositeOver(bgRaw, backdrop) : bgRaw;
|
|
50
|
-
// Effective foreground over effective background
|
|
51
|
-
const effFg = fg.a < 1 ? compositeOver(fg, effBg) : fg;
|
|
52
|
-
|
|
53
|
-
const Lfg = relativeLuminance(effFg);
|
|
54
|
-
const Lbg = relativeLuminance(effBg);
|
|
55
|
-
const ratio = contrastRatio(Lfg, Lbg);
|
|
56
|
-
|
|
57
|
-
if (ratio < p.min) {
|
|
58
|
-
issues.push({
|
|
59
|
-
id: `${p.fg}|${p.bg}`,
|
|
60
|
-
rule: "wcag-contrast",
|
|
61
|
-
level: "error" as const,
|
|
62
|
-
where: p.where,
|
|
63
|
-
message: `Contrast ${ratio.toFixed(2)}:1 < ${p.min}:1`
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return issues;
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
1
|
+
import type { ConstraintPlugin } from "../engine.js";
|
|
2
|
+
import type { TokenId } from "../flatten.js";
|
|
3
|
+
import { parseCssColor, compositeOver, relativeLuminance, contrastRatio } from "../color.js";
|
|
4
|
+
|
|
5
|
+
export type ContrastPair = {
|
|
6
|
+
fg: TokenId;
|
|
7
|
+
bg: TokenId;
|
|
8
|
+
min: number; // e.g., 4.5
|
|
9
|
+
where?: string;
|
|
10
|
+
// Optional ultimate backdrop if bg is transparent; token id or literal color.
|
|
11
|
+
backdrop?: TokenId | string; // default: "#ffffff"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function resolveColor(engineGet: (id: TokenId)=>unknown, x: TokenId | string): string | undefined {
|
|
15
|
+
if (typeof x !== "string") return undefined;
|
|
16
|
+
// If it's a CSS color literal, return as is; else treat as token id.
|
|
17
|
+
const isLiteral = /^#|^rgb|^hsl|^oklch|^oklab|^transparent/i.test(x);
|
|
18
|
+
return isLiteral ? x : String(engineGet(x) ?? "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function WcagContrastPlugin(pairs: ContrastPair[]): ConstraintPlugin {
|
|
22
|
+
return {
|
|
23
|
+
id: "wcag-contrast",
|
|
24
|
+
evaluate(engine, candidates) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
for (const p of pairs) {
|
|
27
|
+
if (!candidates.has(p.fg) && !candidates.has(p.bg)) continue;
|
|
28
|
+
|
|
29
|
+
const fgStr = String(engine.get(p.fg) ?? "");
|
|
30
|
+
const bgStr = String(engine.get(p.bg) ?? "");
|
|
31
|
+
const fg = parseCssColor(fgStr);
|
|
32
|
+
const bgRaw = parseCssColor(bgStr);
|
|
33
|
+
|
|
34
|
+
const backdropStr = resolveColor(engine.get.bind(engine), p.backdrop ?? "#ffffff");
|
|
35
|
+
const backdrop = parseCssColor(backdropStr || "#ffffff");
|
|
36
|
+
|
|
37
|
+
if (!fg || !bgRaw || !backdrop) {
|
|
38
|
+
issues.push({
|
|
39
|
+
id: `${p.fg}|${p.bg}`,
|
|
40
|
+
rule: "wcag-contrast",
|
|
41
|
+
level: "warn" as const,
|
|
42
|
+
where: p.where,
|
|
43
|
+
message: `Unparseable color(s): fg="${fgStr}" bg="${bgStr}" backdrop="${backdropStr}"`
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Effective background (handle bg alpha)
|
|
49
|
+
const effBg = bgRaw.a < 1 ? compositeOver(bgRaw, backdrop) : bgRaw;
|
|
50
|
+
// Effective foreground over effective background
|
|
51
|
+
const effFg = fg.a < 1 ? compositeOver(fg, effBg) : fg;
|
|
52
|
+
|
|
53
|
+
const Lfg = relativeLuminance(effFg);
|
|
54
|
+
const Lbg = relativeLuminance(effBg);
|
|
55
|
+
const ratio = contrastRatio(Lfg, Lbg);
|
|
56
|
+
|
|
57
|
+
if (ratio < p.min) {
|
|
58
|
+
issues.push({
|
|
59
|
+
id: `${p.fg}|${p.bg}`,
|
|
60
|
+
rule: "wcag-contrast",
|
|
61
|
+
level: "error" as const,
|
|
62
|
+
where: p.where,
|
|
63
|
+
message: `Contrast ${ratio.toFixed(2)}:1 < ${p.min}:1`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return issues;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated This module is deprecated. Use cli/cross-axis-loader.ts instead.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3B (Filesystem Separation): This file contains filesystem access logic
|
|
5
|
+
* that has been moved to the CLI layer (cli/cross-axis-loader.ts).
|
|
6
|
+
*
|
|
7
|
+
* Core modules should not import from node:fs. Instead:
|
|
8
|
+
* - CLI code uses cli/cross-axis-loader.ts to read and parse rules
|
|
9
|
+
* - Core plugin (core/constraints/cross-axis.ts) accepts pre-parsed rules
|
|
10
|
+
*
|
|
11
|
+
* Migration:
|
|
12
|
+
* ```ts
|
|
13
|
+
* // OLD (core reads filesystem):
|
|
14
|
+
* import { loadCrossAxisPlugin } from './core/cross-axis-config.js';
|
|
15
|
+
* engine.use(loadCrossAxisPlugin(path, bp, { knownIds }));
|
|
16
|
+
*
|
|
17
|
+
* // NEW (CLI reads, core receives data):
|
|
18
|
+
* import { loadCrossAxisRules } from './cli/cross-axis-loader.js';
|
|
19
|
+
* import { CrossAxisPlugin } from './core/constraints/cross-axis.js';
|
|
20
|
+
* const rules = loadCrossAxisRules(path, { bp, knownIds });
|
|
21
|
+
* engine.use(CrossAxisPlugin(rules, bp));
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* This file will be removed in a future major version.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use cli/cross-axis-loader.ts loadCrossAxisRules() + CrossAxisPlugin() instead.
|
|
28
|
+
* This function will be removed in a future major version.
|
|
29
|
+
*/
|
|
1
30
|
export declare function loadCrossAxisPlugin(path: string, bp?: string, opts?: {
|
|
2
31
|
debug?: boolean;
|
|
3
32
|
knownIds?: Set<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-axis-config.d.ts","sourceRoot":"","sources":["cross-axis-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cross-axis-config.d.ts","sourceRoot":"","sources":["cross-axis-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,0CA2HnD"}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated This module is deprecated. Use cli/cross-axis-loader.ts instead.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3B (Filesystem Separation): This file contains filesystem access logic
|
|
5
|
+
* that has been moved to the CLI layer (cli/cross-axis-loader.ts).
|
|
6
|
+
*
|
|
7
|
+
* Core modules should not import from node:fs. Instead:
|
|
8
|
+
* - CLI code uses cli/cross-axis-loader.ts to read and parse rules
|
|
9
|
+
* - Core plugin (core/constraints/cross-axis.ts) accepts pre-parsed rules
|
|
10
|
+
*
|
|
11
|
+
* Migration:
|
|
12
|
+
* ```ts
|
|
13
|
+
* // OLD (core reads filesystem):
|
|
14
|
+
* import { loadCrossAxisPlugin } from './core/cross-axis-config.js';
|
|
15
|
+
* engine.use(loadCrossAxisPlugin(path, bp, { knownIds }));
|
|
16
|
+
*
|
|
17
|
+
* // NEW (CLI reads, core receives data):
|
|
18
|
+
* import { loadCrossAxisRules } from './cli/cross-axis-loader.js';
|
|
19
|
+
* import { CrossAxisPlugin } from './core/constraints/cross-axis.js';
|
|
20
|
+
* const rules = loadCrossAxisRules(path, { bp, knownIds });
|
|
21
|
+
* engine.use(CrossAxisPlugin(rules, bp));
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* This file will be removed in a future major version.
|
|
25
|
+
*/
|
|
1
26
|
import fs from "node:fs";
|
|
2
27
|
import { CrossAxisPlugin } from "./constraints/cross-axis.js";
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Use cli/cross-axis-loader.ts loadCrossAxisRules() + CrossAxisPlugin() instead.
|
|
30
|
+
* This function will be removed in a future major version.
|
|
31
|
+
*/
|
|
3
32
|
export function loadCrossAxisPlugin(path, bp, opts) {
|
|
4
33
|
const debug = !!opts?.debug;
|
|
5
34
|
const known = opts?.knownIds ?? new Set();
|