design-constraint-validator 1.1.0 → 2.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/README.md +90 -21
- package/adapters/decisionthemes.d.ts +44 -0
- package/adapters/decisionthemes.d.ts.map +1 -0
- package/adapters/decisionthemes.js +35 -0
- package/adapters/decisionthemes.ts +59 -0
- package/cli/commands/build.js +1 -1
- package/cli/commands/build.ts +1 -1
- package/cli/commands/graph.js +4 -4
- package/cli/commands/graph.ts +4 -4
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +43 -6
- package/cli/commands/validate.ts +41 -8
- package/cli/config-schema.d.ts +39 -0
- package/cli/config-schema.d.ts.map +1 -1
- package/cli/config-schema.js +4 -2
- package/cli/config-schema.ts +4 -2
- package/cli/config.d.ts.map +1 -1
- package/cli/config.js +8 -2
- package/cli/config.ts +8 -2
- package/cli/constraint-registry.d.ts +16 -0
- package/cli/constraint-registry.d.ts.map +1 -1
- package/cli/constraint-registry.js +64 -31
- package/cli/constraint-registry.ts +67 -31
- package/cli/dcv.js +8 -24
- package/cli/dcv.ts +8 -20
- package/cli/json-output.d.ts +8 -1
- package/cli/json-output.d.ts.map +1 -1
- package/cli/json-output.js +13 -4
- package/cli/json-output.ts +20 -4
- package/cli/types.d.ts +2 -0
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +2 -0
- package/cli/validate-api.d.ts +40 -0
- package/cli/validate-api.d.ts.map +1 -0
- package/cli/validate-api.js +85 -0
- package/cli/validate-api.ts +126 -0
- package/cli/version-banner.d.ts +20 -0
- package/cli/version-banner.d.ts.map +1 -0
- package/cli/version-banner.js +49 -0
- package/cli/version-banner.ts +61 -0
- package/core/breakpoints.d.ts +8 -2
- package/core/breakpoints.d.ts.map +1 -1
- package/core/breakpoints.js +24 -3
- package/core/breakpoints.ts +22 -3
- package/core/color.js +4 -4
- package/core/color.ts +4 -4
- package/core/constraints/monotonic-lightness.d.ts.map +1 -1
- package/core/constraints/monotonic-lightness.js +9 -5
- package/core/constraints/monotonic-lightness.ts +9 -4
- package/core/constraints/wcag.d.ts.map +1 -1
- package/core/constraints/wcag.js +6 -0
- package/core/constraints/wcag.ts +6 -0
- package/core/dtcg.d.ts +38 -0
- package/core/dtcg.d.ts.map +1 -0
- package/core/dtcg.js +88 -0
- package/core/dtcg.ts +102 -0
- package/core/engine.d.ts +6 -0
- package/core/engine.d.ts.map +1 -1
- package/core/engine.ts +7 -0
- package/core/flatten.d.ts +5 -3
- package/core/flatten.d.ts.map +1 -1
- package/core/flatten.js +24 -10
- package/core/flatten.ts +39 -16
- package/core/image-export.d.ts.map +1 -1
- package/core/image-export.js +10 -7
- package/core/image-export.ts +9 -6
- package/core/index.d.ts +2 -0
- package/core/index.d.ts.map +1 -1
- package/core/index.js +4 -0
- package/core/index.ts +6 -0
- package/core/why.d.ts +1 -1
- package/core/why.d.ts.map +1 -1
- package/core/why.ts +1 -1
- package/mcp/contracts.d.ts +118 -0
- package/mcp/contracts.d.ts.map +1 -0
- package/mcp/contracts.js +30 -0
- package/mcp/contracts.ts +51 -0
- package/mcp/index.d.ts +9 -0
- package/mcp/index.d.ts.map +1 -0
- package/mcp/index.js +32 -0
- package/mcp/index.ts +70 -0
- package/mcp/tools.d.ts +52 -0
- package/mcp/tools.d.ts.map +1 -0
- package/mcp/tools.js +172 -0
- package/mcp/tools.ts +254 -0
- package/package.json +41 -26
- package/server.json +21 -0
- package/cli/constraints-loader.d.ts +0 -30
- package/cli/constraints-loader.d.ts.map +0 -1
- package/cli/constraints-loader.js +0 -58
- package/cli/constraints-loader.ts +0 -83
- package/cli/engine-helpers.d.ts +0 -41
- package/cli/engine-helpers.d.ts.map +0 -1
- package/cli/engine-helpers.js +0 -135
- package/cli/engine-helpers.ts +0 -133
- package/core/cross-axis-config.d.ts +0 -34
- package/core/cross-axis-config.d.ts.map +0 -1
- package/core/cross-axis-config.js +0 -173
- package/core/cross-axis-config.ts +0 -182
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version banner utility
|
|
3
|
+
* Displays DCV version info when validation runs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
interface PackageJson {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
homepage?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let cachedPkg: PackageJson | null = null;
|
|
19
|
+
|
|
20
|
+
function getPackageInfo(): PackageJson {
|
|
21
|
+
if (cachedPkg) return cachedPkg;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
25
|
+
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
|
26
|
+
cachedPkg = JSON.parse(pkgContent);
|
|
27
|
+
return cachedPkg!;
|
|
28
|
+
} catch {
|
|
29
|
+
// Fallback if package.json not found
|
|
30
|
+
return {
|
|
31
|
+
name: 'design-constraint-validator',
|
|
32
|
+
version: '2.0.0',
|
|
33
|
+
homepage: 'https://github.com/CseperkePapp/design-constraint-validator'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Print DCV version banner
|
|
40
|
+
* Shows: DCV v{version} | {repo_url}
|
|
41
|
+
*/
|
|
42
|
+
export function printVersionBanner(options?: { quiet?: boolean }): void {
|
|
43
|
+
if (options?.quiet) return;
|
|
44
|
+
|
|
45
|
+
const pkg = getPackageInfo();
|
|
46
|
+
const repoUrl = pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator';
|
|
47
|
+
|
|
48
|
+
console.log(`\x1b[2m${pkg.name} v${pkg.version} | ${repoUrl}\x1b[0m`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get version string for JSON output
|
|
53
|
+
*/
|
|
54
|
+
export function getVersionInfo(): { name: string; version: string; repository: string } {
|
|
55
|
+
const pkg = getPackageInfo();
|
|
56
|
+
return {
|
|
57
|
+
name: pkg.name,
|
|
58
|
+
version: pkg.version,
|
|
59
|
+
repository: pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator'
|
|
60
|
+
};
|
|
61
|
+
}
|
package/core/breakpoints.d.ts
CHANGED
|
@@ -6,7 +6,13 @@ export declare function loadOrders(axis: string, bp?: Breakpoint): [string, "<="
|
|
|
6
6
|
export declare function mergeTokens(base: unknown, overlay: unknown): TokenNode;
|
|
7
7
|
/** Load tokens with optional breakpoint override: base + overrides/<bp>.json */
|
|
8
8
|
/**
|
|
9
|
-
* Load tokens with override precedence: base < local < breakpoint
|
|
9
|
+
* Load tokens with override precedence: base < local < breakpoint.
|
|
10
|
+
*
|
|
11
|
+
* @param bp optional breakpoint overlay (tokens/overrides/<bp>.json).
|
|
12
|
+
* @param tokensPath explicit base tokens file. When provided, a missing or
|
|
13
|
+
* invalid file throws — callers must never silently validate
|
|
14
|
+
* an empty token set. When omitted, defaults to the repo
|
|
15
|
+
* example file and stays lenient (backward-compatible).
|
|
10
16
|
*/
|
|
11
|
-
export declare function loadTokensWithBreakpoint(bp?: Breakpoint): TokenNode;
|
|
17
|
+
export declare function loadTokensWithBreakpoint(bp?: Breakpoint, tokensPath?: string): TokenNode;
|
|
12
18
|
//# sourceMappingURL=breakpoints.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breakpoints.d.ts","sourceRoot":"","sources":["breakpoints.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAM7D;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAOhE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,CAKzF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,CAQtE;AAED,gFAAgF;AAChF
|
|
1
|
+
{"version":3,"file":"breakpoints.d.ts","sourceRoot":"","sources":["breakpoints.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAM7D;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAOhE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,CAKzF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,CAQtE;AAED,gFAAgF;AAChF;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAkBxF"}
|
package/core/breakpoints.js
CHANGED
|
@@ -38,10 +38,31 @@ export function mergeTokens(base, overlay) {
|
|
|
38
38
|
}
|
|
39
39
|
/** Load tokens with optional breakpoint override: base + overrides/<bp>.json */
|
|
40
40
|
/**
|
|
41
|
-
* Load tokens with override precedence: base < local < breakpoint
|
|
41
|
+
* Load tokens with override precedence: base < local < breakpoint.
|
|
42
|
+
*
|
|
43
|
+
* @param bp optional breakpoint overlay (tokens/overrides/<bp>.json).
|
|
44
|
+
* @param tokensPath explicit base tokens file. When provided, a missing or
|
|
45
|
+
* invalid file throws — callers must never silently validate
|
|
46
|
+
* an empty token set. When omitted, defaults to the repo
|
|
47
|
+
* example file and stays lenient (backward-compatible).
|
|
42
48
|
*/
|
|
43
|
-
export function loadTokensWithBreakpoint(bp) {
|
|
44
|
-
|
|
49
|
+
export function loadTokensWithBreakpoint(bp, tokensPath) {
|
|
50
|
+
let base;
|
|
51
|
+
if (tokensPath !== undefined) {
|
|
52
|
+
if (!fs.existsSync(tokensPath)) {
|
|
53
|
+
throw new Error(`Tokens file not found: ${tokensPath}`);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
base = JSON.parse(fs.readFileSync(tokensPath, "utf8"));
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
60
|
+
throw new Error(`Tokens file is not valid JSON: ${tokensPath} (${detail})`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
base = loadJsonSafe("tokens/tokens.example.json") ?? {};
|
|
65
|
+
}
|
|
45
66
|
const local = loadJsonSafe("tokens/overrides/local.json");
|
|
46
67
|
const ov = bp ? loadJsonSafe(`tokens/overrides/${bp}.json`) : null;
|
|
47
68
|
return mergeTokens(mergeTokens(base, local), ov);
|
package/core/breakpoints.ts
CHANGED
|
@@ -40,10 +40,29 @@ export function mergeTokens(base: unknown, overlay: unknown): TokenNode {
|
|
|
40
40
|
|
|
41
41
|
/** Load tokens with optional breakpoint override: base + overrides/<bp>.json */
|
|
42
42
|
/**
|
|
43
|
-
* Load tokens with override precedence: base < local < breakpoint
|
|
43
|
+
* Load tokens with override precedence: base < local < breakpoint.
|
|
44
|
+
*
|
|
45
|
+
* @param bp optional breakpoint overlay (tokens/overrides/<bp>.json).
|
|
46
|
+
* @param tokensPath explicit base tokens file. When provided, a missing or
|
|
47
|
+
* invalid file throws — callers must never silently validate
|
|
48
|
+
* an empty token set. When omitted, defaults to the repo
|
|
49
|
+
* example file and stays lenient (backward-compatible).
|
|
44
50
|
*/
|
|
45
|
-
export function loadTokensWithBreakpoint(bp?: Breakpoint): TokenNode {
|
|
46
|
-
|
|
51
|
+
export function loadTokensWithBreakpoint(bp?: Breakpoint, tokensPath?: string): TokenNode {
|
|
52
|
+
let base: TokenNode;
|
|
53
|
+
if (tokensPath !== undefined) {
|
|
54
|
+
if (!fs.existsSync(tokensPath)) {
|
|
55
|
+
throw new Error(`Tokens file not found: ${tokensPath}`);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
base = JSON.parse(fs.readFileSync(tokensPath, "utf8")) as TokenNode;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
61
|
+
throw new Error(`Tokens file is not valid JSON: ${tokensPath} (${detail})`);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
base = loadJsonSafe<TokenNode>("tokens/tokens.example.json") ?? {};
|
|
65
|
+
}
|
|
47
66
|
const local = loadJsonSafe<TokenNode>("tokens/overrides/local.json");
|
|
48
67
|
const ov = bp ? loadJsonSafe<TokenNode>(`tokens/overrides/${bp}.json`) : null;
|
|
49
68
|
return mergeTokens(mergeTokens(base, local), ov);
|
package/core/color.js
CHANGED
|
@@ -74,7 +74,7 @@ export function parseCssColor(input) {
|
|
|
74
74
|
const C = Math.max(0, num(m[2]));
|
|
75
75
|
const h = ((num(m[3]) % 360) + 360) % 360;
|
|
76
76
|
const a = m[4] ? (m[4].includes("%") ? pct(m[4]) : num(m[4])) : 1;
|
|
77
|
-
const [r, g, b] =
|
|
77
|
+
const [r, g, b] = oklchToLinearSrgb(L, C, h).map(v => linToSrgb(v));
|
|
78
78
|
return { r, g, b, a: clamp01(a) };
|
|
79
79
|
}
|
|
80
80
|
// named "transparent"
|
|
@@ -103,8 +103,8 @@ function hslToRgb(H, S, L) {
|
|
|
103
103
|
const m = L - C / 2;
|
|
104
104
|
return { r: clamp255((r + m) * 255), g: clamp255((g + m) * 255), b: clamp255((b + m) * 255), a: 1 };
|
|
105
105
|
}
|
|
106
|
-
/* ---------- OKLCH → sRGB (0..1 channels) ---------- */
|
|
107
|
-
function
|
|
106
|
+
/* ---------- OKLCH → linear sRGB (0..1 channels) ---------- */
|
|
107
|
+
function oklchToLinearSrgb(L, C, hDeg) {
|
|
108
108
|
const h = (hDeg * Math.PI) / 180;
|
|
109
109
|
// OKLCH -> OKLab
|
|
110
110
|
const a = C * Math.cos(h);
|
|
@@ -118,7 +118,7 @@ function oklchToSrgb(L, C, hDeg) {
|
|
|
118
118
|
const R = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
119
119
|
const G = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
120
120
|
const B = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
|
121
|
-
// clamp to [0,1] (gamut clip)
|
|
121
|
+
// clamp to [0,1] (gamut clip). Callers gamma-encode to sRGB.
|
|
122
122
|
return [clamp01(R), clamp01(G), clamp01(B)];
|
|
123
123
|
}
|
|
124
124
|
/* ---------- Alpha compositing (linear light) ---------- */
|
package/core/color.ts
CHANGED
|
@@ -87,7 +87,7 @@ export function parseCssColor(input: string | undefined | null): RGBA | null {
|
|
|
87
87
|
const C = Math.max(0, num(m[2]));
|
|
88
88
|
const h = ((num(m[3]) % 360) + 360) % 360;
|
|
89
89
|
const a = m[4] ? (m[4].includes("%") ? pct(m[4]) : num(m[4])) : 1;
|
|
90
|
-
const [r, g, b] =
|
|
90
|
+
const [r, g, b] = oklchToLinearSrgb(L, C, h).map(v => linToSrgb(v));
|
|
91
91
|
return { r, g, b, a: clamp01(a) };
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -113,8 +113,8 @@ function hslToRgb(H: number, S: number, L: number): RGBA {
|
|
|
113
113
|
return { r: clamp255((r + m) * 255), g: clamp255((g + m) * 255), b: clamp255((b + m) * 255), a: 1 };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/* ---------- OKLCH → sRGB (0..1 channels) ---------- */
|
|
117
|
-
function
|
|
116
|
+
/* ---------- OKLCH → linear sRGB (0..1 channels) ---------- */
|
|
117
|
+
function oklchToLinearSrgb(L: number, C: number, hDeg: number): [number, number, number] {
|
|
118
118
|
const h = (hDeg * Math.PI) / 180;
|
|
119
119
|
// OKLCH -> OKLab
|
|
120
120
|
const a = C * Math.cos(h);
|
|
@@ -128,7 +128,7 @@ function oklchToSrgb(L: number, C: number, hDeg: number): [number, number, numbe
|
|
|
128
128
|
const R = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
129
129
|
const G = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
130
130
|
const B = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
|
131
|
-
// clamp to [0,1] (gamut clip)
|
|
131
|
+
// clamp to [0,1] (gamut clip). Callers gamma-encode to sRGB.
|
|
132
132
|
return [clamp01(R), clamp01(G), clamp01(B)];
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monotonic-lightness.d.ts","sourceRoot":"","sources":["monotonic-lightness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"monotonic-lightness.d.ts","sourceRoot":"","sources":["monotonic-lightness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYxD;AAED,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAElD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAuBpE"}
|
|
@@ -2,11 +2,15 @@ import { parseCssColor, relativeLuminance } from "../color.js";
|
|
|
2
2
|
export function parseLightness(v) {
|
|
3
3
|
if (typeof v !== "string")
|
|
4
4
|
return null;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// ONE consistent lightness scale for every format. parseCssColor handles
|
|
6
|
+
// hex / rgb / hsl / oklch (oklch→sRGB is the verified TASK-005 pipeline), so we
|
|
7
|
+
// always compare WCAG relative luminance.
|
|
8
|
+
//
|
|
9
|
+
// BUG FIXED (TASK-009): oklch() previously short-circuited to its raw perceptual
|
|
10
|
+
// L coordinate — a DIFFERENT scale from the relative luminance used for hex. A
|
|
11
|
+
// scale mixing the two formats then compared incomparable numbers (e.g. oklch L
|
|
12
|
+
// 0.60 vs hex luminance 0.216 for the same gray), yielding false pass/fail.
|
|
13
|
+
const rgba = parseCssColor(v.trim());
|
|
10
14
|
return rgba ? relativeLuminance(rgba) : null;
|
|
11
15
|
}
|
|
12
16
|
export function MonotonicLightness(orders) {
|
|
@@ -3,10 +3,15 @@ import { parseCssColor, relativeLuminance } from "../color.js";
|
|
|
3
3
|
|
|
4
4
|
export function parseLightness(v: unknown): number | null {
|
|
5
5
|
if (typeof v !== "string") return null;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// ONE consistent lightness scale for every format. parseCssColor handles
|
|
7
|
+
// hex / rgb / hsl / oklch (oklch→sRGB is the verified TASK-005 pipeline), so we
|
|
8
|
+
// always compare WCAG relative luminance.
|
|
9
|
+
//
|
|
10
|
+
// BUG FIXED (TASK-009): oklch() previously short-circuited to its raw perceptual
|
|
11
|
+
// L coordinate — a DIFFERENT scale from the relative luminance used for hex. A
|
|
12
|
+
// scale mixing the two formats then compared incomparable numbers (e.g. oklch L
|
|
13
|
+
// 0.60 vs hex luminance 0.216 for the same gray), yielding false pass/fail.
|
|
14
|
+
const rgba = parseCssColor(v.trim());
|
|
10
15
|
return rgba ? relativeLuminance(rgba) : null;
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wcag.d.ts","sourceRoot":"","sources":["wcag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC7B,CAAC;AASF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"wcag.d.ts","sourceRoot":"","sources":["wcag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC7B,CAAC;AASF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,gBAAgB,CAuD1E"}
|
package/core/constraints/wcag.js
CHANGED
|
@@ -26,6 +26,7 @@ export function WcagContrastPlugin(pairs) {
|
|
|
26
26
|
rule: "wcag-contrast",
|
|
27
27
|
level: "warn",
|
|
28
28
|
where: p.where,
|
|
29
|
+
involvedTokens: [p.fg, p.bg],
|
|
29
30
|
message: `Unparseable color(s): fg="${fgStr}" bg="${bgStr}" backdrop="${backdropStr}"`
|
|
30
31
|
});
|
|
31
32
|
continue;
|
|
@@ -43,6 +44,11 @@ export function WcagContrastPlugin(pairs) {
|
|
|
43
44
|
rule: "wcag-contrast",
|
|
44
45
|
level: "error",
|
|
45
46
|
where: p.where,
|
|
47
|
+
involvedTokens: [p.fg, p.bg],
|
|
48
|
+
metadata: {
|
|
49
|
+
actual: Number(ratio.toFixed(2)),
|
|
50
|
+
required: p.min
|
|
51
|
+
},
|
|
46
52
|
message: `Contrast ${ratio.toFixed(2)}:1 < ${p.min}:1`
|
|
47
53
|
});
|
|
48
54
|
}
|
package/core/constraints/wcag.ts
CHANGED
|
@@ -40,6 +40,7 @@ export function WcagContrastPlugin(pairs: ContrastPair[]): ConstraintPlugin {
|
|
|
40
40
|
rule: "wcag-contrast",
|
|
41
41
|
level: "warn" as const,
|
|
42
42
|
where: p.where,
|
|
43
|
+
involvedTokens: [p.fg, p.bg],
|
|
43
44
|
message: `Unparseable color(s): fg="${fgStr}" bg="${bgStr}" backdrop="${backdropStr}"`
|
|
44
45
|
});
|
|
45
46
|
continue;
|
|
@@ -60,6 +61,11 @@ export function WcagContrastPlugin(pairs: ContrastPair[]): ConstraintPlugin {
|
|
|
60
61
|
rule: "wcag-contrast",
|
|
61
62
|
level: "error" as const,
|
|
62
63
|
where: p.where,
|
|
64
|
+
involvedTokens: [p.fg, p.bg],
|
|
65
|
+
metadata: {
|
|
66
|
+
actual: Number(ratio.toFixed(2)),
|
|
67
|
+
required: p.min
|
|
68
|
+
},
|
|
63
69
|
message: `Contrast ${ratio.toFixed(2)}:1 < ${p.min}:1`
|
|
64
70
|
});
|
|
65
71
|
}
|
package/core/dtcg.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTCG 2025.10 stable-spec value normalization.
|
|
3
|
+
*
|
|
4
|
+
* The stable spec (2025-10-28) made color and dimension tokens **structured
|
|
5
|
+
* objects** rather than CSS strings — e.g. Figma's native export emits
|
|
6
|
+
* { "colorSpace": "srgb", "components": [0.53, 0.53, 0.53], "alpha": 1, "hex": "#888888" }
|
|
7
|
+
* and
|
|
8
|
+
* { "value": 16, "unit": "px" }.
|
|
9
|
+
*
|
|
10
|
+
* The engine and constraint plugins (WCAG, threshold, monotonic) consume the
|
|
11
|
+
* legacy string/number forms. We normalize structured values to those forms in
|
|
12
|
+
* ONE place — the flatten boundary — so `core/color.ts`'s verified math stays
|
|
13
|
+
* untouched and string aliases (`"{dot.path}"`) keep flowing through unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Non-sRGB color spaces are deliberately NOT coerced into sRGB math: we refuse
|
|
16
|
+
* to treat e.g. display-p3 components as sRGB (that would silently corrupt
|
|
17
|
+
* contrast). Without a `hex` fallback they normalize to a sentinel string that
|
|
18
|
+
* the color parser rejects, producing an explicit "Unparseable color(s)"
|
|
19
|
+
* warning naming the color space — never a wrong-but-silent ratio.
|
|
20
|
+
*/
|
|
21
|
+
export type DtcgColorValue = {
|
|
22
|
+
colorSpace?: string;
|
|
23
|
+
components?: number[];
|
|
24
|
+
alpha?: number;
|
|
25
|
+
hex?: string;
|
|
26
|
+
};
|
|
27
|
+
export type DtcgDimensionValue = {
|
|
28
|
+
value?: number;
|
|
29
|
+
unit?: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Normalize a raw DTCG `$value` to the string/number form the engine expects.
|
|
33
|
+
* Strings (including `"{alias}"` references) and numbers pass through untouched.
|
|
34
|
+
* Structured objects are converted; unrecognized objects (composite types such
|
|
35
|
+
* as typography/shadow — out of scope) become a non-crashing sentinel string.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeDtcgValue(raw: unknown, type?: string): string | number;
|
|
38
|
+
//# sourceMappingURL=dtcg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dtcg.d.ts","sourceRoot":"","sources":["dtcg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAsDF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAU/E"}
|
package/core/dtcg.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTCG 2025.10 stable-spec value normalization.
|
|
3
|
+
*
|
|
4
|
+
* The stable spec (2025-10-28) made color and dimension tokens **structured
|
|
5
|
+
* objects** rather than CSS strings — e.g. Figma's native export emits
|
|
6
|
+
* { "colorSpace": "srgb", "components": [0.53, 0.53, 0.53], "alpha": 1, "hex": "#888888" }
|
|
7
|
+
* and
|
|
8
|
+
* { "value": 16, "unit": "px" }.
|
|
9
|
+
*
|
|
10
|
+
* The engine and constraint plugins (WCAG, threshold, monotonic) consume the
|
|
11
|
+
* legacy string/number forms. We normalize structured values to those forms in
|
|
12
|
+
* ONE place — the flatten boundary — so `core/color.ts`'s verified math stays
|
|
13
|
+
* untouched and string aliases (`"{dot.path}"`) keep flowing through unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Non-sRGB color spaces are deliberately NOT coerced into sRGB math: we refuse
|
|
16
|
+
* to treat e.g. display-p3 components as sRGB (that would silently corrupt
|
|
17
|
+
* contrast). Without a `hex` fallback they normalize to a sentinel string that
|
|
18
|
+
* the color parser rejects, producing an explicit "Unparseable color(s)"
|
|
19
|
+
* warning naming the color space — never a wrong-but-silent ratio.
|
|
20
|
+
*/
|
|
21
|
+
function toByte(c) {
|
|
22
|
+
return Math.max(0, Math.min(255, Math.round(c * 255)));
|
|
23
|
+
}
|
|
24
|
+
function srgbComponentsToCss(c) {
|
|
25
|
+
if (!Array.isArray(c.components) || c.components.length < 3)
|
|
26
|
+
return null;
|
|
27
|
+
const [r, g, b] = c.components;
|
|
28
|
+
if (![r, g, b].every((n) => typeof n === 'number' && Number.isFinite(n)))
|
|
29
|
+
return null;
|
|
30
|
+
const a = typeof c.alpha === 'number' ? c.alpha : 1;
|
|
31
|
+
const R = toByte(r);
|
|
32
|
+
const G = toByte(g);
|
|
33
|
+
const B = toByte(b);
|
|
34
|
+
if (a >= 1) {
|
|
35
|
+
const hx = (n) => n.toString(16).padStart(2, '0');
|
|
36
|
+
return `#${hx(R)}${hx(G)}${hx(B)}`;
|
|
37
|
+
}
|
|
38
|
+
return `rgba(${R}, ${G}, ${B}, ${a})`;
|
|
39
|
+
}
|
|
40
|
+
function normalizeColor(v) {
|
|
41
|
+
// Prefer the spec's `hex` convenience field when present (works for any space).
|
|
42
|
+
if (typeof v.hex === 'string' && v.hex.trim())
|
|
43
|
+
return v.hex.trim();
|
|
44
|
+
// Only gamma-encoded sRGB components map 1:1 to the existing color math.
|
|
45
|
+
if ((v.colorSpace ?? '').toLowerCase() === 'srgb') {
|
|
46
|
+
const css = srgbComponentsToCss(v);
|
|
47
|
+
if (css)
|
|
48
|
+
return css;
|
|
49
|
+
}
|
|
50
|
+
// Unsupported space with no hex fallback → sentinel the parser rejects.
|
|
51
|
+
return `<unsupported colorSpace: ${v.colorSpace ?? 'unknown'}>`;
|
|
52
|
+
}
|
|
53
|
+
function normalizeDimension(v) {
|
|
54
|
+
if (typeof v.value !== 'number' || !Number.isFinite(v.value)) {
|
|
55
|
+
return '<invalid dimension>';
|
|
56
|
+
}
|
|
57
|
+
const unit = typeof v.unit === 'string' && v.unit ? v.unit : 'px';
|
|
58
|
+
return `${v.value}${unit}`;
|
|
59
|
+
}
|
|
60
|
+
function isColorObject(obj, type) {
|
|
61
|
+
return ((type ?? '').toLowerCase() === 'color' ||
|
|
62
|
+
'colorSpace' in obj ||
|
|
63
|
+
'components' in obj ||
|
|
64
|
+
'hex' in obj);
|
|
65
|
+
}
|
|
66
|
+
function isDimensionObject(obj, type) {
|
|
67
|
+
return (type ?? '').toLowerCase() === 'dimension' || ('value' in obj && 'unit' in obj);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Normalize a raw DTCG `$value` to the string/number form the engine expects.
|
|
71
|
+
* Strings (including `"{alias}"` references) and numbers pass through untouched.
|
|
72
|
+
* Structured objects are converted; unrecognized objects (composite types such
|
|
73
|
+
* as typography/shadow — out of scope) become a non-crashing sentinel string.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeDtcgValue(raw, type) {
|
|
76
|
+
if (typeof raw === 'string' || typeof raw === 'number')
|
|
77
|
+
return raw;
|
|
78
|
+
if (raw && typeof raw === 'object') {
|
|
79
|
+
const obj = raw;
|
|
80
|
+
if (isColorObject(obj, type))
|
|
81
|
+
return normalizeColor(obj);
|
|
82
|
+
if (isDimensionObject(obj, type))
|
|
83
|
+
return normalizeDimension(obj);
|
|
84
|
+
return `<unsupported $value: ${(type ?? 'object').toLowerCase()}>`;
|
|
85
|
+
}
|
|
86
|
+
// null / undefined / boolean — return an empty string so nothing downstream crashes.
|
|
87
|
+
return '';
|
|
88
|
+
}
|
package/core/dtcg.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTCG 2025.10 stable-spec value normalization.
|
|
3
|
+
*
|
|
4
|
+
* The stable spec (2025-10-28) made color and dimension tokens **structured
|
|
5
|
+
* objects** rather than CSS strings — e.g. Figma's native export emits
|
|
6
|
+
* { "colorSpace": "srgb", "components": [0.53, 0.53, 0.53], "alpha": 1, "hex": "#888888" }
|
|
7
|
+
* and
|
|
8
|
+
* { "value": 16, "unit": "px" }.
|
|
9
|
+
*
|
|
10
|
+
* The engine and constraint plugins (WCAG, threshold, monotonic) consume the
|
|
11
|
+
* legacy string/number forms. We normalize structured values to those forms in
|
|
12
|
+
* ONE place — the flatten boundary — so `core/color.ts`'s verified math stays
|
|
13
|
+
* untouched and string aliases (`"{dot.path}"`) keep flowing through unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Non-sRGB color spaces are deliberately NOT coerced into sRGB math: we refuse
|
|
16
|
+
* to treat e.g. display-p3 components as sRGB (that would silently corrupt
|
|
17
|
+
* contrast). Without a `hex` fallback they normalize to a sentinel string that
|
|
18
|
+
* the color parser rejects, producing an explicit "Unparseable color(s)"
|
|
19
|
+
* warning naming the color space — never a wrong-but-silent ratio.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type DtcgColorValue = {
|
|
23
|
+
colorSpace?: string;
|
|
24
|
+
components?: number[];
|
|
25
|
+
alpha?: number;
|
|
26
|
+
hex?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type DtcgDimensionValue = {
|
|
30
|
+
value?: number;
|
|
31
|
+
unit?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function toByte(c: number): number {
|
|
35
|
+
return Math.max(0, Math.min(255, Math.round(c * 255)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function srgbComponentsToCss(c: DtcgColorValue): string | null {
|
|
39
|
+
if (!Array.isArray(c.components) || c.components.length < 3) return null;
|
|
40
|
+
const [r, g, b] = c.components;
|
|
41
|
+
if (![r, g, b].every((n) => typeof n === 'number' && Number.isFinite(n))) return null;
|
|
42
|
+
const a = typeof c.alpha === 'number' ? c.alpha : 1;
|
|
43
|
+
const R = toByte(r);
|
|
44
|
+
const G = toByte(g);
|
|
45
|
+
const B = toByte(b);
|
|
46
|
+
if (a >= 1) {
|
|
47
|
+
const hx = (n: number) => n.toString(16).padStart(2, '0');
|
|
48
|
+
return `#${hx(R)}${hx(G)}${hx(B)}`;
|
|
49
|
+
}
|
|
50
|
+
return `rgba(${R}, ${G}, ${B}, ${a})`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeColor(v: DtcgColorValue): string {
|
|
54
|
+
// Prefer the spec's `hex` convenience field when present (works for any space).
|
|
55
|
+
if (typeof v.hex === 'string' && v.hex.trim()) return v.hex.trim();
|
|
56
|
+
// Only gamma-encoded sRGB components map 1:1 to the existing color math.
|
|
57
|
+
if ((v.colorSpace ?? '').toLowerCase() === 'srgb') {
|
|
58
|
+
const css = srgbComponentsToCss(v);
|
|
59
|
+
if (css) return css;
|
|
60
|
+
}
|
|
61
|
+
// Unsupported space with no hex fallback → sentinel the parser rejects.
|
|
62
|
+
return `<unsupported colorSpace: ${v.colorSpace ?? 'unknown'}>`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeDimension(v: DtcgDimensionValue): string {
|
|
66
|
+
if (typeof v.value !== 'number' || !Number.isFinite(v.value)) {
|
|
67
|
+
return '<invalid dimension>';
|
|
68
|
+
}
|
|
69
|
+
const unit = typeof v.unit === 'string' && v.unit ? v.unit : 'px';
|
|
70
|
+
return `${v.value}${unit}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isColorObject(obj: Record<string, unknown>, type?: string): boolean {
|
|
74
|
+
return (
|
|
75
|
+
(type ?? '').toLowerCase() === 'color' ||
|
|
76
|
+
'colorSpace' in obj ||
|
|
77
|
+
'components' in obj ||
|
|
78
|
+
'hex' in obj
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isDimensionObject(obj: Record<string, unknown>, type?: string): boolean {
|
|
83
|
+
return (type ?? '').toLowerCase() === 'dimension' || ('value' in obj && 'unit' in obj);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Normalize a raw DTCG `$value` to the string/number form the engine expects.
|
|
88
|
+
* Strings (including `"{alias}"` references) and numbers pass through untouched.
|
|
89
|
+
* Structured objects are converted; unrecognized objects (composite types such
|
|
90
|
+
* as typography/shadow — out of scope) become a non-crashing sentinel string.
|
|
91
|
+
*/
|
|
92
|
+
export function normalizeDtcgValue(raw: unknown, type?: string): string | number {
|
|
93
|
+
if (typeof raw === 'string' || typeof raw === 'number') return raw;
|
|
94
|
+
if (raw && typeof raw === 'object') {
|
|
95
|
+
const obj = raw as Record<string, unknown>;
|
|
96
|
+
if (isColorObject(obj, type)) return normalizeColor(obj as DtcgColorValue);
|
|
97
|
+
if (isDimensionObject(obj, type)) return normalizeDimension(obj as DtcgDimensionValue);
|
|
98
|
+
return `<unsupported $value: ${(type ?? 'object').toLowerCase()}>`;
|
|
99
|
+
}
|
|
100
|
+
// null / undefined / boolean — return an empty string so nothing downstream crashes.
|
|
101
|
+
return '';
|
|
102
|
+
}
|
package/core/engine.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ export type ConstraintIssue = {
|
|
|
26
26
|
* this captures that reference edge.
|
|
27
27
|
*/
|
|
28
28
|
involvedEdges?: Array<[TokenId, TokenId]>;
|
|
29
|
+
/**
|
|
30
|
+
* Structured, rule-specific data for JSON consumers.
|
|
31
|
+
* For example, WCAG contrast issues can expose actual/required ratios here
|
|
32
|
+
* without forcing callers to parse human-readable messages.
|
|
33
|
+
*/
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
29
35
|
};
|
|
30
36
|
/**
|
|
31
37
|
* Constraint plugin interface.
|
package/core/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAE3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAE3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,CAAC;CACvE,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AAE/C,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,OAAO,CAA0B;gBAE7B,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IASrF,GAAG,CAAC,MAAM,EAAE,gBAAgB;IAE5B,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS;IACxC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU;IAElC;;;;;;;OAOG;IACH,SAAS,IAAI,OAAO,EAAE;IAItB;;;;;;;OAOG;IACH,aAAa,IAAI,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC;IAI5C,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAatC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC;IAIjC,2FAA2F;IAC3F,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU;;;;;CAQtC"}
|
package/core/engine.ts
CHANGED
|
@@ -29,6 +29,13 @@ export type ConstraintIssue = {
|
|
|
29
29
|
* this captures that reference edge.
|
|
30
30
|
*/
|
|
31
31
|
involvedEdges?: Array<[TokenId, TokenId]>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Structured, rule-specific data for JSON consumers.
|
|
35
|
+
* For example, WCAG contrast issues can expose actual/required ratios here
|
|
36
|
+
* without forcing callers to parse human-readable messages.
|
|
37
|
+
*/
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
32
39
|
};
|
|
33
40
|
|
|
34
41
|
/**
|
package/core/flatten.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export type TokenId = string;
|
|
2
2
|
export type TokenValue = string | number;
|
|
3
|
+
export type DtcgStructuredValue = Record<string, unknown>;
|
|
3
4
|
export type TokenNode = {
|
|
4
5
|
$type?: string;
|
|
5
|
-
$value?: TokenValue;
|
|
6
|
-
|
|
6
|
+
$value?: TokenValue | DtcgStructuredValue;
|
|
7
|
+
$extensions?: DtcgStructuredValue;
|
|
8
|
+
[k: string]: TokenNode | string | number | DtcgStructuredValue | undefined;
|
|
7
9
|
};
|
|
8
10
|
export type FlatToken = {
|
|
9
11
|
id: TokenId;
|
|
10
12
|
type: string;
|
|
11
13
|
value: TokenValue;
|
|
12
|
-
raw: TokenValue;
|
|
14
|
+
raw: TokenValue | DtcgStructuredValue;
|
|
13
15
|
refs: TokenId[];
|
|
14
16
|
};
|
|
15
17
|
export type FlattenResult = {
|
package/core/flatten.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["flatten.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["flatten.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAGzC,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1D,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,GAAG,mBAAmB,CAAC;IAC1C,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;CAC5E,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,UAAU,GAAG,mBAAmB,CAAC;IACtC,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;CAC5C,CAAC;AAKF,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,aAAa,CA4G5D"}
|