@mgcrea/react-native-tailwind 0.9.0 → 0.10.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 +356 -30
  2. package/dist/babel/config-loader.test.ts +152 -0
  3. package/dist/babel/index.cjs +575 -60
  4. package/dist/babel/plugin.d.ts +23 -1
  5. package/dist/babel/plugin.test.ts +417 -0
  6. package/dist/babel/plugin.ts +265 -32
  7. package/dist/babel/utils/colorSchemeModifierProcessing.d.ts +34 -0
  8. package/dist/babel/utils/colorSchemeModifierProcessing.ts +89 -0
  9. package/dist/babel/utils/dynamicProcessing.d.ts +33 -2
  10. package/dist/babel/utils/dynamicProcessing.ts +352 -33
  11. package/dist/babel/utils/styleInjection.d.ts +14 -1
  12. package/dist/babel/utils/styleInjection.ts +125 -7
  13. package/dist/babel/utils/styleTransforms.test.ts +56 -0
  14. package/dist/babel/utils/twProcessing.d.ts +2 -0
  15. package/dist/babel/utils/twProcessing.ts +22 -1
  16. package/dist/parser/aspectRatio.js +1 -1
  17. package/dist/parser/aspectRatio.test.js +1 -1
  18. package/dist/parser/index.d.ts +2 -2
  19. package/dist/parser/index.js +1 -1
  20. package/dist/parser/modifiers.d.ts +48 -2
  21. package/dist/parser/modifiers.js +1 -1
  22. package/dist/parser/modifiers.test.js +1 -1
  23. package/dist/parser/spacing.d.ts +1 -1
  24. package/dist/parser/spacing.js +1 -1
  25. package/dist/parser/spacing.test.js +1 -1
  26. package/dist/runtime.cjs +1 -1
  27. package/dist/runtime.cjs.map +3 -3
  28. package/dist/runtime.js +1 -1
  29. package/dist/runtime.js.map +3 -3
  30. package/dist/runtime.test.js +1 -1
  31. package/dist/types/config.d.ts +7 -0
  32. package/dist/types/config.js +0 -0
  33. package/package.json +4 -4
  34. package/src/babel/config-loader.test.ts +152 -0
  35. package/src/babel/plugin.test.ts +417 -0
  36. package/src/babel/plugin.ts +265 -32
  37. package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
  38. package/src/babel/utils/dynamicProcessing.ts +352 -33
  39. package/src/babel/utils/styleInjection.ts +125 -7
  40. package/src/babel/utils/styleTransforms.test.ts +56 -0
  41. package/src/babel/utils/twProcessing.ts +22 -1
  42. package/src/parser/aspectRatio.test.ts +25 -2
  43. package/src/parser/aspectRatio.ts +3 -3
  44. package/src/parser/index.ts +12 -1
  45. package/src/parser/modifiers.test.ts +151 -1
  46. package/src/parser/modifiers.ts +139 -4
  47. package/src/parser/spacing.test.ts +63 -0
  48. package/src/parser/spacing.ts +10 -6
  49. package/src/runtime.test.ts +27 -0
  50. package/src/runtime.ts +2 -1
  51. package/src/types/config.ts +7 -0
  52. package/dist/babel/index.test.ts +0 -481
  53. package/dist/config/palettes.d.ts +0 -302
  54. package/dist/config/palettes.js +0 -1
  55. package/dist/parser/__snapshots__/aspectRatio.test.js.snap +0 -9
  56. package/dist/parser/__snapshots__/borders.test.js.snap +0 -23
  57. package/dist/parser/__snapshots__/colors.test.js.snap +0 -251
  58. package/dist/parser/__snapshots__/shadows.test.js.snap +0 -76
  59. package/dist/parser/__snapshots__/sizing.test.js.snap +0 -61
  60. package/dist/parser/__snapshots__/spacing.test.js.snap +0 -40
  61. package/dist/parser/__snapshots__/transforms.test.js.snap +0 -58
  62. package/dist/parser/__snapshots__/typography.test.js.snap +0 -30
  63. package/dist/parser/aspectRatio.test.d.ts +0 -1
  64. package/dist/parser/borders.test.d.ts +0 -1
  65. package/dist/parser/colors.test.d.ts +0 -1
  66. package/dist/parser/layout.test.d.ts +0 -1
  67. package/dist/parser/modifiers.test.d.ts +0 -1
  68. package/dist/parser/shadows.test.d.ts +0 -1
  69. package/dist/parser/sizing.test.d.ts +0 -1
  70. package/dist/parser/spacing.test.d.ts +0 -1
  71. package/dist/parser/typography.test.d.ts +0 -1
  72. package/dist/types.d.ts +0 -42
  73. package/dist/types.js +0 -1
