@multiplatform.one/theme 7.34.3 → 7.35.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/package.json +20 -5
- package/src/audit/constraintAudit.spec.ts +274 -87
- package/src/audit/constraintAudit.ts +135 -120
- package/src/audit/constraintScope.spec.ts +1 -1
- package/src/audit/designTokens.ts +42 -0
- package/src/audit/themeMatrix.spec.ts +83 -2
- package/src/audit/themeMatrix.ts +33 -1
- package/src/figma/figmaTokens.spec.ts +26 -34
- package/src/figma/figmaTokens.ts +26 -50
- package/src/fonts/createFontLoader.ts +4 -1
- package/src/menuRow.ts +4 -1
- package/src/theme/FontKnobStyles.tsx +2 -1
- package/src/theme/Preset.stories.tsx +0 -1
- package/src/theme/Preset.tsx +6 -1
- package/src/theme/PresetContext.ts +4 -0
- package/src/theme/Surface.spec.tsx +114 -67
- package/src/theme/Surface.stories.tsx +8 -25
- package/src/theme/Surface.tsx +87 -43
- package/src/theme/accentFloor.spec.ts +37 -11
- package/src/theme/activateThemeConfig.ts +22 -6
- package/src/theme/animations/animations.spec.ts +62 -0
- package/src/theme/animations/css.ts +9 -35
- package/src/theme/animations/reactNative.ts +98 -25
- package/src/theme/boundaryFloor.spec.ts +57 -0
- package/src/theme/chartPalette.spec.ts +56 -27
- package/src/theme/chartPalette.ts +33 -28
- package/src/theme/colorRules.spec.ts +32 -72
- package/src/theme/cookies.spec.ts +49 -18
- package/src/theme/cookies.ts +36 -7
- package/src/theme/cookiesPersistence.spec.ts +5 -5
- package/src/theme/createDefaultThemeConfig.spec.ts +182 -20
- package/src/theme/createDefaultThemeConfig.ts +34 -28
- package/src/theme/createThemes.ts +169 -218
- package/src/theme/defaults/accent.ts +3 -35
- package/src/theme/defaults/base.ts +14 -53
- package/src/theme/defaults/builderOptions.ts +68 -85
- package/src/theme/defaults/categoryFonts.ts +10 -0
- package/src/theme/defaults/fonts.spec.ts +84 -4
- package/src/theme/defaults/fonts.ts +166 -124
- package/src/theme/defaults/house.ts +48 -0
- package/src/theme/defaults/index.ts +1 -0
- package/src/theme/defaults/neutralRamp.spec.ts +52 -47
- package/src/theme/devtools/ColorLineVisualizer.tsx +6 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.setMarkers.spec.tsx +13 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.tsx +14 -77
- package/src/theme/devtools/animationOptions.spec.ts +23 -37
- package/src/theme/devtools/animationOptions.ts +4 -6
- package/src/theme/devtools/buildSnapshot.ts +0 -3
- package/src/theme/devtools/knobIcons.tsx +4 -24
- package/src/theme/focusState.spec.ts +9 -30
- package/src/theme/index.ts +17 -0
- package/src/theme/intentFill.spec.ts +42 -0
- package/src/theme/knobs.ts +14 -42
- package/src/theme/layoutTokens.ts +15 -1
- package/src/theme/layoutTokensHooks.spec.tsx +8 -4
- package/src/theme/mergedTokens.ts +20 -0
- package/src/theme/motion.spec.ts +84 -0
- package/src/theme/motion.ts +53 -0
- package/src/theme/motionStops.ts +17 -0
- package/src/theme/motionTiming.ts +59 -0
- package/src/theme/mpo13Proof.spec.ts +43 -46
- package/src/theme/mpo19Proof.spec.ts +13 -14
- package/src/theme/namedInkFloor.spec.ts +60 -0
- package/src/theme/overlaySurfaces.tsx +330 -0
- package/src/theme/preset.types.ts +16 -0
- package/src/theme/presetPreview.ts +32 -0
- package/src/theme/presetRoot.spec.tsx +89 -0
- package/src/theme/presetStore.persistence.spec.ts +141 -0
- package/src/theme/presetStore.spec.ts +205 -0
- package/src/theme/presetStore.ts +286 -0
- package/src/theme/presetUserSync.spec.tsx +168 -0
- package/src/theme/presetUserSync.ts +47 -0
- package/src/theme/presets.spec.ts +22 -15
- package/src/theme/presets.ts +22 -10
- package/src/theme/radiusClass.spec.ts +60 -41
- package/src/theme/radiusClass.ts +17 -4
- package/src/theme/radiusClassTable.generated.ts +3 -3
- package/src/theme/readableColor.spec.ts +59 -152
- package/src/theme/readableColor.ts +34 -174
- package/src/theme/recipeInputs.ts +153 -295
- package/src/theme/recipes.ts +58 -18
- package/src/theme/resolveKnobs.spec.ts +528 -588
- package/src/theme/resolveKnobs.ts +250 -325
- package/src/theme/scale/ScaleStyles.native.tsx +19 -0
- package/src/theme/scale/ScaleStyles.tsx +48 -0
- package/src/theme/scale/activeScale.ts +30 -0
- package/src/theme/scale/deriveRecipeInputs.ts +110 -0
- package/src/theme/scale/generateScale.spec.ts +260 -0
- package/src/theme/scale/generateScale.ts +147 -0
- package/src/theme/scale/index.ts +54 -0
- package/src/theme/scale/installScaleConfig.native.spec.ts +77 -0
- package/src/theme/scale/installScaleConfig.native.ts +57 -0
- package/src/theme/scale/installScaleConfig.ts +7 -0
- package/src/theme/scale/scaleAttribute.ts +21 -0
- package/src/theme/scale/scaleCss.probe.spec.tsx +64 -0
- package/src/theme/scale/scaleCss.spec.ts +132 -0
- package/src/theme/scale/scaleCss.ts +95 -0
- package/src/theme/scale/scaleFonts.ts +106 -0
- package/src/theme/scale/scaleInputs.ts +322 -0
- package/src/theme/scale/themeScales.ts +181 -0
- package/src/theme/scale/useActiveScale.spec.tsx +129 -0
- package/src/theme/scale/useActiveScale.ts +95 -0
- package/src/theme/schemeStore.spec.ts +81 -0
- package/src/theme/schemeStore.ts +67 -0
- package/src/theme/shared.spec.ts +7 -6
- package/src/theme/shared.tsx +37 -3
- package/src/theme/sizeLadder.spec.ts +37 -36
- package/src/theme/sizeRecipes.spec.ts +179 -85
- package/src/theme/sizeRecipes.ts +48 -25
- package/src/theme/subsetThemes.spec.ts +9 -3
- package/src/theme/switchThumb.spec.ts +69 -0
- package/src/theme/syntaxFloor.spec.ts +11 -8
- package/src/theme/theme.hydration.native.spec.tsx +51 -0
- package/src/theme/theme.native.tsx +59 -21
- package/src/theme/theme.palette.spec.tsx +76 -0
- package/src/theme/theme.preset.native.spec.tsx +73 -0
- package/src/theme/theme.provider.spec.ts +143 -1
- package/src/theme/theme.tsx +61 -25
- package/src/theme/tileSizes.ts +6 -7
- package/src/theme/transitionProps.native.ts +25 -2
- package/src/theme/transitionProps.spec.ts +34 -0
- package/src/theme/transitionProps.ts +26 -2
- package/src/theme/usePresetSwitch.native.spec.tsx +68 -0
- package/src/theme/usePresetSwitch.native.ts +32 -0
- package/src/theme/usePresetSwitch.spec.tsx +103 -0
- package/src/theme/usePresetSwitch.ts +54 -0
- package/src/theme/useResolvedKnobs.hydration.spec.tsx +2 -2
- package/src/theme/useResolvedKnobs.ts +23 -90
- package/src/theme/useResolvedKnobsBehavior.spec.tsx +25 -30
- package/src/theme/useSchemePreference.native.ts +33 -0
- package/src/theme/useSchemePreference.spec.tsx +51 -0
- package/src/theme/useSchemePreference.ts +32 -0
- package/src/theme/useSchemePreference.web.spec.tsx +32 -0
- package/src/theme/useTheme.native.spec.tsx +68 -0
- package/src/theme/useTheme.native.ts +49 -24
- package/src/theme/useTheme.ts +7 -4
- package/types/audit/constraintAudit.d.ts +6 -5
- package/types/audit/constraintAudit.d.ts.map +1 -1
- package/types/audit/designTokens.d.ts +13 -0
- package/types/audit/designTokens.d.ts.map +1 -0
- package/types/audit/themeMatrix.d.ts +10 -0
- package/types/audit/themeMatrix.d.ts.map +1 -1
- package/types/figma/figmaTokens.d.ts +11 -28
- package/types/figma/figmaTokens.d.ts.map +1 -1
- package/types/fonts/createFontLoader.d.ts.map +1 -1
- package/types/menuRow.d.ts +4 -1
- package/types/menuRow.d.ts.map +1 -1
- package/types/theme/FontKnobStyles.d.ts.map +1 -1
- package/types/theme/Preset.d.ts.map +1 -1
- package/types/theme/PresetContext.d.ts +4 -0
- package/types/theme/PresetContext.d.ts.map +1 -1
- package/types/theme/Surface.d.ts +57 -26
- package/types/theme/Surface.d.ts.map +1 -1
- package/types/theme/activateThemeConfig.d.ts +5 -3
- package/types/theme/activateThemeConfig.d.ts.map +1 -1
- package/types/theme/animations/css.d.ts +27 -34
- package/types/theme/animations/css.d.ts.map +1 -1
- package/types/theme/animations/reactNative.d.ts +196 -45
- package/types/theme/animations/reactNative.d.ts.map +1 -1
- package/types/theme/chartPalette.d.ts +11 -3
- package/types/theme/chartPalette.d.ts.map +1 -1
- package/types/theme/colorRules.d.ts +1 -1
- package/types/theme/cookies.d.ts +12 -1
- package/types/theme/cookies.d.ts.map +1 -1
- package/types/theme/createDefaultThemeConfig.d.ts +13 -13
- package/types/theme/createDefaultThemeConfig.d.ts.map +1 -1
- package/types/theme/createThemes.d.ts +58 -2
- package/types/theme/createThemes.d.ts.map +1 -1
- package/types/theme/defaults/accent.d.ts.map +1 -1
- package/types/theme/defaults/base.d.ts.map +1 -1
- package/types/theme/defaults/builderOptions.d.ts.map +1 -1
- package/types/theme/defaults/categoryFonts.d.ts +10 -0
- package/types/theme/defaults/categoryFonts.d.ts.map +1 -1
- package/types/theme/defaults/fonts.d.ts +330 -30
- package/types/theme/defaults/fonts.d.ts.map +1 -1
- package/types/theme/defaults/house.d.ts +8 -0
- package/types/theme/defaults/house.d.ts.map +1 -0
- package/types/theme/defaults/index.d.ts +1 -0
- package/types/theme/defaults/index.d.ts.map +1 -1
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts +3 -3
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -1
- package/types/theme/devtools/animationOptions.d.ts.map +1 -1
- package/types/theme/devtools/buildSnapshot.d.ts.map +1 -1
- package/types/theme/devtools/knobIcons.d.ts +0 -3
- package/types/theme/devtools/knobIcons.d.ts.map +1 -1
- package/types/theme/index.d.ts +11 -0
- package/types/theme/index.d.ts.map +1 -1
- package/types/theme/knobs.d.ts +8 -36
- package/types/theme/knobs.d.ts.map +1 -1
- package/types/theme/layoutTokens.d.ts +13 -1
- package/types/theme/layoutTokens.d.ts.map +1 -1
- package/types/theme/mergedTokens.d.ts +11 -0
- package/types/theme/mergedTokens.d.ts.map +1 -0
- package/types/theme/motion.d.ts +17 -0
- package/types/theme/motion.d.ts.map +1 -0
- package/types/theme/motionStops.d.ts +3 -0
- package/types/theme/motionStops.d.ts.map +1 -0
- package/types/theme/motionTiming.d.ts +36 -0
- package/types/theme/motionTiming.d.ts.map +1 -0
- package/types/theme/overlaySurfaces.d.ts +70 -0
- package/types/theme/overlaySurfaces.d.ts.map +1 -0
- package/types/theme/preset.types.d.ts +16 -0
- package/types/theme/preset.types.d.ts.map +1 -1
- package/types/theme/presetPreview.d.ts +14 -0
- package/types/theme/presetPreview.d.ts.map +1 -0
- package/types/theme/presetStore.d.ts +40 -0
- package/types/theme/presetStore.d.ts.map +1 -0
- package/types/theme/presetUserSync.d.ts +7 -0
- package/types/theme/presetUserSync.d.ts.map +1 -0
- package/types/theme/presets.d.ts +12 -4
- package/types/theme/presets.d.ts.map +1 -1
- package/types/theme/radiusClass.d.ts +5 -2
- package/types/theme/radiusClass.d.ts.map +1 -1
- package/types/theme/radiusClassTable.generated.d.ts +3 -3
- package/types/theme/readableColor.d.ts +11 -49
- package/types/theme/readableColor.d.ts.map +1 -1
- package/types/theme/recipeInputs.d.ts +39 -70
- package/types/theme/recipeInputs.d.ts.map +1 -1
- package/types/theme/recipes.d.ts +79 -18
- package/types/theme/recipes.d.ts.map +1 -1
- package/types/theme/resolveKnobs.d.ts +64 -32
- package/types/theme/resolveKnobs.d.ts.map +1 -1
- package/types/theme/scale/ScaleStyles.d.ts +18 -0
- package/types/theme/scale/ScaleStyles.d.ts.map +1 -0
- package/types/theme/scale/ScaleStyles.native.d.ts +10 -0
- package/types/theme/scale/ScaleStyles.native.d.ts.map +1 -0
- package/types/theme/scale/activeScale.d.ts +13 -0
- package/types/theme/scale/activeScale.d.ts.map +1 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts +10 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts.map +1 -0
- package/types/theme/scale/generateScale.d.ts +38 -0
- package/types/theme/scale/generateScale.d.ts.map +1 -0
- package/types/theme/scale/index.d.ts +17 -0
- package/types/theme/scale/index.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.d.ts +4 -0
- package/types/theme/scale/installScaleConfig.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.native.d.ts +13 -0
- package/types/theme/scale/installScaleConfig.native.d.ts.map +1 -0
- package/types/theme/scale/scaleAttribute.d.ts +14 -0
- package/types/theme/scale/scaleAttribute.d.ts.map +1 -0
- package/types/theme/scale/scaleCss.d.ts +25 -0
- package/types/theme/scale/scaleCss.d.ts.map +1 -0
- package/types/theme/scale/scaleFonts.d.ts +16 -0
- package/types/theme/scale/scaleFonts.d.ts.map +1 -0
- package/types/theme/scale/scaleInputs.d.ts +66 -0
- package/types/theme/scale/scaleInputs.d.ts.map +1 -0
- package/types/theme/scale/themeScales.d.ts +52 -0
- package/types/theme/scale/themeScales.d.ts.map +1 -0
- package/types/theme/scale/useActiveScale.d.ts +31 -0
- package/types/theme/scale/useActiveScale.d.ts.map +1 -0
- package/types/theme/schemeStore.d.ts +6 -0
- package/types/theme/schemeStore.d.ts.map +1 -0
- package/types/theme/shared.d.ts +18 -0
- package/types/theme/shared.d.ts.map +1 -1
- package/types/theme/sizeRecipes.d.ts +20 -15
- package/types/theme/sizeRecipes.d.ts.map +1 -1
- package/types/theme/theme.d.ts +2 -1
- package/types/theme/theme.d.ts.map +1 -1
- package/types/theme/theme.native.d.ts +10 -1
- package/types/theme/theme.native.d.ts.map +1 -1
- package/types/theme/tileSizes.d.ts +6 -7
- package/types/theme/tileSizes.d.ts.map +1 -1
- package/types/theme/transitionProps.d.ts +3 -1
- package/types/theme/transitionProps.d.ts.map +1 -1
- package/types/theme/transitionProps.native.d.ts +3 -1
- package/types/theme/transitionProps.native.d.ts.map +1 -1
- package/types/theme/usePresetSwitch.d.ts +10 -0
- package/types/theme/usePresetSwitch.d.ts.map +1 -0
- package/types/theme/usePresetSwitch.native.d.ts +10 -0
- package/types/theme/usePresetSwitch.native.d.ts.map +1 -0
- package/types/theme/useResolvedKnobs.d.ts +13 -13
- package/types/theme/useResolvedKnobs.d.ts.map +1 -1
- package/types/theme/useSchemePreference.d.ts +8 -0
- package/types/theme/useSchemePreference.d.ts.map +1 -0
- package/types/theme/useSchemePreference.native.d.ts +8 -0
- package/types/theme/useSchemePreference.native.d.ts.map +1 -0
- package/types/theme/useTheme.d.ts +4 -2
- package/types/theme/useTheme.d.ts.map +1 -1
- package/types/theme/useTheme.native.d.ts +10 -1
- package/types/theme/useTheme.native.d.ts.map +1 -1
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* Tamagui token steps defined by the knob system. Off-scale values indicate
|
|
6
6
|
* styles that bypassed the constraint system.
|
|
7
7
|
*
|
|
8
|
-
* Token values are
|
|
8
|
+
* Token values are the active generated scale (`globalThis.__MPO_SCALE__`,
|
|
9
|
+
* else the default `generateScale`) + resolveKnobs.ts mappings.
|
|
9
10
|
* Run this in-browser via Playwright's page.evaluate(runConstraintAudit).
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
import { type GeneratedScale, type ScaleTokens, generateScale } from "../theme/scale/generateScale";
|
|
14
|
+
import { defaultScaleInputs } from "../theme/scale/scaleInputs";
|
|
15
|
+
|
|
12
16
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
13
17
|
|
|
14
18
|
export interface PropertyAudit {
|
|
@@ -84,55 +88,41 @@ export interface AuditReport {
|
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
// ── Token scale definitions ───────────────────────────────────────────────────
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
/** Radius token values: token key → px (from @tamagui/themes v5 radius tokens) */
|
|
90
|
-
const RADIUS_TOKENS: Record<number, number> = {
|
|
91
|
-
0: 0,
|
|
92
|
-
1: 3,
|
|
93
|
-
2: 5,
|
|
94
|
-
3: 7,
|
|
95
|
-
4: 9,
|
|
96
|
-
5: 10,
|
|
97
|
-
6: 16,
|
|
98
|
-
7: 19,
|
|
99
|
-
8: 22,
|
|
100
|
-
9: 26,
|
|
101
|
-
10: 34,
|
|
102
|
-
11: 42,
|
|
103
|
-
12: 50,
|
|
104
|
-
};
|
|
91
|
+
// The active generated scale's tokens + resolveKnobs.ts mapping tables.
|
|
105
92
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
93
|
+
function activeScaleTokens(): ScaleTokens {
|
|
94
|
+
const published = (globalThis as { __MPO_SCALE__?: GeneratedScale }).__MPO_SCALE__;
|
|
95
|
+
return published?.tokens ?? generateScale(defaultScaleInputs).tokens;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whole and half-step keys of a token group as numbers (`$1.5` → 1.5); `true` and negatives dropped. */
|
|
99
|
+
function numericTokens(group: Record<string, number>): Record<number, number> {
|
|
100
|
+
const out: Record<number, number> = {};
|
|
101
|
+
for (const [key, px] of Object.entries(group)) {
|
|
102
|
+
const stop = Number(key.replace(/^\$/, ""));
|
|
103
|
+
if (Number.isFinite(stop) && stop >= 0) out[stop] = px;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const scaleTokens = activeScaleTokens();
|
|
109
|
+
|
|
110
|
+
/** Radius token values: token key → px. */
|
|
111
|
+
const RADIUS_TOKENS: Record<number, number> = numericTokens(scaleTokens.radius);
|
|
112
|
+
|
|
113
|
+
/** Space token values: token key → px. */
|
|
114
|
+
const SPACE_TOKENS: Record<number, number> = numericTokens(scaleTokens.space);
|
|
125
115
|
|
|
126
116
|
/**
|
|
127
117
|
* Inner border radius knob → pixel value
|
|
128
118
|
* Source: borderRadiusMap in resolveKnobs.ts
|
|
129
119
|
*/
|
|
130
120
|
const BORDER_RADIUS_KNOB: Record<string, number> = {
|
|
131
|
-
none: RADIUS_TOKENS[0],
|
|
132
|
-
small: RADIUS_TOKENS[2],
|
|
133
|
-
medium: RADIUS_TOKENS[4],
|
|
134
|
-
large: RADIUS_TOKENS[6],
|
|
135
|
-
full: RADIUS_TOKENS[12],
|
|
121
|
+
none: RADIUS_TOKENS[0],
|
|
122
|
+
small: RADIUS_TOKENS[2],
|
|
123
|
+
medium: RADIUS_TOKENS[4],
|
|
124
|
+
large: RADIUS_TOKENS[6],
|
|
125
|
+
full: RADIUS_TOKENS[12],
|
|
136
126
|
};
|
|
137
127
|
|
|
138
128
|
/**
|
|
@@ -140,11 +130,11 @@ const BORDER_RADIUS_KNOB: Record<string, number> = {
|
|
|
140
130
|
* Source: outerRadiusMap in resolveKnobs.ts
|
|
141
131
|
*/
|
|
142
132
|
const OUTER_RADIUS_KNOB: Record<string, number> = {
|
|
143
|
-
none: RADIUS_TOKENS[0],
|
|
144
|
-
small: RADIUS_TOKENS[2],
|
|
145
|
-
medium: RADIUS_TOKENS[3],
|
|
146
|
-
large: RADIUS_TOKENS[4],
|
|
147
|
-
full: RADIUS_TOKENS[5],
|
|
133
|
+
none: RADIUS_TOKENS[0],
|
|
134
|
+
small: RADIUS_TOKENS[2],
|
|
135
|
+
medium: RADIUS_TOKENS[3],
|
|
136
|
+
large: RADIUS_TOKENS[4],
|
|
137
|
+
full: RADIUS_TOKENS[5],
|
|
148
138
|
};
|
|
149
139
|
|
|
150
140
|
/**
|
|
@@ -159,33 +149,29 @@ const BORDER_WIDTH_KNOB: Record<string, number> = {
|
|
|
159
149
|
};
|
|
160
150
|
|
|
161
151
|
/**
|
|
162
|
-
*
|
|
163
|
-
* Source:
|
|
152
|
+
* Within-group gap roles → pixel value on the scale.
|
|
153
|
+
* Source: spacingRoles in resolveKnobs.ts (gapSm $2, gap $3).
|
|
164
154
|
*/
|
|
165
155
|
export const GAP_KNOB: Record<string, number> = {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
large: SPACE_TOKENS[5], // 24px
|
|
156
|
+
gapSm: SPACE_TOKENS[2],
|
|
157
|
+
gap: SPACE_TOKENS[3],
|
|
169
158
|
};
|
|
170
159
|
|
|
171
160
|
/**
|
|
172
|
-
*
|
|
173
|
-
* Source:
|
|
161
|
+
* Between-groups gap role → pixel value on the scale.
|
|
162
|
+
* Source: spacingRoles in resolveKnobs.ts (gapLg $5).
|
|
174
163
|
*/
|
|
175
164
|
export const GAP_LG_KNOB: Record<string, number> = {
|
|
176
|
-
|
|
177
|
-
medium: SPACE_TOKENS[5], // 24px
|
|
178
|
-
large: SPACE_TOKENS[6], // 32px
|
|
165
|
+
gapLg: SPACE_TOKENS[5],
|
|
179
166
|
};
|
|
180
167
|
|
|
181
168
|
/**
|
|
182
|
-
*
|
|
183
|
-
* Source:
|
|
169
|
+
* Inset roles → pixel value on the scale.
|
|
170
|
+
* Source: spacingRoles in resolveKnobs.ts (panelPadding $4, featurePadding $6).
|
|
184
171
|
*/
|
|
185
172
|
const PANEL_PADDING_KNOB: Record<string, number> = {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
large: SPACE_TOKENS[6], // 32px
|
|
173
|
+
panelPadding: SPACE_TOKENS[4],
|
|
174
|
+
featurePadding: SPACE_TOKENS[6],
|
|
189
175
|
};
|
|
190
176
|
|
|
191
177
|
/**
|
|
@@ -225,6 +211,7 @@ const VALID_FONT_WEIGHT = new Set(Object.values(FONT_WEIGHT_KNOB));
|
|
|
225
211
|
const FONT_FAMILY_KNOB: Record<string, string> = {
|
|
226
212
|
inter: "sans-serif ($body/$heading)",
|
|
227
213
|
georgia: "serif",
|
|
214
|
+
"ui-monospace": "mono",
|
|
228
215
|
monospace: "mono",
|
|
229
216
|
"roboto slab": "slab",
|
|
230
217
|
rockwell: "slab (fallback)",
|
|
@@ -335,37 +322,55 @@ function reverseMapFontFamily(computed: string): { knob: string; offScale: boole
|
|
|
335
322
|
*/
|
|
336
323
|
export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
337
324
|
// ── Inline token definitions (must be self-contained for page.evaluate) ──
|
|
325
|
+
// The page's published scale wins; the second argument is the default
|
|
326
|
+
// scale's value, for pages that publish none.
|
|
327
|
+
|
|
328
|
+
const _publishedScale = (
|
|
329
|
+
globalThis as {
|
|
330
|
+
__MPO_SCALE__?: {
|
|
331
|
+
controlGaps?: number[];
|
|
332
|
+
tokens?: {
|
|
333
|
+
radius?: Record<string, number>;
|
|
334
|
+
space?: Record<string, number>;
|
|
335
|
+
size?: Record<string, number>;
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
).__MPO_SCALE__;
|
|
340
|
+
const _scale = _publishedScale?.tokens;
|
|
341
|
+
const _radius = (key: string, px: number): number => _scale?.radius?.[key] ?? px;
|
|
342
|
+
const _space = (key: string, px: number): number => _scale?.space?.[`$${key}`] ?? px;
|
|
343
|
+
const _size = (key: string, px: number): number => _scale?.size?.[`$${key}`] ?? px;
|
|
344
|
+
|
|
345
|
+
// Token-internal values components paint outside the knob maps, each named
|
|
346
|
+
// by the token it is so a preset on another scale checks its own value.
|
|
347
|
+
const _SPACE_1 = _space("1", 2);
|
|
348
|
+
const _SPACE_1_5 = _space("1.5", 4);
|
|
349
|
+
const _SPACE_2 = _space("2", 6);
|
|
350
|
+
const _SPACE_3 = _space("3", 12);
|
|
351
|
+
const _SIZE_0_5 = _size("0.5", 4);
|
|
352
|
+
const _CONTROL_GAPS = new Set<number>(_publishedScale?.controlGaps ?? [3, 4, 5, 6, 7, 8, 9, 14]);
|
|
338
353
|
|
|
339
|
-
const _RADIUS_TOKENS: Record<number, number> = {
|
|
340
|
-
0: 0,
|
|
341
|
-
2: 5,
|
|
342
|
-
3: 7,
|
|
343
|
-
4: 9,
|
|
344
|
-
5: 10,
|
|
345
|
-
6: 16,
|
|
346
|
-
12: 50,
|
|
347
|
-
};
|
|
348
|
-
const _SPACE_TOKENS: Record<number, number> = {
|
|
349
|
-
0: 0,
|
|
350
|
-
2: 7,
|
|
351
|
-
3: 13,
|
|
352
|
-
4: 18,
|
|
353
|
-
5: 24,
|
|
354
|
-
6: 32,
|
|
355
|
-
};
|
|
356
354
|
const _BORDER_RADIUS_KNOB: Record<string, number> = {
|
|
357
|
-
none: 0,
|
|
358
|
-
small:
|
|
359
|
-
medium:
|
|
360
|
-
large:
|
|
361
|
-
full:
|
|
355
|
+
none: _radius("0", 0),
|
|
356
|
+
small: _radius("2", 4),
|
|
357
|
+
medium: _radius("4", 8),
|
|
358
|
+
large: _radius("6", 14),
|
|
359
|
+
full: _radius("12", 44),
|
|
360
|
+
};
|
|
361
|
+
const _CONTAINER_RADIUS_KNOB: Record<string, number> = {
|
|
362
|
+
none: _radius("0", 0),
|
|
363
|
+
small: _radius("4", 8),
|
|
364
|
+
medium: _radius("6", 14),
|
|
365
|
+
large: _radius("8", 20),
|
|
366
|
+
full: _radius("12", 44),
|
|
362
367
|
};
|
|
363
368
|
const _OUTER_RADIUS_KNOB: Record<string, number> = {
|
|
364
|
-
none: 0,
|
|
365
|
-
small:
|
|
366
|
-
medium:
|
|
367
|
-
large:
|
|
368
|
-
full:
|
|
369
|
+
none: _radius("0", 0),
|
|
370
|
+
small: _radius("2", 4),
|
|
371
|
+
medium: _radius("3", 6),
|
|
372
|
+
large: _radius("4", 8),
|
|
373
|
+
full: _radius("5", 9),
|
|
369
374
|
};
|
|
370
375
|
const _BORDER_WIDTH_KNOB: Record<string, number> = {
|
|
371
376
|
none: 0,
|
|
@@ -374,19 +379,15 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
374
379
|
large: 2,
|
|
375
380
|
};
|
|
376
381
|
const _GAP_KNOB: Record<string, number> = {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
large: 24,
|
|
382
|
+
gapSm: _space("2", 6),
|
|
383
|
+
gap: _space("3", 12),
|
|
380
384
|
};
|
|
381
385
|
const _GAP_LG_KNOB: Record<string, number> = {
|
|
382
|
-
|
|
383
|
-
medium: 24,
|
|
384
|
-
large: 32,
|
|
386
|
+
gapLg: _space("5", 21),
|
|
385
387
|
};
|
|
386
388
|
const _PANEL_PADDING_KNOB: Record<string, number> = {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
large: 32,
|
|
389
|
+
panelPadding: _space("4", 16),
|
|
390
|
+
featurePadding: _space("6", 28),
|
|
390
391
|
};
|
|
391
392
|
const _FONT_WEIGHT_KNOB: Record<string, number> = {
|
|
392
393
|
light: 300, // Inter body default weight
|
|
@@ -397,24 +398,29 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
397
398
|
const _VALID_RADIUS = new Set([
|
|
398
399
|
...Object.values(_BORDER_RADIUS_KNOB),
|
|
399
400
|
...Object.values(_OUTER_RADIUS_KNOB),
|
|
401
|
+
...Object.values(_CONTAINER_RADIUS_KNOB),
|
|
402
|
+
...Object.values(_CONTAINER_RADIUS_KNOB).map((radius) =>
|
|
403
|
+
Math.min(radius, _PANEL_PADDING_KNOB.panelPadding),
|
|
404
|
+
),
|
|
400
405
|
1000, // Tamagui pill radius (Switch, RadioGroup.Item)
|
|
401
406
|
100000, // Tamagui circular button radius (Button circular variant)
|
|
402
407
|
]);
|
|
403
408
|
const _VALID_BORDER_WIDTH = new Set(Object.values(_BORDER_WIDTH_KNOB));
|
|
404
409
|
const _VALID_GAP = new Set([
|
|
405
410
|
0,
|
|
406
|
-
|
|
411
|
+
_SPACE_1,
|
|
407
412
|
3.9, // Tamagui InputContainerFrame size-proportional gap
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
_SPACE_1_5,
|
|
414
|
+
_SIZE_0_5, // Tamagui button internals
|
|
415
|
+
..._CONTROL_GAPS,
|
|
411
416
|
...Object.values(_GAP_KNOB),
|
|
412
417
|
...Object.values(_GAP_LG_KNOB),
|
|
413
418
|
]);
|
|
414
419
|
const _VALID_PADDING = new Set([
|
|
415
420
|
0,
|
|
416
|
-
|
|
417
|
-
|
|
421
|
+
_SPACE_1,
|
|
422
|
+
_SPACE_2, // button padding at $size.$2
|
|
423
|
+
_SPACE_3, // a fixed-token inset (chips, OwnInset rows)
|
|
418
424
|
...Object.values(_PANEL_PADDING_KNOB),
|
|
419
425
|
]);
|
|
420
426
|
const _VALID_FONT_WEIGHT = new Set(Object.values(_FONT_WEIGHT_KNOB));
|
|
@@ -422,6 +428,7 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
422
428
|
const _FONT_FAMILY_KNOB: Record<string, string> = {
|
|
423
429
|
inter: "sans-serif ($body/$heading)",
|
|
424
430
|
georgia: "serif",
|
|
431
|
+
"ui-monospace": "mono",
|
|
425
432
|
monospace: "mono",
|
|
426
433
|
"roboto slab": "slab",
|
|
427
434
|
rockwell: "slab (fallback)",
|
|
@@ -470,6 +477,10 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
470
477
|
for (const [k, v] of Object.entries(_OUTER_RADIUS_KNOB)) {
|
|
471
478
|
if (v === px) return `${k} (outer)`;
|
|
472
479
|
}
|
|
480
|
+
for (const [k, v] of Object.entries(_CONTAINER_RADIUS_KNOB)) {
|
|
481
|
+
if (v === px) return `${k} (container)`;
|
|
482
|
+
if (Math.min(v, _PANEL_PADDING_KNOB.panelPadding) === px) return `${k} (container cap)`;
|
|
483
|
+
}
|
|
473
484
|
return "off-scale";
|
|
474
485
|
}
|
|
475
486
|
|
|
@@ -489,9 +500,10 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
489
500
|
if (v === px) return `${k} (lg)`;
|
|
490
501
|
}
|
|
491
502
|
// Tamagui space/size sub-tokens used internally by components
|
|
492
|
-
if (px ===
|
|
503
|
+
if (px === _SPACE_1) return `space-token(${px}px)`;
|
|
493
504
|
if (px === 3.9) return "input-internal(3.9px)";
|
|
494
|
-
if (px ===
|
|
505
|
+
if (px === _SPACE_1_5 || px === _SIZE_0_5 || _CONTROL_GAPS.has(px))
|
|
506
|
+
return `size-token(${px}px)`;
|
|
495
507
|
return "off-scale";
|
|
496
508
|
}
|
|
497
509
|
|
|
@@ -501,8 +513,8 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
501
513
|
if (v === px) return k;
|
|
502
514
|
}
|
|
503
515
|
// Tamagui space/size sub-tokens
|
|
504
|
-
if (px ===
|
|
505
|
-
if (px ===
|
|
516
|
+
if (px === _SPACE_1) return `space-token(${px}px)`;
|
|
517
|
+
if (px === _SPACE_2 || px === _SPACE_3) return `size-token(${px}px)`;
|
|
506
518
|
return "off-scale";
|
|
507
519
|
}
|
|
508
520
|
|
|
@@ -778,19 +790,15 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
778
790
|
}
|
|
779
791
|
if (capContainer) {
|
|
780
792
|
const radiusKnob = el.getAttribute("data-radius-knob") ?? "";
|
|
781
|
-
const
|
|
782
|
-
const radiusStop = Object.hasOwn(
|
|
783
|
-
? _BORDER_RADIUS_KNOB[radiusKnob]
|
|
784
|
-
: undefined;
|
|
793
|
+
const radiusMap = containerName === "TextArea" ? _BORDER_RADIUS_KNOB : _CONTAINER_RADIUS_KNOB;
|
|
794
|
+
const radiusStop = Object.hasOwn(radiusMap, radiusKnob) ? radiusMap[radiusKnob] : undefined;
|
|
785
795
|
const paddingSides = ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"] as const;
|
|
786
796
|
const measuredInsets = paddingSides.map((prop) => parsePx(style[prop]));
|
|
787
797
|
const inset = ownInset
|
|
788
798
|
? measuredInsets.some((v) => v === null)
|
|
789
799
|
? undefined
|
|
790
800
|
: Math.min(...(measuredInsets as number[]))
|
|
791
|
-
:
|
|
792
|
-
? _PANEL_PADDING_KNOB[spaceKnob]
|
|
793
|
-
: undefined;
|
|
801
|
+
: _PANEL_PADDING_KNOB.panelPadding;
|
|
794
802
|
if (ownsInset) {
|
|
795
803
|
for (const [index, prop] of paddingSides.entries()) {
|
|
796
804
|
const value = measuredInsets[index];
|
|
@@ -798,12 +806,12 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
798
806
|
entry[prop] = {
|
|
799
807
|
value: value ?? 0,
|
|
800
808
|
unit: "px",
|
|
801
|
-
knob: `${containerName} panelPadding
|
|
809
|
+
knob: `${containerName} panelPadding`,
|
|
802
810
|
offScale,
|
|
803
811
|
};
|
|
804
812
|
if (offScale)
|
|
805
813
|
entry.violations.push(
|
|
806
|
-
`${prop}: ${style[prop]} does not match ${containerName} panelPadding
|
|
814
|
+
`${prop}: ${style[prop]} does not match ${containerName} panelPadding (${inset ?? "unknown"}px)`,
|
|
807
815
|
);
|
|
808
816
|
}
|
|
809
817
|
}
|
|
@@ -830,7 +838,7 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
830
838
|
? `${containerName} ${stackPosition} inner corner 0`
|
|
831
839
|
: ownInset
|
|
832
840
|
? `${containerName} min(radius:${radiusKnob}, own padding)`
|
|
833
|
-
: `${containerName} min(radius:${radiusKnob}, panelPadding
|
|
841
|
+
: `${containerName} min(radius:${radiusKnob}, panelPadding)`,
|
|
834
842
|
offScale,
|
|
835
843
|
};
|
|
836
844
|
entry[prop] = result;
|
|
@@ -947,7 +955,14 @@ export function runConstraintAudit(rootSelector?: string): AuditReport {
|
|
|
947
955
|
// font_heading while changing its family. These are typography defaults,
|
|
948
956
|
// not alternatives to an explicitly declared regular/bold recipe.
|
|
949
957
|
const configuredHeading =
|
|
950
|
-
|
|
958
|
+
(/^H[1-6]$/.test(el.tagName) ||
|
|
959
|
+
el.getAttribute("role") === "heading" ||
|
|
960
|
+
Array.from(el.classList).some((name) =>
|
|
961
|
+
/^font_heading(?:Serif|Mono|Slab|Rounded|Condensed|Cursive|Handwriting|Pixel|Blackletter|Geometric)?$/.test(
|
|
962
|
+
name,
|
|
963
|
+
),
|
|
964
|
+
)) &&
|
|
965
|
+
[600, 700, 800].includes(fw);
|
|
951
966
|
const expected =
|
|
952
967
|
weightKnob !== null && ["regular", "bold"].includes(weightKnob)
|
|
953
968
|
? _FONT_WEIGHT_KNOB[weightKnob]
|
|
@@ -11,7 +11,7 @@ afterEach(() => {
|
|
|
11
11
|
|
|
12
12
|
describe("constraint audit scope and painted visibility", () => {
|
|
13
13
|
it("audits the requested surface while keeping the document-wide default", () => {
|
|
14
|
-
document.body.innerHTML = `<div id="outside" style="border-radius:
|
|
14
|
+
document.body.innerHTML = `<div id="outside" style="border-radius:17px"></div><main id="story"><button id="inside" style="border-radius:15px">Inside</button></main>`;
|
|
15
15
|
expect(audit("#story").violations.map((entry) => entry.id)).toEqual(["inside"]);
|
|
16
16
|
expect(audit().violations.map((entry) => entry.id)).toEqual(["outside", "inside"]);
|
|
17
17
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { generateScale } from "../theme/scale/generateScale";
|
|
2
|
+
import { defaultPreset } from "../theme/presets";
|
|
3
|
+
import { defaultFonts } from "../theme/defaults/fonts";
|
|
4
|
+
import { createThemesBuilder } from "../theme/createThemes";
|
|
5
|
+
import { defaultBaseTheme } from "../theme/defaults/base";
|
|
6
|
+
import { defaultAccentTheme } from "../theme/defaults/accent";
|
|
7
|
+
import { defaultBuilderOptions } from "../theme/defaults/builderOptions";
|
|
8
|
+
|
|
9
|
+
export function exportDesignTokens() {
|
|
10
|
+
const scale = generateScale(defaultPreset.scale, "web");
|
|
11
|
+
const themes = createThemesBuilder(
|
|
12
|
+
defaultBaseTheme,
|
|
13
|
+
defaultAccentTheme,
|
|
14
|
+
defaultBuilderOptions,
|
|
15
|
+
).themes();
|
|
16
|
+
const values = (table: Record<string, unknown>) =>
|
|
17
|
+
Object.values(table).map((value) =>
|
|
18
|
+
value && typeof value === "object" && "val" in value ? value.val : value,
|
|
19
|
+
);
|
|
20
|
+
const unique = <T>(items: T[]) => [...new Set(items)];
|
|
21
|
+
const colors = (scheme: "light" | "dark") =>
|
|
22
|
+
unique(
|
|
23
|
+
Object.entries(themes)
|
|
24
|
+
.filter(([name]) => name === scheme || name.startsWith(`${scheme}_`))
|
|
25
|
+
.flatMap(([, theme]) => values(theme))
|
|
26
|
+
.filter((value): value is string => typeof value === "string"),
|
|
27
|
+
);
|
|
28
|
+
return {
|
|
29
|
+
preset: "default",
|
|
30
|
+
inputs: scale.inputs,
|
|
31
|
+
space: unique(Object.values(scale.tokens.space)),
|
|
32
|
+
radius: unique(Object.values(scale.tokens.radius)),
|
|
33
|
+
fontSize: unique([
|
|
34
|
+
...Object.values(scale.fonts.bodySizes),
|
|
35
|
+
...Object.values(scale.fonts.headingSizes),
|
|
36
|
+
]),
|
|
37
|
+
fontWeight: unique(
|
|
38
|
+
Object.values(defaultFonts).flatMap((font) => values(font.weight ?? {}).map(String)),
|
|
39
|
+
),
|
|
40
|
+
color: { light: colors("light"), dark: colors("dark") },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -156,7 +156,7 @@ describe("theme-axis geometry invariance (LC-80 tripwire)", () => {
|
|
|
156
156
|
// theme colour. Geometry becomes a function of theme and the theme
|
|
157
157
|
// axis is no longer paint-only.
|
|
158
158
|
const inputsForTheme = (theme: string): Partial<SizeRecipeInputs> =>
|
|
159
|
-
theme === "blue" ? {
|
|
159
|
+
theme === "blue" ? { control: 48, type: 16 } : {};
|
|
160
160
|
|
|
161
161
|
const defaultCell = snapshotFromRecipeInputs(inputsForTheme(""));
|
|
162
162
|
const blueCell = snapshotFromRecipeInputs(inputsForTheme("blue"));
|
|
@@ -205,7 +205,7 @@ describe("preset-axis no-breakage (two assertions, never one)", () => {
|
|
|
205
205
|
// still passes no-breakage as long as nothing broke. Geometry equality
|
|
206
206
|
// belongs exclusively to the theme axis.
|
|
207
207
|
const defaultCell = snapshotFromRecipeInputs({});
|
|
208
|
-
const boldCell = snapshotFromRecipeInputs({
|
|
208
|
+
const boldCell = snapshotFromRecipeInputs({ control: 52, radiusPx: 50 });
|
|
209
209
|
expect(diffGeometry(defaultCell, boldCell).identical).toBe(false);
|
|
210
210
|
|
|
211
211
|
const result = evaluateNoBreakage({
|
|
@@ -296,6 +296,87 @@ describe("preset-axis no-breakage (two assertions, never one)", () => {
|
|
|
296
296
|
).toBe(true);
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
it("reads a known off-scale part at a scaled px as the same finding, not a new one", () => {
|
|
300
|
+
const input = {
|
|
301
|
+
storyRendered: true,
|
|
302
|
+
offScaleViolations: 1,
|
|
303
|
+
baselineOffScaleViolations: 1,
|
|
304
|
+
baselineOffScaleSignatures: [
|
|
305
|
+
"[k1] field :: paddingRight: 15px is off-scale (no matching token)",
|
|
306
|
+
],
|
|
307
|
+
offScaleSignatures: ["[k1] field :: paddingRight: 11px is off-scale (no matching token)"],
|
|
308
|
+
contrast: cleanContrast,
|
|
309
|
+
baselineContrastViolations: 0,
|
|
310
|
+
};
|
|
311
|
+
expect(evaluateNoBreakage(input).ok).toBe(true);
|
|
312
|
+
const moved = evaluateNoBreakage({
|
|
313
|
+
...input,
|
|
314
|
+
offScaleViolations: 2,
|
|
315
|
+
offScaleSignatures: [
|
|
316
|
+
...input.offScaleSignatures,
|
|
317
|
+
"[k2] field :: paddingLeft: 11px is off-scale (no matching token)",
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
expect(moved.ok).toBe(false);
|
|
321
|
+
expect(moved.failures[0]).toContain("[k2] field :: paddingLeft: 11px");
|
|
322
|
+
expect(moved.failures[0]).not.toContain("[k1]");
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("reads a known inline colour at a re-themed value as the same finding, not a new one", () => {
|
|
326
|
+
const input = {
|
|
327
|
+
storyRendered: true,
|
|
328
|
+
offScaleViolations: 1,
|
|
329
|
+
baselineOffScaleViolations: 1,
|
|
330
|
+
baselineOffScaleSignatures: [
|
|
331
|
+
'[k1] body :: inlineColor: backgroundColor="rgb(248, 248, 248)" bypasses theme tokens',
|
|
332
|
+
],
|
|
333
|
+
offScaleSignatures: [
|
|
334
|
+
'[k1] body :: inlineColor: backgroundColor="rgb(254, 246, 250)" bypasses theme tokens',
|
|
335
|
+
],
|
|
336
|
+
contrast: cleanContrast,
|
|
337
|
+
baselineContrastViolations: 0,
|
|
338
|
+
};
|
|
339
|
+
expect(evaluateNoBreakage(input).ok).toBe(true);
|
|
340
|
+
const added = evaluateNoBreakage({
|
|
341
|
+
...input,
|
|
342
|
+
offScaleViolations: 2,
|
|
343
|
+
offScaleSignatures: [
|
|
344
|
+
...input.offScaleSignatures,
|
|
345
|
+
'[k2] chip :: inlineColor: color="#ff0000" bypasses theme tokens',
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
expect(added.ok).toBe(false);
|
|
349
|
+
expect(added.failures[0]).toContain('[k2] chip :: inlineColor: color="#ff0000"');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("reads a known off-scale text node in a preset's own font as the same finding, not a new one", () => {
|
|
353
|
+
const input = {
|
|
354
|
+
storyRendered: true,
|
|
355
|
+
offScaleViolations: 1,
|
|
356
|
+
baselineOffScaleViolations: 1,
|
|
357
|
+
baselineOffScaleSignatures: [
|
|
358
|
+
"[k1] span.is_BadgeText.is_Text.font_body :: fontWeight: 600 does not match a configured typography weight",
|
|
359
|
+
],
|
|
360
|
+
offScaleSignatures: [
|
|
361
|
+
"[k1] span.is_BadgeText.is_Text.font_condensed :: fontWeight: 600 does not match a configured typography weight",
|
|
362
|
+
],
|
|
363
|
+
contrast: cleanContrast,
|
|
364
|
+
baselineContrastViolations: 0,
|
|
365
|
+
};
|
|
366
|
+
expect(evaluateNoBreakage(input).ok).toBe(true);
|
|
367
|
+
const added = evaluateNoBreakage({
|
|
368
|
+
...input,
|
|
369
|
+
offScaleViolations: 2,
|
|
370
|
+
offScaleSignatures: [
|
|
371
|
+
...input.offScaleSignatures,
|
|
372
|
+
"[k2] a.is_Anchor.is_Text.font_condensed :: paddingTop: 10px is off-scale (no matching token)",
|
|
373
|
+
],
|
|
374
|
+
});
|
|
375
|
+
expect(added.ok).toBe(false);
|
|
376
|
+
expect(added.failures[0]).toContain("[k2] a.is_Anchor.is_Text.font_condensed :: paddingTop");
|
|
377
|
+
expect(added.failures[0]).not.toContain("[k1]");
|
|
378
|
+
});
|
|
379
|
+
|
|
299
380
|
it("names replacement text failures regardless of count and preserves repeated identities", () => {
|
|
300
381
|
const old = {
|
|
301
382
|
key: "old text",
|
package/src/audit/themeMatrix.ts
CHANGED
|
@@ -1548,6 +1548,38 @@ function newEntries(before: string[], after: string[]): string[] {
|
|
|
1548
1548
|
return added;
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
|
+
/**
|
|
1552
|
+
* An off-scale signature with its px, colour and font family blanked: the
|
|
1553
|
+
* element and property that left the scale, not the value it landed on. A
|
|
1554
|
+
* preset with its own token scale moves every px, one with its own colour
|
|
1555
|
+
* theme moves every colour, and one with its own body or heading font swaps
|
|
1556
|
+
* every text node's `font_<family>` class, so a part the default cell already
|
|
1557
|
+
* reports is the same finding at a scaled, re-themed or re-typed value, not a
|
|
1558
|
+
* new breakage.
|
|
1559
|
+
*/
|
|
1560
|
+
export function offScaleIdentity(signature: string): string {
|
|
1561
|
+
return signature
|
|
1562
|
+
.replace(/-?\d+(?:\.\d+)?px/g, "#px")
|
|
1563
|
+
.replace(/(inlineColor: \w+=)"[^"]*"/g, '$1"#colour"')
|
|
1564
|
+
.replace(/\.font_[\w-]+/g, ".font_#family");
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/** `newEntries` by off-scale identity, reported as this cell's own signatures. */
|
|
1568
|
+
function newOffScaleEntries(before: string[], after: string[]): string[] {
|
|
1569
|
+
const added = newEntries(before.map(offScaleIdentity), after.map(offScaleIdentity));
|
|
1570
|
+
const pending = new Map<string, number>();
|
|
1571
|
+
for (const identity of added) pending.set(identity, (pending.get(identity) ?? 0) + 1);
|
|
1572
|
+
const named: string[] = [];
|
|
1573
|
+
for (const signature of after) {
|
|
1574
|
+
const identity = offScaleIdentity(signature);
|
|
1575
|
+
const count = pending.get(identity) ?? 0;
|
|
1576
|
+
if (count === 0) continue;
|
|
1577
|
+
pending.set(identity, count - 1);
|
|
1578
|
+
named.push(signature);
|
|
1579
|
+
}
|
|
1580
|
+
return named;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1551
1583
|
const namedList = (entries: string[], limit = 6): string => {
|
|
1552
1584
|
if (entries.length === 0) return "";
|
|
1553
1585
|
const shown = entries.slice(0, limit).join("; ");
|
|
@@ -1575,7 +1607,7 @@ export function evaluateNoBreakage(input: NoBreakageInput): NoBreakageResult {
|
|
|
1575
1607
|
}
|
|
1576
1608
|
const structuralAdded =
|
|
1577
1609
|
input.offScaleSignatures && input.baselineOffScaleSignatures
|
|
1578
|
-
?
|
|
1610
|
+
? newOffScaleEntries(input.baselineOffScaleSignatures, input.offScaleSignatures)
|
|
1579
1611
|
: [];
|
|
1580
1612
|
if (structuralAdded.length || input.offScaleViolations > input.baselineOffScaleViolations) {
|
|
1581
1613
|
failures.push(
|