@mgcrea/react-native-tailwind 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +33 -30
  2. package/dist/babel/config-loader.d.ts +10 -0
  3. package/dist/babel/config-loader.test.ts +75 -21
  4. package/dist/babel/config-loader.ts +100 -2
  5. package/dist/babel/index.cjs +439 -46
  6. package/dist/babel/plugin/state.d.ts +4 -0
  7. package/dist/babel/plugin/state.ts +8 -0
  8. package/dist/babel/plugin/visitors/className.test.ts +313 -0
  9. package/dist/babel/plugin/visitors/className.ts +36 -8
  10. package/dist/babel/plugin/visitors/imports.ts +16 -1
  11. package/dist/babel/plugin/visitors/program.ts +19 -2
  12. package/dist/babel/plugin/visitors/tw.test.ts +151 -0
  13. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  14. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  15. package/dist/babel/utils/styleInjection.d.ts +16 -0
  16. package/dist/babel/utils/styleInjection.ts +138 -7
  17. package/dist/babel/utils/twProcessing.d.ts +2 -0
  18. package/dist/babel/utils/twProcessing.ts +92 -3
  19. package/dist/parser/borders.js +1 -1
  20. package/dist/parser/borders.test.js +1 -1
  21. package/dist/parser/index.d.ts +3 -2
  22. package/dist/parser/index.js +1 -1
  23. package/dist/parser/layout.d.ts +3 -1
  24. package/dist/parser/layout.js +1 -1
  25. package/dist/parser/layout.test.js +1 -1
  26. package/dist/parser/modifiers.d.ts +32 -2
  27. package/dist/parser/modifiers.js +1 -1
  28. package/dist/parser/modifiers.test.js +1 -1
  29. package/dist/parser/sizing.d.ts +3 -1
  30. package/dist/parser/sizing.js +1 -1
  31. package/dist/parser/sizing.test.js +1 -1
  32. package/dist/parser/spacing.d.ts +4 -2
  33. package/dist/parser/spacing.js +1 -1
  34. package/dist/parser/spacing.test.js +1 -1
  35. package/dist/parser/transforms.d.ts +3 -1
  36. package/dist/parser/transforms.js +1 -1
  37. package/dist/parser/transforms.test.js +1 -1
  38. package/dist/parser/typography.test.js +1 -1
  39. package/dist/runtime.cjs +1 -1
  40. package/dist/runtime.cjs.map +3 -3
  41. package/dist/runtime.d.ts +2 -0
  42. package/dist/runtime.js +1 -1
  43. package/dist/runtime.js.map +3 -3
  44. package/dist/runtime.test.js +1 -1
  45. package/package.json +6 -6
  46. package/src/babel/config-loader.test.ts +75 -21
  47. package/src/babel/config-loader.ts +100 -2
  48. package/src/babel/plugin/state.ts +8 -0
  49. package/src/babel/plugin/visitors/className.test.ts +313 -0
  50. package/src/babel/plugin/visitors/className.ts +36 -8
  51. package/src/babel/plugin/visitors/imports.ts +16 -1
  52. package/src/babel/plugin/visitors/program.ts +19 -2
  53. package/src/babel/plugin/visitors/tw.test.ts +151 -0
  54. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  55. package/src/babel/utils/styleInjection.ts +138 -7
  56. package/src/babel/utils/twProcessing.ts +92 -3
  57. package/src/parser/borders.test.ts +104 -0
  58. package/src/parser/borders.ts +50 -7
  59. package/src/parser/index.ts +8 -5
  60. package/src/parser/layout.test.ts +168 -0
  61. package/src/parser/layout.ts +107 -8
  62. package/src/parser/modifiers.test.ts +206 -0
  63. package/src/parser/modifiers.ts +62 -3
  64. package/src/parser/sizing.test.ts +56 -0
  65. package/src/parser/sizing.ts +20 -15
  66. package/src/parser/spacing.test.ts +123 -0
  67. package/src/parser/spacing.ts +30 -15
  68. package/src/parser/transforms.test.ts +57 -0
  69. package/src/parser/transforms.ts +7 -3
  70. package/src/parser/typography.test.ts +8 -0
  71. package/src/parser/typography.ts +4 -0
  72. package/src/runtime.test.ts +149 -0
  73. package/src/runtime.ts +53 -1
