@multiplatform.one/theme 7.34.4 → 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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type TamaguiInternalConfig, getConfig, getTokenValue, setConfig } from "@tamagui/web";
|
|
2
|
+
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
vi.mock("../animations/index", () => ({ animations: {} }));
|
|
5
|
+
|
|
6
|
+
import { createDefaultThemeConfig } from "../createDefaultThemeConfig";
|
|
7
|
+
import { densePreset, roomyPreset } from "../presets";
|
|
8
|
+
import { clearPresets } from "../shared";
|
|
9
|
+
import { installScaleConfig, resetInstalledScaleForTests } from "./installScaleConfig.native";
|
|
10
|
+
import { buildThemeScales } from "./themeScales";
|
|
11
|
+
|
|
12
|
+
const themeConfig = createDefaultThemeConfig({
|
|
13
|
+
presets: { dense: densePreset, roomy: roomyPreset },
|
|
14
|
+
});
|
|
15
|
+
const base = themeConfig.tamagui as unknown as TamaguiInternalConfig;
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
resetInstalledScaleForTests();
|
|
19
|
+
setConfig(base);
|
|
20
|
+
});
|
|
21
|
+
afterAll(() => clearPresets());
|
|
22
|
+
|
|
23
|
+
function parsed(config: TamaguiInternalConfig, category: string, key: string): number {
|
|
24
|
+
return (config.tokensParsed as Record<string, Record<string, { val: number }>>)[category][key]
|
|
25
|
+
.val;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function bodySize(config: TamaguiInternalConfig): number {
|
|
29
|
+
return (config.fontsParsed.$body?.size as Record<string, { val: number }>).$true.val;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("native scale install", () => {
|
|
33
|
+
it("copies a scale's tokens and fonts into the config Tamagui first rendered with", () => {
|
|
34
|
+
setConfig(base);
|
|
35
|
+
const dense = themeConfig.scales!.dense.tamagui();
|
|
36
|
+
expect(dense).not.toBe(base);
|
|
37
|
+
|
|
38
|
+
expect(installScaleConfig(dense)).toBe(true);
|
|
39
|
+
expect(getConfig()).toBe(base);
|
|
40
|
+
expect(parsed(base, "size", "$4")).toBe(36);
|
|
41
|
+
expect(parsed(base, "space", "$4")).toBe(parsed(dense, "space", "$4"));
|
|
42
|
+
expect(bodySize(base)).toBe(14);
|
|
43
|
+
expect(getTokenValue("$4" as Parameters<typeof getTokenValue>[0], "size")).toBe(36);
|
|
44
|
+
expect(installScaleConfig(dense)).toBe(false);
|
|
45
|
+
|
|
46
|
+
const roomy = themeConfig.scales!.roomy.tamagui();
|
|
47
|
+
expect(installScaleConfig(roomy)).toBe(true);
|
|
48
|
+
expect(parsed(base, "size", "$4")).toBe(48);
|
|
49
|
+
expect(bodySize(base)).toBe(16);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("restores the first config's own fields for the default scale", () => {
|
|
53
|
+
setConfig(base);
|
|
54
|
+
const own = { size4: parsed(base, "size", "$4"), body: bodySize(base) };
|
|
55
|
+
installScaleConfig(themeConfig.scales!.roomy.tamagui());
|
|
56
|
+
expect(installScaleConfig(themeConfig.scales!.default.tamagui())).toBe(true);
|
|
57
|
+
expect(parsed(base, "size", "$4")).toBe(own.size4);
|
|
58
|
+
expect(bodySize(base)).toBe(own.body);
|
|
59
|
+
expect(getTokenValue("$4" as Parameters<typeof getTokenValue>[0], "size")).toBe(own.size4);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("gives every preset scale its own config", () => {
|
|
63
|
+
const scales = buildThemeScales({
|
|
64
|
+
base,
|
|
65
|
+
props: { tokens: {}, themes: {} } as never,
|
|
66
|
+
presets: { dense: densePreset, roomy: roomyPreset },
|
|
67
|
+
recipeInputs: themeConfig.recipeInputs!,
|
|
68
|
+
fonts: () => ({}),
|
|
69
|
+
createConfig: (props) => ({ tokens: props.tokens }) as unknown as TamaguiInternalConfig,
|
|
70
|
+
});
|
|
71
|
+
const dense = scales.dense.tamagui() as unknown as { tokens: unknown };
|
|
72
|
+
const roomy = scales.roomy.tamagui() as unknown as { tokens: unknown };
|
|
73
|
+
expect(dense).not.toBe(roomy);
|
|
74
|
+
expect(dense.tokens).toBe(scales.dense.tokens);
|
|
75
|
+
expect(roomy.tokens).toBe(scales.roomy.tokens);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type TamaguiInternalConfig, forceUpdateThemes, getConfig } from "@tamagui/web";
|
|
2
|
+
import { type TokensGlobal, mergedTokens } from "../mergedTokens";
|
|
3
|
+
|
|
4
|
+
const scaleFields = [
|
|
5
|
+
"tokens",
|
|
6
|
+
"tokensParsed",
|
|
7
|
+
"specificTokens",
|
|
8
|
+
"fonts",
|
|
9
|
+
"fontsParsed",
|
|
10
|
+
"fontSizeTokens",
|
|
11
|
+
"defaultFont",
|
|
12
|
+
"defaultFontToken",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
type ScaleFields = Partial<Record<(typeof scaleFields)[number], unknown>>;
|
|
16
|
+
|
|
17
|
+
let live: TamaguiInternalConfig | undefined;
|
|
18
|
+
let liveOwnFields: ScaleFields | undefined;
|
|
19
|
+
let installed: TamaguiInternalConfig | undefined;
|
|
20
|
+
|
|
21
|
+
function fieldsOf(config: TamaguiInternalConfig): ScaleFields {
|
|
22
|
+
const source = config as unknown as Record<string, unknown>;
|
|
23
|
+
return Object.fromEntries(scaleFields.map((field) => [field, source[field]]));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Native Tamagui resolves `$` tokens against the config it held on its first
|
|
28
|
+
* render (getSplitStyles and every styled component keep that object), so
|
|
29
|
+
* installing another config object changes nothing they paint. A scale is
|
|
30
|
+
* installed here by copying its token and font fields into that first config;
|
|
31
|
+
* the first config's own fields are kept so the default scale can come back.
|
|
32
|
+
* Returns true when anything changed.
|
|
33
|
+
*/
|
|
34
|
+
export function installScaleConfig(config: TamaguiInternalConfig): boolean {
|
|
35
|
+
if (!live) {
|
|
36
|
+
live = getConfig();
|
|
37
|
+
liveOwnFields = fieldsOf(live);
|
|
38
|
+
installed = live;
|
|
39
|
+
}
|
|
40
|
+
if (installed === config) return false;
|
|
41
|
+
Object.assign(live, config === live ? liveOwnFields : fieldsOf(config));
|
|
42
|
+
(globalThis as TokensGlobal).__tamaguiTokensMerged = mergedTokens(live);
|
|
43
|
+
installed = config;
|
|
44
|
+
forceUpdateThemes();
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal */
|
|
49
|
+
export function resetInstalledScaleForTests(): void {
|
|
50
|
+
if (live && liveOwnFields) {
|
|
51
|
+
Object.assign(live, liveOwnFields);
|
|
52
|
+
(globalThis as TokensGlobal).__tamaguiTokensMerged = mergedTokens(live);
|
|
53
|
+
}
|
|
54
|
+
live = undefined;
|
|
55
|
+
liveOwnFields = undefined;
|
|
56
|
+
installed = undefined;
|
|
57
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TamaguiInternalConfig } from "@tamagui/web";
|
|
2
|
+
import { activateThemeConfig } from "../activateThemeConfig";
|
|
3
|
+
|
|
4
|
+
/** Make a scale's built config the one token reads resolve against; true when anything changed. */
|
|
5
|
+
export function installScaleConfig(config: TamaguiInternalConfig): boolean {
|
|
6
|
+
return activateThemeConfig(config);
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cookiePreset, readPresetCookie } from "../cookies";
|
|
2
|
+
|
|
3
|
+
/** The `<html>` attribute that names the active preset; scale blocks key off it. */
|
|
4
|
+
export const SCALE_ATTRIBUTE = "data-mp-scale";
|
|
5
|
+
|
|
6
|
+
export { cookiePreset };
|
|
7
|
+
|
|
8
|
+
/** The `data-mp-scale` value for a raw `mp.preset` cookie value (none for an empty or foreign one). */
|
|
9
|
+
export function scaleAttributeFromCookie(value: unknown): string | undefined {
|
|
10
|
+
return readPresetCookie({ [cookiePreset]: value });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const cookiePattern = cookiePreset.replace(/\./g, "\\.");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Blocking `<head>` script for web root layouts: copies the `mp.preset`
|
|
17
|
+
* cookie onto `<html data-mp-scale>` before first paint, so a cold load of a
|
|
18
|
+
* cached or client-rendered document paints the stored preset's scale
|
|
19
|
+
* without waiting for hydration.
|
|
20
|
+
*/
|
|
21
|
+
export const scaleAttributeScript = `(function(){try{var m=document.cookie.match(/(?:^|;\\s*)${cookiePattern}=([^;]*)/);var v=m?decodeURIComponent(m[1]):"";if(v.charAt(0)==='"')v=JSON.parse(v);if(v)document.documentElement.setAttribute(${JSON.stringify(SCALE_ATTRIBUTE)},v)}catch(e){}})()`;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*
|
|
4
|
+
* Web lemma the runtime scale switch builds on: a `$size` style paints
|
|
5
|
+
* through the token's `:root` CSS variable, so redefining that variable
|
|
6
|
+
* moves an already-rendered control with no re-render and no new class.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createElement } from "react";
|
|
10
|
+
import { cleanup, render } from "@testing-library/react";
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
vi.mock("../animations/index", () => ({ animations: {} }));
|
|
14
|
+
|
|
15
|
+
import { Button, TamaguiProvider } from "tamagui";
|
|
16
|
+
import { createDefaultThemeConfig } from "../createDefaultThemeConfig";
|
|
17
|
+
|
|
18
|
+
const themeConfig = createDefaultThemeConfig();
|
|
19
|
+
const OVERRIDE_ID = "scale-css-probe";
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
cleanup();
|
|
23
|
+
document.getElementById(OVERRIDE_ID)?.remove();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function sizeVariable(key: string): string {
|
|
27
|
+
const tokens = themeConfig.tamagui.tokens as unknown as {
|
|
28
|
+
size: Record<string, { variable?: string; val: number }>;
|
|
29
|
+
};
|
|
30
|
+
const variable = tokens.size[key]?.variable ?? "";
|
|
31
|
+
const name = variable.match(/^var\((--[\w-]+)\)$/)?.[1];
|
|
32
|
+
if (!name)
|
|
33
|
+
throw new Error(`size.${key} is not a CSS variable on web: ${JSON.stringify(variable)}`);
|
|
34
|
+
return name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function px(value: string): number {
|
|
38
|
+
const n = Number.parseFloat(value);
|
|
39
|
+
if (!Number.isFinite(n)) throw new Error(`unreadable length ${JSON.stringify(value)}`);
|
|
40
|
+
return n;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("scale tokens repaint through root CSS variables (web)", () => {
|
|
44
|
+
it("a root override of the $size.4 variable moves a rendered Button's computed height", () => {
|
|
45
|
+
const { getByTestId } = render(
|
|
46
|
+
createElement(
|
|
47
|
+
TamaguiProvider,
|
|
48
|
+
{ config: themeConfig.tamagui, defaultTheme: "light" },
|
|
49
|
+
createElement(Button, { size: "$4", testID: "probe" }, "Save"),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
const button = getByTestId("probe");
|
|
53
|
+
const classes = button.className;
|
|
54
|
+
expect(px(getComputedStyle(button).height)).toBe(40);
|
|
55
|
+
|
|
56
|
+
const style = document.createElement("style");
|
|
57
|
+
style.id = OVERRIDE_ID;
|
|
58
|
+
style.textContent = `:root{${sizeVariable("4")}:36px}`;
|
|
59
|
+
document.head.appendChild(style);
|
|
60
|
+
|
|
61
|
+
expect(px(getComputedStyle(button).height)).toBe(36);
|
|
62
|
+
expect(button.className).toBe(classes);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The web repaint blocks a ThemeConfig's preset scales carry: one block per
|
|
3
|
+
* distinct registered scale, every variable named by the base config's own
|
|
4
|
+
* parsed tokens and fonts, px on lengths and none on zIndex.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterAll, describe, expect, it, vi } from "vitest";
|
|
8
|
+
|
|
9
|
+
vi.mock("../animations/index", () => ({ animations: {} }));
|
|
10
|
+
|
|
11
|
+
import { headingCategoryFontKeys } from "../defaults/fonts";
|
|
12
|
+
import { flooredLineHeight, fontCategoryMetrics } from "../fontCategoryMetrics";
|
|
13
|
+
import type { FontCategory } from "../knobs";
|
|
14
|
+
import { boldPreset, defaultPreset, densePreset, heroPreset, roomyPreset } from "../presets";
|
|
15
|
+
import { createDefaultThemeConfig } from "../createDefaultThemeConfig";
|
|
16
|
+
import { clearPresets } from "../shared";
|
|
17
|
+
import { generateScale } from "./generateScale";
|
|
18
|
+
import { scaleCss } from "./scaleCss";
|
|
19
|
+
import { scaleStyleSheet } from "./ScaleStyles";
|
|
20
|
+
import { DEFAULT_SCALE_ID, uniqueScales } from "./themeScales";
|
|
21
|
+
|
|
22
|
+
const themeConfig = createDefaultThemeConfig({
|
|
23
|
+
presets: { hero: heroPreset, bold: boldPreset, dense: densePreset, roomy: roomyPreset },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(() => clearPresets());
|
|
27
|
+
|
|
28
|
+
type Parsed = { name: string; variable: string; val: number };
|
|
29
|
+
|
|
30
|
+
function tokenVariable(category: string, key: string): string {
|
|
31
|
+
const parsed = (
|
|
32
|
+
themeConfig.tamagui.tokensParsed as unknown as Record<string, Record<string, Parsed>>
|
|
33
|
+
)[category][key];
|
|
34
|
+
return parsed.variable.match(/^var\((--[^)]+)\)$/)?.[1] ?? "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fontVariable(family: string, attribute: string, key: string): string {
|
|
38
|
+
const parsed = (
|
|
39
|
+
themeConfig.tamagui.fontsParsed as unknown as Record<
|
|
40
|
+
string,
|
|
41
|
+
Record<string, Record<string, Parsed>>
|
|
42
|
+
>
|
|
43
|
+
)[`$${family}`][attribute][key];
|
|
44
|
+
return parsed.variable.match(/^var\((--[^)]+)\)$/)?.[1] ?? "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function block(css: string, selectorStart: string): string {
|
|
48
|
+
const line = css.split("\n").find((rule) => rule.startsWith(selectorStart));
|
|
49
|
+
if (!line) throw new Error(`no rule starting ${selectorStart} in:\n${css.slice(0, 400)}`);
|
|
50
|
+
return line;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("preset scale CSS", () => {
|
|
54
|
+
it("emits one block per distinct non-default scale and none for presets on the default scale", () => {
|
|
55
|
+
const scales = uniqueScales(themeConfig.scales);
|
|
56
|
+
expect(scales.map((scale) => scale.id)).toEqual([DEFAULT_SCALE_ID, "dense", "roomy"]);
|
|
57
|
+
expect(themeConfig.scales?.hero).toBe(themeConfig.scales?.[DEFAULT_SCALE_ID]);
|
|
58
|
+
expect(themeConfig.scales?.bold).toBe(themeConfig.scales?.[DEFAULT_SCALE_ID]);
|
|
59
|
+
expect(themeConfig.scales?.[DEFAULT_SCALE_ID]?.css).toBe("");
|
|
60
|
+
|
|
61
|
+
const sheet = scaleStyleSheet(themeConfig.scales);
|
|
62
|
+
const roots = sheet
|
|
63
|
+
.split("\n")
|
|
64
|
+
.filter((rule) => !rule.includes(".font_"))
|
|
65
|
+
.map((rule) => rule.slice(0, rule.indexOf("{")));
|
|
66
|
+
expect(roots).toEqual([':root[data-mp-scale="dense"]', ':root[data-mp-scale="roomy"]']);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("names token variables from the base config's parsed tokens, with px on lengths", () => {
|
|
70
|
+
const dense = themeConfig.scales?.dense;
|
|
71
|
+
if (!dense) throw new Error("dense scale missing");
|
|
72
|
+
const tokens = block(dense.css, ':root[data-mp-scale="dense"]{');
|
|
73
|
+
const size4 = tokenVariable("size", "$4");
|
|
74
|
+
const space4 = tokenVariable("space", "$4");
|
|
75
|
+
const radius4 = tokenVariable("radius", "$4");
|
|
76
|
+
expect(size4).toMatch(/^--/);
|
|
77
|
+
expect(tokens).toContain(`${size4}:36px`);
|
|
78
|
+
expect(tokens).toContain(`${space4}:${dense.tokens.space.$4}px`);
|
|
79
|
+
expect(tokens).toContain(`${radius4}:${dense.tokens.radius[4]}px`);
|
|
80
|
+
expect(dense.tokens.size.$4).toBe(36);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("writes zIndex unitless and skips values equal to the base", () => {
|
|
84
|
+
const base = themeConfig.tamagui as unknown as Parameters<typeof scaleCss>[1];
|
|
85
|
+
const tokens = generateScale({ control: 36, type: 14 }).tokens;
|
|
86
|
+
const shifted = { ...tokens, zIndex: { ...tokens.zIndex, "1": 7 } };
|
|
87
|
+
const css = scaleCss([':root[data-mp-scale="x"]'], base, shifted, {});
|
|
88
|
+
expect(css).toMatch(new RegExp(`${tokenVariable("zIndex", "$1")}:7[;}]`));
|
|
89
|
+
expect(css).not.toContain(`${tokenVariable("zIndex", "$2")}:`);
|
|
90
|
+
expect(css).not.toContain(`${tokenVariable("size", "$0")}:`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("scopes font tables to each .font_<family> class under the scale selector", () => {
|
|
94
|
+
const dense = themeConfig.scales?.dense;
|
|
95
|
+
if (!dense) throw new Error("dense scale missing");
|
|
96
|
+
const body = block(dense.css, ':root[data-mp-scale="dense"] .font_body{');
|
|
97
|
+
const heading = block(dense.css, ':root[data-mp-scale="dense"] .font_heading{');
|
|
98
|
+
expect(body).toContain(
|
|
99
|
+
`${fontVariable("body", "size", "$true")}:${dense.fonts.bodySizes.true}px`,
|
|
100
|
+
);
|
|
101
|
+
expect(dense.fonts.bodySizes.true).toBe(14);
|
|
102
|
+
expect(heading).toContain(
|
|
103
|
+
`${fontVariable("heading", "lineHeight", "$4")}:${dense.fonts.headingLineHeights[4]}px`,
|
|
104
|
+
);
|
|
105
|
+
expect(block(dense.css, ':root[data-mp-scale="dense"] .font_mono{')).toContain("px");
|
|
106
|
+
for (const [category, key] of Object.entries(headingCategoryFontKeys)) {
|
|
107
|
+
const headingCategory = block(dense.css, `:root[data-mp-scale="dense"] .font_${key}{`);
|
|
108
|
+
expect(headingCategory).toContain(
|
|
109
|
+
`${fontVariable(key, "size", "$true")}:${dense.fonts.headingSizes.true}px`,
|
|
110
|
+
);
|
|
111
|
+
const leading = flooredLineHeight(
|
|
112
|
+
dense.fonts.headingSizes[10]!,
|
|
113
|
+
dense.fonts.headingLineHeights[10]!,
|
|
114
|
+
fontCategoryMetrics[category as FontCategory].heading,
|
|
115
|
+
);
|
|
116
|
+
expect(headingCategory).toContain(`${fontVariable(key, "lineHeight", "$10")}:${leading}px`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("matches the default scale's tokens to the base config value for value", () => {
|
|
121
|
+
const parsed = themeConfig.tamagui.tokensParsed as unknown as Record<
|
|
122
|
+
string,
|
|
123
|
+
Record<string, Parsed>
|
|
124
|
+
>;
|
|
125
|
+
const defaults = themeConfig.scales?.[DEFAULT_SCALE_ID];
|
|
126
|
+
if (!defaults) throw new Error("default scale missing");
|
|
127
|
+
for (const [key, px] of Object.entries(defaults.tokens.size)) {
|
|
128
|
+
expect(parsed.size[key].val).toBe(px);
|
|
129
|
+
}
|
|
130
|
+
expect(defaultPreset.scale).toEqual({ control: 40, type: 15 });
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createCSSVariable } from "@tamagui/web";
|
|
2
|
+
import type { ScaleTokens } from "./generateScale";
|
|
3
|
+
import { SCALE_ATTRIBUTE } from "./scaleAttribute";
|
|
4
|
+
|
|
5
|
+
export { SCALE_ATTRIBUTE };
|
|
6
|
+
|
|
7
|
+
type ParsedVariable = { name?: string; variable?: string; val?: unknown };
|
|
8
|
+
|
|
9
|
+
/** The slice of a built Tamagui config the scale CSS reads its variable names from. */
|
|
10
|
+
export type ScaleCssSource = {
|
|
11
|
+
tokensParsed?: Record<string, Record<string, ParsedVariable | undefined> | undefined>;
|
|
12
|
+
fontsParsed?: Record<string, Record<string, unknown> | undefined>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type FontTables = Record<string, Record<string, unknown> | string | undefined>;
|
|
16
|
+
|
|
17
|
+
const FONT_LENGTH_ATTRIBUTES = ["size", "lineHeight", "letterSpacing"] as const;
|
|
18
|
+
|
|
19
|
+
/** `--c-size-4` for a parsed token: the name Tamagui's own `:root` block declares. */
|
|
20
|
+
function cssVariableName(variable: ParsedVariable): string | undefined {
|
|
21
|
+
const fromVar = variable.variable?.match(/^var\((--[^)]+)\)$/)?.[1];
|
|
22
|
+
if (fromVar) return fromVar;
|
|
23
|
+
return variable.name ? `--${createCSSVariable(variable.name, false)}` : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function prefixed(key: string): string {
|
|
27
|
+
return key.startsWith("$") ? key : `$${key}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One `:root[data-mp-scale="<name>"]` selector per preset name, plus an optional bare fallback. */
|
|
31
|
+
export function scaleRootSelectors(presetNames: readonly string[], fallback = false): string[] {
|
|
32
|
+
const roots = presetNames.map((name) => `:root[${SCALE_ATTRIBUTE}=${JSON.stringify(name)}]`);
|
|
33
|
+
if (fallback) roots.push(`:root:not([${SCALE_ATTRIBUTE}])`);
|
|
34
|
+
return roots;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The CSS that repaints every token-driven style for one scale on web: the
|
|
39
|
+
* scale's token values under the same `--c-*` variables Tamagui's `:root`
|
|
40
|
+
* block declares, and its font tables under each `.font_<family>` scope.
|
|
41
|
+
* Only values that differ from `base` are written, so the default scale
|
|
42
|
+
* produces nothing and a scale block stays small.
|
|
43
|
+
*/
|
|
44
|
+
export function scaleCss(
|
|
45
|
+
roots: readonly string[],
|
|
46
|
+
base: ScaleCssSource,
|
|
47
|
+
tokens: ScaleTokens,
|
|
48
|
+
fonts: Record<string, FontTables | undefined>,
|
|
49
|
+
): string {
|
|
50
|
+
if (roots.length === 0) return "";
|
|
51
|
+
const rules: string[] = [];
|
|
52
|
+
|
|
53
|
+
const tokenDeclarations: string[] = [];
|
|
54
|
+
for (const category of Object.keys(tokens).sort()) {
|
|
55
|
+
const group = tokens[category as keyof ScaleTokens] as Record<string, number>;
|
|
56
|
+
const parsedGroup = base.tokensParsed?.[category];
|
|
57
|
+
if (!parsedGroup) continue;
|
|
58
|
+
const unit = category === "zIndex" ? "" : "px";
|
|
59
|
+
for (const key of Object.keys(group).sort()) {
|
|
60
|
+
const parsed = parsedGroup[prefixed(key)];
|
|
61
|
+
const value = group[key];
|
|
62
|
+
if (!parsed || parsed.val === value) continue;
|
|
63
|
+
const name = cssVariableName(parsed);
|
|
64
|
+
if (name) tokenDeclarations.push(`${name}:${value}${unit}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (tokenDeclarations.length > 0) {
|
|
68
|
+
rules.push(`${roots.join(",")}{${tokenDeclarations.join(";")}}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const family of Object.keys(fonts).sort()) {
|
|
72
|
+
const font = fonts[family];
|
|
73
|
+
const parsedFont = base.fontsParsed?.[`$${family}`];
|
|
74
|
+
if (!font || !parsedFont) continue;
|
|
75
|
+
const declarations: string[] = [];
|
|
76
|
+
for (const attribute of FONT_LENGTH_ATTRIBUTES) {
|
|
77
|
+
const table = font[attribute];
|
|
78
|
+
const parsedTable = parsedFont[attribute] as Record<string, ParsedVariable> | undefined;
|
|
79
|
+
if (!table || typeof table !== "object" || !parsedTable) continue;
|
|
80
|
+
for (const key of Object.keys(table).sort()) {
|
|
81
|
+
const value = table[key];
|
|
82
|
+
const parsed = parsedTable[prefixed(key)];
|
|
83
|
+
if (typeof value !== "number" || !parsed || parsed.val === value) continue;
|
|
84
|
+
const name = cssVariableName(parsed);
|
|
85
|
+
if (name) declarations.push(`${name}:${value}px`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (declarations.length > 0) {
|
|
89
|
+
const selector = roots.map((root) => `${root} .font_${family}`).join(",");
|
|
90
|
+
rules.push(`${selector}{${declarations.join(";")}}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return rules.join("\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { GenericFont } from "@tamagui/web";
|
|
2
|
+
import {
|
|
3
|
+
createBodyFont,
|
|
4
|
+
createCategoryFont,
|
|
5
|
+
createHeadingFont,
|
|
6
|
+
defaultFonts,
|
|
7
|
+
fontFamilyStacks,
|
|
8
|
+
headingCategoryFontKeys,
|
|
9
|
+
} from "../defaults/fonts";
|
|
10
|
+
import type { FontCategory } from "../knobs";
|
|
11
|
+
import type { ScaleFonts } from "./generateScale";
|
|
12
|
+
|
|
13
|
+
type NumberTable = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
function at(table: Record<string, number>, key: string): number | undefined {
|
|
16
|
+
return Object.hasOwn(table, key) ? table[key] : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Move a font that was built on the `from` ladder onto the `to` ladder: a
|
|
21
|
+
* size or leading that IS the `from` body value at its key becomes the `to`
|
|
22
|
+
* value there, anything else scales with the type ratio (leading with its own
|
|
23
|
+
* size). Family, face, weight and tracking are untouched.
|
|
24
|
+
*/
|
|
25
|
+
export function rescaleFont<F extends GenericFont>(font: F, from: ScaleFonts, to: ScaleFonts): F {
|
|
26
|
+
const size = font.size as NumberTable | undefined;
|
|
27
|
+
if (!size || typeof size !== "object") return font;
|
|
28
|
+
const ratio = to.bodySizes.true / from.bodySizes.true;
|
|
29
|
+
|
|
30
|
+
const nextSize: NumberTable = {};
|
|
31
|
+
for (const [key, value] of Object.entries(size)) {
|
|
32
|
+
if (typeof value !== "number") {
|
|
33
|
+
nextSize[key] = value;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const target = value === at(from.bodySizes, key) ? at(to.bodySizes, key) : undefined;
|
|
37
|
+
nextSize[key] = target ?? Math.round(value * ratio);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lineHeight = font.lineHeight as NumberTable | undefined;
|
|
41
|
+
let nextLineHeight: NumberTable | undefined = lineHeight;
|
|
42
|
+
if (lineHeight && typeof lineHeight === "object") {
|
|
43
|
+
nextLineHeight = {};
|
|
44
|
+
for (const [key, value] of Object.entries(lineHeight)) {
|
|
45
|
+
if (typeof value !== "number") {
|
|
46
|
+
nextLineHeight[key] = value;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const target =
|
|
50
|
+
value === at(from.bodyLineHeights, key) ? at(to.bodyLineHeights, key) : undefined;
|
|
51
|
+
const before = size[key];
|
|
52
|
+
const after = nextSize[key];
|
|
53
|
+
nextLineHeight[key] =
|
|
54
|
+
target ??
|
|
55
|
+
(typeof before === "number" && typeof after === "number" && before > 0
|
|
56
|
+
? Math.round((value * after) / before)
|
|
57
|
+
: Math.round(value * ratio));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { ...font, size: nextSize, lineHeight: nextLineHeight } as F;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const headingCategories = new Map<string, FontCategory>(
|
|
65
|
+
Object.entries(headingCategoryFontKeys).map(([category, key]) => [key, category as FontCategory]),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const builtInCategories = new Set<string>(Object.keys(fontFamilyStacks));
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A scale's font map. The built-in heading/body and category fonts are
|
|
72
|
+
* rebuilt on the scale's own type tables; any other font (a consumer's
|
|
73
|
+
* `createDefaultFont` family) is moved onto the new ladder by `rescaleFont`.
|
|
74
|
+
*/
|
|
75
|
+
export function scaleFontSet(
|
|
76
|
+
fonts: Record<string, GenericFont>,
|
|
77
|
+
from: ScaleFonts,
|
|
78
|
+
to: ScaleFonts,
|
|
79
|
+
): Record<string, GenericFont> {
|
|
80
|
+
const out: Record<string, GenericFont> = {};
|
|
81
|
+
for (const [name, font] of Object.entries(fonts)) {
|
|
82
|
+
if (name === "heading" && font === defaultFonts.heading) {
|
|
83
|
+
out[name] = createHeadingFont(to) as GenericFont;
|
|
84
|
+
} else if (name === "body" && font === defaultFonts.body) {
|
|
85
|
+
out[name] = createBodyFont(to) as GenericFont;
|
|
86
|
+
} else if (
|
|
87
|
+
headingCategories.has(name) &&
|
|
88
|
+
font === (defaultFonts as Record<string, GenericFont>)[name]
|
|
89
|
+
) {
|
|
90
|
+
const category = headingCategories.get(name)!;
|
|
91
|
+
out[name] = createCategoryFont(category, fontFamilyStacks[category], to, "heading");
|
|
92
|
+
} else if (
|
|
93
|
+
builtInCategories.has(name) &&
|
|
94
|
+
font === (defaultFonts as Record<string, GenericFont>)[name]
|
|
95
|
+
) {
|
|
96
|
+
out[name] = createCategoryFont(
|
|
97
|
+
name as FontCategory,
|
|
98
|
+
fontFamilyStacks[name as FontCategory],
|
|
99
|
+
to,
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
out[name] = rescaleFont(font, from, to);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|