@@ -65,7 +65,7 @@ function parseAspectRatio(cls) {
65
65
  if (cls in ASPECT_RATIO_PRESETS) {
66
66
  const aspectRatio2 = ASPECT_RATIO_PRESETS[cls];
67
67
  if (aspectRatio2 === void 0) {
68
- return {};
68
+ return { aspectRatio: void 0 };
69
69
  }
70
70
  return { aspectRatio: aspectRatio2 };
71
71
  }
@@ -1209,16 +1209,19 @@ function parseArbitrarySpacing(value) {
1209
1209
  return null;
1210
1210
  }
1211
1211
  function parseSpacing(cls) {
1212
- const marginMatch = cls.match(/^m([xytrbls]?)-(.+)$/);
1212
+ const marginMatch = cls.match(/^(-?)m([xytrbls]?)-(.+)$/);
1213
1213
  if (marginMatch) {
1214
- const [, dir, valueStr] = marginMatch;
1214
+ const [, negativePrefix, dir, valueStr] = marginMatch;
1215
+ const isNegative = negativePrefix === "-";
1215
1216
  const arbitraryValue = parseArbitrarySpacing(valueStr);
1216
1217
  if (arbitraryValue !== null) {
1217
- return getMarginStyle(dir, arbitraryValue);
1218
+ const finalValue = isNegative ? -arbitraryValue : arbitraryValue;
1219
+ return getMarginStyle(dir, finalValue);
1218
1220
  }
1219
1221
  const scaleValue = SPACING_SCALE[valueStr];
1220
1222
  if (scaleValue !== void 0) {
1221
- return getMarginStyle(dir, scaleValue);
1223
+ const finalValue = isNegative ? -scaleValue : scaleValue;
1224
+ return getMarginStyle(dir, finalValue);
1222
1225
  }
1223
1226
  }
1224
1227
  const paddingMatch = cls.match(/^p([xytrbls]?)-(.+)$/);
@@ -1738,7 +1741,14 @@ var STATE_MODIFIERS = [
1738
1741
  "placeholder"
1739
1742
  ];
1740
1743
  var PLATFORM_MODIFIERS = ["ios", "android", "web"];
1741
- var SUPPORTED_MODIFIERS = [...STATE_MODIFIERS, ...PLATFORM_MODIFIERS];
1744
+ var COLOR_SCHEME_MODIFIERS = ["dark", "light"];
1745
+ var SCHEME_MODIFIERS = ["scheme"];
1746
+ var SUPPORTED_MODIFIERS = [
1747
+ ...STATE_MODIFIERS,
1748
+ ...PLATFORM_MODIFIERS,
1749
+ ...COLOR_SCHEME_MODIFIERS,
1750
+ ...SCHEME_MODIFIERS
1751
+ ];
1742
1752
  function parseModifier(cls) {
1743
1753
  const colonIndex = cls.indexOf(":");
1744
1754
  if (colonIndex === -1) {
@@ -1766,6 +1776,56 @@ function isStateModifier(modifier) {
1766
1776
  function isPlatformModifier(modifier) {
1767
1777
  return PLATFORM_MODIFIERS.includes(modifier);
1768
1778
  }
1779
+ function isColorSchemeModifier(modifier) {
1780
+ return COLOR_SCHEME_MODIFIERS.includes(modifier);
1781
+ }
1782
+ function isSchemeModifier(modifier) {
1783
+ return SCHEME_MODIFIERS.includes(modifier);
1784
+ }
1785
+ function isColorClass(className) {
1786
+ return className.startsWith("text-") || className.startsWith("bg-") || className.startsWith("border-");
1787
+ }
1788
+ function expandSchemeModifier(schemeModifier, customColors, darkSuffix = "-dark", lightSuffix = "-light") {
1789
+ const { baseClass } = schemeModifier;
1790
+ if (!isColorClass(baseClass)) {
1791
+ if (process.env.NODE_ENV !== "production") {
1792
+ console.warn(
1793
+ `[react-native-tailwind] scheme: modifier only supports color classes (text-*, bg-*, border-*). Found: "${baseClass}". This modifier will be ignored.`
1794
+ );
1795
+ }
1796
+ return [];
1797
+ }
1798
+ const match = baseClass.match(/^(text|bg|border)-(.+)$/);
1799
+ if (!match) {
1800
+ return [];
1801
+ }
1802
+ const [, prefix, colorName] = match;
1803
+ const darkColorName = `${colorName}${darkSuffix}`;
1804
+ const lightColorName = `${colorName}${lightSuffix}`;
1805
+ const darkColorExists = customColors[darkColorName] !== void 0;
1806
+ const lightColorExists = customColors[lightColorName] !== void 0;
1807
+ if (!darkColorExists || !lightColorExists) {
1808
+ if (process.env.NODE_ENV !== "production") {
1809
+ const missing = [];
1810
+ if (!darkColorExists) missing.push(`${colorName}${darkSuffix}`);
1811
+ if (!lightColorExists) missing.push(`${colorName}${lightSuffix}`);
1812
+ console.warn(
1813
+ `[react-native-tailwind] scheme:${baseClass} requires both color variants to exist. Missing: ${missing.join(", ")}. This modifier will be ignored.`
1814
+ );
1815
+ }
1816
+ return [];
1817
+ }
1818
+ return [
1819
+ {
1820
+ modifier: "dark",
1821
+ baseClass: `${prefix}-${darkColorName}`
1822
+ },
1823
+ {
1824
+ modifier: "light",
1825
+ baseClass: `${prefix}-${lightColorName}`
1826
+ }
1827
+ ];
1828
+ }
1769
1829
  function splitModifierClasses(className) {
1770
1830
  const classes = className.trim().split(/\s+/).filter(Boolean);
1771
1831
  const baseClasses = [];
@@ -1920,6 +1980,38 @@ function getTargetStyleProp(attributeName) {
1920
1980
  return attributeName.endsWith("ClassName") ? attributeName.replace("ClassName", "Style") : "style";
1921
1981
  }
1922
1982
 
1983
+ // src/babel/utils/colorSchemeModifierProcessing.ts
1984
+ function processColorSchemeModifiers(colorSchemeModifiers, state, parseClassName2, generateStyleKey2, t) {
1985
+ state.needsColorSchemeImport = true;
1986
+ const modifiersByScheme = /* @__PURE__ */ new Map();
1987
+ for (const mod of colorSchemeModifiers) {
1988
+ const scheme = mod.modifier;
1989
+ if (!modifiersByScheme.has(scheme)) {
1990
+ modifiersByScheme.set(scheme, []);
1991
+ }
1992
+ const schemeGroup = modifiersByScheme.get(scheme);
1993
+ if (schemeGroup) {
1994
+ schemeGroup.push(mod);
1995
+ }
1996
+ }
1997
+ const conditionalExpressions = [];
1998
+ for (const [scheme, modifiers] of modifiersByScheme) {
1999
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2000
+ const styleObject = parseClassName2(classNames, state.customColors);
2001
+ const styleKey = generateStyleKey2(`${scheme}_${classNames}`);
2002
+ state.styleRegistry.set(styleKey, styleObject);
2003
+ const colorSchemeCheck = t.binaryExpression(
2004
+ "===",
2005
+ t.identifier(state.colorSchemeVariableName),
2006
+ t.stringLiteral(scheme)
2007
+ );
2008
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2009
+ const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
2010
+ conditionalExpressions.push(conditionalExpression);
2011
+ }
2012
+ return conditionalExpressions;
2013
+ }
2014
+
1923
2015
  // src/babel/utils/componentSupport.ts
1924
2016
  function getComponentModifierSupport(jsxElement, t) {
1925
2017
  if (!t.isJSXOpeningElement(jsxElement)) {
@@ -1964,32 +2056,89 @@ function getStatePropertyForModifier(modifier) {
1964
2056
  }
1965
2057
 
1966
2058
  // src/babel/utils/dynamicProcessing.ts
1967
- function processDynamicExpression(expression, state, parseClassName2, generateStyleKey2, t) {
2059
+ function processDynamicExpression(expression, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
1968
2060
  if (t.isTemplateLiteral(expression)) {
1969
- return processTemplateLiteral(expression, state, parseClassName2, generateStyleKey2, t);
2061
+ return processTemplateLiteral(
2062
+ expression,
2063
+ state,
2064
+ parseClassName2,
2065
+ generateStyleKey2,
2066
+ splitModifierClasses2,
2067
+ processPlatformModifiers2,
2068
+ processColorSchemeModifiers2,
2069
+ componentScope,
2070
+ isPlatformModifier2,
2071
+ isColorSchemeModifier2,
2072
+ isSchemeModifier2,
2073
+ expandSchemeModifier2,
2074
+ t
2075
+ );
1970
2076
  }
1971
2077
  if (t.isConditionalExpression(expression)) {
1972
- return processConditionalExpression(expression, state, parseClassName2, generateStyleKey2, t);
2078
+ return processConditionalExpression(
2079
+ expression,
2080
+ state,
2081
+ parseClassName2,
2082
+ generateStyleKey2,
2083
+ splitModifierClasses2,
2084
+ processPlatformModifiers2,
2085
+ processColorSchemeModifiers2,
2086
+ componentScope,
2087
+ isPlatformModifier2,
2088
+ isColorSchemeModifier2,
2089
+ isSchemeModifier2,
2090
+ expandSchemeModifier2,
2091
+ t
2092
+ );
1973
2093
  }
1974
2094
  if (t.isLogicalExpression(expression)) {
1975
- return processLogicalExpression(expression, state, parseClassName2, generateStyleKey2, t);
2095
+ return processLogicalExpression(
2096
+ expression,
2097
+ state,
2098
+ parseClassName2,
2099
+ generateStyleKey2,
2100
+ splitModifierClasses2,
2101
+ processPlatformModifiers2,
2102
+ processColorSchemeModifiers2,
2103
+ componentScope,
2104
+ isPlatformModifier2,
2105
+ isColorSchemeModifier2,
2106
+ isSchemeModifier2,
2107
+ expandSchemeModifier2,
2108
+ t
2109
+ );
1976
2110
  }
1977
2111
  return null;
1978
2112
  }
1979
- function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, t) {
2113
+ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
1980
2114
  const parts = [];
1981
2115
  const staticParts = [];
1982
2116
  for (let i = 0; i < node.quasis.length; i++) {
1983
2117
  const quasi = node.quasis[i];
1984
2118
  const staticText = quasi.value.cooked?.trim();
1985
2119
  if (staticText) {
1986
- const classes = staticText.split(/\s+/).filter(Boolean);
1987
- for (const cls of classes) {
1988
- const styleObject = parseClassName2(cls, state.customColors);
1989
- const styleKey = generateStyleKey2(cls);
1990
- state.styleRegistry.set(styleKey, styleObject);
1991
- staticParts.push(cls);
1992
- parts.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
2120
+ const processedExpression = processStringOrExpressionHelper(
2121
+ t.stringLiteral(staticText),
2122
+ state,
2123
+ parseClassName2,
2124
+ generateStyleKey2,
2125
+ splitModifierClasses2,
2126
+ processPlatformModifiers2,
2127
+ processColorSchemeModifiers2,
2128
+ componentScope,
2129
+ isPlatformModifier2,
2130
+ isColorSchemeModifier2,
2131
+ isSchemeModifier2,
2132
+ expandSchemeModifier2,
2133
+ t
2134
+ );
2135
+ if (processedExpression) {
2136
+ staticParts.push(staticText);
2137
+ if (t.isArrayExpression(processedExpression)) {
2138
+ parts.push(...processedExpression.elements);
2139
+ } else {
2140
+ parts.push(processedExpression);
2141
+ }
1993
2142
  }
1994
2143
  }
1995
2144
  if (i < node.expressions.length) {
@@ -1999,6 +2148,14 @@ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2,
1999
2148
  state,
2000
2149
  parseClassName2,
2001
2150
  generateStyleKey2,
2151
+ splitModifierClasses2,
2152
+ processPlatformModifiers2,
2153
+ processColorSchemeModifiers2,
2154
+ componentScope,
2155
+ isPlatformModifier2,
2156
+ isColorSchemeModifier2,
2157
+ isSchemeModifier2,
2158
+ expandSchemeModifier2,
2002
2159
  t
2003
2160
  );
2004
2161
  if (result) {
@@ -2017,9 +2174,37 @@ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2,
2017
2174
  staticParts: staticParts.length > 0 ? staticParts : void 0
2018
2175
  };
2019
2176
  }
2020
- function processConditionalExpression(node, state, parseClassName2, generateStyleKey2, t) {
2021
- const consequent = processStringOrExpression(node.consequent, state, parseClassName2, generateStyleKey2, t);
2022
- const alternate = processStringOrExpression(node.alternate, state, parseClassName2, generateStyleKey2, t);
2177
+ function processConditionalExpression(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2178
+ const consequent = processStringOrExpressionHelper(
2179
+ node.consequent,
2180
+ state,
2181
+ parseClassName2,
2182
+ generateStyleKey2,
2183
+ splitModifierClasses2,
2184
+ processPlatformModifiers2,
2185
+ processColorSchemeModifiers2,
2186
+ componentScope,
2187
+ isPlatformModifier2,
2188
+ isColorSchemeModifier2,
2189
+ isSchemeModifier2,
2190
+ expandSchemeModifier2,
2191
+ t
2192
+ );
2193
+ const alternate = processStringOrExpressionHelper(
2194
+ node.alternate,
2195
+ state,
2196
+ parseClassName2,
2197
+ generateStyleKey2,
2198
+ splitModifierClasses2,
2199
+ processPlatformModifiers2,
2200
+ processColorSchemeModifiers2,
2201
+ componentScope,
2202
+ isPlatformModifier2,
2203
+ isColorSchemeModifier2,
2204
+ isSchemeModifier2,
2205
+ expandSchemeModifier2,
2206
+ t
2207
+ );
2023
2208
  if (!consequent && !alternate) {
2024
2209
  return null;
2025
2210
  }
@@ -2030,38 +2215,153 @@ function processConditionalExpression(node, state, parseClassName2, generateStyl
2030
2215
  );
2031
2216
  return { expression };
2032
2217
  }
2033
- function processLogicalExpression(node, state, parseClassName2, generateStyleKey2, t) {
2218
+ function processLogicalExpression(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2034
2219
  if (node.operator !== "&&") {
2035
2220
  return null;
2036
2221
  }
2037
- const right = processStringOrExpression(node.right, state, parseClassName2, generateStyleKey2, t);
2222
+ const right = processStringOrExpressionHelper(
2223
+ node.right,
2224
+ state,
2225
+ parseClassName2,
2226
+ generateStyleKey2,
2227
+ splitModifierClasses2,
2228
+ processPlatformModifiers2,
2229
+ processColorSchemeModifiers2,
2230
+ componentScope,
2231
+ isPlatformModifier2,
2232
+ isColorSchemeModifier2,
2233
+ isSchemeModifier2,
2234
+ expandSchemeModifier2,
2235
+ t
2236
+ );
2038
2237
  if (!right) {
2039
2238
  return null;
2040
2239
  }
2041
2240
  const expression = t.logicalExpression("&&", node.left, right);
2042
2241
  return { expression };
2043
2242
  }
2044
- function processStringOrExpression(node, state, parseClassName2, generateStyleKey2, t) {
2243
+ function processStringOrExpressionHelper(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2045
2244
  if (t.isStringLiteral(node)) {
2046
2245
  const className = node.value.trim();
2047
2246
  if (!className) {
2048
2247
  return null;
2049
2248
  }
2050
- const styleObject = parseClassName2(className, state.customColors);
2051
- const styleKey = generateStyleKey2(className);
2052
- state.styleRegistry.set(styleKey, styleObject);
2053
- return t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2249
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2250
+ const modifierClasses = [];
2251
+ for (const modifier of rawModifierClasses) {
2252
+ if (isSchemeModifier2(modifier.modifier)) {
2253
+ const expanded = expandSchemeModifier2(
2254
+ modifier,
2255
+ state.customColors,
2256
+ state.schemeModifierConfig.darkSuffix ?? "-dark",
2257
+ state.schemeModifierConfig.lightSuffix ?? "-light"
2258
+ );
2259
+ modifierClasses.push(...expanded);
2260
+ } else {
2261
+ modifierClasses.push(modifier);
2262
+ }
2263
+ }
2264
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier2(m.modifier));
2265
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier2(m.modifier));
2266
+ const styleElements = [];
2267
+ if (baseClasses.length > 0) {
2268
+ const baseClassName = baseClasses.join(" ");
2269
+ const styleObject = parseClassName2(baseClassName, state.customColors);
2270
+ const styleKey = generateStyleKey2(baseClassName);
2271
+ state.styleRegistry.set(styleKey, styleObject);
2272
+ styleElements.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
2273
+ }
2274
+ if (platformModifiers.length > 0) {
2275
+ state.needsPlatformImport = true;
2276
+ const platformExpression = processPlatformModifiers2(
2277
+ platformModifiers,
2278
+ state,
2279
+ parseClassName2,
2280
+ generateStyleKey2,
2281
+ t
2282
+ );
2283
+ styleElements.push(platformExpression);
2284
+ }
2285
+ if (colorSchemeModifiers.length > 0) {
2286
+ if (componentScope) {
2287
+ state.needsColorSchemeImport = true;
2288
+ state.functionComponentsNeedingColorScheme.add(componentScope);
2289
+ const colorSchemeExpressions = processColorSchemeModifiers2(
2290
+ colorSchemeModifiers,
2291
+ state,
2292
+ parseClassName2,
2293
+ generateStyleKey2,
2294
+ t
2295
+ );
2296
+ styleElements.push(...colorSchemeExpressions);
2297
+ } else {
2298
+ if (process.env.NODE_ENV !== "production") {
2299
+ console.warn(
2300
+ "[react-native-tailwind] dark:/light: modifiers in dynamic expressions require a function component scope. These modifiers will be ignored."
2301
+ );
2302
+ }
2303
+ }
2304
+ }
2305
+ if (styleElements.length === 0) {
2306
+ return null;
2307
+ }
2308
+ if (styleElements.length === 1) {
2309
+ return styleElements[0];
2310
+ }
2311
+ return t.arrayExpression(styleElements);
2054
2312
  }
2055
2313
  if (t.isConditionalExpression(node)) {
2056
- const result = processConditionalExpression(node, state, parseClassName2, generateStyleKey2, t);
2314
+ const result = processConditionalExpression(
2315
+ node,
2316
+ state,
2317
+ parseClassName2,
2318
+ generateStyleKey2,
2319
+ splitModifierClasses2,
2320
+ processPlatformModifiers2,
2321
+ processColorSchemeModifiers2,
2322
+ componentScope,
2323
+ isPlatformModifier2,
2324
+ isColorSchemeModifier2,
2325
+ isSchemeModifier2,
2326
+ expandSchemeModifier2,
2327
+ t
2328
+ );
2057
2329
  return result?.expression ?? null;
2058
2330
  }
2059
2331
  if (t.isLogicalExpression(node)) {
2060
- const result = processLogicalExpression(node, state, parseClassName2, generateStyleKey2, t);
2332
+ const result = processLogicalExpression(
2333
+ node,
2334
+ state,
2335
+ parseClassName2,
2336
+ generateStyleKey2,
2337
+ splitModifierClasses2,
2338
+ processPlatformModifiers2,
2339
+ processColorSchemeModifiers2,
2340
+ componentScope,
2341
+ isPlatformModifier2,
2342
+ isColorSchemeModifier2,
2343
+ isSchemeModifier2,
2344
+ expandSchemeModifier2,
2345
+ t
2346
+ );
2061
2347
  return result?.expression ?? null;
2062
2348
  }
2063
2349
  if (t.isTemplateLiteral(node)) {
2064
- const result = processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, t);
2350
+ const result = processTemplateLiteral(
2351
+ node,
2352
+ state,
2353
+ parseClassName2,
2354
+ generateStyleKey2,
2355
+ splitModifierClasses2,
2356
+ processPlatformModifiers2,
2357
+ processColorSchemeModifiers2,
2358
+ componentScope,
2359
+ isPlatformModifier2,
2360
+ isColorSchemeModifier2,
2361
+ isSchemeModifier2,
2362
+ expandSchemeModifier2,
2363
+ t
2364
+ );
2065
2365
  return result?.expression ?? null;
2066
2366
  }
2067
2367
  return null;
@@ -2156,11 +2456,25 @@ function processPlatformModifiers(platformModifiers, state, parseClassName2, gen
2156
2456
 
2157
2457
  // src/babel/utils/styleInjection.ts
2158
2458
  function addStyleSheetImport(path2, t) {
2159
- const importDeclaration = t.importDeclaration(
2160
- [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
2161
- t.stringLiteral("react-native")
2162
- );
2163
- path2.unshiftContainer("body", importDeclaration);
2459
+ const body = path2.node.body;
2460
+ let reactNativeImport = null;
2461
+ for (const statement of body) {
2462
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2463
+ reactNativeImport = statement;
2464
+ break;
2465
+ }
2466
+ }
2467
+ if (reactNativeImport) {
2468
+ reactNativeImport.specifiers.push(
2469
+ t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))
2470
+ );
2471
+ } else {
2472
+ const importDeclaration = t.importDeclaration(
2473
+ [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
2474
+ t.stringLiteral("react-native")
2475
+ );
2476
+ path2.unshiftContainer("body", importDeclaration);
2477
+ }
2164
2478
  }
2165
2479
  function addPlatformImport(path2, t) {
2166
2480
  const body = path2.node.body;
@@ -2181,6 +2495,63 @@ function addPlatformImport(path2, t) {
2181
2495
  path2.unshiftContainer("body", importDeclaration);
2182
2496
  }
2183
2497
  }
2498
+ function addColorSchemeImport(path2, t) {
2499
+ const body = path2.node.body;
2500
+ let reactNativeImport = null;
2501
+ for (const statement of body) {
2502
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2503
+ reactNativeImport = statement;
2504
+ break;
2505
+ }
2506
+ }
2507
+ if (reactNativeImport) {
2508
+ const hasUseColorScheme = reactNativeImport.specifiers.some(
2509
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "useColorScheme"
2510
+ );
2511
+ if (!hasUseColorScheme) {
2512
+ reactNativeImport.specifiers.push(
2513
+ t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))
2514
+ );
2515
+ }
2516
+ } else {
2517
+ const importDeclaration = t.importDeclaration(
2518
+ [t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))],
2519
+ t.stringLiteral("react-native")
2520
+ );
2521
+ path2.unshiftContainer("body", importDeclaration);
2522
+ }
2523
+ }
2524
+ function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
2525
+ let body = functionPath.node.body;
2526
+ if (!t.isBlockStatement(body)) {
2527
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
2528
+ const returnStatement = t.returnStatement(body);
2529
+ const blockStatement = t.blockStatement([returnStatement]);
2530
+ functionPath.node.body = blockStatement;
2531
+ body = blockStatement;
2532
+ } else {
2533
+ return false;
2534
+ }
2535
+ }
2536
+ const hasHook = body.body.some((statement) => {
2537
+ if (t.isVariableDeclaration(statement) && statement.declarations.length > 0 && t.isVariableDeclarator(statement.declarations[0])) {
2538
+ const declarator = statement.declarations[0];
2539
+ return t.isIdentifier(declarator.id) && declarator.id.name === colorSchemeVariableName;
2540
+ }
2541
+ return false;
2542
+ });
2543
+ if (hasHook) {
2544
+ return false;
2545
+ }
2546
+ const hookCall = t.variableDeclaration("const", [
2547
+ t.variableDeclarator(
2548
+ t.identifier(colorSchemeVariableName),
2549
+ t.callExpression(t.identifier("useColorScheme"), [])
2550
+ )
2551
+ ]);
2552
+ body.body.unshift(hookCall);
2553
+ return true;
2554
+ }
2184
2555
  function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
2185
2556
  const styleProperties = [];
2186
2557
  for (const [key, styleObject] of styleRegistry) {
@@ -2331,7 +2702,21 @@ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2331
2702
 
2332
2703
  // src/babel/utils/twProcessing.ts
2333
2704
  function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, t) {
2334
- const { baseClasses, modifierClasses } = splitModifierClasses2(className);
2705
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2706
+ const modifierClasses = [];
2707
+ for (const modifier of rawModifierClasses) {
2708
+ if (isSchemeModifier(modifier.modifier)) {
2709
+ const expanded = expandSchemeModifier(
2710
+ modifier,
2711
+ state.customColors,
2712
+ state.schemeModifierConfig.darkSuffix ?? "-dark",
2713
+ state.schemeModifierConfig.lightSuffix ?? "-light"
2714
+ );
2715
+ modifierClasses.push(...expanded);
2716
+ } else {
2717
+ modifierClasses.push(modifier);
2718
+ }
2719
+ }
2335
2720
  const objectProperties = [];
2336
2721
  if (baseClasses.length > 0) {
2337
2722
  const baseClassName = baseClasses.join(" ");
@@ -2398,10 +2783,52 @@ function removeTwImports(path2, t) {
2398
2783
 
2399
2784
  // src/babel/plugin.ts
2400
2785
  var DEFAULT_STYLES_IDENTIFIER = "_twStyles";
2786
+ function isComponentScope(functionPath, t) {
2787
+ const node = functionPath.node;
2788
+ const parent = functionPath.parent;
2789
+ const parentPath = functionPath.parentPath;
2790
+ if (t.isClassMethod(parent)) {
2791
+ return false;
2792
+ }
2793
+ if (functionPath.findParent((p) => t.isClassBody(p.node))) {
2794
+ return false;
2795
+ }
2796
+ if (t.isFunctionDeclaration(node)) {
2797
+ if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
2798
+ return true;
2799
+ }
2800
+ }
2801
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
2802
+ if (t.isVariableDeclarator(parent)) {
2803
+ const varDeclarationPath = parentPath?.parentPath;
2804
+ if (varDeclarationPath && t.isVariableDeclaration(varDeclarationPath.node) && (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))) {
2805
+ if (t.isIdentifier(parent.id)) {
2806
+ const name = parent.id.name;
2807
+ return /^[A-Z]/.test(name);
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ return false;
2813
+ }
2814
+ function findComponentScope(path2, t) {
2815
+ let current = path2.getFunctionParent();
2816
+ while (current) {
2817
+ if (t.isFunction(current.node) && isComponentScope(current, t)) {
2818
+ return current;
2819
+ }
2820
+ current = current.getFunctionParent();
2821
+ }
2822
+ return null;
2823
+ }
2401
2824
  function reactNativeTailwindBabelPlugin({ types: t }, options) {
2402
2825
  const attributes = options?.attributes ?? [...DEFAULT_CLASS_ATTRIBUTES];
2403
2826
  const { exactMatches, patterns } = buildAttributeMatchers(attributes);
2404
2827
  const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
2828
+ const schemeModifierConfig = {
2829
+ darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
2830
+ lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light"
2831
+ };
2405
2832
  return {
2406
2833
  name: "react-native-tailwind",
2407
2834
  visitor: {
@@ -2412,12 +2839,17 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2412
2839
  state.hasStyleSheetImport = false;
2413
2840
  state.hasPlatformImport = false;
2414
2841
  state.needsPlatformImport = false;
2842
+ state.hasColorSchemeImport = false;
2843
+ state.needsColorSchemeImport = false;
2844
+ state.colorSchemeVariableName = "_twColorScheme";
2415
2845
  state.supportedAttributes = exactMatches;
2416
2846
  state.attributePatterns = patterns;
2417
2847
  state.stylesIdentifier = stylesIdentifier;
2418
2848
  state.twImportNames = /* @__PURE__ */ new Set();
2419
2849
  state.hasTwImport = false;
2850
+ state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
2420
2851
  state.customColors = extractCustomColors(state.file.opts.filename ?? "");
2852
+ state.schemeModifierConfig = schemeModifierConfig;
2421
2853
  },
2422
2854
  exit(path2, state) {
2423
2855
  if (state.hasTwImport) {
@@ -2432,6 +2864,14 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2432
2864
  if (state.needsPlatformImport && !state.hasPlatformImport) {
2433
2865
  addPlatformImport(path2, t);
2434
2866
  }
2867
+ if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
2868
+ addColorSchemeImport(path2, t);
2869
+ }
2870
+ if (state.needsColorSchemeImport) {
2871
+ for (const functionPath of state.functionComponentsNeedingColorScheme) {
2872
+ injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
2873
+ }
2874
+ }
2435
2875
  injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
2436
2876
  }
2437
2877
  },
@@ -2452,15 +2892,22 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2452
2892
  }
2453
2893
  return false;
2454
2894
  });
2895
+ const hasUseColorScheme = specifiers.some((spec) => {
2896
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
2897
+ return spec.imported.name === "useColorScheme";
2898
+ }
2899
+ return false;
2900
+ });
2455
2901
  if (hasStyleSheet) {
2456
2902
  state.hasStyleSheetImport = true;
2457
- } else {
2458
- node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
2459
- state.hasStyleSheetImport = true;
2460
2903
  }
2461
2904
  if (hasPlatform) {
2462
2905
  state.hasPlatformImport = true;
2463
2906
  }
2907
+ if (hasUseColorScheme) {
2908
+ state.hasColorSchemeImport = true;
2909
+ }
2910
+ state.reactNativeImportPath = path2;
2464
2911
  }
2465
2912
  if (node.source.value === "@mgcrea/react-native-tailwind") {
2466
2913
  const specifiers = node.specifiers;
@@ -2561,9 +3008,24 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2561
3008
  return;
2562
3009
  }
2563
3010
  state.hasClassNames = true;
2564
- const { baseClasses, modifierClasses } = splitModifierClasses(className);
3011
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
3012
+ const modifierClasses = [];
3013
+ for (const modifier of rawModifierClasses) {
3014
+ if (isSchemeModifier(modifier.modifier)) {
3015
+ const expanded = expandSchemeModifier(
3016
+ modifier,
3017
+ state.customColors,
3018
+ state.schemeModifierConfig.darkSuffix,
3019
+ state.schemeModifierConfig.lightSuffix
3020
+ );
3021
+ modifierClasses.push(...expanded);
3022
+ } else {
3023
+ modifierClasses.push(modifier);
3024
+ }
3025
+ }
2565
3026
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
2566
3027
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
3028
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
2567
3029
  const stateModifiers = modifierClasses.filter(
2568
3030
  (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
2569
3031
  );
@@ -2585,9 +3047,23 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2585
3047
  }
2586
3048
  }
2587
3049
  const hasPlatformModifiers = platformModifiers.length > 0;
3050
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
2588
3051
  const hasStateModifiers = stateModifiers.length > 0;
2589
3052
  const hasBaseClasses = baseClasses.length > 0;
2590
- if (hasStateModifiers && hasPlatformModifiers) {
3053
+ let componentScope = null;
3054
+ if (hasColorSchemeModifiers) {
3055
+ componentScope = findComponentScope(path2, t);
3056
+ if (componentScope) {
3057
+ state.functionComponentsNeedingColorScheme.add(componentScope);
3058
+ } else {
3059
+ if (process.env.NODE_ENV !== "production") {
3060
+ console.warn(
3061
+ `[react-native-tailwind] dark:/light: modifiers require a function component scope. Found in non-component context at ${state.file.opts.filename ?? "unknown"}. These modifiers are not supported in class components or nested callbacks.`
3062
+ );
3063
+ }
3064
+ }
3065
+ }
3066
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
2591
3067
  const jsxOpeningElement = path2.parent;
2592
3068
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2593
3069
  if (componentSupport) {
@@ -2601,14 +3077,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2601
3077
  t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2602
3078
  );
2603
3079
  }
2604
- const platformSelectExpression = processPlatformModifiers(
2605
- platformModifiers,
2606
- state,
2607
- parseClassName,
2608
- generateStyleKey,
2609
- t
2610
- );
2611
- styleArrayElements.push(platformSelectExpression);
3080
+ if (hasPlatformModifiers) {
3081
+ const platformSelectExpression = processPlatformModifiers(
3082
+ platformModifiers,
3083
+ state,
3084
+ parseClassName,
3085
+ generateStyleKey,
3086
+ t
3087
+ );
3088
+ styleArrayElements.push(platformSelectExpression);
3089
+ }
3090
+ if (hasColorSchemeModifiers && componentScope) {
3091
+ const colorSchemeConditionals = processColorSchemeModifiers(
3092
+ colorSchemeModifiers,
3093
+ state,
3094
+ parseClassName,
3095
+ generateStyleKey,
3096
+ t
3097
+ );
3098
+ styleArrayElements.push(...colorSchemeConditionals);
3099
+ }
2612
3100
  const modifiersByType = /* @__PURE__ */ new Map();
2613
3101
  for (const mod of stateModifiers) {
2614
3102
  const modType = mod.modifier;
@@ -2648,7 +3136,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2648
3136
  } else {
2649
3137
  }
2650
3138
  }
2651
- if (hasPlatformModifiers && !hasStateModifiers) {
3139
+ if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
2652
3140
  const styleExpressions = [];
2653
3141
  if (hasBaseClasses) {
2654
3142
  const baseClassName = baseClasses.join(" ");
@@ -2659,14 +3147,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2659
3147
  t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2660
3148
  );
2661
3149
  }
2662
- const platformSelectExpression = processPlatformModifiers(
2663
- platformModifiers,
2664
- state,
2665
- parseClassName,
2666
- generateStyleKey,
2667
- t
2668
- );
2669
- styleExpressions.push(platformSelectExpression);
3150
+ if (hasPlatformModifiers) {
3151
+ const platformSelectExpression = processPlatformModifiers(
3152
+ platformModifiers,
3153
+ state,
3154
+ parseClassName,
3155
+ generateStyleKey,
3156
+ t
3157
+ );
3158
+ styleExpressions.push(platformSelectExpression);
3159
+ }
3160
+ if (hasColorSchemeModifiers && componentScope) {
3161
+ const colorSchemeConditionals = processColorSchemeModifiers(
3162
+ colorSchemeModifiers,
3163
+ state,
3164
+ parseClassName,
3165
+ generateStyleKey,
3166
+ t
3167
+ );
3168
+ styleExpressions.push(...colorSchemeConditionals);
3169
+ }
2670
3170
  const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
2671
3171
  const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
2672
3172
  if (styleAttribute2) {
@@ -2773,7 +3273,22 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2773
3273
  return;
2774
3274
  }
2775
3275
  try {
2776
- const result = processDynamicExpression(expression, state, parseClassName, generateStyleKey, t);
3276
+ const componentScope = findComponentScope(path2, t);
3277
+ const result = processDynamicExpression(
3278
+ expression,
3279
+ state,
3280
+ parseClassName,
3281
+ generateStyleKey,
3282
+ splitModifierClasses,
3283
+ processPlatformModifiers,
3284
+ processColorSchemeModifiers,
3285
+ componentScope,
3286
+ isPlatformModifier,
3287
+ isColorSchemeModifier,
3288
+ isSchemeModifier,
3289
+ expandSchemeModifier,
3290
+ t
3291
+ );
2777
3292
  if (result) {
2778
3293
  state.hasClassNames = true;
2779
3294
  const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);