design-constraint-validator 1.0.0 → 2.0.1

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.
Files changed (121) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +229 -659
  3. package/adapters/README.md +46 -46
  4. package/adapters/css.ts +116 -116
  5. package/adapters/decisionthemes.d.ts +44 -0
  6. package/adapters/decisionthemes.d.ts.map +1 -0
  7. package/adapters/decisionthemes.js +35 -0
  8. package/adapters/decisionthemes.ts +59 -0
  9. package/adapters/js.ts +14 -14
  10. package/adapters/json.ts +45 -45
  11. package/cli/build-css.ts +32 -32
  12. package/cli/commands/build.ts +65 -65
  13. package/cli/commands/graph.d.ts.map +1 -1
  14. package/cli/commands/graph.js +26 -10
  15. package/cli/commands/graph.ts +180 -137
  16. package/cli/commands/index.ts +7 -7
  17. package/cli/commands/patch-apply.ts +80 -80
  18. package/cli/commands/patch.ts +22 -22
  19. package/cli/commands/set.d.ts.map +1 -1
  20. package/cli/commands/set.js +12 -4
  21. package/cli/commands/set.ts +239 -225
  22. package/cli/commands/utils.ts +50 -50
  23. package/cli/commands/validate.d.ts.map +1 -1
  24. package/cli/commands/validate.js +89 -33
  25. package/cli/commands/validate.ts +180 -115
  26. package/cli/commands/why.d.ts.map +1 -1
  27. package/cli/commands/why.js +86 -20
  28. package/cli/commands/why.ts +158 -46
  29. package/cli/config-schema.ts +27 -27
  30. package/cli/config.ts +35 -35
  31. package/cli/constraint-registry.d.ts +101 -0
  32. package/cli/constraint-registry.d.ts.map +1 -0
  33. package/cli/constraint-registry.js +225 -0
  34. package/cli/constraint-registry.ts +304 -0
  35. package/cli/constraints-loader.d.ts.map +1 -0
  36. package/cli/cross-axis-loader.d.ts +91 -0
  37. package/cli/cross-axis-loader.d.ts.map +1 -0
  38. package/cli/cross-axis-loader.js +222 -0
  39. package/cli/cross-axis-loader.ts +289 -0
  40. package/cli/dcv.js +4 -0
  41. package/cli/dcv.ts +111 -107
  42. package/cli/engine-helpers.d.ts.map +1 -1
  43. package/cli/graph-poset.ts +74 -74
  44. package/cli/json-output.d.ts +69 -0
  45. package/cli/json-output.d.ts.map +1 -0
  46. package/cli/json-output.js +109 -0
  47. package/cli/json-output.ts +184 -0
  48. package/cli/result.ts +27 -27
  49. package/cli/run.ts +54 -54
  50. package/cli/smoke-test.ts +40 -40
  51. package/cli/types.d.ts +6 -0
  52. package/cli/types.d.ts.map +1 -1
  53. package/cli/types.ts +84 -78
  54. package/cli/version-banner.d.ts +20 -0
  55. package/cli/version-banner.d.ts.map +1 -0
  56. package/cli/version-banner.js +49 -0
  57. package/cli/version-banner.ts +61 -0
  58. package/core/breakpoints.ts +50 -50
  59. package/core/cli-format.ts +31 -31
  60. package/core/color.ts +148 -148
  61. package/core/constraints/cross-axis.ts +114 -114
  62. package/core/constraints/monotonic-lightness.ts +38 -38
  63. package/core/constraints/monotonic.ts +74 -74
  64. package/core/constraints/threshold.ts +43 -43
  65. package/core/constraints/wcag.ts +70 -70
  66. package/core/cross-axis-config.d.ts.map +1 -1
  67. package/core/engine.d.ts +95 -0
  68. package/core/engine.d.ts.map +1 -1
  69. package/core/engine.js +22 -0
  70. package/core/engine.ts +167 -65
  71. package/core/flatten.ts +116 -116
  72. package/core/image-export.ts +48 -48
  73. package/core/index.d.ts +9 -30
  74. package/core/index.d.ts.map +1 -1
  75. package/core/index.js +7 -54
  76. package/core/index.ts +10 -72
  77. package/core/patch.ts +134 -134
  78. package/core/poset.ts +311 -311
  79. package/core/why.ts +63 -63
  80. package/package.json +96 -90
  81. package/themes/color.lg.order.json +15 -15
  82. package/themes/color.md.order.json +15 -15
  83. package/themes/color.order.json +15 -15
  84. package/themes/color.sm.order.json +15 -15
  85. package/themes/cross-axis.rules.json +35 -35
  86. package/themes/cross-axis.sm.rules.json +12 -12
  87. package/themes/layout.lg.order.json +18 -18
  88. package/themes/layout.md.order.json +18 -18
  89. package/themes/layout.order.json +18 -18
  90. package/themes/layout.sm.order.json +18 -18
  91. package/themes/spacing.order.json +14 -14
  92. package/themes/typography.lg.order.json +15 -15
  93. package/themes/typography.md.order.json +15 -15
  94. package/themes/typography.order.json +15 -15
  95. package/themes/typography.sm.order.json +15 -15
  96. package/cli/engine-helpers.d.ts +0 -8
  97. package/cli/engine-helpers.js +0 -70
  98. package/cli/engine-helpers.ts +0 -61
  99. package/core/cross-axis-config.d.ts +0 -5
  100. package/core/cross-axis-config.js +0 -144
  101. package/core/cross-axis-config.ts +0 -152
  102. package/dist/test-overrides-removal.json +0 -4
  103. package/dist/tmp.patch.json +0 -35
  104. package/tokens/overrides/base.json +0 -22
  105. package/tokens/overrides/lg.json +0 -20
  106. package/tokens/overrides/md.json +0 -16
  107. package/tokens/overrides/sm.json +0 -16
  108. package/tokens/overrides/viol.color.json +0 -6
  109. package/tokens/overrides/viol.typography.json +0 -6
  110. package/tokens/tokens.demo-violations.json +0 -116
  111. package/tokens/tokens.example.json +0 -128
  112. package/tokens/tokens.json +0 -67
  113. package/tokens/tokens.multi-violations.json +0 -21
  114. package/tokens/tokens.schema.d.ts +0 -2298
  115. package/tokens/tokens.schema.d.ts.map +0 -1
  116. package/tokens/tokens.schema.js +0 -148
  117. package/tokens/tokens.schema.ts +0 -196
  118. package/tokens/tokens.test.json +0 -38
  119. package/tokens/tokens.touch-violation.json +0 -8
  120. package/tokens/typography.classes.css +0 -11
  121. package/tokens/typography.css +0 -20
