@oxyhq/bloom 0.7.6 → 0.8.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/lib/commonjs/normalize-colors.d.js +2 -0
- package/lib/commonjs/normalize-colors.d.js.map +1 -0
- package/lib/commonjs/theme/apply-dark-class.js +15 -13
- package/lib/commonjs/theme/apply-dark-class.js.map +1 -1
- package/lib/commonjs/theme/build-theme.js +39 -61
- package/lib/commonjs/theme/build-theme.js.map +1 -1
- package/lib/commonjs/theme/color-presets.js +11 -5
- package/lib/commonjs/theme/color-presets.js.map +1 -1
- package/lib/commonjs/theme/color-scope/index.web.js +13 -3
- package/lib/commonjs/theme/color-scope/index.web.js.map +1 -1
- package/lib/commonjs/theme/color-scope/style-builder.js +7 -6
- package/lib/commonjs/theme/color-scope/style-builder.js.map +1 -1
- package/lib/commonjs/theme/color-space.js +76 -0
- package/lib/commonjs/theme/color-space.js.map +1 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/native-root-vars.native.js +28 -18
- package/lib/commonjs/theme/native-root-vars.native.js.map +1 -1
- package/lib/commonjs/theme/preset-vars.js +20 -99
- package/lib/commonjs/theme/preset-vars.js.map +1 -1
- package/lib/commonjs/theme/token-registry.js +123 -0
- package/lib/commonjs/theme/token-registry.js.map +1 -0
- package/lib/module/normalize-colors.d.js +2 -0
- package/lib/module/normalize-colors.d.js.map +1 -0
- package/lib/module/theme/apply-dark-class.js +15 -13
- package/lib/module/theme/apply-dark-class.js.map +1 -1
- package/lib/module/theme/build-theme.js +39 -61
- package/lib/module/theme/build-theme.js.map +1 -1
- package/lib/module/theme/color-presets.js +11 -5
- package/lib/module/theme/color-presets.js.map +1 -1
- package/lib/module/theme/color-scope/index.web.js +16 -4
- package/lib/module/theme/color-scope/index.web.js.map +1 -1
- package/lib/module/theme/color-scope/style-builder.js +7 -6
- package/lib/module/theme/color-scope/style-builder.js.map +1 -1
- package/lib/module/theme/color-space.js +69 -0
- package/lib/module/theme/color-space.js.map +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/native-root-vars.native.js +29 -18
- package/lib/module/theme/native-root-vars.native.js.map +1 -1
- package/lib/module/theme/preset-vars.js +19 -98
- package/lib/module/theme/preset-vars.js.map +1 -1
- package/lib/module/theme/token-registry.js +113 -0
- package/lib/module/theme/token-registry.js.map +1 -0
- package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +13 -9
- package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/build-theme.d.ts +1 -1
- package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-presets.d.ts +11 -5
- package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts +5 -2
- package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-space.d.ts +28 -0
- package/lib/typescript/commonjs/theme/color-space.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/index.d.ts +1 -2
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts +25 -15
- package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/preset-vars.d.ts +12 -45
- package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/token-registry.d.ts +53 -0
- package/lib/typescript/commonjs/theme/token-registry.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/types.d.ts +3 -0
- package/lib/typescript/commonjs/theme/types.d.ts.map +1 -1
- package/lib/typescript/module/theme/apply-dark-class.d.ts +13 -9
- package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
- package/lib/typescript/module/theme/build-theme.d.ts +1 -1
- package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-presets.d.ts +11 -5
- package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/index.web.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/style-builder.d.ts +5 -2
- package/lib/typescript/module/theme/color-scope/style-builder.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-space.d.ts +28 -0
- package/lib/typescript/module/theme/color-space.d.ts.map +1 -0
- package/lib/typescript/module/theme/index.d.ts +1 -2
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/native-root-vars.native.d.ts +25 -15
- package/lib/typescript/module/theme/native-root-vars.native.d.ts.map +1 -1
- package/lib/typescript/module/theme/preset-vars.d.ts +12 -45
- package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/module/theme/token-registry.d.ts +53 -0
- package/lib/typescript/module/theme/token-registry.d.ts.map +1 -0
- package/lib/typescript/module/theme/types.d.ts +3 -0
- package/lib/typescript/module/theme/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/BloomColorScope.test.tsx +14 -0
- package/src/__tests__/BloomThemeProvider.test.tsx +4 -3
- package/src/__tests__/apply-dark-class.test.ts +70 -0
- package/src/__tests__/native-root-vars.test.ts +8 -7
- package/src/__tests__/preset-vars.test.ts +42 -42
- package/src/__tests__/theme.test.ts +6 -6
- package/src/normalize-colors.d.ts +22 -0
- package/src/react-native-css.d.ts +3 -3
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +574 -0
- package/src/theme/__tests__/__snapshots__/visual-gallery.test.tsx.snap +838 -0
- package/src/theme/__tests__/color-space.test.ts +28 -0
- package/src/theme/__tests__/golden-tokens.test.ts +33 -0
- package/src/theme/__tests__/runtime-contract.test.ts +49 -0
- package/src/theme/__tests__/token-registry.test.ts +30 -0
- package/src/theme/__tests__/visual-gallery.test.tsx +10 -0
- package/src/theme/apply-dark-class.ts +15 -11
- package/src/theme/build-theme.ts +39 -67
- package/src/theme/color-presets.ts +11 -5
- package/src/theme/color-scope/index.web.tsx +17 -3
- package/src/theme/color-scope/style-builder.ts +7 -4
- package/src/theme/color-space.ts +56 -0
- package/src/theme/index.ts +1 -5
- package/src/theme/native-root-vars.native.ts +28 -18
- package/src/theme/preset-vars.ts +17 -126
- package/src/theme/token-registry.ts +159 -0
- package/src/theme/types.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/index.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAI3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/index.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAI3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAiBrD,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yGAAyG;IACzG,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAuBD,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,OAAe,EACf,KAAK,EACL,QAAQ,GACT,EAAE,oBAAoB,2CA4CtB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,YAAY,GAAG,KAAK,CAAC,aAAa,CAUjF"}
|
|
@@ -13,8 +13,11 @@ export type VariableContextProviderComponent = React.ComponentType<{
|
|
|
13
13
|
}>;
|
|
14
14
|
/**
|
|
15
15
|
* Build the CSS custom-property map for a preset, ready to be applied to a
|
|
16
|
-
* subtree.
|
|
17
|
-
*
|
|
16
|
+
* subtree. Every token is resolved to an sRGB `rgb(...)` string via
|
|
17
|
+
* `getResolvedTokens` — the single canonical token pipeline shared by web and
|
|
18
|
+
* native — so Tailwind v4 `@theme` utilities (e.g. `bg-background`) and
|
|
19
|
+
* `color-mix`-based alpha utilities (`bg-primary/10`) both resolve against the
|
|
20
|
+
* same color values. Keys retain the leading `--`.
|
|
18
21
|
*/
|
|
19
22
|
export declare function buildScopeVars(colorPreset: AppColorName, mode: 'light' | 'dark'): Record<string, string>;
|
|
20
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-builder.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/style-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,KAAK,CAAC,aAAa,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC,CAAC;AASH
|
|
1
|
+
{"version":3,"file":"style-builder.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/style-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,KAAK,CAAC,aAAa,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC,CAAC;AASH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,YAAY,EACzB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,IAAI,gCAAgC,GAAG,IAAI,CAKpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,YAAY,EACzB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,SAAS,CAAC,SAAS,CAAC,CAKtB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Oklch {
|
|
2
|
+
l: number;
|
|
3
|
+
c: number;
|
|
4
|
+
h: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Rgb {
|
|
7
|
+
r: number;
|
|
8
|
+
g: number;
|
|
9
|
+
b: number;
|
|
10
|
+
}
|
|
11
|
+
/** OKLCH → linear OKLab → linear sRGB → gamma sRGB → 0..255. Pure. */
|
|
12
|
+
export declare function oklchToSrgb({ l, c, h }: Oklch): Rgb;
|
|
13
|
+
export declare function srgbToRgbString({ r, g, b }: Rgb, alpha?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Parse an `rgb(...)` / `rgba(...)` string into an {@link Rgb}.
|
|
16
|
+
*
|
|
17
|
+
* Contract: input MUST be an `rgb(...)` / `rgba(...)` string (e.g. a resolved
|
|
18
|
+
* token from `getResolvedTokens`). Both modern (`rgb(r g b)`) and legacy
|
|
19
|
+
* (`rgb(r, g, b)`) syntaxes are accepted. Anything else throws — this is a
|
|
20
|
+
* canonical-pipeline primitive and a non-rgb input signals a real bug at the
|
|
21
|
+
* call site, never a value to silently coerce to black.
|
|
22
|
+
*
|
|
23
|
+
* @throws {Error} if `s` is not an `rgb()` / `rgba()` string.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseRgbString(s: string): Rgb;
|
|
26
|
+
/** Cheap perceptual-ish distance in sRGB (sufficient for a ≤1 rounding gate). */
|
|
27
|
+
export declare function deltaE(a: Rgb, b: Rgb): number;
|
|
28
|
+
//# sourceMappingURL=color-space.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-space.d.ts","sourceRoot":"","sources":["../../../../src/theme/color-space.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AAC1D,MAAM,WAAW,GAAG;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AAMxD,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,GAAG,GAAG,CAgBnD;AAED,wBAAgB,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAIxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAO7C;AAED,iFAAiF;AACjF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAE7C"}
|
|
@@ -7,8 +7,7 @@ export { useTheme, useThemeColor, useBloomTheme } from './use-theme';
|
|
|
7
7
|
export type { Theme, ThemeColors, ThemeMode } from './types';
|
|
8
8
|
export type { AppColorName, AppColorPreset, PresetTokens } from './color-presets';
|
|
9
9
|
export { APP_COLOR_NAMES, PREMIUM_COLOR_NAMES, APP_COLOR_PRESETS, HEX_TO_APP_COLOR, hexToAppColorName, } from './color-presets';
|
|
10
|
-
export { getPresetVars, applyPresetVarsToDocument
|
|
11
|
-
export type { PresetVarsOptions } from './preset-vars';
|
|
10
|
+
export { getPresetVars, applyPresetVarsToDocument } from './preset-vars';
|
|
12
11
|
export { applyDarkClass } from './apply-dark-class';
|
|
13
12
|
export { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
14
13
|
export { initCssInteropDarkMode } from './init-css-interop';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -21,18 +21,28 @@ import { type AppColorName } from './color-presets';
|
|
|
21
21
|
* `BloomThemeProvider`'s `VariableContextProvider` only feeds path #1, so the navigator
|
|
22
22
|
* host and every portal/modal — which render outside that subtree — never see the
|
|
23
23
|
* preset vars and fall through to `rootVariables`/`universalVariables`. By default those
|
|
24
|
-
* families hold only react-native-css's own `__rn-css-rem`/`__rn-css-color` entries
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* content resolves the palette identically regardless of React-tree position.
|
|
24
|
+
* families hold only react-native-css's own `__rn-css-rem`/`__rn-css-color` entries, so
|
|
25
|
+
* the whole tree below the navigator renders monochrome until the palette is injected at
|
|
26
|
+
* runtime. Writing the vars into `rootVariables` here is the exact same mechanism
|
|
27
|
+
* react-native-css uses for compiled `:root {}` / `@theme` vars (`StyleCollection.inject`
|
|
28
|
+
* does `rootVariables(name).set(valueArray)`), so portaled content resolves the palette
|
|
29
|
+
* identically regardless of React-tree position.
|
|
31
30
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* Consumer contract (CRITICAL — single canonical color pipeline)
|
|
32
|
+
* -------------------------------------------------------------
|
|
33
|
+
* Each base `--x` token is now written as a FULL `rgb(...)` color (not a raw HSL
|
|
34
|
+
* triple). A consuming app's Tailwind `@theme` must therefore reference these
|
|
35
|
+
* base tokens DIRECTLY (its Tailwind color var equals `var(--x)`) and must NOT
|
|
36
|
+
* wrap them in `hsl(...)`. The legacy `hsl(var(--x))` indirection (which assumed
|
|
37
|
+
* a raw triple) would produce the invalid `hsl(rgb(...))` and must be removed in
|
|
38
|
+
* every consumer. Web and native now share this one contract.
|
|
39
|
+
*
|
|
40
|
+
* Every token is written as an sRGB `rgb(...)` string from the single canonical
|
|
41
|
+
* token pipeline (`getResolvedTokens`) — `primary: 'rgb(31 153 239)'` — the SAME
|
|
42
|
+
* values web writes to `document.documentElement`. sRGB is the space
|
|
43
|
+
* react-native-css's `color-mix` resolver registers, so `color-mix`-based alpha
|
|
44
|
+
* utilities (`bg-primary/10`) resolve on native; full (non-alpha) utilities
|
|
45
|
+
* resolve directly through bloom's `global.css` `var(--x)` indirection.
|
|
36
46
|
*
|
|
37
47
|
* Why a STATIC `import`, not a runtime `require` (critical)
|
|
38
48
|
* ---------------------------------------------------------
|
|
@@ -62,15 +72,15 @@ import { type AppColorName } from './color-presets';
|
|
|
62
72
|
* `shared.rootVariables[name]`, and `@property --x` goes through
|
|
63
73
|
* `name.startsWith("--") ? name.slice(2) : name`. The lookup side matches: a `var(--x)`
|
|
64
74
|
* reference compiles to `[{}, "var", ident.slice(2)]`, so `varResolver` looks up the
|
|
65
|
-
* bare name. `
|
|
66
|
-
* writing each entry.
|
|
75
|
+
* bare name. `getResolvedTokens` returns keys WITH the `--` prefix, so we strip it
|
|
76
|
+
* before writing each entry.
|
|
67
77
|
*
|
|
68
78
|
* Value shape
|
|
69
79
|
* -----------
|
|
70
80
|
* Each entry is a `VariableValue[]` — an array of `[value, mediaCondition?]` tuples.
|
|
71
81
|
* Bloom's preset vars are unconditional, so each is a single-element `[[value]]`, e.g.
|
|
72
|
-
* `rootVariables('primary').set([['
|
|
73
|
-
* emits for unconditional `:root` vars.
|
|
82
|
+
* `rootVariables('primary').set([['rgb(31 153 239)']])`. This is the same shape the
|
|
83
|
+
* compiler emits for unconditional `:root` vars.
|
|
74
84
|
*
|
|
75
85
|
* This is native-only and additive: the `VariableContextProvider` wrapper stays (it is
|
|
76
86
|
* the correct, scoped mechanism for `BloomColorScope` subtree overrides and harmlessly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-root-vars.native.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native-root-vars.native.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.native.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS3F"}
|
|
@@ -1,47 +1,4 @@
|
|
|
1
1
|
import { type AppColorName, type PresetTokens } from './color-presets';
|
|
2
|
-
/**
|
|
3
|
-
* Convert a shadcn-style HSL triple (`'H S% L%'`, optionally with an alpha tail
|
|
4
|
-
* `'H S% L% / A'`) into an sRGB `rgb(...)` string.
|
|
5
|
-
*
|
|
6
|
-
* Why rgb and not hsl (the native alpha-utility bug this fixes)
|
|
7
|
-
* -------------------------------------------------------------
|
|
8
|
-
* Tailwind v4 compiles an alpha-on-theme-color utility (`bg-primary/10`) into a
|
|
9
|
-
* `color-mix(... var(--color-primary) 10%, transparent)` declaration. On native,
|
|
10
|
-
* react-native-css resolves that `color-mix` at runtime via `colorjs.io/fn`, but
|
|
11
|
-
* its `color-mix` implementation registers ONLY the `sRGB`, `P3`, and `OKLab`
|
|
12
|
-
* color spaces — never `HSL`. So if `--color-primary` resolves to an `hsl(...)`
|
|
13
|
-
* string, `parse('hsl(...)')` throws, the mix is swallowed, and the utility
|
|
14
|
-
* silently produces no color. Emitting the resolved var as `rgb(...)` (a space
|
|
15
|
-
* colorjs.io always has registered) makes the alpha utilities resolve on native,
|
|
16
|
-
* matching web. Full (non-alpha) utilities already worked because React Native's
|
|
17
|
-
* native color parser handles `hsl()` directly; only `color-mix` was affected.
|
|
18
|
-
*
|
|
19
|
-
* Output is space-separated channels (`rgb(31 153 239)`), the modern CSS syntax
|
|
20
|
-
* parsed by both `colorjs.io/fn` (with only sRGB/P3/OKLab registered) and
|
|
21
|
-
* react-native-web / browsers. An alpha tail emits `rgb(r g b / a)`, likewise
|
|
22
|
-
* parsed by both.
|
|
23
|
-
*
|
|
24
|
-
* Pure function — standard HSL→sRGB conversion, channels rounded to integers.
|
|
25
|
-
* Tolerates a `deg` suffix on the hue and `%` suffixes on saturation/lightness.
|
|
26
|
-
*/
|
|
27
|
-
export declare function hslTripletToRgb(triplet: string): string;
|
|
28
|
-
export interface PresetVarsOptions {
|
|
29
|
-
/**
|
|
30
|
-
* Also emit Tailwind v4 resolved `--color-*` vars (as sRGB `rgb(...)` strings)
|
|
31
|
-
* alongside the raw HSL triples. Needed when scoping a subtree where
|
|
32
|
-
* Tailwind's `@theme` block has already precomputed `--color-*` at `:root`,
|
|
33
|
-
* so overriding `--background` alone wouldn't cascade to `bg-background`.
|
|
34
|
-
*
|
|
35
|
-
* The resolved vars are emitted as `rgb(...)` rather than `hsl(...)` so the
|
|
36
|
-
* alpha-on-theme-color utilities (`bg-primary/10`, `text-foreground/50`)
|
|
37
|
-
* resolve on native: Tailwind compiles those to a runtime `color-mix(...)` that
|
|
38
|
-
* react-native-css evaluates via `colorjs.io/fn`, which only registers the
|
|
39
|
-
* sRGB/P3/OKLab spaces (not HSL) — so an `hsl(...)` var would throw and the
|
|
40
|
-
* utility would silently render no color. See `hslTripletToRgb`. Default
|
|
41
|
-
* `false`.
|
|
42
|
-
*/
|
|
43
|
-
includeResolvedColorVars?: boolean;
|
|
44
|
-
}
|
|
45
2
|
/**
|
|
46
3
|
* Bloom's base preset tokens extended with the surface/card/chart/sidebar
|
|
47
4
|
* tokens that apps layer on top of the core shadcn palette. Synthesized from
|
|
@@ -49,13 +6,23 @@ export interface PresetVarsOptions {
|
|
|
49
6
|
*
|
|
50
7
|
* This is the single source of truth for extended theming vars — consumer apps
|
|
51
8
|
* must not redefine these per-app.
|
|
9
|
+
*
|
|
10
|
+
* Values are platform-agnostic **raw HSL triples** (`'185 100% 20%'`, optionally
|
|
11
|
+
* with a `/ A` alpha tail). The single canonical resolver `getResolvedTokens`
|
|
12
|
+
* (in `token-registry.ts`) converts these to sRGB `rgb(...)` for BOTH web and
|
|
13
|
+
* native runtime writes — this function only produces the raw triple map.
|
|
52
14
|
*/
|
|
53
|
-
export declare function getPresetVars(colorName: AppColorName, mode: 'light' | 'dark'
|
|
15
|
+
export declare function getPresetVars(colorName: AppColorName, mode: 'light' | 'dark'): PresetTokens;
|
|
54
16
|
/**
|
|
55
17
|
* Apply a preset's extended vars to `document.documentElement`. Web-only no-op
|
|
56
18
|
* on native. `BloomThemeProvider` already writes the base preset vars on web;
|
|
57
19
|
* call this only when an app needs the extended (card/chart/sidebar) tokens on
|
|
58
20
|
* the document root.
|
|
21
|
+
*
|
|
22
|
+
* Tokens are resolved to sRGB `rgb(...)` via the single canonical
|
|
23
|
+
* `getResolvedTokens` pipeline, so `var(--x)` — the form Tailwind v4
|
|
24
|
+
* `@theme inline` compiles its color utilities to — resolves directly to a
|
|
25
|
+
* valid color on web.
|
|
59
26
|
*/
|
|
60
|
-
export declare function applyPresetVarsToDocument(colorName: AppColorName, mode: 'light' | 'dark'
|
|
27
|
+
export declare function applyPresetVarsToDocument(colorName: AppColorName, mode: 'light' | 'dark'): void;
|
|
61
28
|
//# sourceMappingURL=preset-vars.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/preset-vars.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"preset-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/preset-vars.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAe1F;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,YAAY,CAiCd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,IAAI,CAQN"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { APP_COLOR_NAMES, type AppColorName } from './color-presets';
|
|
2
|
+
/**
|
|
3
|
+
* The canonical set of Bloom theme token names — WITHOUT the leading `--`.
|
|
4
|
+
*
|
|
5
|
+
* This is the single source of truth for which tokens a preset produces. It
|
|
6
|
+
* covers exactly the keys returned by `getPresetVars(name, mode)` (the base
|
|
7
|
+
* shadcn palette plus the synthesized extended tokens: card/chart/content-area/
|
|
8
|
+
* sidebar-*). The list is intentionally hand-maintained and verified by
|
|
9
|
+
* `token-registry.test.ts` against the live `getPresetVars('oxy', 'light')`
|
|
10
|
+
* output, so any drift in the preset shape fails CI.
|
|
11
|
+
*
|
|
12
|
+
* Order mirrors `getPresetVars`'s insertion order: base tokens first (in the
|
|
13
|
+
* order they appear in the preset data), then the synthesized extended tokens.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CANONICAL_TOKENS: readonly ["background", "foreground", "surface", "surface-foreground", "popover", "popover-foreground", "primary", "primary-foreground", "secondary", "secondary-foreground", "muted", "muted-foreground", "accent", "accent-foreground", "destructive", "border", "input", "ring", "sidebar", "card", "card-foreground", "chart-1", "chart-2", "chart-3", "chart-4", "chart-5", "content-area", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground", "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring"];
|
|
16
|
+
/** A canonical token name (no leading `--`). */
|
|
17
|
+
export type CanonicalToken = (typeof CANONICAL_TOKENS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* Convert a shadcn-style HSL triple (`'H S% L%'`, optionally with an alpha tail
|
|
20
|
+
* `'H S% L% / A'`) into an sRGB `rgb(...)` string.
|
|
21
|
+
*
|
|
22
|
+
* Standard HSL→sRGB math with integer-rounded channels and modern
|
|
23
|
+
* space-separated output (`rgb(r g b)` / `rgb(r g b / a)`). This is the SINGLE
|
|
24
|
+
* conversion the whole library uses — web document writes, web scope vars, and
|
|
25
|
+
* native `rootVariables` all flow through `getResolvedTokens`, which calls this.
|
|
26
|
+
* The byte-for-byte output is frozen by the golden parity fixture
|
|
27
|
+
* (`__fixtures__/golden-resolved-tokens.json`), so the visible palette cannot
|
|
28
|
+
* drift.
|
|
29
|
+
*
|
|
30
|
+
* Task 2's `oklchToSrgb` (in `color-space.ts`) stays available for a FUTURE
|
|
31
|
+
* OKLCH re-authoring of the seed palette; it is intentionally NOT used here —
|
|
32
|
+
* routing through OKLCH would shift the rounded channels off the frozen palette.
|
|
33
|
+
*
|
|
34
|
+
* Pure function — no side effects.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hslToSrgb(triple: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a preset's canonical runtime color tokens to sRGB `rgb(...)` strings.
|
|
39
|
+
*
|
|
40
|
+
* THE single source of runtime color tokens for the whole library, on BOTH web
|
|
41
|
+
* and native. Takes the raw triple map from `getPresetVars(preset, mode)` (base
|
|
42
|
+
* + extended) and converts every value that looks like an HSL triple via
|
|
43
|
+
* `hslToSrgb`. Non-triple values (already-resolved colors, length/radius tokens)
|
|
44
|
+
* pass through unchanged. Keys retain the `--` prefix, so each `--x` token IS a
|
|
45
|
+
* full CSS color — no resolved-color companion universe, no per-platform wrapping.
|
|
46
|
+
*
|
|
47
|
+
* The result is frozen by the golden parity fixture
|
|
48
|
+
* (`__fixtures__/golden-resolved-tokens.json`), guaranteeing the visible palette
|
|
49
|
+
* matches the previous resolved-color pipeline byte-for-byte.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getResolvedTokens(preset: AppColorName, mode: 'light' | 'dark'): Record<string, string>;
|
|
52
|
+
export { APP_COLOR_NAMES };
|
|
53
|
+
//# sourceMappingURL=token-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-registry.d.ts","sourceRoot":"","sources":["../../../../src/theme/token-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGrE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,0hBAmCnB,CAAC;AAEX,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAW/D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA+ChD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -10,8 +10,11 @@ export interface ThemeColors {
|
|
|
10
10
|
borderLight: string;
|
|
11
11
|
primary: string;
|
|
12
12
|
primaryForeground: string;
|
|
13
|
+
/** Preset accent tint (`--accent`) — a soft brand-tinted surface, NOT the page surface. */
|
|
13
14
|
primaryLight: string;
|
|
15
|
+
/** Focus-ring shade (`--ring`) — the preset's emphasized ring/border color, NOT the page background. */
|
|
14
16
|
primaryDark: string;
|
|
17
|
+
/** Preset secondary surface (`--secondary`) — a muted companion surface, NOT a mirror of `primary`. */
|
|
15
18
|
secondary: string;
|
|
16
19
|
tint: string;
|
|
17
20
|
icon: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAE3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IAEpB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IAEpB,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAE3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IAEpB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAC;IACrB,wGAAwG;IACxG,WAAW,EAAE,MAAM,CAAC;IAEpB,uGAAuG;IACvG,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
package/package.json
CHANGED
|
@@ -151,6 +151,20 @@ describe('BloomColorScope', () => {
|
|
|
151
151
|
expect(flat).toEqual(
|
|
152
152
|
expect.arrayContaining([expect.objectContaining({ padding: 8 })]),
|
|
153
153
|
);
|
|
154
|
+
|
|
155
|
+
// Web var contract: the scoped base tokens (`--background`, `--primary`)
|
|
156
|
+
// must be full `rgb(...)` colors from the single canonical pipeline —
|
|
157
|
+
// never bare HSL triples — so the web apps' `var(--x)` color utilities
|
|
158
|
+
// resolve. (The production incident was a bare triple here too.)
|
|
159
|
+
const vars = Object.assign(
|
|
160
|
+
{},
|
|
161
|
+
...flat.filter((entry) => '--background' in entry || '--primary' in entry),
|
|
162
|
+
) as Record<string, unknown>;
|
|
163
|
+
const background = String(vars['--background'] ?? '');
|
|
164
|
+
const primary = String(vars['--primary'] ?? '');
|
|
165
|
+
expect(background.startsWith('rgb(')).toBe(true);
|
|
166
|
+
expect(primary.startsWith('rgb(')).toBe(true);
|
|
167
|
+
expect(/^-?\d[\d.]*\s+[\d.]+%/.test(background)).toBe(false);
|
|
154
168
|
});
|
|
155
169
|
});
|
|
156
170
|
});
|
|
@@ -72,8 +72,8 @@ describe('BloomThemeProvider', () => {
|
|
|
72
72
|
<ThemeDisplay />
|
|
73
73
|
</BloomThemeProvider>,
|
|
74
74
|
);
|
|
75
|
-
// Blue preset primary is HSL(205, 87%, 53%)
|
|
76
|
-
expect(getByTestId('primary').props.children).toBe('
|
|
75
|
+
// Blue preset primary is HSL(205, 87%, 53%) → canonical sRGB rgb(31 153 239)
|
|
76
|
+
expect(getByTestId('primary').props.children).toBe('rgb(31 153 239)');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('generates different background colors for light and dark modes', () => {
|
|
@@ -135,7 +135,8 @@ describe('useThemeColor', () => {
|
|
|
135
135
|
<ColorDisplay colorKey="primary" />
|
|
136
136
|
</BloomThemeProvider>,
|
|
137
137
|
);
|
|
138
|
-
|
|
138
|
+
// teal preset primary HSL(185, 100%, 20%) → canonical sRGB rgb(0 94 102)
|
|
139
|
+
expect(getByTestId('color').props.children).toBe('rgb(0 94 102)');
|
|
139
140
|
});
|
|
140
141
|
});
|
|
141
142
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Platform } from 'react-native';
|
|
6
|
+
|
|
7
|
+
import { applyColorPresetVars } from '../theme/apply-dark-class';
|
|
8
|
+
|
|
9
|
+
// `applyColorPresetVars` is the WEB write path that feeds the live document. The
|
|
10
|
+
// single canonical token pipeline (`getResolvedTokens`) resolves every base
|
|
11
|
+
// token to a full `rgb(...)` color, so the shadcn/Tailwind-v4 web apps that
|
|
12
|
+
// consume `background-color: var(--background)` directly get a valid color (the
|
|
13
|
+
// production incident was a bare HSL triple here). These tests pin that contract.
|
|
14
|
+
|
|
15
|
+
describe('applyColorPresetVars — web document var contract', () => {
|
|
16
|
+
const originalOS = Platform.OS;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
Platform.OS = 'web';
|
|
20
|
+
document.documentElement.removeAttribute('style');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
Platform.OS = originalOS;
|
|
25
|
+
document.documentElement.removeAttribute('style');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('writes base tokens to the document as full rgb() colors, not bare triples', () => {
|
|
29
|
+
applyColorPresetVars('oxy', 'dark');
|
|
30
|
+
|
|
31
|
+
const style = document.documentElement.style;
|
|
32
|
+
const background = style.getPropertyValue('--background').trim();
|
|
33
|
+
const primary = style.getPropertyValue('--primary').trim();
|
|
34
|
+
const foreground = style.getPropertyValue('--foreground').trim();
|
|
35
|
+
|
|
36
|
+
expect(background).not.toBe('');
|
|
37
|
+
// The exact bug: a bare `H S% L%` triple. Must be a resolved color now.
|
|
38
|
+
expect(background.startsWith('rgb(')).toBe(true);
|
|
39
|
+
expect(background.endsWith(')')).toBe(true);
|
|
40
|
+
expect(/^-?\d[\d.]*\s+[\d.]+%/.test(background)).toBe(false);
|
|
41
|
+
|
|
42
|
+
expect(primary.startsWith('rgb(')).toBe(true);
|
|
43
|
+
expect(foreground.startsWith('rgb(')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('writes the extended tokens (card/chart/sidebar) as rgb() colors too', () => {
|
|
47
|
+
applyColorPresetVars('oxy', 'dark');
|
|
48
|
+
|
|
49
|
+
const style = document.documentElement.style;
|
|
50
|
+
expect(style.getPropertyValue('--card').trim().startsWith('rgb(')).toBe(true);
|
|
51
|
+
expect(style.getPropertyValue('--sidebar-primary').trim().startsWith('rgb(')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does NOT emit any legacy resolved-color companion vars', () => {
|
|
55
|
+
applyColorPresetVars('oxy', 'dark');
|
|
56
|
+
const style = document.documentElement.style;
|
|
57
|
+
// The base `--x` token IS the color now; no resolved-color companion universe
|
|
58
|
+
// should be written. Scan every property that was set rather than referencing
|
|
59
|
+
// the legacy name literally.
|
|
60
|
+
const prefix = `--${'color'}-`;
|
|
61
|
+
const written = Array.from({ length: style.length }, (_, i) => style.item(i));
|
|
62
|
+
expect(written.some((name) => name.startsWith(prefix))).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('no-ops on native (does not touch the document)', () => {
|
|
66
|
+
Platform.OS = 'ios';
|
|
67
|
+
applyColorPresetVars('oxy', 'dark');
|
|
68
|
+
expect(document.documentElement.getAttribute('style')).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -52,16 +52,17 @@ describe('applyNativeRootVars (native variant)', () => {
|
|
|
52
52
|
applyNativeRootVars('blue', 'light');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
// Keys are bare (no `--`), and the
|
|
55
|
+
// Keys are bare (no `--`), and the value is the single canonical sRGB
|
|
56
|
+
// `rgb(...)` color from `getResolvedTokens` (`205 87% 53%` → `rgb(31 153 239)`).
|
|
57
|
+
// sRGB is the space react-native-css's `color-mix` resolver registers, so
|
|
58
|
+
// alpha utilities (`bg-primary/10`) resolve on native.
|
|
56
59
|
expect(sets).toHaveProperty('primary');
|
|
57
60
|
expect(sets).not.toHaveProperty('--primary');
|
|
58
|
-
expect(sets.primary).toEqual([['
|
|
61
|
+
expect(sets.primary).toEqual([['rgb(31 153 239)']]);
|
|
59
62
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
expect(sets).toHaveProperty('color-primary');
|
|
64
|
-
expect(sets['color-primary']).toEqual([['rgb(31 153 239)']]);
|
|
63
|
+
// The legacy resolved-color companion universe is gone — the base token IS
|
|
64
|
+
// rgb, so no `color-primary` companion is written.
|
|
65
|
+
expect(sets).not.toHaveProperty('color-primary');
|
|
65
66
|
|
|
66
67
|
// Every value is a single-element value-array `[[value]]` of strings.
|
|
67
68
|
for (const value of Object.values(sets)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { APP_COLOR_NAMES, APP_COLOR_PRESETS } from '../theme/color-presets';
|
|
2
|
-
import { getPresetVars
|
|
2
|
+
import { getPresetVars } from '../theme/preset-vars';
|
|
3
|
+
import { getResolvedTokens } from '../theme/token-registry';
|
|
3
4
|
|
|
4
5
|
const EXTENDED_KEYS = [
|
|
5
6
|
'--card',
|
|
@@ -45,55 +46,54 @@ describe('getPresetVars', () => {
|
|
|
45
46
|
expect(vars['--sidebar-primary']).toBe(primary);
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
it('
|
|
49
|
+
it('returns raw HSL triples (no resolved colors) — getResolvedTokens owns rgb', () => {
|
|
50
|
+
// `getPresetVars` is the raw triple source feeding the single canonical
|
|
51
|
+
// resolver. It must NOT pre-resolve to rgb/hsl, and the legacy resolved-color
|
|
52
|
+
// companion universe must be gone (no key carries the resolved-color prefix).
|
|
49
53
|
const vars = getPresetVars('green', 'dark');
|
|
50
|
-
expect(vars['--
|
|
54
|
+
expect(vars['--primary']).toBe(APP_COLOR_PRESETS.green.dark['--primary']);
|
|
55
|
+
expect(/^-?\d[\d.]*\s+[\d.]+%/.test(vars['--primary'] ?? '')).toBe(true);
|
|
56
|
+
const legacyPrefix = `--${'color'}-`;
|
|
57
|
+
expect(Object.keys(vars).some((k) => k.startsWith(legacyPrefix))).toBe(false);
|
|
51
58
|
});
|
|
59
|
+
});
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
describe('getResolvedTokens (single canonical rgb pipeline)', () => {
|
|
62
|
+
it('resolves every base + extended token to a space-separated rgb() string', () => {
|
|
63
|
+
const resolved = getResolvedTokens('green', 'dark');
|
|
55
64
|
const primaryTriple = APP_COLOR_PRESETS.green.dark['--primary'] ?? '';
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(
|
|
65
|
+
// No resolved-color companion universe anymore — the base token is rgb.
|
|
66
|
+
const legacyPrefix = `--${'color'}-`;
|
|
67
|
+
expect(Object.keys(resolved).some((k) => k.startsWith(legacyPrefix))).toBe(false);
|
|
68
|
+
expect(resolved['--primary']).toMatch(/^rgb\(\d+ \d+ \d+\)$/);
|
|
69
|
+
expect(resolved['--card']).toMatch(/^rgb\(\d+ \d+ \d+\)$/);
|
|
70
|
+
// primary triple resolves to the same rgb the registry produces.
|
|
71
|
+
expect(resolved['--primary']).toBe(getResolvedTokens('green', 'dark')['--primary']);
|
|
72
|
+
expect(primaryTriple).toMatch(/%/);
|
|
59
73
|
});
|
|
60
74
|
|
|
61
|
-
it('resolves --
|
|
75
|
+
it('resolves --primary to rgb for the blue preset (matches web reference)', () => {
|
|
62
76
|
// `blue` preset light `--primary` is `205 87% 53%` → rgb(31 153 239).
|
|
63
|
-
const
|
|
64
|
-
expect(
|
|
77
|
+
const resolved = getResolvedTokens('blue', 'light');
|
|
78
|
+
expect(resolved['--primary']).toBe('rgb(31 153 239)');
|
|
65
79
|
});
|
|
66
|
-
});
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const b = Number(match?.[3] ?? NaN);
|
|
86
|
-
expect(Math.abs(r - 0)).toBeLessThanOrEqual(1);
|
|
87
|
-
expect(Math.abs(g - 94)).toBeLessThanOrEqual(1);
|
|
88
|
-
expect(Math.abs(b - 102)).toBeLessThanOrEqual(1);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('tolerates a deg suffix on the hue', () => {
|
|
92
|
-
expect(hslTripletToRgb('205deg 87% 53%')).toBe('rgb(31 153 239)');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('emits rgb(r g b / a) for an alpha tail (decimal or percentage)', () => {
|
|
96
|
-
expect(hslTripletToRgb('205 87% 53% / 0.5')).toBe('rgb(31 153 239 / 0.5)');
|
|
97
|
-
expect(hslTripletToRgb('205 87% 53% / 50%')).toBe('rgb(31 153 239 / 0.5)');
|
|
81
|
+
it('produces a full CSS color for every base preset token of every preset/mode', () => {
|
|
82
|
+
for (const name of APP_COLOR_NAMES) {
|
|
83
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
84
|
+
const base = mode === 'light' ? APP_COLOR_PRESETS[name].light : APP_COLOR_PRESETS[name].dark;
|
|
85
|
+
const resolved = getResolvedTokens(name, mode);
|
|
86
|
+
for (const key of Object.keys(base)) {
|
|
87
|
+
const value = resolved[key] ?? '';
|
|
88
|
+
// A bare triple (`185 50% 5%`) is INVALID CSS — the incident. Every
|
|
89
|
+
// base token must come out as a complete color the browser parses.
|
|
90
|
+
expect(value.startsWith('rgb(') || value.startsWith('hsl(') || value.startsWith('#')).toBe(
|
|
91
|
+
true,
|
|
92
|
+
);
|
|
93
|
+
// Never a bare HSL triple.
|
|
94
|
+
expect(/^-?\d[\d.]*\s+[\d.]+%/.test(value)).toBe(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
98
|
});
|
|
99
99
|
});
|
|
@@ -50,12 +50,12 @@ describe('Theme system', () => {
|
|
|
50
50
|
|
|
51
51
|
describe('buildTheme primaryForeground', () => {
|
|
52
52
|
it('resolves a readable foreground for the primary fill per preset', () => {
|
|
53
|
-
// blue's --primary-foreground is `0 0% 100%` → white
|
|
54
|
-
expect(buildTheme('blue', 'light').colors.primaryForeground).toBe('
|
|
55
|
-
expect(buildTheme('blue', 'dark').colors.primaryForeground).toBe('
|
|
56
|
-
// yellow's --primary-foreground is `0 0% 0%` → black
|
|
57
|
-
expect(buildTheme('yellow', 'light').colors.primaryForeground).toBe('
|
|
58
|
-
expect(buildTheme('yellow', 'dark').colors.primaryForeground).toBe('
|
|
53
|
+
// blue's --primary-foreground is `0 0% 100%` → white (canonical sRGB rgb)
|
|
54
|
+
expect(buildTheme('blue', 'light').colors.primaryForeground).toBe('rgb(255 255 255)');
|
|
55
|
+
expect(buildTheme('blue', 'dark').colors.primaryForeground).toBe('rgb(255 255 255)');
|
|
56
|
+
// yellow's --primary-foreground is `0 0% 0%` → black (canonical sRGB rgb)
|
|
57
|
+
expect(buildTheme('yellow', 'light').colors.primaryForeground).toBe('rgb(0 0 0)');
|
|
58
|
+
expect(buildTheme('yellow', 'dark').colors.primaryForeground).toBe('rgb(0 0 0)');
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('sets a primaryForeground for every preset in both modes', () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declaration for `@react-native/normalize-colors`.
|
|
3
|
+
*
|
|
4
|
+
* The package ships JS-only (`module.exports = normalizeColor`) with no bundled
|
|
5
|
+
* type declarations, so `tsc` can't see its shape. It arrives transitively via
|
|
6
|
+
* react-native / react-native-web — Bloom only references it in tests, as the
|
|
7
|
+
* exact parser React Native (native) and react-native-web use behind
|
|
8
|
+
* `StyleSheet`/`processColor` to validate that a color string is renderable.
|
|
9
|
+
*
|
|
10
|
+
* Typed to match the package's runtime contract (`index.js`): a default export
|
|
11
|
+
* that takes a CSS color string or a packed 32-bit RGBA number and returns the
|
|
12
|
+
* normalized packed RGBA integer, or `null` when the input is unparseable.
|
|
13
|
+
*/
|
|
14
|
+
declare module '@react-native/normalize-colors' {
|
|
15
|
+
/**
|
|
16
|
+
* Parse a CSS color string (or packed RGBA number) into a packed 32-bit RGBA
|
|
17
|
+
* integer (`0xRRGGBBAA`). Returns `null` when the input cannot be parsed — an
|
|
18
|
+
* unparseable color renders nothing in React Native / react-native-web.
|
|
19
|
+
*/
|
|
20
|
+
const normalizeColor: (color: string | number) => number | null;
|
|
21
|
+
export default normalizeColor;
|
|
22
|
+
}
|