@oxyhq/bloom 0.7.5 → 0.7.7

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 (66) hide show
  1. package/lib/commonjs/theme/apply-dark-class.js +28 -10
  2. package/lib/commonjs/theme/apply-dark-class.js.map +1 -1
  3. package/lib/commonjs/theme/color-presets.js +10 -4
  4. package/lib/commonjs/theme/color-presets.js.map +1 -1
  5. package/lib/commonjs/theme/color-scope/index.web.js +19 -2
  6. package/lib/commonjs/theme/color-scope/index.web.js.map +1 -1
  7. package/lib/commonjs/theme/index.js +6 -0
  8. package/lib/commonjs/theme/index.js.map +1 -1
  9. package/lib/commonjs/theme/native-root-vars.js +32 -117
  10. package/lib/commonjs/theme/native-root-vars.js.map +1 -1
  11. package/lib/commonjs/theme/native-root-vars.native.js +127 -0
  12. package/lib/commonjs/theme/native-root-vars.native.js.map +1 -0
  13. package/lib/commonjs/theme/preset-vars.js +54 -1
  14. package/lib/commonjs/theme/preset-vars.js.map +1 -1
  15. package/lib/module/theme/apply-dark-class.js +24 -11
  16. package/lib/module/theme/apply-dark-class.js.map +1 -1
  17. package/lib/module/theme/color-presets.js +10 -4
  18. package/lib/module/theme/color-presets.js.map +1 -1
  19. package/lib/module/theme/color-scope/index.web.js +22 -3
  20. package/lib/module/theme/color-scope/index.web.js.map +1 -1
  21. package/lib/module/theme/index.js +1 -1
  22. package/lib/module/theme/index.js.map +1 -1
  23. package/lib/module/theme/native-root-vars.js +32 -116
  24. package/lib/module/theme/native-root-vars.js.map +1 -1
  25. package/lib/module/theme/native-root-vars.native.js +122 -0
  26. package/lib/module/theme/native-root-vars.native.js.map +1 -0
  27. package/lib/module/theme/preset-vars.js +53 -1
  28. package/lib/module/theme/preset-vars.js.map +1 -1
  29. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +18 -9
  30. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/theme/color-presets.d.ts +10 -4
  32. package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/theme/index.d.ts +1 -1
  35. package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/theme/native-root-vars.d.ts +30 -90
  37. package/lib/typescript/commonjs/theme/native-root-vars.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts +94 -0
  39. package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/theme/preset-vars.d.ts +30 -0
  41. package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
  42. package/lib/typescript/module/theme/apply-dark-class.d.ts +18 -9
  43. package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
  44. package/lib/typescript/module/theme/color-presets.d.ts +10 -4
  45. package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
  46. package/lib/typescript/module/theme/color-scope/index.web.d.ts.map +1 -1
  47. package/lib/typescript/module/theme/index.d.ts +1 -1
  48. package/lib/typescript/module/theme/index.d.ts.map +1 -1
  49. package/lib/typescript/module/theme/native-root-vars.d.ts +30 -90
  50. package/lib/typescript/module/theme/native-root-vars.d.ts.map +1 -1
  51. package/lib/typescript/module/theme/native-root-vars.native.d.ts +94 -0
  52. package/lib/typescript/module/theme/native-root-vars.native.d.ts.map +1 -0
  53. package/lib/typescript/module/theme/preset-vars.d.ts +30 -0
  54. package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/__tests__/BloomColorScope.test.tsx +14 -0
  57. package/src/__tests__/apply-dark-class.test.ts +65 -0
  58. package/src/__tests__/native-root-vars.test.ts +30 -4
  59. package/src/__tests__/preset-vars.test.ts +47 -1
  60. package/src/theme/apply-dark-class.ts +24 -11
  61. package/src/theme/color-presets.ts +10 -4
  62. package/src/theme/color-scope/index.web.tsx +23 -2
  63. package/src/theme/index.ts +1 -0
  64. package/src/theme/native-root-vars.native.ts +123 -0
  65. package/src/theme/native-root-vars.ts +32 -118
  66. package/src/theme/preset-vars.ts +53 -1
@@ -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;AAU1F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CvD;AAyBD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAwCd;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAQN"}
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;AAU1F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CvD;AAmBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlE;AAyBD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAwCd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAQN"}
@@ -1,17 +1,26 @@
1
1
  import { type AppColorName } from './color-presets';
2
+ export { toWebColorValue } from './preset-vars';
2
3
  export declare function applyDarkClass(resolved: 'light' | 'dark'): void;
