@multiplatform.one/theme 7.4.1 → 7.6.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.
Files changed (119) hide show
  1. package/README.md +2 -1
  2. package/package.json +5 -5
  3. package/src/audit/index.ts +19 -0
  4. package/src/audit/themeMatrix.spec.ts +274 -0
  5. package/src/audit/themeMatrix.ts +630 -0
  6. package/src/figma/figmaTokens.spec.ts +25 -21
  7. package/src/figma/figmaTokens.ts +2 -2
  8. package/src/font.ts +37 -25
  9. package/src/index.ts +12 -0
  10. package/src/keyboardFocusRing.ts +12 -6
  11. package/src/theme/FontKnobStyles.tsx +14 -31
  12. package/src/theme/Tint.tsx +3 -2
  13. package/src/theme/animations/css.ts +14 -1
  14. package/src/theme/colorRules.spec.ts +306 -2
  15. package/src/theme/colorRules.ts +118 -0
  16. package/src/theme/cornerSmoothing.native.ts +39 -2
  17. package/src/theme/cornerSmoothing.spec.ts +270 -3
  18. package/src/theme/cornerSmoothing.ts +147 -10
  19. package/src/theme/createDefaultThemeConfig.spec.ts +107 -1
  20. package/src/theme/createDefaultThemeConfig.ts +27 -8
  21. package/src/theme/defaults/builderOptions.ts +23 -10
  22. package/src/theme/defaults/categoryFonts.ts +24 -0
  23. package/src/theme/defaults/fonts.spec.ts +163 -10
  24. package/src/theme/defaults/fonts.theme.spec.tsx +139 -0
  25. package/src/theme/defaults/fonts.ts +214 -50
  26. package/src/theme/defaults/index.ts +1 -0
  27. package/src/theme/devtools/ThemeDevtoolsPanel.tsx +0 -11
  28. package/src/theme/focusState.spec.ts +119 -1
  29. package/src/theme/focusState.ts +32 -7
  30. package/src/theme/fontCategoryMetrics.ts +57 -0
  31. package/src/theme/fontCategoryStacks.ts +37 -0
  32. package/src/theme/fontKnobStyles.spec.ts +15 -2
  33. package/src/theme/glyphPaint.spec.ts +26 -0
  34. package/src/theme/glyphPaint.ts +46 -0
  35. package/src/theme/index.ts +4 -0
  36. package/src/theme/knobs.ts +18 -2
  37. package/src/theme/layoutTokens.spec.ts +3 -1
  38. package/src/theme/layoutTokens.ts +47 -4
  39. package/src/theme/mpo13Proof.spec.ts +3 -3
  40. package/src/theme/mpo19Proof.spec.ts +285 -0
  41. package/src/theme/presets.ts +1 -1
  42. package/src/theme/radiusClass.spec.ts +394 -0
  43. package/src/theme/radiusClass.ts +203 -4
  44. package/src/theme/radiusClassTable.generated.ts +121 -0
  45. package/src/theme/recipeInputs.ts +223 -36
  46. package/src/theme/recipes.ts +68 -4
  47. package/src/theme/resolveKnobs.spec.ts +155 -13
  48. package/src/theme/resolveKnobs.ts +193 -27
  49. package/src/theme/sizeLadder.spec.ts +84 -0
  50. package/src/theme/sizeRecipes.spec.ts +19 -6
  51. package/src/theme/sizeRecipes.ts +43 -13
  52. package/src/theme/theme.native.tsx +2 -0
  53. package/src/theme/theme.provider.spec.ts +9 -0
  54. package/src/theme/theme.tsx +6 -3
  55. package/src/theme/tint.spec.tsx +36 -35
  56. package/src/theme/useResolvedKnobs.ts +6 -3
  57. package/types/audit/index.d.ts +2 -0
  58. package/types/audit/index.d.ts.map +1 -1
  59. package/types/audit/themeMatrix.d.ts +172 -0
  60. package/types/audit/themeMatrix.d.ts.map +1 -0
  61. package/types/figma/figmaTokens.d.ts +1 -1
  62. package/types/figma/figmaTokens.d.ts.map +1 -1
  63. package/types/font.d.ts.map +1 -1
  64. package/types/index.d.ts +2 -0
  65. package/types/index.d.ts.map +1 -1
  66. package/types/keyboardFocusRing.d.ts +11 -5
  67. package/types/keyboardFocusRing.d.ts.map +1 -1
  68. package/types/theme/FontKnobStyles.d.ts +8 -12
  69. package/types/theme/FontKnobStyles.d.ts.map +1 -1
  70. package/types/theme/Tint.d.ts +2 -1
  71. package/types/theme/Tint.d.ts.map +1 -1
  72. package/types/theme/animations/css.d.ts +15 -2
  73. package/types/theme/animations/css.d.ts.map +1 -1
  74. package/types/theme/colorRules.d.ts +59 -1
  75. package/types/theme/colorRules.d.ts.map +1 -1
  76. package/types/theme/cornerSmoothing.d.ts +42 -20
  77. package/types/theme/cornerSmoothing.d.ts.map +1 -1
  78. package/types/theme/cornerSmoothing.native.d.ts +22 -2
  79. package/types/theme/cornerSmoothing.native.d.ts.map +1 -1
  80. package/types/theme/createDefaultThemeConfig.d.ts +21 -3
  81. package/types/theme/createDefaultThemeConfig.d.ts.map +1 -1
  82. package/types/theme/defaults/builderOptions.d.ts.map +1 -1
  83. package/types/theme/defaults/categoryFonts.d.ts +23 -0
  84. package/types/theme/defaults/categoryFonts.d.ts.map +1 -0
  85. package/types/theme/defaults/fonts.d.ts +140 -2
  86. package/types/theme/defaults/fonts.d.ts.map +1 -1
  87. package/types/theme/defaults/index.d.ts +1 -0
  88. package/types/theme/defaults/index.d.ts.map +1 -1
  89. package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -1
  90. package/types/theme/focusState.d.ts +32 -6
  91. package/types/theme/focusState.d.ts.map +1 -1
  92. package/types/theme/fontCategoryMetrics.d.ts +36 -0
  93. package/types/theme/fontCategoryMetrics.d.ts.map +1 -0
  94. package/types/theme/fontCategoryStacks.d.ts +24 -0
  95. package/types/theme/fontCategoryStacks.d.ts.map +1 -0
  96. package/types/theme/glyphPaint.d.ts +16 -0
  97. package/types/theme/glyphPaint.d.ts.map +1 -0
  98. package/types/theme/index.d.ts +4 -0
  99. package/types/theme/index.d.ts.map +1 -1
  100. package/types/theme/knobs.d.ts +10 -2
  101. package/types/theme/knobs.d.ts.map +1 -1
  102. package/types/theme/layoutTokens.d.ts +46 -4
  103. package/types/theme/layoutTokens.d.ts.map +1 -1
  104. package/types/theme/radiusClass.d.ts +96 -4
  105. package/types/theme/radiusClass.d.ts.map +1 -1
  106. package/types/theme/radiusClassTable.generated.d.ts +146 -0
  107. package/types/theme/radiusClassTable.generated.d.ts.map +1 -0
  108. package/types/theme/recipeInputs.d.ts +85 -14
  109. package/types/theme/recipeInputs.d.ts.map +1 -1
  110. package/types/theme/recipes.d.ts +78 -4
  111. package/types/theme/recipes.d.ts.map +1 -1
  112. package/types/theme/resolveKnobs.d.ts +12 -1
  113. package/types/theme/resolveKnobs.d.ts.map +1 -1
  114. package/types/theme/sizeRecipes.d.ts +27 -14
  115. package/types/theme/sizeRecipes.d.ts.map +1 -1
  116. package/types/theme/theme.d.ts.map +1 -1
  117. package/types/theme/theme.native.d.ts.map +1 -1
  118. package/types/theme/useResolvedKnobs.d.ts +2 -2
  119. package/types/theme/useResolvedKnobs.d.ts.map +1 -1
