@mgcrea/react-native-tailwind 0.11.1 → 0.12.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.
- package/dist/babel/config-loader.d.ts +3 -0
- package/dist/babel/config-loader.test.ts +2 -2
- package/dist/babel/config-loader.ts +37 -2
- package/dist/babel/index.cjs +325 -42
- package/dist/babel/plugin.test.ts +498 -0
- package/dist/babel/plugin.ts +66 -17
- package/dist/babel/utils/styleInjection.ts +57 -17
- package/dist/babel/utils/twProcessing.d.ts +8 -1
- package/dist/babel/utils/twProcessing.ts +212 -4
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.js +1 -1
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.js +1 -1
- package/dist/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.js +1 -1
- package/dist/parser/typography.d.ts +2 -1
- package/dist/parser/typography.js +1 -1
- package/dist/parser/typography.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/runtime.test.js +1 -1
- package/dist/types/runtime.d.ts +8 -1
- package/package.json +1 -1
- package/src/babel/config-loader.test.ts +2 -2
- package/src/babel/config-loader.ts +37 -2
- package/src/babel/plugin.test.ts +498 -0
- package/src/babel/plugin.ts +66 -17
- package/src/babel/utils/styleInjection.ts +57 -17
- package/src/babel/utils/twProcessing.ts +212 -4
- package/src/parser/index.ts +2 -1
- package/src/parser/layout.test.ts +61 -0
- package/src/parser/layout.ts +55 -1
- package/src/parser/spacing.test.ts +62 -0
- package/src/parser/spacing.ts +7 -7
- package/src/parser/typography.test.ts +102 -0
- package/src/parser/typography.ts +61 -15
- package/src/runtime.test.ts +4 -1
- package/src/types/runtime.ts +8 -1
package/src/parser/layout.ts
CHANGED
|
@@ -60,6 +60,31 @@ function parseArbitraryZIndex(value: string): number | null {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Parse arbitrary grow/shrink value: [1.5], [2], [0.5], [.5]
|
|
65
|
+
* Returns number for valid non-negative values, null otherwise
|
|
66
|
+
*/
|
|
67
|
+
function parseArbitraryGrowShrink(value: string): number | null {
|
|
68
|
+
// Match: [1.5], [2], [0], [0.5], [.5] (non-negative decimals, optional leading digit)
|
|
69
|
+
const match = value.match(/^\[(\d+(?:\.\d+)?|\.\d+)\]$/);
|
|
70
|
+
if (match) {
|
|
71
|
+
return parseFloat(match[1]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Warn about invalid formats (negative values, unsupported formats)
|
|
75
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
76
|
+
/* v8 ignore next 5 */
|
|
77
|
+
if (process.env.NODE_ENV !== "production") {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[react-native-tailwind] Invalid arbitrary grow/shrink value: ${value}. Only non-negative numbers are supported (e.g., [1.5], [2], [0.5], [.5]).`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
// Display utilities
|
|
64
89
|
const DISPLAY_MAP: Record<string, StyleObject> = {
|
|
65
90
|
flex: { display: "flex" },
|
|
@@ -88,12 +113,17 @@ const FLEX_MAP: Record<string, StyleObject> = {
|
|
|
88
113
|
"flex-none": { flex: 0 },
|
|
89
114
|
};
|
|
90
115
|
|
|
91
|
-
// Flex grow/shrink utilities
|
|
116
|
+
// Flex grow/shrink utilities (includes CSS-style aliases)
|
|
92
117
|
const GROW_SHRINK_MAP: Record<string, StyleObject> = {
|
|
93
118
|
grow: { flexGrow: 1 },
|
|
94
119
|
"grow-0": { flexGrow: 0 },
|
|
95
120
|
shrink: { flexShrink: 1 },
|
|
96
121
|
"shrink-0": { flexShrink: 0 },
|
|
122
|
+
// CSS-style aliases
|
|
123
|
+
"flex-grow": { flexGrow: 1 },
|
|
124
|
+
"flex-grow-0": { flexGrow: 0 },
|
|
125
|
+
"flex-shrink": { flexShrink: 1 },
|
|
126
|
+
"flex-shrink-0": { flexShrink: 0 },
|
|
97
127
|
};
|
|
98
128
|
|
|
99
129
|
// Justify content utilities
|
|
@@ -357,6 +387,30 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
357
387
|
}
|
|
358
388
|
}
|
|
359
389
|
|
|
390
|
+
// Flex grow: grow-[1.5], flex-grow-[2], etc. (arbitrary values)
|
|
391
|
+
if (cls.startsWith("grow-") || cls.startsWith("flex-grow-")) {
|
|
392
|
+
const prefix = cls.startsWith("flex-grow-") ? "flex-grow-" : "grow-";
|
|
393
|
+
const growKey = cls.substring(prefix.length);
|
|
394
|
+
|
|
395
|
+
// Arbitrary values: grow-[1.5], flex-grow-[2]
|
|
396
|
+
const arbitraryGrow = parseArbitraryGrowShrink(growKey);
|
|
397
|
+
if (arbitraryGrow !== null) {
|
|
398
|
+
return { flexGrow: arbitraryGrow };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Flex shrink: shrink-[0.5], flex-shrink-[1], etc. (arbitrary values)
|
|
403
|
+
if (cls.startsWith("shrink-") || cls.startsWith("flex-shrink-")) {
|
|
404
|
+
const prefix = cls.startsWith("flex-shrink-") ? "flex-shrink-" : "shrink-";
|
|
405
|
+
const shrinkKey = cls.substring(prefix.length);
|
|
406
|
+
|
|
407
|
+
// Arbitrary values: shrink-[0.5], flex-shrink-[1]
|
|
408
|
+
const arbitraryShrink = parseArbitraryGrowShrink(shrinkKey);
|
|
409
|
+
if (arbitraryShrink !== null) {
|
|
410
|
+
return { flexShrink: arbitraryShrink };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
360
414
|
// Try each lookup table in order
|
|
361
415
|
return (
|
|
362
416
|
DISPLAY_MAP[cls] ??
|
|
@@ -238,6 +238,68 @@ describe("parseSpacing - edge cases", () => {
|
|
|
238
238
|
});
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
+
describe("parseSpacing - decimal arbitrary values", () => {
|
|
242
|
+
it("should parse margin with decimal arbitrary values", () => {
|
|
243
|
+
expect(parseSpacing("m-[4.5px]")).toEqual({ margin: 4.5 });
|
|
244
|
+
expect(parseSpacing("m-[4.5]")).toEqual({ margin: 4.5 });
|
|
245
|
+
expect(parseSpacing("m-[16.75px]")).toEqual({ margin: 16.75 });
|
|
246
|
+
expect(parseSpacing("m-[16.75]")).toEqual({ margin: 16.75 });
|
|
247
|
+
expect(parseSpacing("m-[100.25px]")).toEqual({ margin: 100.25 });
|
|
248
|
+
expect(parseSpacing("m-[0.5]")).toEqual({ margin: 0.5 });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should parse padding with decimal arbitrary values", () => {
|
|
252
|
+
expect(parseSpacing("p-[4.5px]")).toEqual({ padding: 4.5 });
|
|
253
|
+
expect(parseSpacing("p-[4.5]")).toEqual({ padding: 4.5 });
|
|
254
|
+
expect(parseSpacing("pl-[4.5px]")).toEqual({ paddingLeft: 4.5 });
|
|
255
|
+
expect(parseSpacing("pl-[4.5]")).toEqual({ paddingLeft: 4.5 });
|
|
256
|
+
expect(parseSpacing("pr-[16.75px]")).toEqual({ paddingRight: 16.75 });
|
|
257
|
+
expect(parseSpacing("pt-[10.5]")).toEqual({ paddingTop: 10.5 });
|
|
258
|
+
expect(parseSpacing("pb-[20.25px]")).toEqual({ paddingBottom: 20.25 });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should parse padding horizontal/vertical with decimal arbitrary values", () => {
|
|
262
|
+
expect(parseSpacing("px-[4.5px]")).toEqual({ paddingHorizontal: 4.5 });
|
|
263
|
+
expect(parseSpacing("py-[10.75]")).toEqual({ paddingVertical: 10.75 });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should parse gap with decimal arbitrary values", () => {
|
|
267
|
+
expect(parseSpacing("gap-[4.5px]")).toEqual({ gap: 4.5 });
|
|
268
|
+
expect(parseSpacing("gap-[4.5]")).toEqual({ gap: 4.5 });
|
|
269
|
+
expect(parseSpacing("gap-[16.75px]")).toEqual({ gap: 16.75 });
|
|
270
|
+
expect(parseSpacing("gap-[0.5]")).toEqual({ gap: 0.5 });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should parse negative margin with decimal arbitrary values", () => {
|
|
274
|
+
expect(parseSpacing("-m-[4.5px]")).toEqual({ margin: -4.5 });
|
|
275
|
+
expect(parseSpacing("-m-[4.5]")).toEqual({ margin: -4.5 });
|
|
276
|
+
expect(parseSpacing("-m-[10.5px]")).toEqual({ margin: -10.5 });
|
|
277
|
+
expect(parseSpacing("-mt-[16.75px]")).toEqual({ marginTop: -16.75 });
|
|
278
|
+
expect(parseSpacing("-ml-[8.25]")).toEqual({ marginLeft: -8.25 });
|
|
279
|
+
expect(parseSpacing("-mx-[12.5px]")).toEqual({ marginHorizontal: -12.5 });
|
|
280
|
+
expect(parseSpacing("-my-[20.75]")).toEqual({ marginVertical: -20.75 });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should parse margin directional with decimal arbitrary values", () => {
|
|
284
|
+
expect(parseSpacing("mt-[4.5px]")).toEqual({ marginTop: 4.5 });
|
|
285
|
+
expect(parseSpacing("mr-[8.25]")).toEqual({ marginRight: 8.25 });
|
|
286
|
+
expect(parseSpacing("mb-[16.75px]")).toEqual({ marginBottom: 16.75 });
|
|
287
|
+
expect(parseSpacing("ml-[12.5]")).toEqual({ marginLeft: 12.5 });
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should parse margin horizontal/vertical with decimal arbitrary values", () => {
|
|
291
|
+
expect(parseSpacing("mx-[4.5px]")).toEqual({ marginHorizontal: 4.5 });
|
|
292
|
+
expect(parseSpacing("my-[10.75]")).toEqual({ marginVertical: 10.75 });
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should handle edge case decimal values", () => {
|
|
296
|
+
expect(parseSpacing("m-[0.1px]")).toEqual({ margin: 0.1 });
|
|
297
|
+
expect(parseSpacing("p-[0.001]")).toEqual({ padding: 0.001 });
|
|
298
|
+
expect(parseSpacing("gap-[999.999px]")).toEqual({ gap: 999.999 });
|
|
299
|
+
expect(parseSpacing("-m-[0.5]")).toEqual({ margin: -0.5 });
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
241
303
|
describe("parseSpacing - comprehensive coverage", () => {
|
|
242
304
|
it("should parse all margin directions with same value", () => {
|
|
243
305
|
const value = 16;
|
package/src/parser/spacing.ts
CHANGED
|
@@ -43,14 +43,14 @@ export const SPACING_SCALE: Record<string, number> = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Parse arbitrary spacing value: [16px], [20]
|
|
47
|
-
* Returns number for px values, null for unsupported formats
|
|
46
|
+
* Parse arbitrary spacing value: [16px], [20], [4.5px], [16.75]
|
|
47
|
+
* Returns number for px values (including decimals), null for unsupported formats
|
|
48
48
|
*/
|
|
49
49
|
function parseArbitrarySpacing(value: string): number | null {
|
|
50
|
-
// Match: [16px]
|
|
51
|
-
const pxMatch = value.match(/^\[(
|
|
50
|
+
// Match: [16px], [16], [4.5px], [4.5] (pixels, including decimals)
|
|
51
|
+
const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?)(?:px)?\]$/);
|
|
52
52
|
if (pxMatch) {
|
|
53
|
-
return
|
|
53
|
+
return parseFloat(pxMatch[1]);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Warn about unsupported formats
|
|
@@ -58,7 +58,7 @@ function parseArbitrarySpacing(value: string): number | null {
|
|
|
58
58
|
/* v8 ignore next 5 */
|
|
59
59
|
if (process.env.NODE_ENV !== "production") {
|
|
60
60
|
console.warn(
|
|
61
|
-
`[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px]
|
|
61
|
+
`[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px], [16], [4.5px], [4.5]).`,
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
return null;
|
|
@@ -69,7 +69,7 @@ function parseArbitrarySpacing(value: string): number | null {
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Parse spacing classes (margin, padding, gap)
|
|
72
|
-
* Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], -m-4, -mt-[10px]
|
|
72
|
+
* Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], pl-[4.5px], -m-4, -mt-[10px]
|
|
73
73
|
*/
|
|
74
74
|
export function parseSpacing(cls: string): StyleObject | null {
|
|
75
75
|
// Margin: m-4, mx-2, mt-8, m-[16px], -m-4, -mt-2, etc.
|
|
@@ -39,6 +39,20 @@ describe("parseTypography - font size", () => {
|
|
|
39
39
|
expect(parseTypography("text-[22]")).toEqual({ fontSize: 22 });
|
|
40
40
|
expect(parseTypography("text-[100px]")).toEqual({ fontSize: 100 });
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
it("should parse font size with decimal arbitrary values", () => {
|
|
44
|
+
expect(parseTypography("text-[13.5px]")).toEqual({ fontSize: 13.5 });
|
|
45
|
+
expect(parseTypography("text-[13.5]")).toEqual({ fontSize: 13.5 });
|
|
46
|
+
expect(parseTypography("text-[18.75px]")).toEqual({ fontSize: 18.75 });
|
|
47
|
+
expect(parseTypography("text-[18.75]")).toEqual({ fontSize: 18.75 });
|
|
48
|
+
expect(parseTypography("text-[22.5]")).toEqual({ fontSize: 22.5 });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should parse font size with Tailwind shorthand decimals", () => {
|
|
52
|
+
expect(parseTypography("text-[.5]")).toEqual({ fontSize: 0.5 });
|
|
53
|
+
expect(parseTypography("text-[.75px]")).toEqual({ fontSize: 0.75 });
|
|
54
|
+
expect(parseTypography("text-[.5px]")).toEqual({ fontSize: 0.5 });
|
|
55
|
+
});
|
|
42
56
|
});
|
|
43
57
|
|
|
44
58
|
describe("parseTypography - font family", () => {
|
|
@@ -136,6 +150,20 @@ describe("parseTypography - line height", () => {
|
|
|
136
150
|
expect(parseTypography("leading-[30]")).toEqual({ lineHeight: 30 });
|
|
137
151
|
expect(parseTypography("leading-[50px]")).toEqual({ lineHeight: 50 });
|
|
138
152
|
});
|
|
153
|
+
|
|
154
|
+
it("should parse line height with decimal arbitrary values", () => {
|
|
155
|
+
expect(parseTypography("leading-[21.5px]")).toEqual({ lineHeight: 21.5 });
|
|
156
|
+
expect(parseTypography("leading-[21.5]")).toEqual({ lineHeight: 21.5 });
|
|
157
|
+
expect(parseTypography("leading-[28.75px]")).toEqual({ lineHeight: 28.75 });
|
|
158
|
+
expect(parseTypography("leading-[28.75]")).toEqual({ lineHeight: 28.75 });
|
|
159
|
+
expect(parseTypography("leading-[32.5]")).toEqual({ lineHeight: 32.5 });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should parse line height with Tailwind shorthand decimals", () => {
|
|
163
|
+
expect(parseTypography("leading-[.5]")).toEqual({ lineHeight: 0.5 });
|
|
164
|
+
expect(parseTypography("leading-[.75px]")).toEqual({ lineHeight: 0.75 });
|
|
165
|
+
expect(parseTypography("leading-[.5px]")).toEqual({ lineHeight: 0.5 });
|
|
166
|
+
});
|
|
139
167
|
});
|
|
140
168
|
|
|
141
169
|
describe("parseTypography - letter spacing", () => {
|
|
@@ -149,6 +177,80 @@ describe("parseTypography - letter spacing", () => {
|
|
|
149
177
|
expect(parseTypography("tracking-wider")).toEqual({ letterSpacing: 0.8 });
|
|
150
178
|
expect(parseTypography("tracking-widest")).toEqual({ letterSpacing: 1.6 });
|
|
151
179
|
});
|
|
180
|
+
|
|
181
|
+
it("should parse letter spacing with arbitrary values", () => {
|
|
182
|
+
expect(parseTypography("tracking-[0.5px]")).toEqual({ letterSpacing: 0.5 });
|
|
183
|
+
expect(parseTypography("tracking-[0.5]")).toEqual({ letterSpacing: 0.5 });
|
|
184
|
+
expect(parseTypography("tracking-[0.3]")).toEqual({ letterSpacing: 0.3 });
|
|
185
|
+
expect(parseTypography("tracking-[1.2px]")).toEqual({ letterSpacing: 1.2 });
|
|
186
|
+
expect(parseTypography("tracking-[2]")).toEqual({ letterSpacing: 2 });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should parse letter spacing with Tailwind shorthand decimals", () => {
|
|
190
|
+
expect(parseTypography("tracking-[.5]")).toEqual({ letterSpacing: 0.5 });
|
|
191
|
+
expect(parseTypography("tracking-[.3px]")).toEqual({ letterSpacing: 0.3 });
|
|
192
|
+
expect(parseTypography("tracking-[.75]")).toEqual({ letterSpacing: 0.75 });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should parse negative letter spacing with arbitrary values", () => {
|
|
196
|
+
expect(parseTypography("tracking-[-0.4px]")).toEqual({ letterSpacing: -0.4 });
|
|
197
|
+
expect(parseTypography("tracking-[-0.4]")).toEqual({ letterSpacing: -0.4 });
|
|
198
|
+
expect(parseTypography("tracking-[-0.5]")).toEqual({ letterSpacing: -0.5 });
|
|
199
|
+
expect(parseTypography("tracking-[-1px]")).toEqual({ letterSpacing: -1 });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should parse negative letter spacing with shorthand decimals", () => {
|
|
203
|
+
expect(parseTypography("tracking-[-.4]")).toEqual({ letterSpacing: -0.4 });
|
|
204
|
+
expect(parseTypography("tracking-[-.5px]")).toEqual({ letterSpacing: -0.5 });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should handle edge case letter spacing values", () => {
|
|
208
|
+
expect(parseTypography("tracking-[0]")).toEqual({ letterSpacing: 0 });
|
|
209
|
+
expect(parseTypography("tracking-[0.1]")).toEqual({ letterSpacing: 0.1 });
|
|
210
|
+
expect(parseTypography("tracking-[10]")).toEqual({ letterSpacing: 10 });
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("parseTypography - custom fontSize", () => {
|
|
215
|
+
const customFontSize = {
|
|
216
|
+
tiny: 10,
|
|
217
|
+
huge: 96,
|
|
218
|
+
xl: 22, // Override default (default is 20)
|
|
219
|
+
custom: 17,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
it("should support custom font sizes", () => {
|
|
223
|
+
expect(parseTypography("text-tiny", undefined, customFontSize)).toEqual({ fontSize: 10 });
|
|
224
|
+
expect(parseTypography("text-huge", undefined, customFontSize)).toEqual({ fontSize: 96 });
|
|
225
|
+
expect(parseTypography("text-custom", undefined, customFontSize)).toEqual({ fontSize: 17 });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should allow custom fontSize to override preset sizes", () => {
|
|
229
|
+
expect(parseTypography("text-xl", undefined, customFontSize)).toEqual({ fontSize: 22 });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should fallback to preset sizes when custom fontSize not found", () => {
|
|
233
|
+
expect(parseTypography("text-base", undefined, customFontSize)).toEqual({ fontSize: 16 });
|
|
234
|
+
expect(parseTypography("text-lg", undefined, customFontSize)).toEqual({ fontSize: 18 });
|
|
235
|
+
expect(parseTypography("text-2xl", undefined, customFontSize)).toEqual({ fontSize: 24 });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should prefer arbitrary values over custom fontSize", () => {
|
|
239
|
+
expect(parseTypography("text-[15px]", undefined, customFontSize)).toEqual({ fontSize: 15 });
|
|
240
|
+
expect(parseTypography("text-[13.5]", undefined, customFontSize)).toEqual({ fontSize: 13.5 });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should work with both customFontFamily and customFontSize", () => {
|
|
244
|
+
const customFontFamily = { brand: "MyCustomFont" };
|
|
245
|
+
expect(parseTypography("text-tiny", customFontFamily, customFontSize)).toEqual({ fontSize: 10 });
|
|
246
|
+
expect(parseTypography("font-brand", customFontFamily, customFontSize)).toEqual({
|
|
247
|
+
fontFamily: "MyCustomFont",
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should return null for unknown custom fontSize keys", () => {
|
|
252
|
+
expect(parseTypography("text-nonexistent", undefined, customFontSize)).toBeNull();
|
|
253
|
+
});
|
|
152
254
|
});
|
|
153
255
|
|
|
154
256
|
describe("parseTypography - edge cases", () => {
|
package/src/parser/typography.ts
CHANGED
|
@@ -113,14 +113,14 @@ const TRACKING_MAP: Record<string, StyleObject> = {
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* Parse arbitrary font size value: [18px], [20]
|
|
117
|
-
* Returns number for px values, null for unsupported formats
|
|
116
|
+
* Parse arbitrary font size value: [18px], [20], [13.5px], [.5]
|
|
117
|
+
* Returns number for px values (including decimals), null for unsupported formats
|
|
118
118
|
*/
|
|
119
119
|
function parseArbitraryFontSize(value: string): number | null {
|
|
120
|
-
// Match: [18px]
|
|
121
|
-
const pxMatch = value.match(/^\[(
|
|
120
|
+
// Match: [18px], [18], [13.5px], [13.5], [.5] (pixels, including decimals)
|
|
121
|
+
const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
|
|
122
122
|
if (pxMatch) {
|
|
123
|
-
return
|
|
123
|
+
return parseFloat(pxMatch[1]);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// Warn about unsupported formats
|
|
@@ -128,7 +128,7 @@ function parseArbitraryFontSize(value: string): number | null {
|
|
|
128
128
|
/* v8 ignore next 5 */
|
|
129
129
|
if (process.env.NODE_ENV !== "production") {
|
|
130
130
|
console.warn(
|
|
131
|
-
`[react-native-tailwind] Unsupported arbitrary font size value: ${value}. Only px values are supported (e.g., [18px]
|
|
131
|
+
`[react-native-tailwind] Unsupported arbitrary font size value: ${value}. Only px values are supported (e.g., [18px], [13.5px], [.5]).`,
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
return null;
|
|
@@ -138,14 +138,14 @@ function parseArbitraryFontSize(value: string): number | null {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
|
-
* Parse arbitrary line height value: [24px], [28]
|
|
142
|
-
* Returns number for px values, null for unsupported formats
|
|
141
|
+
* Parse arbitrary line height value: [24px], [28], [21.5px], [.5]
|
|
142
|
+
* Returns number for px values (including decimals), null for unsupported formats
|
|
143
143
|
*/
|
|
144
144
|
function parseArbitraryLineHeight(value: string): number | null {
|
|
145
|
-
// Match: [24px]
|
|
146
|
-
const pxMatch = value.match(/^\[(
|
|
145
|
+
// Match: [24px], [24], [21.5px], [21.5], [.5] (pixels, including decimals)
|
|
146
|
+
const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
|
|
147
147
|
if (pxMatch) {
|
|
148
|
-
return
|
|
148
|
+
return parseFloat(pxMatch[1]);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Warn about unsupported formats
|
|
@@ -153,7 +153,32 @@ function parseArbitraryLineHeight(value: string): number | null {
|
|
|
153
153
|
/* v8 ignore next 5 */
|
|
154
154
|
if (process.env.NODE_ENV !== "production") {
|
|
155
155
|
console.warn(
|
|
156
|
-
`[react-native-tailwind] Unsupported arbitrary line height value: ${value}. Only px values are supported (e.g., [24px]
|
|
156
|
+
`[react-native-tailwind] Unsupported arbitrary line height value: ${value}. Only px values are supported (e.g., [24px], [21.5px], [.5]).`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parse arbitrary letter spacing value: [0.5px], [0.3], [.5], [-0.4]
|
|
167
|
+
* Returns number for px values (including decimals), null for unsupported formats
|
|
168
|
+
*/
|
|
169
|
+
function parseArbitraryLetterSpacing(value: string): number | null {
|
|
170
|
+
// Match: [0.5px], [0.3], [.5], [-0.4px] (pixels, including decimals and negatives)
|
|
171
|
+
const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
|
|
172
|
+
if (pxMatch) {
|
|
173
|
+
return parseFloat(pxMatch[1]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Warn about unsupported formats
|
|
177
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
178
|
+
/* v8 ignore next 5 */
|
|
179
|
+
if (process.env.NODE_ENV !== "production") {
|
|
180
|
+
console.warn(
|
|
181
|
+
`[react-native-tailwind] Unsupported arbitrary letter spacing value: ${value}. Only px values are supported (e.g., [0.5px], [0.3], [.5], [-0.4]).`,
|
|
157
182
|
);
|
|
158
183
|
}
|
|
159
184
|
return null;
|
|
@@ -166,8 +191,13 @@ function parseArbitraryLineHeight(value: string): number | null {
|
|
|
166
191
|
* Parse typography classes
|
|
167
192
|
* @param cls - Class name to parse
|
|
168
193
|
* @param customFontFamily - Optional custom fontFamily from tailwind.config
|
|
194
|
+
* @param customFontSize - Optional custom fontSize from tailwind.config
|
|
169
195
|
*/
|
|
170
|
-
export function parseTypography(
|
|
196
|
+
export function parseTypography(
|
|
197
|
+
cls: string,
|
|
198
|
+
customFontFamily?: Record<string, string>,
|
|
199
|
+
customFontSize?: Record<string, number>,
|
|
200
|
+
): StyleObject | null {
|
|
171
201
|
// Merge custom fontFamily with defaults (custom takes precedence)
|
|
172
202
|
const fontFamilyMap = customFontFamily
|
|
173
203
|
? {
|
|
@@ -182,13 +212,18 @@ export function parseTypography(cls: string, customFontFamily?: Record<string, s
|
|
|
182
212
|
if (cls.startsWith("text-")) {
|
|
183
213
|
const sizeKey = cls.substring(5);
|
|
184
214
|
|
|
185
|
-
// Try arbitrary value first
|
|
215
|
+
// Try arbitrary value first (highest priority)
|
|
186
216
|
const arbitraryValue = parseArbitraryFontSize(sizeKey);
|
|
187
217
|
if (arbitraryValue !== null) {
|
|
188
218
|
return { fontSize: arbitraryValue };
|
|
189
219
|
}
|
|
190
220
|
|
|
191
|
-
// Try
|
|
221
|
+
// Try custom fontSize from config
|
|
222
|
+
if (customFontSize?.[sizeKey] !== undefined) {
|
|
223
|
+
return { fontSize: customFontSize[sizeKey] };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Try preset scale (fallback)
|
|
192
227
|
const fontSize = FONT_SIZES[sizeKey];
|
|
193
228
|
if (fontSize !== undefined) {
|
|
194
229
|
return { fontSize };
|
|
@@ -212,6 +247,17 @@ export function parseTypography(cls: string, customFontFamily?: Record<string, s
|
|
|
212
247
|
}
|
|
213
248
|
}
|
|
214
249
|
|
|
250
|
+
// Letter spacing: tracking-wide, tracking-[0.5px], tracking-[.3], etc.
|
|
251
|
+
if (cls.startsWith("tracking-")) {
|
|
252
|
+
const trackingKey = cls.substring(9);
|
|
253
|
+
|
|
254
|
+
// Try arbitrary value first
|
|
255
|
+
const arbitraryValue = parseArbitraryLetterSpacing(trackingKey);
|
|
256
|
+
if (arbitraryValue !== null) {
|
|
257
|
+
return { letterSpacing: arbitraryValue };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
215
261
|
// Try each lookup table in order
|
|
216
262
|
return (
|
|
217
263
|
fontFamilyMap[cls] ??
|
package/src/runtime.test.ts
CHANGED
|
@@ -317,7 +317,10 @@ describe("runtime", () => {
|
|
|
317
317
|
it("should provide raw hex values for animations", () => {
|
|
318
318
|
const result = tw`bg-blue-500 active:bg-blue-700`;
|
|
319
319
|
// Access raw backgroundColor value for use with reanimated
|
|
320
|
-
|
|
320
|
+
const style = Array.isArray(result?.style) ? result.style.find((s) => s !== false) : result?.style;
|
|
321
|
+
expect(
|
|
322
|
+
style && typeof style === "object" && "backgroundColor" in style ? style.backgroundColor : undefined,
|
|
323
|
+
).toBe("#2b7fff");
|
|
321
324
|
expect(result?.activeStyle?.backgroundColor).toBe("#1447e6");
|
|
322
325
|
});
|
|
323
326
|
|
package/src/types/runtime.ts
CHANGED
|
@@ -7,11 +7,18 @@ export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Return type for tw/twStyle functions with separate style properties for modifiers
|
|
10
|
+
* When color-scheme modifiers (dark:, light:) are present, style becomes an array with runtime conditionals
|
|
11
|
+
* When platform modifiers (ios:, android:, web:) are present, style becomes an array with Platform.select()
|
|
10
12
|
*/
|
|
11
13
|
export type TwStyle<T extends NativeStyle = NativeStyle> = {
|
|
12
|
-
style: T
|
|
14
|
+
style: T | Array<T | false>;
|
|
13
15
|
activeStyle?: T;
|
|
14
16
|
focusStyle?: T;
|
|
15
17
|
disabledStyle?: T;
|
|
16
18
|
placeholderStyle?: TextStyle;
|
|
19
|
+
lightStyle?: T;
|
|
20
|
+
darkStyle?: T;
|
|
21
|
+
iosStyle?: T;
|
|
22
|
+
androidStyle?: T;
|
|
23
|
+
webStyle?: T;
|
|
17
24
|
};
|