3
4
  /**
4
5
  * Apply a color preset's CSS custom properties to the document root.
5
- * No-op on native — only affects web.
6
+ * No-op on native — only affects web (early-returns on `Platform.OS !== 'web'`).
6
7
  *
7
- * Writes both the raw HSL triples (e.g. `--primary: 185 100% 20%`) and the
8
- * resolved `--color-*` vars (`--color-primary: rgb(0 94 102)`) so both
9
- * shadcn-style `hsl(var(--primary))` plumbing and Tailwind v4 `@theme`
10
- * utilities resolve consistently. The resolved vars are emitted as `rgb(...)`
11
- * (not `hsl(...)`) so the alpha utilities (`bg-primary/10`) resolve on native
12
- * see `getPresetVars` / `hslTripletToRgb`. Includes extended tokens (card,
13
- * chart-*, content-area, sidebar-*) so consumer apps don't need to synthesize
14
- * them.
8
+ * Web var contract (the form Tailwind v4 `@theme inline` compiles to)
9
+ * -------------------------------------------------------------------
10
+ * The shadcn/Tailwind-v4 web apps compile their color utilities to reference the
11
+ * BASE token directly (`.bg-background { background-color: var(--background) }`),
12
+ * so on web the base `--x` tokens MUST be FULL CSS colors. We therefore write
13
+ * them as `hsl(...)` (e.g. `--primary: hsl(185 100% 20%)`) — `var(--primary)`
14
+ * then resolves to a valid color. The resolved `--color-*` vars are written
15
+ * verbatim as `rgb(...)` (already full colors, used by native `color-mix` alpha
16
+ * utilities; harmless on web). Non-color tokens (`--radius`, etc.) pass through
17
+ * unchanged. See `toWebColorValue`.
18
+ *
19
+ * NATIVE writes the SAME tokens as RAW HSL triples (no `hsl()` wrapper) via
20
+ * `rootVariables` (`native-root-vars.native.ts`), consumed through bloom's
21
+ * native `global.css` `hsl(var(--x))` indirection — that path is untouched here.
22
+ * Includes extended tokens (card, chart-*, content-area, sidebar-*) so consumer
23
+ * apps don't need to synthesize them.
15
24
  */
16
25
  export declare function applyColorPresetVars(preset: AppColorName, resolved: 'light' | 'dark'): void;
17
26
  //# sourceMappingURL=apply-dark-class.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-dark-class.d.ts","sourceRoot":"","sources":["../../../../src/theme/apply-dark-class.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGvE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,QAIxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,QAUpF"}
1
+ {"version":3,"file":"apply-dark-class.d.ts","sourceRoot":"","sources":["../../../../src/theme/apply-dark-class.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMvE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,QAIxD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,QAUpF"}
@@ -6,10 +6,16 @@ export type AppColorName = 'teal' | 'blue' | 'green' | 'amber' | 'yellow' | 'red
6
6
  * names) — keep in mind the values are platform-agnostic **raw HSL triples**
7
7
  * (e.g. `'185 100% 20%'` or `'185 100% 20% / 0.5'`), not CSS-resolved colors.
8
8
  * The same map drives both:
