@mgcrea/react-native-tailwind 0.7.0 → 0.8.1

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 (92) hide show
  1. package/README.md +2 -1
  2. package/dist/babel/index.cjs +334 -196
  3. package/dist/babel/index.d.ts +4 -40
  4. package/dist/babel/index.test.ts +214 -1
  5. package/dist/babel/index.ts +4 -1169
  6. package/dist/babel/plugin.d.ts +42 -0
  7. package/{src/babel/index.test.ts → dist/babel/plugin.test.ts} +216 -2
  8. package/dist/babel/plugin.ts +491 -0
  9. package/dist/babel/utils/attributeMatchers.d.ts +23 -0
  10. package/dist/babel/utils/attributeMatchers.test.ts +294 -0
  11. package/dist/babel/utils/attributeMatchers.ts +71 -0
  12. package/dist/babel/utils/componentSupport.d.ts +18 -0
  13. package/dist/babel/utils/componentSupport.test.ts +426 -0
  14. package/dist/babel/utils/componentSupport.ts +68 -0
  15. package/dist/babel/utils/dynamicProcessing.d.ts +32 -0
  16. package/dist/babel/utils/dynamicProcessing.ts +223 -0
  17. package/dist/babel/utils/modifierProcessing.d.ts +26 -0
  18. package/dist/babel/utils/modifierProcessing.ts +118 -0
  19. package/dist/babel/utils/styleInjection.d.ts +15 -0
  20. package/dist/babel/utils/styleInjection.ts +80 -0
  21. package/dist/babel/utils/styleTransforms.d.ts +39 -0
  22. package/dist/babel/utils/styleTransforms.test.ts +349 -0
  23. package/dist/babel/utils/styleTransforms.ts +258 -0
  24. package/dist/babel/utils/twProcessing.d.ts +28 -0
  25. package/dist/babel/utils/twProcessing.ts +124 -0
  26. package/dist/components/TextInput.d.ts +171 -14
  27. package/dist/config/tailwind.d.ts +302 -0
  28. package/dist/config/tailwind.js +1 -0
  29. package/dist/index.d.ts +5 -4
  30. package/dist/index.js +1 -1
  31. package/dist/parser/colors.js +1 -1
  32. package/dist/parser/colors.test.js +1 -1
  33. package/dist/parser/index.d.ts +1 -0
  34. package/dist/parser/index.js +1 -1
  35. package/dist/parser/modifiers.d.ts +2 -2
  36. package/dist/parser/modifiers.js +1 -1
  37. package/dist/parser/placeholder.d.ts +36 -0
  38. package/dist/parser/placeholder.js +1 -0
  39. package/dist/parser/placeholder.test.js +1 -0
  40. package/dist/parser/typography.d.ts +1 -0
  41. package/dist/parser/typography.js +1 -1
  42. package/dist/parser/typography.test.js +1 -1
  43. package/dist/runtime.cjs +1 -1
  44. package/dist/runtime.cjs.map +4 -4
  45. package/dist/runtime.d.ts +1 -14
  46. package/dist/runtime.js +1 -1
  47. package/dist/runtime.js.map +4 -4
  48. package/dist/stubs/tw.d.ts +1 -14
  49. package/dist/types/core.d.ts +40 -0
  50. package/dist/types/core.js +0 -0
  51. package/dist/types/index.d.ts +2 -0
  52. package/dist/types/index.js +1 -0
  53. package/dist/types/runtime.d.ts +15 -0
  54. package/dist/types/runtime.js +1 -0
  55. package/dist/types/util.d.ts +3 -0
  56. package/dist/types/util.js +0 -0
  57. package/dist/utils/flattenColors.d.ts +1 -0
  58. package/dist/utils/flattenColors.js +1 -1
  59. package/dist/utils/flattenColors.test.js +1 -1
  60. package/package.json +1 -1
  61. package/src/babel/index.ts +4 -1169
  62. package/src/babel/plugin.test.ts +482 -0
  63. package/src/babel/plugin.ts +491 -0
  64. package/src/babel/utils/attributeMatchers.test.ts +294 -0
  65. package/src/babel/utils/attributeMatchers.ts +71 -0
  66. package/src/babel/utils/componentSupport.test.ts +426 -0
  67. package/src/babel/utils/componentSupport.ts +68 -0
  68. package/src/babel/utils/dynamicProcessing.ts +223 -0
  69. package/src/babel/utils/modifierProcessing.ts +118 -0
  70. package/src/babel/utils/styleInjection.ts +80 -0
  71. package/src/babel/utils/styleTransforms.test.ts +349 -0
  72. package/src/babel/utils/styleTransforms.ts +258 -0
  73. package/src/babel/utils/twProcessing.ts +124 -0
  74. package/src/components/TextInput.tsx +17 -14
  75. package/src/config/{palettes.ts → tailwind.ts} +2 -2
  76. package/src/index.ts +6 -3
  77. package/src/parser/colors.test.ts +32 -0
  78. package/src/parser/colors.ts +2 -2
  79. package/src/parser/index.ts +2 -1
  80. package/src/parser/modifiers.ts +10 -4
  81. package/src/parser/placeholder.test.ts +105 -0
  82. package/src/parser/placeholder.ts +78 -0
  83. package/src/parser/typography.test.ts +11 -0
  84. package/src/parser/typography.ts +20 -2
  85. package/src/runtime.ts +1 -16
  86. package/src/stubs/tw.ts +1 -16
  87. package/src/{types.ts → types/core.ts} +0 -4
  88. package/src/types/index.ts +2 -0
  89. package/src/types/runtime.ts +17 -0
  90. package/src/types/util.ts +1 -0
  91. package/src/utils/flattenColors.test.ts +100 -0
  92. package/src/utils/flattenColors.ts +3 -1