@@ -1 +1 @@
1
- var _vitest=require("vitest");var _runtime=require("./runtime");(0,_vitest.describe)("runtime",function(){(0,_vitest.beforeEach)(function(){(0,_runtime.clearCache)();(0,_runtime.setConfig)({});});(0,_vitest.describe)("tw template tag",function(){(0,_vitest.it)("should parse static classes",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should handle interpolated values",function(){var isActive=true;var result=(0,_runtime.tw)`m-4 ${isActive&&"bg-blue-500"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,backgroundColor:"#2b7fff"});});(0,_vitest.it)("should handle conditional classes",function(){var isLarge=true;var result=(0,_runtime.tw)`p-4 ${isLarge?"text-xl":"text-sm"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,fontSize:20});});(0,_vitest.it)("should handle falsy values",function(){var result=(0,_runtime.tw)`m-4 ${false} ${null} ${undefined} p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should preserve zero values in template literals",function(){var result=(0,_runtime.tw)`opacity-${0} m-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({opacity:0,margin:16});});(0,_vitest.it)("should preserve empty string values in template literals",function(){var result=(0,_runtime.tw)`m-4 ${""}p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should handle mixed falsy and truthy numeric values",function(){var spacing=0;var result=(0,_runtime.tw)`m-${spacing} p-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:0,padding:16});});(0,_vitest.it)("should return empty style object for empty className",function(){var result=(0,_runtime.tw)``;(0,_vitest.expect)(result).toEqual({style:{}});});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("twStyle function",function(){(0,_vitest.it)("should parse className string",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return undefined for empty string",function(){var result=(0,_runtime.twStyle)("");(0,_vitest.expect)(result).toBeUndefined();});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("setConfig",function(){(0,_vitest.it)("should set custom colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",secondary:"#5856D6"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF",secondary:"#5856D6"});});(0,_vitest.it)("should flatten nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({"brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should handle mixed flat and nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF","brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should clear cache when config changes",function(){var style=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(1);(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});(0,_vitest.it)("should use custom colors in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});var result=(0,_runtime.tw)`bg-primary`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#007AFF"});});});(0,_vitest.describe)("cache",function(){(0,_vitest.it)("should cache parsed styles",function(){var result1=(0,_runtime.tw)`m-4 p-2`;var result2=(0,_runtime.tw)`m-4 p-2`;(0,_vitest.expect)(result1).toBe(result2);});(0,_vitest.it)("should track cache stats",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;var style3=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)(style3).toBeDefined();var stats=(0,_runtime.getCacheStats)();(0,_vitest.expect)(stats.size).toBe(3);(0,_vitest.expect)(stats.keys).toContain("m-4");(0,_vitest.expect)(stats.keys).toContain("p-2");(0,_vitest.expect)(stats.keys).toContain("bg-blue-500");});(0,_vitest.it)("should clear cache",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(2);(0,_runtime.clearCache)();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});});(0,_vitest.describe)("state modifiers",function(){(0,_vitest.it)("should return activeStyle when active: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return disabledStyle when disabled: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return both activeStyle and disabledStyle when both modifiers are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});(0,_vitest.it)("should merge base and active styles with multiple properties",function(){var result=(0,_runtime.tw)`p-4 m-2 bg-blue-500 active:bg-blue-700 active:p-6`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,margin:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6",padding:24});});(0,_vitest.it)("should handle only modifier classes (no base)",function(){var result=(0,_runtime.tw)`active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should work with twStyle function",function(){var result=(0,_runtime.twStyle)("bg-blue-500 active:bg-blue-700");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should provide raw hex values for animations",function(){var _result$activeStyle;var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;var style=Array.isArray(result==null?void 0:result.style)?result.style.find(function(s){return s!==false;}):result==null?void 0:result.style;(0,_vitest.expect)(style&&typeof style==="object"&&"backgroundColor"in style?style.backgroundColor:undefined).toBe("#2b7fff");(0,_vitest.expect)(result==null||(_result$activeStyle=result.activeStyle)==null?void 0:_result$activeStyle.backgroundColor).toBe("#1447e6");});(0,_vitest.it)("should return focusStyle when focus: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 focus:bg-blue-800`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return all three modifier styles when all are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 focus:bg-blue-800 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});});});
1
+ var _vitest=require("vitest");var _runtime=require("./runtime");(0,_vitest.describe)("runtime",function(){(0,_vitest.beforeEach)(function(){(0,_runtime.clearCache)();(0,_runtime.setConfig)({});});(0,_vitest.describe)("tw template tag",function(){(0,_vitest.it)("should parse static classes",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should handle interpolated values",function(){var isActive=true;var result=(0,_runtime.tw)`m-4 ${isActive&&"bg-blue-500"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,backgroundColor:"#2b7fff"});});(0,_vitest.it)("should handle conditional classes",function(){var isLarge=true;var result=(0,_runtime.tw)`p-4 ${isLarge?"text-xl":"text-sm"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,fontSize:20});});(0,_vitest.it)("should handle falsy values",function(){var result=(0,_runtime.tw)`m-4 ${false} ${null} ${undefined} p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should preserve zero values in template literals",function(){var result=(0,_runtime.tw)`opacity-${0} m-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({opacity:0,margin:16});});(0,_vitest.it)("should preserve empty string values in template literals",function(){var result=(0,_runtime.tw)`m-4 ${""}p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should handle mixed falsy and truthy numeric values",function(){var spacing=0;var result=(0,_runtime.tw)`m-${spacing} p-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:0,padding:16});});(0,_vitest.it)("should return empty style object for empty className",function(){var result=(0,_runtime.tw)``;(0,_vitest.expect)(result).toEqual({style:{}});});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("twStyle function",function(){(0,_vitest.it)("should parse className string",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return undefined for empty string",function(){var result=(0,_runtime.twStyle)("");(0,_vitest.expect)(result).toBeUndefined();});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("setConfig",function(){(0,_vitest.it)("should set custom colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",secondary:"#5856D6"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF",secondary:"#5856D6"});});(0,_vitest.it)("should flatten nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({"brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should handle mixed flat and nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF","brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should clear cache when config changes",function(){var style=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(1);(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});(0,_vitest.it)("should use custom colors in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});var result=(0,_runtime.tw)`bg-primary`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#007AFF"});});(0,_vitest.it)("should set custom fontFamily",function(){(0,_runtime.setConfig)({theme:{extend:{fontFamily:{display:"Inter",body:["Roboto","sans-serif"]}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.fontFamily).toEqual({display:"Inter",body:"Roboto"});});(0,_vitest.it)("should set custom fontSize",function(){(0,_runtime.setConfig)({theme:{extend:{fontSize:{tiny:10,small:"12px",medium:16,large:"24"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.fontSize).toEqual({tiny:10,small:12,medium:16,large:24});});(0,_vitest.it)("should set custom spacing",function(){(0,_runtime.setConfig)({theme:{extend:{spacing:{xs:4,sm:"8px",md:16,lg:"2rem"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.spacing).toEqual({xs:4,sm:8,md:16,lg:32});});(0,_vitest.it)("should use custom fontSize in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{fontSize:{tiny:10}}}});var result=(0,_runtime.tw)`text-tiny`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({fontSize:10});});(0,_vitest.it)("should use custom spacing in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{spacing:{xs:4}}}});var result=(0,_runtime.tw)`m-xs p-xs`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:4,padding:4});});(0,_vitest.it)("should reset fontSize and spacing when config is cleared",function(){(0,_runtime.setConfig)({theme:{extend:{fontSize:{tiny:10},spacing:{xs:4}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.fontSize).toEqual({tiny:10});(0,_vitest.expect)(theme.spacing).toEqual({xs:4});(0,_runtime.setConfig)({});theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.fontSize).toEqual({});(0,_vitest.expect)(theme.spacing).toEqual({});});(0,_vitest.it)("should handle all theme extensions together",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"},fontFamily:{display:"Inter"},fontSize:{tiny:10},spacing:{xs:4}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF"});(0,_vitest.expect)(theme.fontFamily).toEqual({display:"Inter"});(0,_vitest.expect)(theme.fontSize).toEqual({tiny:10});(0,_vitest.expect)(theme.spacing).toEqual({xs:4});var result=(0,_runtime.tw)`bg-primary font-display text-tiny m-xs`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#007AFF",fontFamily:"Inter",fontSize:10,margin:4});});});(0,_vitest.describe)("cache",function(){(0,_vitest.it)("should cache parsed styles",function(){var result1=(0,_runtime.tw)`m-4 p-2`;var result2=(0,_runtime.tw)`m-4 p-2`;(0,_vitest.expect)(result1).toBe(result2);});(0,_vitest.it)("should track cache stats",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;var style3=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)(style3).toBeDefined();var stats=(0,_runtime.getCacheStats)();(0,_vitest.expect)(stats.size).toBe(3);(0,_vitest.expect)(stats.keys).toContain("m-4");(0,_vitest.expect)(stats.keys).toContain("p-2");(0,_vitest.expect)(stats.keys).toContain("bg-blue-500");});(0,_vitest.it)("should clear cache",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(2);(0,_runtime.clearCache)();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});});(0,_vitest.describe)("state modifiers",function(){(0,_vitest.it)("should return activeStyle when active: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return disabledStyle when disabled: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return both activeStyle and disabledStyle when both modifiers are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});(0,_vitest.it)("should merge base and active styles with multiple properties",function(){var result=(0,_runtime.tw)`p-4 m-2 bg-blue-500 active:bg-blue-700 active:p-6`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,margin:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6",padding:24});});(0,_vitest.it)("should handle only modifier classes (no base)",function(){var result=(0,_runtime.tw)`active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should work with twStyle function",function(){var result=(0,_runtime.twStyle)("bg-blue-500 active:bg-blue-700");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should provide raw hex values for animations",function(){var _result$activeStyle;var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;var style=Array.isArray(result==null?void 0:result.style)?result.style.find(function(s){return s!==false;}):result==null?void 0:result.style;(0,_vitest.expect)(style&&typeof style==="object"&&"backgroundColor"in style?style.backgroundColor:undefined).toBe("#2b7fff");(0,_vitest.expect)(result==null||(_result$activeStyle=result.activeStyle)==null?void 0:_result$activeStyle.backgroundColor).toBe("#1447e6");});(0,_vitest.it)("should return focusStyle when focus: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 focus:bg-blue-800`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return all three modifier styles when all are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 focus:bg-blue-800 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});});});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Compile-time Tailwind CSS for React Native with zero runtime overhead",
5
5
  "author": "Olivier Louvignes <olivier@mgcrea.io> (https://github.com/mgcrea)",
6
6
  "homepage": "https://github.com/mgcrea/react-native-tailwind#readme",
@@ -68,23 +68,23 @@
68
68
  "@testing-library/react-native": "^13.3.3",
69
69
  "@types/babel__core": "^7.20.5",
70
70
  "@types/babel__traverse": "^7.28.0",
71
- "@types/react": "^19.2.6",
72
- "@vitest/coverage-v8": "^4.0.13",
71
+ "@types/react": "^19.2.7",
72
+ "@vitest/coverage-v8": "^4.0.14",
73
73
  "babel-plugin-module-resolver": "^5.0.2",
74
74
  "esbuild": "^0.27.0",
75
75
  "eslint": "^9.39.1",
76
76
  "jest": "^30.2.0",
77
- "prettier": "^3.6.2",
77
+ "prettier": "^3.7.1",
78
78
  "prettier-plugin-organize-imports": "^4.3.0",
79
79
  "react": "^19.2.0",
80
80
  "react-native": "0.82.1",
81
81
  "typescript": "^5.9.3",
82
- "vitest": "^4.0.13"
82
+ "vitest": "^4.0.14"
83
83
  },
84
84
  "engines": {
85
85
  "node": ">=18"
86
86
  },
87
- "packageManager": "pnpm@10.23.0",
87
+ "packageManager": "pnpm@10.24.0",
88
88
  "publishConfig": {
89
89
  "access": "public"
90
90
  },
@@ -1,6 +1,11 @@
1
1
  import * as fs from "fs";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import { extractCustomTheme, findTailwindConfig, loadTailwindConfig } from "./config-loader";
3
+ import {
4
+ extractCustomTheme,
5
+ findTailwindConfig,
6
+ loadTailwindConfig,
7
+ warnUnsupportedThemeKeys,
8
+ } from "./config-loader";
4
9
 
5
10
  // Mock fs
6
11
  vi.mock("fs");
@@ -120,35 +125,84 @@ describe("config-loader", () => {
120
125
  vi.spyOn(fs, "existsSync").mockReturnValue(false);
121
126
 
122
127
  const result = extractCustomTheme("/project/src/file.ts");
123
- expect(result).toEqual({ colors: {}, fontFamily: {}, fontSize: {} });
128
+ expect(result).toEqual({ colors: {}, fontFamily: {}, fontSize: {}, spacing: {} });
124
129
  });
130
+ });
125
131
 
126
- it("should return empty theme when config has no theme", () => {
127
- const configPath = "/project/tailwind.config.js";
132
+ describe("warnUnsupportedThemeKeys", () => {
133
+ it("should warn about unsupported theme keys", () => {
134
+ const configPath = "/project/unsupported/tailwind.config.js";
135
+ const mockConfig = {
136
+ theme: {
137
+ extend: {
138
+ colors: { brand: "#123456" },
139
+ spacing: { "72": "18rem" }, // Supported (now!)
140
+ borderRadius: { xl: "1rem" }, // Unsupported
141
+ lineHeight: { tight: "1.25" }, // Unsupported
142
+ },
143
+ screens: { tablet: "640px" }, // Unsupported
144
+ },
145
+ };
128
146
 
129
- vi.spyOn(fs, "existsSync").mockImplementation((filepath) => filepath === configPath);
130
- vi.spyOn(require, "resolve").mockReturnValue(configPath);
147
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
131
148
 
132
- // loadTailwindConfig will be called, but we've already tested it
133
- // For integration, we'd need to mock the entire flow
134
- const result = extractCustomTheme("/project/src/file.ts");
149
+ warnUnsupportedThemeKeys(mockConfig, configPath);
135
150
 
136
- // Without actual config loading, this returns empty
137
- expect(result).toEqual({ colors: {}, fontFamily: {}, fontSize: {} });
151
+ expect(consoleSpy).toHaveBeenCalledWith(
152
+ expect.stringContaining("Unsupported theme configuration detected"),
153
+ );
154
+ // spacing is now supported, so should NOT warn about it
155
+ expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining("theme.extend.spacing"));
156
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.extend.borderRadius"));
157
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.extend.lineHeight"));
158
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.screens"));
159
+ expect(consoleSpy).toHaveBeenCalledWith(
160
+ expect.stringContaining("https://github.com/mgcrea/react-native-tailwind/issues/new"),
161
+ );
162
+
163
+ consoleSpy.mockRestore();
138
164
  });
139
165
 
140
- it("should extract colors and fontFamily from theme.extend", () => {
141
- // This would require complex mocking of the entire require flow
142
- // Testing the logic: theme.extend is preferred
143
- const colors = { brand: { light: "#fff", dark: "#000" } };
144
- const fontFamily = { sans: ['"SF Pro"'], custom: ['"Custom Font"'] };
145
- const theme = {
146
- extend: { colors, fontFamily },
166
+ it("should not warn for supported theme keys only", () => {
167
+ const configPath = "/project/supported/tailwind.config.js";
168
+ const mockConfig = {
169
+ theme: {
170
+ extend: {
171
+ colors: { brand: "#123456" },
172
+ fontFamily: { custom: "CustomFont" },
173
+ fontSize: { huge: "48px" },
174
+ spacing: { "72": "18rem" },
175
+ },
176
+ },
177
+ };
178
+
179
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
180
+
181
+ warnUnsupportedThemeKeys(mockConfig, configPath);
182
+
183
+ expect(consoleSpy).not.toHaveBeenCalled();
184
+
185
+ consoleSpy.mockRestore();
186
+ });
187
+
188
+ it("should only warn once per config path", () => {
189
+ const configPath = "/project/once/tailwind.config.js";
190
+ const mockConfig = {
191
+ theme: {
192
+ extend: {
193
+ borderRadius: { xl: "1rem" }, // Unsupported
194
+ },
195
+ },
147
196
  };
148
197
 
149
- // If we had the config, we'd flatten the colors and convert fontFamily
150
- expect(theme.extend.colors).toEqual(colors);
151
- expect(theme.extend.fontFamily).toEqual(fontFamily);
198
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
199
+
200
+ warnUnsupportedThemeKeys(mockConfig, configPath);
201
+ warnUnsupportedThemeKeys(mockConfig, configPath);
202
+
203
+ expect(consoleSpy).toHaveBeenCalledTimes(1);
204
+
205
+ consoleSpy.mockRestore();
152
206
  });
153
207
  });
154
208
  });
@@ -15,13 +15,68 @@ export type TailwindConfig = {
15
15
  colors?: Record<string, string | Record<string, string>>;
16
16
  fontFamily?: Record<string, string | string[]>;
17
17
  fontSize?: Record<string, string | number>;
18
+ spacing?: Record<string, string | number>;
19
+ [key: string]: unknown;
18
20
  };
19
21
  colors?: Record<string, string | Record<string, string>>;
20
22
  fontFamily?: Record<string, string | string[]>;
21
23
  fontSize?: Record<string, string | number>;
24
+ spacing?: Record<string, string | number>;
25
+ [key: string]: unknown;
22
26
  };
23
27
  };
24
28
 
29
+ /**
30
+ * Theme keys currently supported by react-native-tailwind
31
+ */
32
+ const SUPPORTED_THEME_KEYS = new Set(["colors", "fontFamily", "fontSize", "spacing", "extend"]);
33
+
34
+ /**
35
+ * Cache for warned config paths to avoid duplicate warnings
36
+ */
37
+ const warnedConfigPaths = new Set<string>();
38
+
39
+ /**
40
+ * Check for unsupported theme extensions and warn the user
41
+ * @internal Exported for testing
42
+ */
43
+ export function warnUnsupportedThemeKeys(config: TailwindConfig, configPath: string): void {
44
+ if (process.env.NODE_ENV === "production" || warnedConfigPaths.has(configPath)) {
45
+ return;
46
+ }
47
+
48
+ const unsupportedKeys: string[] = [];
49
+
50
+ // Check theme.extend keys
51
+ if (config.theme?.extend && typeof config.theme.extend === "object") {
52
+ for (const key of Object.keys(config.theme.extend)) {
53
+ if (!SUPPORTED_THEME_KEYS.has(key)) {
54
+ unsupportedKeys.push(`theme.extend.${key}`);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Check direct theme keys (excluding 'extend')
60
+ if (config.theme && typeof config.theme === "object") {
61
+ for (const key of Object.keys(config.theme)) {
62
+ if (key !== "extend" && !SUPPORTED_THEME_KEYS.has(key)) {
63
+ unsupportedKeys.push(`theme.${key}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ if (unsupportedKeys.length > 0) {
69
+ warnedConfigPaths.add(configPath);
70
+ console.warn(
71
+ `[react-native-tailwind] Unsupported theme configuration detected:\n` +
72
+ ` ${unsupportedKeys.join(", ")}\n\n` +
73
+ ` Currently supported: colors, fontFamily, fontSize, spacing\n\n` +
74
+ ` These extensions will be ignored. If you need support for these features,\n` +
75
+ ` please open an issue: https://github.com/mgcrea/react-native-tailwind/issues/new`,
76
+ );
77
+ }
78
+ }
79
+
25
80
  // Cache configs per path to avoid repeated file I/O
26
81
  const configCache = new Map<string, TailwindConfig | null>();
27
82
 
@@ -92,6 +147,7 @@ export type CustomTheme = {
92
147
  colors: Record<string, string>;
93
148
  fontFamily: Record<string, string>;
94
149
  fontSize: Record<string, number>;
150
+ spacing: Record<string, number>;
95
151
  };
96
152
 
97
153
  /**
@@ -103,14 +159,17 @@ export function extractCustomTheme(filename: string): CustomTheme {
103
159
  const configPath = findTailwindConfig(projectDir);
104
160
 
105
161
  if (!configPath) {
106
- return { colors: {}, fontFamily: {}, fontSize: {} };
162
+ return { colors: {}, fontFamily: {}, fontSize: {}, spacing: {} };
107
163
  }
108
164
 
109
165
  const config = loadTailwindConfig(configPath);
110
166
  if (!config?.theme) {
111
- return { colors: {}, fontFamily: {}, fontSize: {} };
167
+ return { colors: {}, fontFamily: {}, fontSize: {}, spacing: {} };
112
168
  }
113
169
 
170
+ // Warn about unsupported theme keys
171
+ warnUnsupportedThemeKeys(config, configPath);
172
+
114
173
  // Extract colors
115
174
  /* v8 ignore next 5 */
116
175
  if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
@@ -173,9 +232,48 @@ export function extractCustomTheme(filename: string): CustomTheme {
173
232
  }
174
233
  }
175
234
 
235
+ // Extract spacing
236
+ /* v8 ignore next 5 */
237
+ if (config.theme.spacing && !config.theme.extend?.spacing && process.env.NODE_ENV !== "production") {
238
+ console.warn(
239
+ "[react-native-tailwind] Using theme.spacing will override all default spacing. " +
240
+ "Use theme.extend.spacing to add custom spacing while keeping defaults.",
241
+ );
242
+ }
243
+ const spacing = config.theme.extend?.spacing ?? config.theme.spacing ?? {};
244
+
245
+ // Convert spacing values to numbers (handle rem, px, or number values)
246
+ const spacingResult: Record<string, number> = {};
247
+ for (const [key, value] of Object.entries(spacing)) {
248
+ if (typeof value === "number") {
249
+ spacingResult[key] = value;
250
+ } else if (typeof value === "string") {
251
+ // Parse string values: "18rem" -> 288, "16px" -> 16, "16" -> 16
252
+ let parsed: number;
253
+ if (value.endsWith("rem")) {
254
+ // Convert rem to px (1rem = 16px)
255
+ parsed = parseFloat(value.replace(/rem$/, "")) * 16;
256
+ } else {
257
+ // Parse px or unitless values
258
+ parsed = parseFloat(value.replace(/px$/, ""));
259
+ }
260
+ if (!isNaN(parsed)) {
261
+ spacingResult[key] = parsed;
262
+ } else {
263
+ /* v8 ignore next 5 */
264
+ if (process.env.NODE_ENV !== "production") {
265
+ console.warn(
266
+ `[react-native-tailwind] Invalid spacing value for "${key}": ${value}. Expected number or string like "16px" or "1rem".`,
267
+ );
268
+ }
269
+ }
270
+ }
271
+ }
272
+
176
273
  return {
177
274
  colors: flattenColors(colors),
178
275
  fontFamily: fontFamilyResult,
179
276
  fontSize: fontSizeResult,
277
+ spacing: spacingResult,
180
278
  };
181
279
  }
@@ -103,6 +103,10 @@ export type PluginState = PluginPass & {
103
103
  needsWindowDimensionsImport: boolean;
104
104
  windowDimensionsVariableName: string;
105
105
  windowDimensionsLocalIdentifier?: string; // Local identifier if hook is already imported with an alias
106
+ hasI18nManagerImport: boolean;
107
+ needsI18nManagerImport: boolean;
108
+ i18nManagerVariableName: string; // Variable name for the RTL state (e.g., '_twIsRTL')
109
+ i18nManagerLocalIdentifier?: string; // Local identifier if I18nManager is already imported with an alias
106
110
  customTheme: CustomTheme;
107
111
  schemeModifierConfig: SchemeModifierConfig;
108
112
  supportedAttributes: Set<string>;
@@ -163,6 +167,10 @@ export function createInitialState(
163
167
  needsWindowDimensionsImport: false,
164
168
  windowDimensionsVariableName: "_twDimensions",
165
169
  windowDimensionsLocalIdentifier: undefined,
170
+ hasI18nManagerImport: false,
171
+ needsI18nManagerImport: false,
172
+ i18nManagerVariableName: "_twIsRTL",
173
+ i18nManagerLocalIdentifier: undefined,
166
174
  customTheme,
167
175
  schemeModifierConfig,
168
176
  supportedAttributes: exactMatches,