9
- * - the web layer (written verbatim into `document.documentElement.style`
10
- * so Tailwind's `hsl(var(--primary))` plumbing picks them up), and
11
- * - the native layer (`buildTheme` resolves them into `hsl(...)` strings
12
- * consumable by React Native styles).
9
+ * - the web layer: the base `--x` tokens are wrapped to full `hsl(...)` colors
10
+ * (via `toWebColorValue`) before being written to `document.documentElement`
11
+ * / a `BloomColorScope` element, so `var(--x)` — the form Tailwind v4
12
+ * `@theme inline` compiles its color utilities to — resolves to a valid
13
+ * color directly; the resolved `--color-*` companions are written as
14
+ * `rgb(...)`, and
15
+ * - the native layer (`buildTheme` resolves the raw triples into `hsl(...)`
16
+ * strings consumable by React Native styles; `native-root-vars.native.ts`
17
+ * writes the raw triples through bloom's `hsl(var(--x))` `global.css`
18
+ * indirection).
13
19
  *
14
20
  * The `--` prefix is an implementation detail we will drop in a future major.
15
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"color-presets.d.ts","sourceRoot":"","sources":["../../../../src/theme/color-presets.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAEvJ;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAAsH,CAAC;AAE1K,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,EAAE,SAAS,YAAY,EAAwB,CAAC;AAEhF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAczD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAE3D;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CAkmBlE,CAAC"}
1
+ {"version":3,"file":"color-presets.d.ts","sourceRoot":"","sources":["../../../../src/theme/color-presets.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAEvJ;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAAsH,CAAC;AAE1K,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,EAAE,SAAS,YAAY,EAAwB,CAAC;AAEhF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAczD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAE3D;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CAkmBlE,CAAC"}
@@ -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;AAGrD,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"}
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;AAwBrD,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"}
@@ -7,7 +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, } from './preset-vars';
10
+ export { getPresetVars, applyPresetVarsToDocument, toWebColorValue, } from './preset-vars';
11
11
  export type { PresetVarsOptions } from './preset-vars';
12
12
  export { applyDarkClass } from './apply-dark-class';
13
13
  export { setColorSchemeSafe } from './set-color-scheme-safe';
@@ -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,EACL,aAAa,EACb,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,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"}
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,EACL,aAAa,EACb,yBAAyB,EACzB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,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"}
@@ -1,94 +1,34 @@
1
1
  import { type AppColorName } from './color-presets';
2
2
  /**
3
- * Publish the active color preset's CSS custom properties into react-native-css's
4
- * GLOBAL root-variable family so they reach the ENTIRE native app tree — including
5
- * content rendered outside the React subtree of `BloomThemeProvider`: expo-router
6
- * navigator screens, `Portal`s, `Dialog`/`BottomSheet` overlays, and any other host
7
- * that paints through a separate React root.
8
- *
9
- * Why a global write is required (native only)
10
- * --------------------------------------------
11
- * react-native-css resolves a `var(--x)` reference (its `varResolver`) in this order:
12
- * 1. `name in inheritedVariables` — the React-context-inherited vars. This is what
13
- * `VariableContextProvider` populates, but it is React-context-scoped, so it only
14
- * reaches *direct descendants* of the provider in the React tree.
15
- * 2. `inlineVariables[name]` — same-component inline vars.
16
- * 3. `variables[name]` — resolved inherited vars.
17
- * 4. `universalVariables(name)` a GLOBAL observable family.
18
- * 5. `rootVariables(name)` — a GLOBAL observable family.
19
- * 6. the declared fallback.
20
- *
21
- * `BloomThemeProvider`'s `VariableContextProvider` only feeds path #1, so the navigator
22
- * host and every portal/modal which render outside that subtree — never see the
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
- * (Bloom's `global.css` defines the `hsl(var(--primary))` indirection but not the HSL
26
- * triples themselves those are injected at runtime), so the whole tree below the
27
- * navigator renders monochrome. Writing the vars into `rootVariables` here is the
28
- * exact same mechanism react-native-css uses for compiled `:root {}` / `@theme` vars
29
- * (`StyleCollection.inject` does `rootVariables(name).set(valueArray)`), so portaled
30
- * content resolves the palette identically regardless of React-tree position.
31
- *
32
- * The raw tokens are written as HSL triples (`primary: '205 87% 53%'`) and the
33
- * resolved Tailwind v4 `--color-*` vars as sRGB `rgb(...)` (`color-primary:
34
- * 'rgb(31 153 239)'`) — the latter so `color-mix`-based alpha utilities resolve
35
- * on native (see `getPresetVars` / `hslTripletToRgb`).
36
- *
37
- * Why a STATIC `import`, not a runtime `require` (critical)
38
- * ---------------------------------------------------------
39
- * react-native-css@3.0.7 ships both a `dist/commonjs` and a `dist/module` build, and
40
- * Metro bundles BOTH. In `native-internal/root.js` the variable families are plain
41
- * module-local consts — NOT `globalThis`-guarded the way `StyleCollection` is — so the
42
- * commonjs `root.js` and the module `root.js` each instantiate their OWN separate
43
- * `rootVariables` family. The react-native-css renderer (`react-native-css/src/components/View.tsx`
44
- * → `varResolver`) imports its `root.js` via ESM `import`, i.e. the MODULE build. A
45
- * runtime `require('react-native-css/native-internal')` resolves the package's `require`
46
- * export condition → the COMMONJS build → a DIFFERENT family the renderer never
47
- * subscribes to, so the writes are a silent no-op on device. A static top-level
48
- * `import` resolves the `import` condition → the MODULE build → the renderer's instance.
49
- *
50
- * This plain file is the NATIVE/default variant (Metro selects it on iOS/Android,
51
- * which have no `.native` override here); the web variant lives in the sibling
52
- * `native-root-vars.web.ts` no-op, which web bundlers pick over this one. So the
53
- * static `react-native-css/native-internal` import below is never pulled into a web
54
- * bundle — only into native. This mirrors Bloom's existing platform-split convention
55
- * (`color-scope/index.tsx` + `color-scope/index.web.tsx`, `FontLoader.native.tsx`).
56
- *
57
- * Keying contract (verified against react-native-css@3.0.7)
58
- * ---------------------------------------------------------
59
- * The `rootVariables` family is keyed by the BARE variable name, WITHOUT the leading
60
- * `--`. The compiler strips it uniformly: a `:root { --primary: ... }` declaration is
61
- * stored via `rule.v.push([property.slice(2), value])` then routed into
62
- * `shared.rootVariables[name]`, and `@property --x` goes through
63
- * `name.startsWith("--") ? name.slice(2) : name`. The lookup side matches: a `var(--x)`
64
- * reference compiles to `[{}, "var", ident.slice(2)]`, so `varResolver` looks up the
65
- * bare name. `buildScopeVars` returns keys WITH the `--` prefix, so we strip it before
66
- * writing each entry.
67
- *
68
- * Value shape
69
- * -----------
70
- * Each entry is a `VariableValue[]` — an array of `[value, mediaCondition?]` tuples.
71
- * Bloom's preset vars are unconditional, so each is a single-element `[[value]]`, e.g.
72
- * `rootVariables('primary').set([['205 87% 53%']])`. This is the same shape the compiler
73
- * emits for unconditional `:root` vars.
74
- *
75
- * This is native-only and additive: the `VariableContextProvider` wrapper stays (it is
76
- * the correct, scoped mechanism for `BloomColorScope` subtree overrides and harmlessly
77
- * covers direct descendants). The web fork (`native-root-vars.web.ts`) no-ops — web
78
- * writes the same vars to `document.documentElement` via `applyColorPresetVars`.
79
- */
80
- /**
81
- * Write the preset's CSS vars to react-native-css's global `rootVariables` family
82
- * so the whole native tree (navigator screens + portals/modals/bottom-sheets)
83
- * resolves the active palette.
84
- *
85
- * Each `.set` notifies its own observers synchronously (react-native-css only
86
- * batches notifications inside its own `StyleCollection.inject`; outside it,
87
- * `observableBatch.current` is unset so every `.set` runs observers immediately),
88
- * so sequential writes correctly trigger a re-render when the preset/mode changes.
89
- *
90
- * Defensive guard: if a host ships a react-native-css build that doesn't expose
91
- * `rootVariables` as a callable (it always does on @3), we bail rather than throw.
3
+ * Platform-neutral DEFAULT variant of `applyNativeRootVars` a no-op.
4
+ *
5
+ * This is the file resolved by every bundler / type-checker that does NOT do
6
+ * React Native's platform-extension resolution:
7
+ * - a consumer's `tsc` (resolving Bloom's `"react-native"` -> `./src/...` source
8
+ * export condition: `theme/index.ts` -> `BloomThemeProvider` -> this import);
9
+ * - non-Metro web bundlers (Vite/Rolldown, webpack) resolving the package
10
+ * `exports` `import`/`require` conditions to `lib/.../native-root-vars.js`.
11
+ *
12
+ * It deliberately does NOT import `react-native-css/native-internal`. That subpath
13
+ * is native-only: it has no web build, and react-native-css is not even a Bloom
14
+ * dependency. Statically importing it from a file a web bundler resolves breaks
15
+ * module resolution outright (the original report: `Rolldown failed to resolve
16
+ * import "react-native-css/native-internal" from .../native-root-vars.js`).
17
+ * Importing it from a file a consumer's `tsc` resolves breaks type-check (TS2307).
18
+ *
19
+ * Confining the real implementation (and that import) to the `.native` sibling —
20
+ * `native-root-vars.native.ts`, which ONLY Metro selects — keeps both web bundling
21
+ * AND downstream `tsc` green, while native still publishes the preset vars into
22
+ * react-native-css's GLOBAL `rootVariables` family so the whole native tree
23
+ * (navigator screens + portals/modals) resolves the palette.
24
+ *
25
+ * The other variants:
26
+ * - `native-root-vars.native.ts`iOS/Android (Metro), the real implementation;
27
+ * - `native-root-vars.web.ts` web, also a no-op (web writes the same vars to
28
+ * `document.documentElement` via `applyColorPresetVars` instead).
29
+ *
30
+ * The signature is intentionally identical to those variants so the import site in
31
+ * `BloomThemeProvider` is platform-agnostic.
92
32
  */
93
- export declare function applyNativeRootVars(colorPreset: AppColorName, mode: 'light' | 'dark'): void;
33
+ export declare function applyNativeRootVars(_colorPreset: AppColorName, _mode: 'light' | 'dark'): void;
94
34
  //# sourceMappingURL=native-root-vars.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-root-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS3F"}
1
+ {"version":3,"file":"native-root-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG7F"}
@@ -0,0 +1,94 @@
1
+ import { type AppColorName } from './color-presets';
2
+ /**
3
+ * Publish the active color preset's CSS custom properties into react-native-css's
4
+ * GLOBAL root-variable family so they reach the ENTIRE native app tree — including
5
+ * content rendered outside the React subtree of `BloomThemeProvider`: expo-router
6
+ * navigator screens, `Portal`s, `Dialog`/`BottomSheet` overlays, and any other host
7
+ * that paints through a separate React root.
8
+ *
9
+ * Why a global write is required (native only)
10
+ * --------------------------------------------
11
+ * react-native-css resolves a `var(--x)` reference (its `varResolver`) in this order:
12
+ * 1. `name in inheritedVariables` — the React-context-inherited vars. This is what
13
+ * `VariableContextProvider` populates, but it is React-context-scoped, so it only
14
+ * reaches *direct descendants* of the provider in the React tree.
15
+ * 2. `inlineVariables[name]` — same-component inline vars.
16
+ * 3. `variables[name]` — resolved inherited vars.
17
+ * 4. `universalVariables(name)` — a GLOBAL observable family.
18
+ * 5. `rootVariables(name)` — a GLOBAL observable family.
19
+ * 6. the declared fallback.
20
+ *
21
+ * `BloomThemeProvider`'s `VariableContextProvider` only feeds path #1, so the navigator
22
+ * host and every portal/modal — which render outside that subtree — never see the
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
+ * (Bloom's `global.css` defines the `hsl(var(--primary))` indirection but not the HSL
26
+ * triples themselves — those are injected at runtime), so the whole tree below the
27
+ * navigator renders monochrome. Writing the vars into `rootVariables` here is the
28
+ * exact same mechanism react-native-css uses for compiled `:root {}` / `@theme` vars
29
+ * (`StyleCollection.inject` does `rootVariables(name).set(valueArray)`), so portaled
30
+ * content resolves the palette identically regardless of React-tree position.
31
+ *
32
+ * The raw tokens are written as HSL triples (`primary: '205 87% 53%'`) and the
33
+ * resolved Tailwind v4 `--color-*` vars as sRGB `rgb(...)` (`color-primary:
34
+ * 'rgb(31 153 239)'`) — the latter so `color-mix`-based alpha utilities resolve
35
+ * on native (see `getPresetVars` / `hslTripletToRgb`).
36
+ *
37
+ * Why a STATIC `import`, not a runtime `require` (critical)
38
+ * ---------------------------------------------------------
39
+ * react-native-css@3.0.7 ships both a `dist/commonjs` and a `dist/module` build, and
40
+ * Metro bundles BOTH. In `native-internal/root.js` the variable families are plain
41
+ * module-local consts — NOT `globalThis`-guarded the way `StyleCollection` is — so the
42
+ * commonjs `root.js` and the module `root.js` each instantiate their OWN separate
43
+ * `rootVariables` family. The react-native-css renderer (`react-native-css/src/components/View.tsx`
44
+ * → `varResolver`) imports its `root.js` via ESM `import`, i.e. the MODULE build. A
45
+ * runtime `require('react-native-css/native-internal')` resolves the package's `require`
46
+ * export condition → the COMMONJS build → a DIFFERENT family the renderer never
47
+ * subscribes to, so the writes are a silent no-op on device. A static top-level
48
+ * `import` resolves the `import` condition → the MODULE build → the renderer's instance.
49
+ *
50
+ * This plain file is the NATIVE/default variant (Metro selects it on iOS/Android,
51
+ * which have no `.native` override here); the web variant lives in the sibling
52
+ * `native-root-vars.web.ts` no-op, which web bundlers pick over this one. So the
53
+ * static `react-native-css/native-internal` import below is never pulled into a web
54
+ * bundle — only into native. This mirrors Bloom's existing platform-split convention
55
+ * (`color-scope/index.tsx` + `color-scope/index.web.tsx`, `FontLoader.native.tsx`).
56
+ *
57
+ * Keying contract (verified against react-native-css@3.0.7)
58
+ * ---------------------------------------------------------
59
+ * The `rootVariables` family is keyed by the BARE variable name, WITHOUT the leading
60
+ * `--`. The compiler strips it uniformly: a `:root { --primary: ... }` declaration is
61
+ * stored via `rule.v.push([property.slice(2), value])` then routed into
62
+ * `shared.rootVariables[name]`, and `@property --x` goes through
63
+ * `name.startsWith("--") ? name.slice(2) : name`. The lookup side matches: a `var(--x)`
64
+ * reference compiles to `[{}, "var", ident.slice(2)]`, so `varResolver` looks up the
65
+ * bare name. `buildScopeVars` returns keys WITH the `--` prefix, so we strip it before
66
+ * writing each entry.
67
+ *
68
+ * Value shape
69
+ * -----------
70
+ * Each entry is a `VariableValue[]` — an array of `[value, mediaCondition?]` tuples.
71
+ * Bloom's preset vars are unconditional, so each is a single-element `[[value]]`, e.g.
72
+ * `rootVariables('primary').set([['205 87% 53%']])`. This is the same shape the compiler
73
+ * emits for unconditional `:root` vars.
74
+ *
75
+ * This is native-only and additive: the `VariableContextProvider` wrapper stays (it is
76
+ * the correct, scoped mechanism for `BloomColorScope` subtree overrides and harmlessly
77
+ * covers direct descendants). The web fork (`native-root-vars.web.ts`) no-ops — web
78
+ * writes the same vars to `document.documentElement` via `applyColorPresetVars`.
79
+ */
80
+ /**
81
+ * Write the preset's CSS vars to react-native-css's global `rootVariables` family
82
+ * so the whole native tree (navigator screens + portals/modals/bottom-sheets)
83
+ * resolves the active palette.
84
+ *
85
+ * Each `.set` notifies its own observers synchronously (react-native-css only
86
+ * batches notifications inside its own `StyleCollection.inject`; outside it,
87
+ * `observableBatch.current` is unset so every `.set` runs observers immediately),
88
+ * so sequential writes correctly trigger a re-render when the preset/mode changes.
89
+ *
90
+ * Defensive guard: if a host ships a react-native-css build that doesn't expose
91
+ * `rootVariables` as a callable (it always does on @3), we bail rather than throw.
92
+ */
93
+ export declare function applyNativeRootVars(colorPreset: AppColorName, mode: 'light' | 'dark'): void;
94
+ //# sourceMappingURL=native-root-vars.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-root-vars.native.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.native.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS3F"}
@@ -25,6 +25,31 @@ import { type AppColorName, type PresetTokens } from './color-presets';
25
25
  * Tolerates a `deg` suffix on the hue and `%` suffixes on saturation/lightness.
26
26
  */
27
27
  export declare function hslTripletToRgb(triplet: string): string;
28
+ /**
29
+ * Transform a single preset var into the value to write to a WEB element
30
+ * (`document.documentElement` or a `BloomColorScope` subtree element).
31
+ *
32
+ * The web apps (Vite + Tailwind v4 + shadcn) compile their `@theme inline`
33
+ * block so the color utilities reference the BASE token directly — e.g.
34
+ * `.bg-background { background-color: var(--background) }`. So on web the base
35
+ * `--x` tokens MUST resolve to a COMPLETE CSS color; a bare HSL triple
36
+ * (`185 50% 5%`) is invalid as a `background-color` value and renders
37
+ * transparent (the production incident this fixes). We therefore wrap raw
38
+ * triples in `hsl(...)`, preserving the exact preset color math (and any `/ A`
39
+ * alpha tail, valid in modern `hsl(H S% L% / A)` syntax) with zero rounding
40
+ * drift.
41
+ *
42
+ * Values that are already full colors pass through unchanged:
43
+ * - `--color-*` resolved vars (already `rgb(...)` from `getPresetVars`),
44
+ * - any value not shaped like a raw HSL triple (`--radius: 0.5rem`,
45
+ * pre-resolved `rgb(...)`/`hsl(...)`, etc.).
46
+ *
47
+ * NATIVE never calls this — `getPresetVars` returns raw triples verbatim and
48
+ * `native-root-vars.native.ts` writes them through bloom's `hsl(var(--x))`
49
+ * native `global.css` indirection. Pure function — no side effects — so it is
50
+ * unit-testable in isolation.
51
+ */
52
+ export declare function toWebColorValue(key: string, value: string): string;
28
53
  export interface PresetVarsOptions {
29
54
  /**
30
55
  * Also emit Tailwind v4 resolved `--color-*` vars (as sRGB `rgb(...)` strings)
@@ -56,6 +81,11 @@ export declare function getPresetVars(colorName: AppColorName, mode: 'light' | '
56
81
  * on native. `BloomThemeProvider` already writes the base preset vars on web;
57
82
  * call this only when an app needs the extended (card/chart/sidebar) tokens on
58
83
  * the document root.
84
+ *
85
+ * The base raw-HSL-triple tokens are wrapped to full `hsl(...)` colors via
86
+ * `toWebColorValue` so `var(--x)` (the form Tailwind v4 `@theme inline`
87
+ * compiles to) resolves directly on web; `--color-*` rgb vars pass through. See
88
+ * `toWebColorValue` for the web var contract.
59
89
  */
60
90
  export declare function applyPresetVarsToDocument(colorName: AppColorName, mode: 'light' | 'dark', options?: PresetVarsOptions): void;
61
91
  //# 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;AAU1F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CvD;AAyBD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAwCd;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAQN"}
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;AAU1F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CvD;AAmBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlE;AAyBD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAwCd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAQN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -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 `hsl(...)` colors — never bare HSL triples — so the web
157
+ // apps' `var(--x)` color utilities resolve. (The production incident was a
158
+ // 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('hsl(')).toBe(true);
166
+ expect(primary.startsWith('hsl(')).toBe(true);
167
+ expect(/^-?\d[\d.]*\s+[\d.]+%/.test(background)).toBe(false);
154
168
  });