@@ -0,0 +1,294 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DEFAULT_CLASS_ATTRIBUTES,
4
+ buildAttributeMatchers,
5
+ getTargetStyleProp,
6
+ isAttributeSupported,
7
+ } from "./attributeMatchers";
8
+
9
+ describe("DEFAULT_CLASS_ATTRIBUTES", () => {
10
+ it("should contain standard className attributes", () => {
11
+ expect(DEFAULT_CLASS_ATTRIBUTES).toEqual([
12
+ "className",
13
+ "contentContainerClassName",
14
+ "columnWrapperClassName",
15
+ "ListHeaderComponentClassName",
16
+ "ListFooterComponentClassName",
17
+ ]);
18
+ });
19
+
20
+ it("should be a readonly array", () => {
21
+ // TypeScript compile-time check - if this compiles, the const assertion works
22
+ const _typeCheck: readonly string[] = DEFAULT_CLASS_ATTRIBUTES;
23
+ expect(_typeCheck).toBeDefined();
24
+ });
25
+ });
26
+
27
+ describe("buildAttributeMatchers", () => {
28
+ it("should separate exact matches from patterns", () => {
29
+ const attributes = ["className", "containerClassName", "*Style", "custom*"];
30
+ const result = buildAttributeMatchers(attributes);
31
+
32
+ // Exact matches should be in a Set
33
+ expect(result.exactMatches).toBeInstanceOf(Set);
34
+ expect(result.exactMatches.has("className")).toBe(true);
35
+ expect(result.exactMatches.has("containerClassName")).toBe(true);
36
+ expect(result.exactMatches.size).toBe(2);
37
+
38
+ // Patterns should be RegExp objects
39
+ expect(result.patterns).toHaveLength(2);
40
+ expect(result.patterns[0]).toBeInstanceOf(RegExp);
41
+ expect(result.patterns[1]).toBeInstanceOf(RegExp);
42
+ });
43
+
44
+ it("should handle only exact matches", () => {
45
+ const attributes = ["className", "customClass", "anotherClass"];
46
+ const result = buildAttributeMatchers(attributes);
47
+
48
+ expect(result.exactMatches.size).toBe(3);
49
+ expect(result.patterns).toHaveLength(0);
50
+ });
51
+
52
+ it("should handle only patterns", () => {
53
+ const attributes = ["*ClassName", "container*", "*Style*"];
54
+ const result = buildAttributeMatchers(attributes);
55
+
56
+ expect(result.exactMatches.size).toBe(0);
57
+ expect(result.patterns).toHaveLength(3);
58
+ });
59
+
60
+ it("should handle empty array", () => {
61
+ const result = buildAttributeMatchers([]);
62
+
63
+ expect(result.exactMatches.size).toBe(0);
64
+ expect(result.patterns).toHaveLength(0);
65
+ });
66
+
67
+ it("should convert glob patterns to regex correctly", () => {
68
+ const attributes = ["*ClassName", "container*", "*custom*"];
69
+ const result = buildAttributeMatchers(attributes);
70
+
71
+ // Test that patterns match expected strings
72
+ expect(result.patterns[0]?.test("myClassName")).toBe(true);
73
+ expect(result.patterns[0]?.test("fooClassName")).toBe(true);
74
+ expect(result.patterns[0]?.test("className")).toBe(false); // Doesn't start with anything
75
+
76
+ expect(result.patterns[1]?.test("containerStyle")).toBe(true);
77
+ expect(result.patterns[1]?.test("containerFoo")).toBe(true);
78
+ expect(result.patterns[1]?.test("myContainer")).toBe(false);
79
+
80
+ expect(result.patterns[2]?.test("mycustomattr")).toBe(true);
81
+ expect(result.patterns[2]?.test("customattr")).toBe(true);
82
+ expect(result.patterns[2]?.test("attrcustom")).toBe(true);
83
+ });
84
+
85
+ it("should handle multiple wildcards in same pattern", () => {
86
+ const attributes = ["*custom*Class*"];
87
+ const result = buildAttributeMatchers(attributes);
88
+
89
+ expect(result.patterns[0]?.test("mycustomFooClassName")).toBe(true);
90
+ expect(result.patterns[0]?.test("customClassName")).toBe(true);
91
+ expect(result.patterns[0]?.test("foocustombarClassbaz")).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("isAttributeSupported", () => {
96
+ it("should match exact attributes", () => {
97
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "customClass"]);
98
+
99
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
100
+ expect(isAttributeSupported("customClass", exactMatches, patterns)).toBe(true);
101
+ expect(isAttributeSupported("otherClass", exactMatches, patterns)).toBe(false);
102
+ });
103
+
104
+ it("should match pattern-based attributes", () => {
105
+ const { exactMatches, patterns } = buildAttributeMatchers(["*ClassName", "container*"]);
106
+
107
+ expect(isAttributeSupported("myClassName", exactMatches, patterns)).toBe(true);
108
+ expect(isAttributeSupported("fooClassName", exactMatches, patterns)).toBe(true);
109
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
110
+ expect(isAttributeSupported("containerFoo", exactMatches, patterns)).toBe(true);
111
+ expect(isAttributeSupported("randomAttr", exactMatches, patterns)).toBe(false);
112
+ });
113
+
114
+ it("should match both exact and pattern attributes", () => {
115
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "*Style"]);
116
+
117
+ // Exact match
118
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
119
+
120
+ // Pattern match
121
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
122
+ expect(isAttributeSupported("customStyle", exactMatches, patterns)).toBe(true);
123
+
124
+ // No match
125
+ expect(isAttributeSupported("otherAttr", exactMatches, patterns)).toBe(false);
126
+ });
127
+
128
+ it("should prioritize exact matches (performance)", () => {
129
+ // Even if a pattern would match, exact match should work
130
+ const { exactMatches, patterns } = buildAttributeMatchers(["className", "*Name"]);
131
+
132
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
133
+ });
134
+
135
+ it("should handle empty matchers", () => {
136
+ const { exactMatches, patterns } = buildAttributeMatchers([]);
137
+
138
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
139
+ expect(isAttributeSupported("anyAttr", exactMatches, patterns)).toBe(false);
140
+ });
141
+
142
+ it("should be case-sensitive", () => {
143
+ const { exactMatches, patterns } = buildAttributeMatchers(["className"]);
144
+
145
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
146
+ expect(isAttributeSupported("ClassName", exactMatches, patterns)).toBe(false);
147
+ expect(isAttributeSupported("classname", exactMatches, patterns)).toBe(false);
148
+ });
149
+
150
+ it("should match default React Native FlatList attributes", () => {
151
+ const { exactMatches, patterns } = buildAttributeMatchers([...DEFAULT_CLASS_ATTRIBUTES]);
152
+
153
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
154
+ expect(isAttributeSupported("contentContainerClassName", exactMatches, patterns)).toBe(true);
155
+ expect(isAttributeSupported("columnWrapperClassName", exactMatches, patterns)).toBe(true);
156
+ expect(isAttributeSupported("ListHeaderComponentClassName", exactMatches, patterns)).toBe(true);
157
+ expect(isAttributeSupported("ListFooterComponentClassName", exactMatches, patterns)).toBe(true);
158
+ });
159
+
160
+ it("should work with complex glob patterns", () => {
161
+ const { exactMatches, patterns } = buildAttributeMatchers(["*Container*Class*"]);
162
+
163
+ expect(isAttributeSupported("myContainerFooClassName", exactMatches, patterns)).toBe(true);
164
+ expect(isAttributeSupported("ContainerClassName", exactMatches, patterns)).toBe(true);
165
+ expect(isAttributeSupported("fooContainerBarClassBaz", exactMatches, patterns)).toBe(true);
166
+ expect(isAttributeSupported("ContainerStyle", exactMatches, patterns)).toBe(false);
167
+ });
168
+ });
169
+
170
+ describe("getTargetStyleProp", () => {
171
+ it("should convert className to style", () => {
172
+ expect(getTargetStyleProp("className")).toBe("style");
173
+ });
174
+
175
+ it("should convert *ClassName to *Style", () => {
176
+ expect(getTargetStyleProp("contentContainerClassName")).toBe("contentContainerStyle");
177
+ expect(getTargetStyleProp("columnWrapperClassName")).toBe("columnWrapperStyle");
178
+ expect(getTargetStyleProp("ListHeaderComponentClassName")).toBe("ListHeaderComponentStyle");
179
+ expect(getTargetStyleProp("ListFooterComponentClassName")).toBe("ListFooterComponentStyle");
180
+ });
181
+
182
+ it("should handle custom className attributes", () => {
183
+ expect(getTargetStyleProp("customClassName")).toBe("customStyle");
184
+ expect(getTargetStyleProp("myCustomClassName")).toBe("myCustomStyle");
185
+ expect(getTargetStyleProp("fooBarClassName")).toBe("fooBarStyle");
186
+ });
187
+
188
+ it("should return style for attributes not ending in ClassName", () => {
189
+ expect(getTargetStyleProp("customClass")).toBe("style");
190
+ expect(getTargetStyleProp("class")).toBe("style");
191
+ expect(getTargetStyleProp("myAttr")).toBe("style");
192
+ expect(getTargetStyleProp("")).toBe("style");
193
+ });
194
+
195
+ it("should handle edge cases", () => {
196
+ // Attribute IS exactly "ClassName"
197
+ expect(getTargetStyleProp("ClassName")).toBe("Style");
198
+
199
+ // Multiple "ClassName" occurrences (only last one replaced)
200
+ expect(getTargetStyleProp("classNameClassName")).toBe("classNameStyle");
201
+ });
202
+
203
+ it("should be case-sensitive", () => {
204
+ expect(getTargetStyleProp("classname")).toBe("style");
205
+ expect(getTargetStyleProp("CLASSNAME")).toBe("style");
206
+ expect(getTargetStyleProp("classNamee")).toBe("style");
207
+ });
208
+
209
+ it("should handle all default attributes correctly", () => {
210
+ const expectedMappings = [
211
+ ["className", "style"],
212
+ ["contentContainerClassName", "contentContainerStyle"],
213
+ ["columnWrapperClassName", "columnWrapperStyle"],
214
+ ["ListHeaderComponentClassName", "ListHeaderComponentStyle"],
215
+ ["ListFooterComponentClassName", "ListFooterComponentStyle"],
216
+ ] as const;
217
+
218
+ for (const [input, expected] of expectedMappings) {
219
+ expect(getTargetStyleProp(input)).toBe(expected);
220
+ }
221
+ });
222
+ });
223
+
224
+ describe("Integration - Real-world scenarios", () => {
225
+ it("should handle FlatList with multiple className attributes", () => {
226
+ const { exactMatches, patterns } = buildAttributeMatchers([...DEFAULT_CLASS_ATTRIBUTES]);
227
+
228
+ // All FlatList className props should be supported
229
+ const flatListAttrs = [
230
+ ["className", "style"],
231
+ ["contentContainerClassName", "contentContainerStyle"],
232
+ ["columnWrapperClassName", "columnWrapperStyle"],
233
+ ["ListHeaderComponentClassName", "ListHeaderComponentStyle"],
234
+ ["ListFooterComponentClassName", "ListFooterComponentStyle"],
235
+ ] as const;
236
+
237
+ for (const [attr, expectedStyle] of flatListAttrs) {
238
+ expect(isAttributeSupported(attr, exactMatches, patterns)).toBe(true);
239
+ expect(getTargetStyleProp(attr)).toBe(expectedStyle);
240
+ }
241
+ });
242
+
243
+ it("should support custom wildcard pattern for all *ClassName attributes", () => {
244
+ const { exactMatches, patterns } = buildAttributeMatchers(["*ClassName"]);
245
+
246
+ // Should match any attribute ending in ClassName (with at least one char before)
247
+ // Note: The regex ^.*ClassName$ requires at least one character due to .*
248
+ expect(isAttributeSupported("myCustomClassName", exactMatches, patterns)).toBe(true);
249
+ expect(isAttributeSupported("fooBarBazClassName", exactMatches, patterns)).toBe(true);
250
+ expect(isAttributeSupported("xClassName", exactMatches, patterns)).toBe(true);
251
+
252
+ // Edge case: bare "className" needs at least one char before it for .* to match
253
+ // If you want to include "className", add it explicitly or use a different pattern
254
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
255
+
256
+ // Should convert to corresponding style prop
257
+ expect(getTargetStyleProp("myCustomClassName")).toBe("myCustomStyle");
258
+
259
+ // Should not match non-className attributes
260
+ expect(isAttributeSupported("myCustomClass", exactMatches, patterns)).toBe(false);
261
+ });
262
+
263
+ it("should support combining exact matches with patterns", () => {
264
+ const { exactMatches, patterns } = buildAttributeMatchers([
265
+ "className",
266
+ "customClass",
267
+ "*ClassName",
268
+ "container*",
269
+ ]);
270
+
271
+ // Exact matches
272
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(true);
273
+ expect(isAttributeSupported("customClass", exactMatches, patterns)).toBe(true);
274
+
275
+ // Pattern matches
276
+ expect(isAttributeSupported("myClassName", exactMatches, patterns)).toBe(true);
277
+ expect(isAttributeSupported("containerStyle", exactMatches, patterns)).toBe(true);
278
+
279
+ // No matches
280
+ expect(isAttributeSupported("randomAttr", exactMatches, patterns)).toBe(false);
281
+ });
282
+
283
+ it("should handle empty configuration gracefully", () => {
284
+ const { exactMatches, patterns } = buildAttributeMatchers([]);
285
+
286
+ // Nothing should be supported
287
+ expect(isAttributeSupported("className", exactMatches, patterns)).toBe(false);
288
+ expect(isAttributeSupported("anything", exactMatches, patterns)).toBe(false);
289
+
290
+ // getTargetStyleProp should still work
291
+ expect(getTargetStyleProp("className")).toBe("style");
292
+ expect(getTargetStyleProp("customClassName")).toBe("customStyle");
293
+ });
294
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Utility functions for matching and handling JSX attribute names
3
+ */
4
+
5
+ /**
6
+ * Default className-like attributes (used when no custom attributes are provided)
7
+ */
8
+ export const DEFAULT_CLASS_ATTRIBUTES = [
9
+ "className",
10
+ "contentContainerClassName",
11
+ "columnWrapperClassName",
12
+ "ListHeaderComponentClassName",
13
+ "ListFooterComponentClassName",
14
+ ] as const;
15
+
16
+ /**
17
+ * Build attribute matching structures from plugin options
18
+ * Separates exact matches from pattern-based matches
19
+ */
20
+ export function buildAttributeMatchers(attributes: string[]): {
21
+ exactMatches: Set<string>;
22
+ patterns: RegExp[];
23
+ } {
24
+ const exactMatches = new Set<string>();
25
+ const patterns: RegExp[] = [];
26
+
27
+ for (const attr of attributes) {
28
+ if (attr.includes("*")) {
29
+ // Convert glob pattern to regex
30
+ // *ClassName -> /^.*ClassName$/
31
+ // container* -> /^container.*$/
32
+ const regexPattern = "^" + attr.replace(/\*/g, ".*") + "$";
33
+ patterns.push(new RegExp(regexPattern));
34
+ } else {
35
+ // Exact match
36
+ exactMatches.add(attr);
37
+ }
38
+ }
39
+
40
+ return { exactMatches, patterns };
41
+ }
42
+
43
+ /**
44
+ * Check if an attribute name matches the configured attributes
45
+ */
46
+ export function isAttributeSupported(
47
+ attributeName: string,
48
+ exactMatches: Set<string>,
49
+ patterns: RegExp[],
50
+ ): boolean {
51
+ // Check exact matches first (faster)
52
+ if (exactMatches.has(attributeName)) {
53
+ return true;
54
+ }
55
+
56
+ // Check pattern matches
57
+ for (const pattern of patterns) {
58
+ if (pattern.test(attributeName)) {
59
+ return true;
60
+ }
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Get the target style prop name based on the className attribute
68
+ */
69
+ export function getTargetStyleProp(attributeName: string): string {
70
+ return attributeName.endsWith("ClassName") ? attributeName.replace("ClassName", "Style") : "style";
71
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Utility functions for determining component modifier support
3
+ */
4
+ import type * as BabelTypes from "@babel/types";
5
+ import type { ModifierType } from "../../parser/index.js";
6
+ /**
7
+ * Check if a JSX element supports modifiers and determine which modifiers are supported
8
+ * Returns an object with component info and supported modifiers
9
+ */
10
+ export declare function getComponentModifierSupport(jsxElement: BabelTypes.Node, t: typeof BabelTypes): {
11
+ component: string;
12
+ supportedModifiers: ModifierType[];
13
+ } | null;
14
+ /**
15
+ * Get the state property name for a modifier type
16
+ * Maps modifier types to component state parameter properties
17
+ */
18
+ export declare function getStatePropertyForModifier(modifier: ModifierType): string;