@@ -7,6 +7,7 @@ import {
7
7
  SIZE_RECIPE_COMPACT_RATIOS,
8
8
  SIZE_RECIPE_HEIGHTS,
9
9
  SIZE_RECIPE_HEIGHTS_TOUCH,
10
+ SIZE_RECIPE_RADII,
10
11
  SIZE_RECIPE_RATIOS,
11
12
  applyTouchFloor,
12
13
  defaultRecipeInputs,
@@ -22,6 +23,7 @@ import {
22
23
  sizeRecipeIconVariants,
23
24
  sizeRecipes,
24
25
  sizeRecipeTypeVariants,
26
+ textFieldInset,
25
27
  type SizeRecipe,
26
28
  } from "./sizeRecipes";
27
29
 
@@ -75,10 +77,9 @@ describe("size recipes", () => {
75
77
  }
76
78
  });
77
79
 
78
- it("static variants match sizeRecipeFromHeight for every token", () => {
80
+ it("static variants match the generated table for every token", () => {
79
81
  for (const token of RECIPE_KEYS) {
80
- const recipe = sizeRecipeFromHeight(SIZE_RECIPE_HEIGHTS[token]);
81
- expect(sizeRecipes[token]).toEqual(recipe);
82
+ const recipe = sizeRecipes[token];
82
83
  expect(sizeRecipeBoxVariants[token]).toEqual({
83
84
  height: recipe.height,
84
85
  paddingHorizontal: recipe.paddingHorizontal,
@@ -90,6 +91,7 @@ describe("size recipes", () => {
90
91
  height: recipe.iconSize,
91
92
  });
92
93
  }
94
+ expect(sizeRecipeFromHeight(44)).toEqual(sizeRecipes.$4);
93
95
  });
94
96
 
95
97
  it("sizeRecipeForToken falls back to $4", () => {
@@ -230,7 +232,7 @@ describe("generateSizeRecipes (LC-70)", () => {
230
232
  expect(generated.families.control.recipes.$4.height).toBe(44);
231
233
  expect(generated.families.controlCompact.recipes.$4.height).toBe(44);
232
234
  expect(generated.families.control.recipes.$4.paddingHorizontal).toBe(18);
233
- expect(generated.families.controlCompact.recipes.$4.paddingHorizontal).toBe(13);
235
+ expect(generated.families.controlCompact.recipes.$4.paddingHorizontal).toBe(16);
234
236
  expect(generated.families.controlCompact.recipes.$4.paddingHorizontal).toBeLessThan(
235
237
  generated.families.control.recipes.$4.paddingHorizontal,
236
238
  );
@@ -260,12 +262,23 @@ describe("generateSizeRecipes (LC-70)", () => {
260
262
  expect(sizeRecipeTypeVariants).toBe(recipeFamilies.control.typeVariants);
261
263
  expect(sizeRecipeIconVariants).toBe(recipeFamilies.control.iconVariants);
262
264
  expect(SIZE_RECIPE_HEIGHTS).toBe(recipeFamilies.control.heights);
265
+ expect(SIZE_RECIPE_RADII).toBe(recipeFamilies.control.radii);
263
266
  });
264
267
 
265
268
  it("sizeRecipeEscape is identity", () => {
266
269
  expect(sizeRecipeEscape(32)).toBe(32);
267
270
  expect(sizeRecipeEscape("keep")).toBe("keep");
268
271
  });
272
+
273
+ it("textFieldInset is min(padX, floor((height-fontSize)/2)) — $4 → 15 (LC-84)", () => {
274
+ expect(textFieldInset(sizeRecipes.$4)).toBe(15);
275
+ expect(textFieldInset({ height: 44, paddingHorizontal: 18, fontSize: 14 })).toBe(15);
276
+ expect(textFieldInset({ height: 32, paddingHorizontal: 12, fontSize: 14 })).toBe(9);
277
+ expect(textFieldInset({ height: 48, paddingHorizontal: 8, fontSize: 14 })).toBe(8);
278
+ expect(textFieldInset(sizeRecipes.$4)).toBeLessThanOrEqual(
279
+ Math.floor((sizeRecipes.$4.height - sizeRecipes.$4.fontSize) / 2),
280
+ );
281
+ });
269
282
  });
270
283
 
271
284
  describe("size recipe families", () => {
@@ -291,8 +304,8 @@ describe("size recipe families", () => {
291
304
  expect(compact.boxVariants.$4.paddingHorizontal).toBeLessThan(
292
305
  control.boxVariants.$4.paddingHorizontal,
293
306
  );
294
- expect(compact.boxVariants.$4.paddingHorizontal).toBe(13);
295
- expect(compact.boxVariants.$4.gap).toBe(7);
307
+ expect(compact.boxVariants.$4.paddingHorizontal).toBe(16);
308
+ expect(compact.boxVariants.$4.gap).toBe(9);
296
309
  expect(sizeRecipeForToken("$4", { family: "controlCompact" })).toEqual(compact.recipes.$4);
297
310
  });
298
311
  });
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Coordinated control-size recipes (LC-65 SIZE-RECIPE, LC-70 RECIPE-INPUTS).
2
+ * Coordinated control-size recipes (LC-75 SIZE-RECIPE, LC-80 RECIPE-INPUTS).
3
3
  *
4
- * A flat size ramp cannot encode relationships: padding, font, and icon
5
- * must stay in ratio with height. Radius is excluded — it stays on the
6
- * borderRadius knob and must never scale with size.
4
+ * The default ladder is the measured Tamagui v5 table in recipeInputs
5
+ * (padX / font / icon / radius per size token). Gap equals radius at
6
+ * every size and at every borderRadius knob stop (`applyRadiusStop`).
7
+ * A linear ratio is only the `{ baseHeight }` fallback — not a
8
+ * documented divergence (MPO-46 / R6).
7
9
  *
8
10
  * Tables are GENERATED from `defaultRecipeInputs` at module eval (Tamagui
9
11
  * 2.0.0-rc.41 flattens `styled(View, { variants: { size: tables } })` when
@@ -13,25 +15,30 @@
13
15
  * reserve it for non-enumerable values only. Do NOT put recipe tables on a
14
16
  * `createStyledContext` primitive (`neverFlatten`).
15
17
  *
16
- * Reference md ($4 / $true): height 44, padX 18, font 14, icon 16, gap 9
17
- * (the measured tamagui.dev $4 control — MPO-13).
18
+ * Reference md ($4 / $true): height 44, padX 18, font 14, icon 16, radius 9,
19
+ * gap 9 (the measured tamagui.dev $4 control — MPO-13 / MPO-19 X1/X2).
18
20
  * `sizeToken` remains the recipe KEY (string "$4") so styled() variants
19
21
  * can switch; it is not a height scalar.
20
22
  *
21
- * Families: `control` uses `inputs.ratios`. `controlCompact` uses
22
- * `compactRatios` at the SAME heights (tighter pad/font/icon/gap).
23
+ * Families: `control` reads `inputs.tokenTable`. `controlCompact` reads
24
+ * `compactTokenTable` at the SAME heights (Input padX, tighter type).
23
25
  *
24
- * On touch (LC-67), `applyTouchFloor` / `sizeRecipeForToken(token, { touch: true })`
25
- * lift painted height to ≥ 44 while pad/font/icon stay in ratio. Static
26
- * `sizeRecipe*Variants` stay on the desktop table — runtime fragments
27
- * carry the floor; consumers spread `knobProps.control` after `size`.
26
+ * On touch (LC-77), `applyTouchFloor` / `sizeRecipeForToken(token, { touch: true })`
27
+ * lift painted height to ≥ 44. Static `sizeRecipe*Variants` stay on the
28
+ * desktop table — runtime fragments carry the floor; consumers spread
29
+ * `knobProps.control` after `size`.
28
30
  */
29
31
 
30
32
  export {
31
33
  DEFAULT_SIZE_RECIPE_INPUTS,
34
+ SIZE_RECIPE_RADIUS_STOPS,
32
35
  SIZE_RECIPE_TOKENS,
33
36
  SIZE_TOKEN_EXPONENTS,
37
+ TAMAGUI_COMPACT_SIZE_TABLE,
38
+ TAMAGUI_CONTROL_SIZE_TABLE,
39
+ TAMAGUI_INPUT_PADX,
34
40
  TOUCH_HEIGHT_FLOOR,
41
+ applyRadiusStop,
35
42
  cloneSizeRecipeInputs,
36
43
  createRecipeFamilies,
37
44
  defaultRecipeInputs,
@@ -39,6 +46,7 @@ export {
39
46
  generateSizeRecipeTables,
40
47
  generateSizeRecipes,
41
48
  mergeRecipeInputs,
49
+ recipeFromTokenChannels,
42
50
  resolveSizeRecipeInputs,
43
51
  } from "./recipeInputs";
44
52
  export type {
@@ -52,13 +60,17 @@ export type {
52
60
  SizeRecipeFamilyName,
53
61
  SizeRecipeIconVariant,
54
62
  SizeRecipeInputs,
63
+ SizeRecipeRadiusStop,
55
64
  SizeRecipeRatios,
56
65
  SizeRecipeTables,
57
66
  SizeRecipeToken,
67
+ SizeRecipeTokenChannels,
68
+ SizeRecipeTokenTable,
58
69
  SizeRecipeTypeVariant,
59
70
  } from "./recipeInputs";
60
71
 
61
72
  import {
73
+ cloneSizeRecipeInputs,
62
74
  defaultRecipeInputs,
63
75
  generateSizeRecipes,
64
76
  sizeRecipeFromHeight as recipeFromHeight,
@@ -88,6 +100,7 @@ function adoptFamily(target: SizeRecipeFamily, source: SizeRecipeFamily): void {
88
100
  replaceRecord(target.typeVariants, source.typeVariants);
89
101
  replaceRecord(target.iconVariants, source.iconVariants);
90
102
  replaceRecord(target.heightVariants, source.heightVariants);
103
+ replaceRecord(target.radii, source.radii);
91
104
  }
92
105
 
93
106
  const initial = generateSizeRecipes(defaultRecipeInputs);
@@ -106,6 +119,7 @@ export const sizeRecipeTypeVariants = recipeFamilies.control.typeVariants;
106
119
  export const sizeRecipeIconVariants = recipeFamilies.control.iconVariants;
107
120
  export const SIZE_RECIPE_HEIGHTS = recipeFamilies.control.heights;
108
121
  export const SIZE_RECIPE_HEIGHTS_TOUCH = recipeFamilies.control.touchHeights;
122
+ export const SIZE_RECIPE_RADII = recipeFamilies.control.radii;
109
123
  export const SIZE_RECIPE_RATIOS = recipeFamilies.control.ratios;
110
124
  export const SIZE_RECIPE_COMPACT_RATIOS = recipeFamilies.controlCompact.ratios;
111
125
 
@@ -139,6 +153,7 @@ function adoptGenerated(next: GeneratedSizeRecipes): GeneratedSizeRecipes {
139
153
  boxVariants: sizeRecipeBoxVariants,
140
154
  typeVariants: sizeRecipeTypeVariants,
141
155
  iconVariants: sizeRecipeIconVariants,
156
+ radii: SIZE_RECIPE_RADII,
142
157
  families: recipeFamilies,
143
158
  };
144
159
  return current;
@@ -150,7 +165,7 @@ export function setSizeRecipeInputs(partial: Partial<RecipeInputs> = {}): Genera
150
165
  }
151
166
 
152
167
  export function getSizeRecipeInputs(): RecipeInputs {
153
- return { ...current.inputs, ratios: { ...current.inputs.ratios } };
168
+ return cloneSizeRecipeInputs(current.inputs);
154
169
  }
155
170
 
156
171
  export function getGeneratedSizeRecipes(): GeneratedSizeRecipes {
@@ -210,3 +225,18 @@ export function sizeRecipeForToken(
210
225
  export function sizeRecipeEscape<T>(value: T): T {
211
226
  return value;
212
227
  }
228
+
229
+ /**
230
+ * LC-84 TEXT-INSET — optical vertical inset of a text field, never larger
231
+ * than the recipe padX. Extra horizontal space is legal only when an icon
232
+ * well occupies that side, not as empty-field padX.
233
+ *
234
+ * min(paddingHorizontal, floor((height − fontSize) / 2))
235
+ *
236
+ * `$4` (44 / 18 / 14) → 15. Button ladders keep recipe.paddingHorizontal (18).
237
+ */
238
+ export function textFieldInset(
239
+ recipe: Pick<SizeRecipe, "height" | "paddingHorizontal" | "fontSize">,
240
+ ): number {
241
+ return Math.min(recipe.paddingHorizontal, Math.floor((recipe.height - recipe.fontSize) / 2));
242
+ }
@@ -2,6 +2,7 @@ import { ToastProvider } from "@tamagui/toast";
2
2
  import { useContext, useMemo } from "react";
3
3
  import { type CreateTamaguiProps, TamaguiProvider, type TamaguiProviderProps } from "tamagui";
4
4
  import type { PropsWithChildren } from "react";
5
+ import { CornerSmoothingStyles } from "./cornerSmoothing";
5
6
  import { FontKnobStyles } from "./FontKnobStyles";
6
7
  import { PresetContext, type PresetContextValue } from "./PresetContext";
7
8
  import { defaultPreset } from "./presets";
@@ -61,6 +62,7 @@ function KnobBridge({ children }: PropsWithChildren) {
61
62
  const tree = (
62
63
  <>
63
64
  <FontKnobStyles />
65
+ <CornerSmoothingStyles />
64
66
  {children}
65
67
  </>
66
68
  );
@@ -15,6 +15,15 @@ describe("ThemeProvider knob injectors", () => {
15
15
  expect(bridge).toContain("<FontKnobStyles");
16
16
  });
17
17
 
18
+ it("mounts CornerSmoothingStyles inside KnobBridge so the squircle sheet is not tree-shaken", () => {
19
+ const src = readFileSync(join(here, "theme.tsx"), "utf8");
20
+ expect(src).toMatch(/import \{ CornerSmoothingStyles \} from "\.\/cornerSmoothing"/);
21
+ const bridge = src.slice(src.indexOf("function KnobBridge"));
22
+ expect(bridge).toContain("<CornerSmoothingStyles");
23
+ const native = readFileSync(join(here, "theme.native.tsx"), "utf8");
24
+ expect(native).toContain("<CornerSmoothingStyles");
25
+ });
26
+
18
27
  it("does not invent a second knob store — cookies still feed PresetContext", () => {
19
28
  const src = readFileSync(join(here, "theme.tsx"), "utf8");
20
29
  expect(src).toContain("readPresetCookie");
@@ -5,6 +5,7 @@ import { type CreateTamaguiProps, TamaguiProvider, type TamaguiProviderProps } f
5
5
  import type { PropsWithChildren } from "react";
6
6
  import { animationDurations } from "./animations/css";
7
7
  import { getCookieWatchList, readOverridesCookie, readPresetCookie } from "./cookies";
8
+ import { CornerSmoothingStyles } from "./cornerSmoothing";
8
9
  import { FontKnobStyles } from "./FontKnobStyles";
9
10
  import { PresetContext, type PresetContextValue } from "./PresetContext";
10
11
  import { defaultPreset } from "./presets";
@@ -157,9 +158,10 @@ export function ThemeProvider<T extends CreateTamaguiProps>({
157
158
  * If an ancestor PresetContext already exists (e.g. from a Storybook decorator),
158
159
  * the bridge is a pass-through to avoid overriding it.
159
160
  *
160
- * FontKnobStyles mounts here (not only in Storybook CreatePreview) so
161
- * headingFont/bodyFont restyle every shipped app that uses ThemeProvider.
162
- * Hairline and corner-smoothing already self-mount on module load.
161
+ * FontKnobStyles and CornerSmoothingStyles mount here (not only in
162
+ * Storybook CreatePreview) so heading/body fonts and the corner-shape
163
+ * stylesheet restyle every shipped app that uses ThemeProvider. Hairline
164
+ * still self-mounts on module load.
163
165
  */
164
166
  function KnobBridge({ children }: PropsWithChildren) {
165
167
  const existingCtx = useContext(PresetContext);
@@ -192,6 +194,7 @@ function KnobBridge({ children }: PropsWithChildren) {
192
194
  const tree = (
193
195
  <>
194
196
  <FontKnobStyles />
197
+ <CornerSmoothingStyles />
195
198
  {children}
196
199
  </>
197
200
  );
@@ -13,12 +13,16 @@ import type { ThemeName } from "@tamagui/web";
13
13
  import { PresetContext, type PresetContextValue } from "./PresetContext";
14
14
  import type { Preset } from "./preset.types";
15
15
  import { defaultKnobs } from "./knobs";
16
+ import { defaultPreset } from "./presets";
16
17
  import { Tint, useTintDepth } from "./Tint";
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // Helpers
20
21
  // ---------------------------------------------------------------------------
21
22
 
23
+ /** Decorative nest family (R12) — the same four names as `defaultPreset.tints`. */
24
+ const FAMILY: ThemeName[] = ["orange", "blue", "purple", "pink"];
25
+
22
26
  function makePreset(tints: ThemeName[]): Preset {
23
27
  return {
24
28
  theme: "light" as ThemeName,
@@ -42,71 +46,70 @@ function collectThemes(html: string): string[] {
42
46
  return Array.from(html.matchAll(/data-tamagui-theme="([^"]+)"/g), (m) => m[1]);
43
47
  }
44
48
 
49
+ function nestTints(
50
+ count: number,
51
+ innermost: ReactNode = createElement("span", null, "inner"),
52
+ ): ReactNode {
53
+ let node: ReactNode = innermost;
54
+ for (let i = 0; i < count; i++) {
55
+ node = createElement(Tint, null, node);
56
+ }
57
+ return node;
58
+ }
59
+
45
60
  // ---------------------------------------------------------------------------
46
61
  // Tests
47
62
  // ---------------------------------------------------------------------------
48
63
 
49
64
  describe("Tint", () => {
50
- it("selects tints[1 % length] for a single <Tint>", () => {
51
- const tints: ThemeName[] = ["red", "green", "blue"];
65
+ it("defaultPreset.tints is the four-name decorative family", () => {
66
+ expect(defaultPreset.tints).toEqual(FAMILY);
67
+ });
68
+
69
+ it("selects tints[0] for a single <Tint> (depth 1)", () => {
52
70
  const tree = wrapWithPreset(
53
- tints,
71
+ FAMILY,
54
72
  createElement(Tint, null, createElement("span", null, "hello")),
55
73
  );
56
74
  const html = renderToHtml(tree);
57
75
  const themes = collectThemes(html);
58
- // depth = 0 (parent) + 1 = 1 → tints[1 % 3] = "green"
59
- expect(themes).toEqual(["green"]);
76
+ // depth = 0 (parent) + 1 = 1 → tints[(1 - 1) % 4] = "orange"
77
+ expect(themes).toEqual(["orange"]);
60
78
  });
61
79
 
62
80
  it("increments depth correctly with nested <Tint>", () => {
63
- const tints: ThemeName[] = ["red", "green", "blue"];
64
81
  const tree = wrapWithPreset(
65
- tints,
82
+ FAMILY,
66
83
  createElement(Tint, null, createElement(Tint, null, createElement("span", null, "inner"))),
67
84
  );
68
85
  const html = renderToHtml(tree);
69
86
  const themes = collectThemes(html);
70
- // outer: depth=1 → tints[1%3]="green", inner: depth=2 → tints[2%3]="blue"
71
- expect(themes).toEqual(["green", "blue"]);
87
+ // outer: depth=1 → tints[0]="orange", inner: depth=2 → tints[1]="blue"
88
+ expect(themes).toEqual(["orange", "blue"]);
72
89
  });
73
90
 
74
91
  it("wraps via modulo when depth exceeds tints.length", () => {
75
- const tints: ThemeName[] = ["red", "green", "blue"];
76
- // 4 nested Tints: depths 1,2,3,4 → indices 1,2,0,1 → green,blue,red,green
77
- const tree = wrapWithPreset(
78
- tints,
79
- createElement(
80
- Tint,
81
- null,
82
- createElement(
83
- Tint,
84
- null,
85
- createElement(Tint, null, createElement(Tint, null, createElement("span", null, "deep"))),
86
- ),
87
- ),
88
- );
92
+ // 5 nested Tints: depths 1..5 → indices 0,1,2,3,0 → orange,blue,purple,pink,orange
93
+ const tree = wrapWithPreset(FAMILY, nestTints(5, createElement("span", null, "deep")));
89
94
  const html = renderToHtml(tree);
90
95
  const themes = collectThemes(html);
91
- expect(themes).toEqual(["green", "blue", "red", "green"]);
96
+ expect(themes).toEqual(["orange", "blue", "purple", "pink", "orange"]);
92
97
  });
93
98
 
94
99
  it("applies alt prop offset", () => {
95
- const tints: ThemeName[] = ["red", "green", "blue"];
96
- // alt=2 at top level: depth = 0 + 1 + 2 = 3 → tints[3%3] = "red"
100
+ // alt=2 at top level: depth = 0 + 1 + 2 = 3 → tints[(3 - 1) % 4] = "purple"
97
101
  const tree = wrapWithPreset(
98
- tints,
102
+ FAMILY,
99
103
  createElement(Tint, { alt: 2 }, createElement("span", null, "alt")),
100
104
  );
101
105
  const html = renderToHtml(tree);
102
106
  const themes = collectThemes(html);
103
- expect(themes).toEqual(["red"]);
107
+ expect(themes).toEqual(["purple"]);
104
108
  });
105
109
 
106
110
  it("skips Theme wrapper when disable is true", () => {
107
- const tints: ThemeName[] = ["red", "green", "blue"];
108
111
  const tree = wrapWithPreset(
109
- tints,
112
+ FAMILY,
110
113
  createElement(Tint, { disable: true }, createElement("span", null, "no theme")),
111
114
  );
112
115
  const html = renderToHtml(tree);
@@ -115,10 +118,9 @@ describe("Tint", () => {
115
118
  });
116
119
 
117
120
  it("still increments depth when disabled (so children see correct depth)", () => {
118
- const tints: ThemeName[] = ["red", "green", "blue"];
119
- // disabled outer (depth=1, no theme wrap), inner (depth=2, wraps with tints[2%3]="blue")
121
+ // disabled outer (depth=1, no theme wrap), inner (depth=2, wraps with tints[1]="blue")
120
122
  const tree = wrapWithPreset(
121
- tints,
123
+ FAMILY,
122
124
  createElement(
123
125
  Tint,
124
126
  { disable: true },
@@ -163,14 +165,13 @@ describe("useTintDepth", () => {
163
165
  });
164
166
 
165
167
  it("returns current depth inside nested <Tint> components", () => {
166
- const tints: ThemeName[] = ["red", "green", "blue"];
167
168
  let capturedDepth = -1;
168
169
  const Probe = () => {
169
170
  capturedDepth = useTintDepth();
170
171
  return createElement("span", null, "probe");
171
172
  };
172
173
  const tree = wrapWithPreset(
173
- tints,
174
+ FAMILY,
174
175
  createElement(Tint, null, createElement(Tint, null, createElement(Probe))),
175
176
  );
176
177
  renderToHtml(tree);
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext, useEffect, useMemo, useState } from "react";
2
- import type { SizeTokens } from "tamagui";
2
+ import { type SizeTokens, useThemeName } from "tamagui";
3
3
  import { defaultKnobs, type Knobs } from "./knobs";
4
4
  import type { IntentOverride } from "./preset.types";
5
5
  import type { KnobPropsOverride, ResolvedKnobs } from "./recipes";
@@ -111,12 +111,14 @@ export const solidButtonIntents = new Set(["error", "warning", "success"]);
111
111
  * 6. Apply density (space only; compact option wins when provided)
112
112
  * 7. Re-clamp to Surface ceiling (compact option cannot exceed parent)
113
113
  * 8. OS reduced-motion forces animation="none"
114
- * 9. Call resolveKnobs(mergedKnobs, override) — override merges last
114
+ * 9. Call resolveKnobs(mergedKnobs, override, scheme) — override merges last
115
115
  */
116
116
  export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKnobs {
117
117
  const presetCtx = usePresetContext();
118
118
  const contextIntent = useIntentContext();
119
119
  const prefersReducedMotion = usePrefersReducedMotion();
120
+ const themeName = useThemeName() as string | undefined;
121
+ const scheme = themeName?.startsWith("dark") ? "dark" : "light";
120
122
  const surfaceCtx = SurfaceContext.useStyledContext();
121
123
  const surfaceSize = surfaceCtx?.size ?? "unset";
122
124
  const surfaceDensity = surfaceCtx?.density ?? "unset";
@@ -186,7 +188,7 @@ export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKno
186
188
  merged = { ...merged, animation: "none" };
187
189
  }
188
190
 
189
- const resolved = resolveKnobs(merged, override);
191
+ const resolved = resolveKnobs(merged, override, scheme);
190
192
 
191
193
  if (intent && component === "Button" && solidButtonIntents.has(intent)) {
192
194
  const labelColor = merged.fillStyle === "filled" ? "$color" : "$color11";
@@ -210,5 +212,6 @@ export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKno
210
212
  prefersReducedMotion,
211
213
  surfaceSize,
212
214
  surfaceDensity,
215
+ scheme,
213
216
  ]);
214
217
  }
@@ -2,4 +2,6 @@ export type { AuditReport, AuditSummary, ElementAudit, PropertyAudit, TextProper
2
2
  export { runConstraintAudit } from "./constraintAudit";
3
3
  export type { AuditDiff, ElementDiff, PropertyDiff } from "./diffReport";
4
4
  export { diffAuditReports, formatAuditReport } from "./diffReport";
5
+ export type { GeometryDiff, GeometryMove, MatrixElementGeometry, MatrixRect, MatrixSnapshot, MatrixTextSample, NoBreakageInput, NoBreakageResult, TextContrastResult, TextContrastViolation, } from "./themeMatrix";
6
+ export { auditSignatures, captureMatrixSnapshot, diffGeometry, evaluateNoBreakage, evaluateTextContrast, } from "./themeMatrix";
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnE,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Theme-matrix invariance + no-breakage assertions (MPO-18).
3
+ *
4
+ * The theme matrix sweeps every story cell across the globals axes
5
+ * (scheme × theme colour × preset) and holds TWO separate assertions,
6
+ * never one:
7
+ *
8
+ * 1. GEOMETRY INVARIANCE across the THEME axis — switching the theme
9
+ * colour may repaint ink, never move a box. Colour is the one axis
10
+ * whose whole contract is "paint only" (LC-80 RECIPE-INPUTS: size
11
+ * recipes are generated from inputs at config time; Tamagui colour
12
+ * themes stay the colour path). Any geometry delta across colours
13
+ * means a recipe consumed a theme option.
14
+ *
15
+ * 2. NO-BREAKAGE across the PRESET axis — presets are EXPECTED to move
16
+ * pixels (boldPreset flips radius/border/weight/space; heroPreset
17
+ * moves the page title scale), so geometry equality is deliberately
18
+ * NOT asserted there. What must hold in every preset cell: the story
19
+ * renders, structural values stay on the knob scales
20
+ * (runConstraintAudit off-scale count vs the default cell), and text
21
+ * ink keeps the WCAG floor (evaluateTextContrast).
22
+ *
23
+ * MEASURING RULE: every text assertion here measures TEXT NODES — the
24
+ * computed style of the element that DIRECTLY contains the text, and the
25
+ * Range-measured rect of the text itself — never the story frame. The
26
+ * frame's font-family is always Inter and its colour is not the ink
27
+ * (that trap shipped three wrong tickets: MPO-40/44/48).
28
+ *
29
+ * `captureMatrixSnapshot` is completely self-contained (no imports, no
30
+ * closures) so it can be passed straight to Playwright's
31
+ * `page.evaluate(captureMatrixSnapshot)`, exactly like
32
+ * `runConstraintAudit`. The comparators are pure Node-side functions so
33
+ * vitest can hold them to the tripwire tests without a browser.
34
+ */
35
+ export interface MatrixRect {
36
+ x: number;
37
+ y: number;
38
+ w: number;
39
+ h: number;
40
+ }
41
+ export interface MatrixElementGeometry {
42
+ /** Stable structural path from the story root (tag:index-among-same-tag). */
43
+ key: string;
44
+ tag: string;
45
+ rect: MatrixRect;
46
+ /**
47
+ * Geometry-bearing computed properties. The theme axis must never move
48
+ * any of these; colour properties are deliberately absent.
49
+ */
50
+ props: Record<string, string>;
51
+ /** Range-measured rect of the element's DIRECT text nodes, when any. */
52
+ textRect?: MatrixRect;
53
+ }
54
+ export interface MatrixTextSample {
55
+ /** Structural key of the element that DIRECTLY contains the text node. */
56
+ key: string;
57
+ /** First characters of the text node (identification only). */
58
+ text: string;
59
+ /** Composited ink colour as #rrggbb (alpha already blended over background). */
60
+ color: string;
61
+ /** Composited effective background as #rrggbb. */
62
+ background: string;
63
+ /** False when no opaque backdrop could be resolved (gradient/image/alpha chain). */
64
+ backgroundResolved: boolean;
65
+ /** True when a background-image/gradient sits between text and backdrop. */
66
+ overImage: boolean;
67
+ fontSizePx: number;
68
+ fontWeightNum: number;
69
+ rect: MatrixRect;
70
+ }
71
+ export interface MatrixSnapshot {
72
+ /** False when the story root is missing or empty — always a failure. */
73
+ storyRendered: boolean;
74
+ geometry: MatrixElementGeometry[];
75
+ textSamples: MatrixTextSample[];
76
+ }
77
+ export interface GeometryMove {
78
+ key: string;
79
+ property: string;
80
+ before: string;
81
+ after: string;
82
+ }
83
+ export interface GeometryDiff {
84
+ identical: boolean;
85
+ /** Elements present in `before` but missing in `after`. */
86
+ removedKeys: string[];
87
+ /** Elements present in `after` but missing in `before`. */
88
+ addedKeys: string[];
89
+ moves: GeometryMove[];
90
+ }
91
+ export interface TextContrastViolation {
92
+ key: string;
93
+ text: string;
94
+ color: string;
95
+ background: string;
96
+ ratio: number;
97
+ required: number;
98
+ }
99
+ export interface TextContrastResult {
100
+ measured: number;
101
+ unmeasurable: number;
102
+ violations: TextContrastViolation[];
103
+ }
104
+ /**
105
+ * Captures the geometry + text-ink snapshot of the rendered story.
106
+ *
107
+ * Self-contained by construction — pass directly to
108
+ * `page.evaluate(captureMatrixSnapshot)`.
109
+ */
110
+ export declare function captureMatrixSnapshot(): MatrixSnapshot;
111
+ /**
112
+ * Geometry invariance diff for the THEME axis: every element must keep its
113
+ * rect, its text rect, and every geometry-bearing computed property.
114
+ * `epsilonPx` absorbs sub-pixel raster jitter in rect measurements only —
115
+ * computed properties compare exactly.
116
+ */
117
+ export declare function diffGeometry(before: MatrixSnapshot, after: MatrixSnapshot, options?: {
118
+ epsilonPx?: number;
119
+ }): GeometryDiff;
120
+ /**
121
+ * The contrast assertion for the matrix (and for downstream repos — call
122
+ * this instead of keeping a local contrast spec): WCAG AA floors from
123
+ * `colorRules`, large text (≥24px, or ≥18.66px at ≥700) takes the 3:1
124
+ * floor, everything else 4.5:1. Samples whose backdrop could not be
125
+ * resolved (gradient/image/never-opaque chain) are counted, never
126
+ * silently dropped.
127
+ */
128
+ export declare function evaluateTextContrast(samples: MatrixTextSample[]): TextContrastResult;
129
+ /** One matrix cell's no-breakage inputs (preset axis). */
130
+ export interface NoBreakageInput {
131
+ storyRendered: boolean;
132
+ /** Off-scale violation count from `runConstraintAudit` in this cell. */
133
+ offScaleViolations: number;
134
+ /** Off-scale count in the same story+scheme at the DEFAULT preset. */
135
+ baselineOffScaleViolations: number;
136
+ /**
137
+ * Per-violation signatures in this cell — see {@link auditSignatures}. A
138
+ * gate that reports "32 violations where the default cell had 31" names
139
+ * nothing a person can go and fix; with these it names the elements.
140
+ */
141
+ offScaleSignatures?: string[];
142
+ /** The same signatures in the DEFAULT-preset cell. */
143
+ baselineOffScaleSignatures?: string[];
144
+ /** Contrast result of this cell. */
145
+ contrast: TextContrastResult;
146
+ /** Contrast violation count in the same story+scheme at the DEFAULT preset. */
147
+ baselineContrastViolations: number;
148
+ /** The DEFAULT-preset cell's contrast result, for naming the new misses. */
149
+ baselineContrast?: TextContrastResult;
150
+ }
151
+ /**
152
+ * Flattens a `runConstraintAudit` report into one signature per violation,
153
+ * `"<element label> :: <violation>"`, so two cells can be compared by what
154
+ * broke rather than by how many things broke.
155
+ */
156
+ export declare function auditSignatures(violations: Array<{
157
+ label: string;
158
+ violations: string[];
159
+ }>): string[];
160
+ export interface NoBreakageResult {
161
+ ok: boolean;
162
+ failures: string[];
163
+ }
164
+ /**
165
+ * NO-BREAKAGE evaluation for the preset axis. Geometry equality is
166
+ * deliberately NOT part of this: presets are expected to move pixels.
167
+ * Breakage means: the story failed to render, structural values fell off
168
+ * the knob scales beyond the default cell's standing debt, or text ink
169
+ * lost contrast the default cell still had.
170
+ */
171
+ export declare function evaluateNoBreakage(input: NoBreakageInput): NoBreakageResult;
172
+ //# sourceMappingURL=themeMatrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themeMatrix.d.ts","sourceRoot":"","sources":["../../src/audit/themeMatrix.ts"],"names":[],"mappings":"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"}
@@ -65,7 +65,7 @@ export interface FigmaRadiusMode {
65
65
  /** `Sizing` collection variable to alias (e.g. `radius-4`). */
66
66
  figmaName: string;
67
67
  px: number;
68
- /** Outer/nested radius fragment (`knobProps.borderRadiusOuter`). */
68
+ /** Outer/nested radius fragment (`knobProps.borderRadiusNested`). */
69
69
  outerToken: string;
70
70
  outerFigmaName: string;
71
71
  outerPx: number;