155
169
  });
156
170
  });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { Platform } from 'react-native';
6
+
7
+ import { applyColorPresetVars, toWebColorValue } from '../theme/apply-dark-class';
8
+
9
+ // `applyColorPresetVars` is the WEB write path that feeds the live document. It
10
+ // regressed by writing the base `--x` tokens as bare HSL triples (`185 50% 5%`),
11
+ // which the shadcn/Tailwind-v4 web apps consume directly as
12
+ // `background-color: var(--background)` → invalid CSS → transparent → unstyled
13
+ // app. These tests pin the corrected contract: base tokens are full `hsl(...)`
14
+ // colors, `--color-*` companions stay `rgb(...)`.
15
+
16
+ describe('applyColorPresetVars — web document var contract', () => {
17
+ const originalOS = Platform.OS;
18
+
19
+ beforeEach(() => {
20
+ Platform.OS = 'web';
21
+ document.documentElement.removeAttribute('style');
22
+ });
23
+
24
+ afterEach(() => {
25
+ Platform.OS = originalOS;
26
+ document.documentElement.removeAttribute('style');
27
+ });
28
+
29
+ it('re-exports toWebColorValue (helper is consumed by the web write paths)', () => {
30
+ expect(toWebColorValue('--background', '185 50% 5%')).toBe('hsl(185 50% 5%)');
31
+ });
32
+
33
+ it('writes base tokens to the document as full hsl() colors, not bare triples', () => {
34
+ applyColorPresetVars('oxy', 'dark');
35
+
36
+ const style = document.documentElement.style;
37
+ const background = style.getPropertyValue('--background').trim();
38
+ const primary = style.getPropertyValue('--primary').trim();
39
+ const foreground = style.getPropertyValue('--foreground').trim();
40
+
41
+ expect(background).not.toBe('');
42
+ // The exact bug: a bare `H S% L%` triple. Must be a wrapped color now.
43
+ expect(background.startsWith('hsl(')).toBe(true);
44
+ expect(background.endsWith(')')).toBe(true);
45
+ expect(/^-?\d[\d.]*\s+[\d.]+%/.test(background)).toBe(false);
46
+
47
+ expect(primary.startsWith('hsl(')).toBe(true);
48
+ expect(foreground.startsWith('hsl(')).toBe(true);
49
+ });
50
+
51
+ it('keeps the resolved --color-* vars as rgb() full colors', () => {
52
+ applyColorPresetVars('oxy', 'dark');
53
+
54
+ const colorPrimary = document.documentElement.style
55
+ .getPropertyValue('--color-primary')
56
+ .trim();
57
+ expect(colorPrimary.startsWith('rgb(')).toBe(true);
58
+ });
59
+
60
+ it('no-ops on native (does not touch the document)', () => {
61
+ Platform.OS = 'ios';
62
+ applyColorPresetVars('oxy', 'dark');
63
+ expect(document.documentElement.getAttribute('style')).toBeNull();
64
+ });
65
+ });
@@ -1,10 +1,11 @@
1
1
  type SetCall = ReadonlyArray<readonly [string]>;