@@ -1,18 +1,18 @@
1
- {
2
- "$description": "Layout hierarchy order constraints - container widths grow, padding hierarchy, gutters ≤ paddings, control touch targets",
3
- "order": [
4
- ["layout.container.xs", "<=", "layout.container.sm"],
5
- ["layout.container.sm", "<=", "layout.container.md"],
6
- ["layout.container.md", "<=", "layout.container.lg"],
7
- ["layout.container.lg", "<=", "layout.container.xl"],
8
-
9
- ["layout.padding.page", ">=", "layout.padding.section"],
10
- ["layout.padding.section", ">=", "layout.padding.card"],
11
- ["layout.padding.card", ">=", "layout.padding.chip"],
12
-
13
- ["layout.grid.gutter", "<=", "layout.padding.section"],
14
- ["layout.grid.gutter", "<=", "layout.padding.card"],
15
-
16
- ["control.size.min", ">=", "layout.padding.card"]
17
- ]
18
- }
1
+ {
2
+ "$description": "Layout hierarchy order constraints - container widths grow, padding hierarchy, gutters ≤ paddings, control touch targets",
3
+ "order": [
4
+ ["layout.container.xs", "<=", "layout.container.sm"],
5
+ ["layout.container.sm", "<=", "layout.container.md"],
6
+ ["layout.container.md", "<=", "layout.container.lg"],
7
+ ["layout.container.lg", "<=", "layout.container.xl"],
8
+
9
+ ["layout.padding.page", ">=", "layout.padding.section"],
10
+ ["layout.padding.section", ">=", "layout.padding.card"],
11
+ ["layout.padding.card", ">=", "layout.padding.chip"],
12
+
13
+ ["layout.grid.gutter", "<=", "layout.padding.section"],
14
+ ["layout.grid.gutter", "<=", "layout.padding.card"],
15
+
16
+ ["control.size.min", ">=", "layout.padding.card"]
17
+ ]
18
+ }
@@ -1,18 +1,18 @@
1
- {
2
- "$description": "Layout hierarchy order constraints - SM breakpoint specific",
3
- "order": [
4
- ["layout.container.xs", "<=", "layout.container.sm"],
5
- ["layout.container.sm", "<=", "layout.container.md"],
6
- ["layout.container.md", "<=", "layout.container.lg"],
7
- ["layout.container.lg", "<=", "layout.container.xl"],
8
-
9
- ["layout.padding.page", ">=", "layout.padding.section"],
10
- ["layout.padding.section", ">=", "layout.padding.card"],
11
- ["layout.padding.card", ">=", "layout.padding.chip"],
12
-
13
- ["layout.grid.gutter", "<=", "layout.padding.section"],
14
- ["layout.grid.gutter", "<=", "layout.padding.card"],
15
-
16
- ["control.size.min", ">=", "layout.padding.card"]
17
- ]
18
- }
1
+ {
2
+ "$description": "Layout hierarchy order constraints - SM breakpoint specific",
3
+ "order": [
4
+ ["layout.container.xs", "<=", "layout.container.sm"],
5
+ ["layout.container.sm", "<=", "layout.container.md"],
6
+ ["layout.container.md", "<=", "layout.container.lg"],
7
+ ["layout.container.lg", "<=", "layout.container.xl"],
8
+
9
+ ["layout.padding.page", ">=", "layout.padding.section"],
10
+ ["layout.padding.section", ">=", "layout.padding.card"],
11
+ ["layout.padding.card", ">=", "layout.padding.chip"],
12
+
13
+ ["layout.grid.gutter", "<=", "layout.padding.section"],
14
+ ["layout.grid.gutter", "<=", "layout.padding.card"],
15
+
16
+ ["control.size.min", ">=", "layout.padding.card"]
17
+ ]
18
+ }
@@ -1,14 +1,14 @@
1
- {
2
- "$description": "Spacing scale order constraints - defines monotonic spacing hierarchy",
3
- "order": [
4
- ["size.spacing.12", ">=", "size.spacing.10"],
5
- ["size.spacing.10", ">=", "size.spacing.8"],
6
- ["size.spacing.8", ">=", "size.spacing.6"],
7
- ["size.spacing.6", ">=", "size.spacing.4"],
8
- ["size.spacing.4", ">=", "size.spacing.3"],
9
- ["size.spacing.3", ">=", "size.spacing.2-5"],
10
- ["size.spacing.2-5", ">=", "size.spacing.2"],
11
- ["size.spacing.2", ">=", "size.spacing.1"],
12
- ["size.spacing.1", ">=", "size.spacing.0-5"]
13
- ]
14
- }
1
+ {
2
+ "$description": "Spacing scale order constraints - defines monotonic spacing hierarchy",
3
+ "order": [
4
+ ["size.spacing.12", ">=", "size.spacing.10"],
5
+ ["size.spacing.10", ">=", "size.spacing.8"],
6
+ ["size.spacing.8", ">=", "size.spacing.6"],
7
+ ["size.spacing.6", ">=", "size.spacing.4"],
8
+ ["size.spacing.4", ">=", "size.spacing.3"],
9
+ ["size.spacing.3", ">=", "size.spacing.2-5"],
10
+ ["size.spacing.2-5", ">=", "size.spacing.2"],
11
+ ["size.spacing.2", ">=", "size.spacing.1"],
12
+ ["size.spacing.1", ">=", "size.spacing.0-5"]
13
+ ]
14
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "$description": "Typography hierarchy order constraints - LG breakpoint specific",
3
- "order": [
4
- ["typography.size.h1", ">=", "typography.size.h2"],
5
- ["typography.size.h2", ">=", "typography.size.h3"],
6
- ["typography.size.h3", ">=", "typography.size.h4"],
7
- ["typography.size.h4", ">=", "typography.size.h5"],
8
- ["typography.size.h5", ">=", "typography.size.h6"],
9
-
10
- ["typography.size.xl", ">=", "typography.size.lg"],
11
- ["typography.size.lg", ">=", "typography.size.base"],
12
- ["typography.size.base", ">=", "typography.size.sm"],
13
- ["typography.size.sm", ">=", "typography.size.xs"]
14
- ]
15
- }
1
+ {
2
+ "$description": "Typography hierarchy order constraints - LG breakpoint specific",
3
+ "order": [
4
+ ["typography.size.h1", ">=", "typography.size.h2"],
5
+ ["typography.size.h2", ">=", "typography.size.h3"],
6
+ ["typography.size.h3", ">=", "typography.size.h4"],
7
+ ["typography.size.h4", ">=", "typography.size.h5"],
8
+ ["typography.size.h5", ">=", "typography.size.h6"],
9
+
10
+ ["typography.size.xl", ">=", "typography.size.lg"],
11
+ ["typography.size.lg", ">=", "typography.size.base"],
12
+ ["typography.size.base", ">=", "typography.size.sm"],
13
+ ["typography.size.sm", ">=", "typography.size.xs"]
14
+ ]
15
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "$description": "Typography hierarchy order constraints - MD breakpoint specific",
3
- "order": [
4
- ["typography.size.h1", ">=", "typography.size.h2"],
5
- ["typography.size.h2", ">=", "typography.size.h3"],
6
- ["typography.size.h3", ">=", "typography.size.h4"],
7
- ["typography.size.h4", ">=", "typography.size.h5"],
8
- ["typography.size.h5", ">=", "typography.size.h6"],
9
-
10
- ["typography.size.xl", ">=", "typography.size.lg"],
11
- ["typography.size.lg", ">=", "typography.size.base"],
12
- ["typography.size.base", ">=", "typography.size.sm"],
13
- ["typography.size.sm", ">=", "typography.size.xs"]
14
- ]
15
- }
1
+ {
2
+ "$description": "Typography hierarchy order constraints - MD breakpoint specific",
3
+ "order": [
4
+ ["typography.size.h1", ">=", "typography.size.h2"],
5
+ ["typography.size.h2", ">=", "typography.size.h3"],
6
+ ["typography.size.h3", ">=", "typography.size.h4"],
7
+ ["typography.size.h4", ">=", "typography.size.h5"],
8
+ ["typography.size.h5", ">=", "typography.size.h6"],
9
+
10
+ ["typography.size.xl", ">=", "typography.size.lg"],
11
+ ["typography.size.lg", ">=", "typography.size.base"],
12
+ ["typography.size.base", ">=", "typography.size.sm"],
13
+ ["typography.size.sm", ">=", "typography.size.xs"]
14
+ ]
15
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "$description": "Typography hierarchy order constraints - defines that h1 ≥ h2 ≥ ... ≥ h6 by font size",
3
- "order": [
4
- ["typography.size.h1", ">=", "typography.size.h2"],
5
- ["typography.size.h2", ">=", "typography.size.h3"],
6
- ["typography.size.h3", ">=", "typography.size.h4"],
7
- ["typography.size.h4", ">=", "typography.size.h5"],
8
- ["typography.size.h5", ">=", "typography.size.h6"],
9
-
10
- ["typography.size.xl", ">=", "typography.size.lg"],
11
- ["typography.size.lg", ">=", "typography.size.base"],
12
- ["typography.size.base", ">=", "typography.size.sm"],
13
- ["typography.size.sm", ">=", "typography.size.xs"]
14
- ]
15
- }
1
+ {
2
+ "$description": "Typography hierarchy order constraints - defines that h1 ≥ h2 ≥ ... ≥ h6 by font size",
3
+ "order": [
4
+ ["typography.size.h1", ">=", "typography.size.h2"],
5
+ ["typography.size.h2", ">=", "typography.size.h3"],
6
+ ["typography.size.h3", ">=", "typography.size.h4"],
7
+ ["typography.size.h4", ">=", "typography.size.h5"],
8
+ ["typography.size.h5", ">=", "typography.size.h6"],
9
+
10
+ ["typography.size.xl", ">=", "typography.size.lg"],
11
+ ["typography.size.lg", ">=", "typography.size.base"],
12
+ ["typography.size.base", ">=", "typography.size.sm"],
13
+ ["typography.size.sm", ">=", "typography.size.xs"]
14
+ ]
15
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "$description": "Typography hierarchy order constraints - SM breakpoint specific",
3
- "order": [
4
- ["typography.size.h1", ">=", "typography.size.h2"],
5
- ["typography.size.h2", ">=", "typography.size.h3"],
6
- ["typography.size.h3", ">=", "typography.size.h4"],
7
- ["typography.size.h4", ">=", "typography.size.h5"],
8
- ["typography.size.h5", ">=", "typography.size.h6"],
9
-
10
- ["typography.size.xl", ">=", "typography.size.lg"],
11
- ["typography.size.lg", ">=", "typography.size.base"],
12
- ["typography.size.base", ">=", "typography.size.sm"],
13
- ["typography.size.sm", ">=", "typography.size.xs"]
14
- ]
15
- }
1
+ {
2
+ "$description": "Typography hierarchy order constraints - SM breakpoint specific",
3
+ "order": [
4
+ ["typography.size.h1", ">=", "typography.size.h2"],
5
+ ["typography.size.h2", ">=", "typography.size.h3"],
6
+ ["typography.size.h3", ">=", "typography.size.h4"],
7
+ ["typography.size.h4", ">=", "typography.size.h5"],
8
+ ["typography.size.h5", ">=", "typography.size.h6"],
9
+
10
+ ["typography.size.xl", ">=", "typography.size.lg"],
11
+ ["typography.size.lg", ">=", "typography.size.base"],
12
+ ["typography.size.base", ">=", "typography.size.sm"],
13
+ ["typography.size.sm", ">=", "typography.size.xs"]
14
+ ]
15
+ }
@@ -1,8 +0,0 @@
1
- import { type TokenNode } from '../core/flatten.js';
2
- import { Engine } from '../core/engine.js';
3
- import { loadTokensWithBreakpoint, type Breakpoint } from '../core/breakpoints.js';
4
- import type { DcvConfig } from './types.js';
5
- export declare function createEngine(tokensRoot: TokenNode, config?: DcvConfig): Engine;
6
- export declare function createValidationEngine(tokensRoot: TokenNode, bp: Breakpoint | undefined, config: DcvConfig): Engine;
7
- export { loadTokensWithBreakpoint };
8
- //# sourceMappingURL=engine-helpers.d.ts.map
@@ -1,70 +0,0 @@
1
- import { flattenTokens } from '../core/flatten.js';
2
- import { Engine } from '../core/engine.js';
3
- import { MonotonicPlugin, parseSize as parseSizePx } from '../core/constraints/monotonic.js';
4
- import { MonotonicLightness } from '../core/constraints/monotonic-lightness.js';
5
- import { WcagContrastPlugin } from '../core/constraints/wcag.js';
6
- import { loadOrders as loadOrdersBP, loadTokensWithBreakpoint } from '../core/breakpoints.js';
7
- export function createEngine(tokensRoot, config = {}) {
8
- const { flat, edges } = flattenTokens(tokensRoot);
9
- const init = {};
10
- for (const [id, token] of Object.entries(flat))
11
- init[id] = token.value;
12
- const engine = new Engine(init, edges);
13
- function loadOrders(path) {
14
- try {
15
- // eslint-disable-next-line @typescript-eslint/no-require-imports
16
- return JSON.parse(require('node:fs').readFileSync(path, 'utf8')).order;
17
- }
18
- catch {
19
- return [];
20
- }
21
- }
22
- const typOrders = loadOrders('themes/typography.order.json');
23
- const spacingOrders = loadOrders('themes/spacing.order.json');
24
- const layoutOrders = loadOrders('themes/layout.order.json');
25
- const colorOrders = loadOrders('themes/color.order.json');
26
- if (typOrders.length)
27
- engine.use(MonotonicPlugin(typOrders, parseSizePx, 'monotonic-typography'));
28
- if (spacingOrders.length)
29
- engine.use(MonotonicPlugin(spacingOrders, parseSizePx, 'monotonic-spacing'));
30
- if (layoutOrders.length)
31
- engine.use(MonotonicPlugin(layoutOrders, parseSizePx, 'monotonic-layout'));
32
- if (colorOrders.length)
33
- engine.use(MonotonicLightness(colorOrders));
34
- if (config.constraints?.wcag) {
35
- const wcagRules = config.constraints.wcag.map((r) => ({ fg: r.foreground, bg: r.background, min: r.ratio || 4.5, where: r.description || 'Unknown' }));
36
- engine.use(WcagContrastPlugin(wcagRules));
37
- }
38
- const defaultWcagPairs = [
39
- { fg: 'color.role.text.default', bg: 'color.role.bg.surface', min: 4.5, where: 'Body text on surface' },
40
- { fg: 'color.role.accent.default', bg: 'color.role.bg.surface', min: 3.0, where: 'Accent on surface' },
41
- { fg: 'color.role.focus.ring', bg: 'color.role.bg.surface', min: 3.0, where: 'Focus ring on surface', backdrop: '#ffffff' }
42
- ];
43
- engine.use(WcagContrastPlugin(defaultWcagPairs));
44
- return engine;
45
- }
46
- export function createValidationEngine(tokensRoot, bp, config) {
47
- const { flat, edges } = flattenTokens(tokensRoot);
48
- const init = {};
49
- for (const t of Object.values(flat))
50
- init[t.id] = t.value;
51
- const engine = new Engine(init, edges);
52
- const typ = loadOrdersBP('typography', bp);
53
- const spc = loadOrdersBP('spacing', bp);
54
- const lay = loadOrdersBP('layout', bp);
55
- const col = loadOrdersBP('color', bp);
56
- if (typ.length)
57
- engine.use(MonotonicPlugin(typ, parseSizePx, 'monotonic-typography'));
58
- if (spc.length)
59
- engine.use(MonotonicPlugin(spc, parseSizePx, 'monotonic-spacing'));
60
- if (lay.length)
61
- engine.use(MonotonicPlugin(lay, parseSizePx, 'monotonic-layout'));
62
- if (col.length)
63
- engine.use(MonotonicLightness(col));
64
- if (config.constraints?.wcag) {
65
- const wcagRules = config.constraints.wcag.map((rule) => ({ fg: rule.foreground, bg: rule.background, min: rule.ratio || 4.5, where: rule.description || 'Unknown' }));
66
- engine.use(WcagContrastPlugin(wcagRules));
67
- }
68
- return engine;
69
- }
70
- export { loadTokensWithBreakpoint };
@@ -1,61 +0,0 @@
1
- import { flattenTokens, type TokenNode, type FlatToken } from '../core/flatten.js';
2
- import { Engine } from '../core/engine.js';
3
- import { MonotonicPlugin, parseSize as parseSizePx } from '../core/constraints/monotonic.js';
4
- import { MonotonicLightness } from '../core/constraints/monotonic-lightness.js';
5
- import { WcagContrastPlugin } from '../core/constraints/wcag.js';
6
- import { loadOrders as loadOrdersBP, loadTokensWithBreakpoint, type Breakpoint } from '../core/breakpoints.js';
7
- import type { DcvConfig } from './types.js';
8
-
9
- export function createEngine(tokensRoot: TokenNode, config: DcvConfig = {}): Engine {
10
- const { flat, edges } = flattenTokens(tokensRoot);
11
- const init: Record<string, string | number> = {};
12
- for (const [id, token] of Object.entries(flat)) init[id] = (token as FlatToken).value;
13
- const engine = new Engine(init, edges);
14
- function loadOrders(path: string) {
15
- try {
16
- // eslint-disable-next-line @typescript-eslint/no-require-imports
17
- return JSON.parse(require('node:fs').readFileSync(path, 'utf8')).order as [string, '<='|'>=', string][];
18
- } catch { return []; }
19
- }
20
- const typOrders = loadOrders('themes/typography.order.json');
21
- const spacingOrders = loadOrders('themes/spacing.order.json');
22
- const layoutOrders = loadOrders('themes/layout.order.json');
23
- const colorOrders = loadOrders('themes/color.order.json');
24
- if (typOrders.length) engine.use(MonotonicPlugin(typOrders, parseSizePx, 'monotonic-typography'));
25
- if (spacingOrders.length) engine.use(MonotonicPlugin(spacingOrders, parseSizePx, 'monotonic-spacing'));
26
- if (layoutOrders.length) engine.use(MonotonicPlugin(layoutOrders, parseSizePx, 'monotonic-layout'));
27
- if (colorOrders.length) engine.use(MonotonicLightness(colorOrders));
28
- if (config.constraints?.wcag) {
29
- const wcagRules = config.constraints.wcag.map((r: any) => ({ fg: r.foreground, bg: r.background, min: r.ratio || 4.5, where: r.description || 'Unknown' }));
30
- engine.use(WcagContrastPlugin(wcagRules));
31
- }
32
- const defaultWcagPairs = [
33
- { fg: 'color.role.text.default', bg: 'color.role.bg.surface', min: 4.5, where: 'Body text on surface' },
34
- { fg: 'color.role.accent.default', bg: 'color.role.bg.surface', min: 3.0, where: 'Accent on surface' },
35
- { fg: 'color.role.focus.ring', bg: 'color.role.bg.surface', min: 3.0, where: 'Focus ring on surface', backdrop: '#ffffff' }
36
- ];
37
- engine.use(WcagContrastPlugin(defaultWcagPairs));
38
- return engine;
39
- }
40
-
41
- export function createValidationEngine(tokensRoot: TokenNode, bp: Breakpoint | undefined, config: DcvConfig): Engine {
42
- const { flat, edges } = flattenTokens(tokensRoot);
43
- const init: Record<string, string | number> = {};
44
- for (const t of Object.values(flat)) init[(t as FlatToken).id] = (t as FlatToken).value;
45
- const engine = new Engine(init, edges);
46
- const typ = loadOrdersBP('typography', bp);
47
- const spc = loadOrdersBP('spacing', bp);
48
- const lay = loadOrdersBP('layout', bp);
49
- const col = loadOrdersBP('color', bp);
50
- if (typ.length) engine.use(MonotonicPlugin(typ, parseSizePx, 'monotonic-typography'));
51
- if (spc.length) engine.use(MonotonicPlugin(spc, parseSizePx, 'monotonic-spacing'));
52
- if (lay.length) engine.use(MonotonicPlugin(lay, parseSizePx, 'monotonic-layout'));
53
- if (col.length) engine.use(MonotonicLightness(col));
54
- if (config.constraints?.wcag) {
55
- const wcagRules = config.constraints.wcag.map((rule: any) => ({ fg: rule.foreground, bg: rule.background, min: rule.ratio || 4.5, where: rule.description || 'Unknown' }));
56
- engine.use(WcagContrastPlugin(wcagRules));
57
- }
58
- return engine;
59
- }
60
-
61
- export { loadTokensWithBreakpoint };
@@ -1,5 +0,0 @@
1
- export declare function loadCrossAxisPlugin(path: string, bp?: string, opts?: {
2
- debug?: boolean;
3
- knownIds?: Set<string>;
4
- }): import("./engine.js").ConstraintPlugin;
5
- //# sourceMappingURL=cross-axis-config.d.ts.map
@@ -1,144 +0,0 @@
1
- import fs from "node:fs";
2
- import { CrossAxisPlugin } from "./constraints/cross-axis.js";
3
- export function loadCrossAxisPlugin(path, bp, opts) {
4
- const debug = !!opts?.debug;
5
- const known = opts?.knownIds ?? new Set();
6
- const log = (...args) => { if (debug)
7
- console.log("[cross-axis]", ...args); };
8
- if (!fs.existsSync(path)) {
9
- log(`no rules file at ${path} (bp=${bp ?? "global"})`);
10
- return CrossAxisPlugin([], bp);
11
- }
12
- const raw = JSON.parse(fs.readFileSync(path, "utf8"));
13
- const rules = [];
14
- const unknownIds = new Set();
15
- const skipped = [];
16
- // Fuzzy suggestion helpers (lightweight Levenshtein)
17
- function levenshtein(a, b) {
18
- const dp = Array(b.length + 1).fill(0).map((_, j) => j);
19
- for (let i = 1; i <= a.length; i++) {
20
- let prev = i - 1, cur = i;
21
- for (let j = 1; j <= b.length; j++) {
22
- const tmp = cur;
23
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
24
- cur = Math.min(dp[j] + 1, cur + 1, prev + cost);
25
- dp[j] = tmp;
26
- prev = tmp;
27
- }
28
- dp[b.length] = cur;
29
- }
30
- return dp[b.length];
31
- }
32
- function suggest(id, k = 3) {
33
- return [...known].map(c => ({ id: c, d: levenshtein(id, c) }))
34
- .sort((a, b) => a.d - b.d)
35
- .slice(0, k);
36
- }
37
- const needId = (id) => {
38
- if (!id)
39
- return false;
40
- if (!known.has(id)) {
41
- unknownIds.add(id);
42
- }
43
- return true;
44
- };
45
- for (const r of raw.rules || []) {
46
- if (r.bp && bp && r.bp !== bp) {
47
- continue;
48
- }
49
- if (r.bp && !bp) { // rule targets specific breakpoint; skip in global run
50
- continue;
51
- }
52
- try {
53
- if (r.when && r.require) {
54
- // Validate IDs
55
- needId(r.when.id);
56
- needId(r.require.id);
57
- if (r.require.ref)
58
- needId(r.require.ref);
59
- rules.push({
60
- id: r.id, level: r.level, where: r.where,
61
- when: { id: r.when.id, test: makeOp(r.when.op, r.when.value) },
62
- require: {
63
- id: r.require.id,
64
- test: (v, ctx) => {
65
- const rhs = valueOrRef(ctx, r.require.ref, r.require.fallback);
66
- return cmp(v, rhs, r.require.op);
67
- },
68
- msg: (v, ctx) => {
69
- const rhs = valueOrRef(ctx, r.require.ref, r.require.fallback);
70
- return `${r.require.id} ${prettyFail(r.require.op)} ${fmt(rhs)} (was ${fmt(v)})`;
71
- }
72
- }
73
- });
74
- }
75
- else if (r.compare) {
76
- needId(r.compare.a);
77
- needId(r.compare.b);
78
- rules.push({
79
- id: r.id, level: r.level, where: r.where,
80
- when: { id: r.compare.a, test: () => true },
81
- require: {
82
- id: r.compare.a,
83
- test: (_, ctx) => {
84
- const a = ctx.getPx(r.compare.a) ?? NaN;
85
- const b = ctx.getPx(r.compare.b) ?? NaN;
86
- const delta = px(r.compare.delta ?? 0);
87
- if (Number.isNaN(a) || Number.isNaN(b))
88
- return true; // skip check if missing
89
- return cmp(a, b + delta, r.compare.op);
90
- },
91
- msg: (_, ctx) => {
92
- const a = ctx.getPx(r.compare.a);
93
- const b = ctx.getPx(r.compare.b);
94
- const delta = px(r.compare.delta ?? 0);
95
- return `${r.compare.a} ${prettyFail(r.compare.op)} ${fmt((b ?? 0) + delta)} (was ${fmt(a ?? NaN)})`;
96
- }
97
- }
98
- });
99
- }
100
- else {
101
- skipped.push({ id: r.id, reason: "neither when+require nor compare present" });
102
- }
103
- }
104
- catch (e) {
105
- skipped.push({ id: r.id, reason: `exception: ${e?.message ?? e}` });
106
- }
107
- }
108
- log(`loaded ${rules.length} rule(s) from ${path}${bp ? ` [bp=${bp}]` : ""}`);
109
- if (unknownIds.size) {
110
- log(`unknown ids referenced:`, [...unknownIds].join(", "));
111
- for (const u of unknownIds) {
112
- const s = suggest(u, 3);
113
- if (s.length)
114
- log(` did you mean: ${s.map(x => `${x.id} (d=${x.d})`).join(', ')}`);
115
- }
116
- }
117
- if (skipped.length) {
118
- for (const s of skipped)
119
- log(`skipped rule ${s.id ?? "(no id)"} — ${s.reason}`);
120
- }
121
- // Extra hint for common anchor pitfall
122
- for (const r of raw.rules || []) {
123
- if (r.require?.ref && !known.has(r.require.ref)) {
124
- log(`anchor missing: ${r.require.ref} → will use fallback=${JSON.stringify(r.require.fallback)} when evaluating`);
125
- }
126
- }
127
- return CrossAxisPlugin(rules, bp);
128
- }
129
- // helpers
130
- const px = (v) => typeof v === "number" ? v : parseFloat(String(v)) * (String(v).trim().endsWith("rem") ? 16 : 1);
131
- const cmp = (a, b, op) => op === ">=" ? a >= b : op === ">" ? a > b : op === "<=" ? a <= b : op === "<" ? a < b : op === "==" ? a === b : a !== b;
132
- const prettyFail = (op) => ({ ">=": "<", ">": "≤", "<=": ">", "<": "≥", "==": "≠", "!=": "=" }[op] || "≠");
133
- const fmt = (v) => Number.isFinite(Number(v)) ? `${Number(v)}px` : String(v);
134
- function valueOrRef(ctx, ref, fallback) {
135
- if (ref) {
136
- const v = ctx.getPx(ref);
137
- if (v != null)
138
- return v;
139
- }
140
- return typeof fallback === "number" ? fallback : px(fallback ?? 0);
141
- }
142
- function makeOp(op, rhs) {
143
- return (v) => cmp(v, rhs, op);
144
- }