2
2
 
3
3
  /**
4
- * Verifies the native/default variant `theme/native-root-vars.ts` publishes the
4
+ * Verifies the native variant `theme/native-root-vars.native.ts` publishes the
5
5
  * active preset into react-native-css's GLOBAL `rootVariables` family with the
6
6
  * exact keying and value shape react-native-css itself uses for compiled `:root`
7
- * vars, and that the web fork `theme/native-root-vars.web.ts` is an inert no-op.
7
+ * vars, and that BOTH the platform-neutral default (`theme/native-root-vars.ts`)
8
+ * and the web fork (`theme/native-root-vars.web.ts`) are inert no-ops.
8
9
  *
9
10
  * - keys are written WITHOUT the leading `--` (the family is keyed by the bare
10
11
  * name — confirmed against react-native-css@3.0.7's compiler, which stores
@@ -12,6 +13,11 @@ type SetCall = ReadonlyArray<readonly [string]>;
12
13
  * - each value is a single-element value-array `[[value]]` (`VariableValue[]`);
13
14
  * - the native variant reaches react-native-css via a STATIC ESM import (so
14
15
  * Metro resolves the `module` build — the SAME family the renderer reads);
16
+ * - the default variant never imports react-native-css and writes nothing — it
17
+ * is what `tsc` and non-Metro web bundlers (Vite/Rolldown) resolve, so
18
+ * confining the `react-native-css/native-internal` import to the `.native`
19
+ * variant keeps web bundling resolvable AND a NativeWind-4 consumer's `tsc`
20
+ * from failing with TS2307;
15
21
  * - the web variant never imports react-native-css and writes nothing.
16
22
  *
17
23
  * react-native-css is NOT a Bloom dependency (it arrives transitively via the
@@ -40,7 +46,7 @@ describe('applyNativeRootVars (native variant)', () => {
40
46
  { virtual: true },
41
47
  );
42
48
 
43
- const { applyNativeRootVars } = require('../theme/native-root-vars') as {
49
+ const { applyNativeRootVars } = require('../theme/native-root-vars.native') as {
44
50
  applyNativeRootVars: (preset: string, mode: 'light' | 'dark') => void;
45
51
  };
46
52
  applyNativeRootVars('blue', 'light');
@@ -81,7 +87,7 @@ describe('applyNativeRootVars (native variant)', () => {
81
87
  }),
82
88
  { virtual: true },
83
89
  );
84
- const { applyNativeRootVars } = require('../theme/native-root-vars') as {
90
+ const { applyNativeRootVars } = require('../theme/native-root-vars.native') as {
85
91
  applyNativeRootVars: (preset: string, mode: 'light' | 'dark') => void;
86
92
  };
87
93
  applyNativeRootVars('teal', mode);
@@ -99,10 +105,30 @@ describe('applyNativeRootVars (native variant)', () => {
99
105
  () => ({ rootVariables: undefined }),
100
106
  { virtual: true },
101
107
  );
108
+ const { applyNativeRootVars } = require('../theme/native-root-vars.native') as {
109
+ applyNativeRootVars: (preset: string, mode: 'light' | 'dark') => void;
110
+ };
111
+ expect(() => applyNativeRootVars('blue', 'light')).not.toThrow();
112
+ });
113
+ });
114
+ });
115
+
116
+ describe('applyNativeRootVars (default variant - tsc / web-bundler resolution)', () => {
117
+ beforeEach(() => {
118
+ jest.resetModules();
119
+ });
120
+
121
+ it('no-ops without importing react-native-css (keeps web bundling + tsc green)', () => {
122
+ // The platform-neutral default is what a consumer's `tsc` and non-Metro web
123
+ // bundlers (Vite/Rolldown) resolve. It must never touch react-native-css.
124
+ // Confining the `react-native-css/native-internal` import to the `.native`
125
+ // sibling fixes both the downstream TS2307 AND the Vite/Rolldown resolve fail.
126
+ jest.isolateModules(() => {
102
127
  const { applyNativeRootVars } = require('../theme/native-root-vars') as {
103
128
  applyNativeRootVars: (preset: string, mode: 'light' | 'dark') => void;
104
129
  };
105
130
  expect(() => applyNativeRootVars('blue', 'light')).not.toThrow();
131
+ expect(applyNativeRootVars('blue', 'light')).toBeUndefined();
106
132
  });
107
133
  });
108
134
  });
@@ -1,5 +1,5 @@
1
1
  import { APP_COLOR_NAMES, APP_COLOR_PRESETS } from '../theme/color-presets';
2
- import { getPresetVars, hslTripletToRgb } from '../theme/preset-vars';
2
+ import { getPresetVars, hslTripletToRgb, toWebColorValue } from '../theme/preset-vars';
3
3
 
4
4
  const EXTENDED_KEYS = [
5
5
  '--card',
@@ -97,3 +97,49 @@ describe('hslTripletToRgb', () => {
97
97
  expect(hslTripletToRgb('205 87% 53% / 50%')).toBe('rgb(31 153 239 / 0.5)');
98
98
  });
99
99
  });
100
+
101
+ describe('toWebColorValue (web var contract)', () => {
102
+ it('wraps a raw HSL triple base token in hsl()', () => {
103
+ expect(toWebColorValue('--background', '185 50% 5%')).toBe('hsl(185 50% 5%)');
104
+ expect(toWebColorValue('--primary', '185 100% 20%')).toBe('hsl(185 100% 20%)');
105
+ expect(toWebColorValue('--foreground', '0 0% 12%')).toBe('hsl(0 0% 12%)');
106
+ });
107
+
108
+ it('wraps a raw HSL triple with an alpha tail into modern hsl(H S% L% / A) syntax', () => {
109
+ expect(toWebColorValue('--ring', '0 0% 0% / 0.5')).toBe('hsl(0 0% 0% / 0.5)');
110
+ });
111
+
112
+ it('passes --color-* resolved rgb() vars through unchanged', () => {
113
+ expect(toWebColorValue('--color-primary', 'rgb(31 153 239)')).toBe('rgb(31 153 239)');
114
+ expect(toWebColorValue('--color-background', 'rgb(0 94 102 / 0.5)')).toBe(
115
+ 'rgb(0 94 102 / 0.5)',
116
+ );
117
+ });
118
+
119
+ it('passes non-color tokens (radius / lengths) through unchanged', () => {
120
+ expect(toWebColorValue('--radius', '0.5rem')).toBe('0.5rem');
121
+ expect(toWebColorValue('--radius-lg', '12px')).toBe('12px');
122
+ });
123
+
124
+ it('passes already-resolved color values through unchanged', () => {
125
+ expect(toWebColorValue('--primary', 'rgb(31 153 239)')).toBe('rgb(31 153 239)');
126
+ expect(toWebColorValue('--primary', 'hsl(185 100% 20%)')).toBe('hsl(185 100% 20%)');
127
+ expect(toWebColorValue('--primary', '#1d9bf0')).toBe('#1d9bf0');
128
+ });
129
+
130
+ it('produces a full CSS color for every base preset token of every preset/mode', () => {
131
+ for (const name of APP_COLOR_NAMES) {
132
+ for (const mode of ['light', 'dark'] as const) {
133
+ const base = mode === 'light' ? APP_COLOR_PRESETS[name].light : APP_COLOR_PRESETS[name].dark;
134
+ for (const [key, value] of Object.entries(base)) {
135
+ const web = toWebColorValue(key, value);
136
+ // A bare triple (`185 50% 5%`) is INVALID CSS — the incident. Every
137
+ // base token must come out as a complete color the browser parses.
138
+ expect(web.startsWith('hsl(') || web.startsWith('rgb(') || web.startsWith('#')).toBe(
139
+ true,
140
+ );
141
+ }
142
+ }
143
+ }
144
+ });
145
+ });