@mgcrea/react-native-tailwind 0.12.1 → 0.14.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.
- package/README.md +45 -2031
- package/dist/babel/index.cjs +1726 -1094
- package/dist/babel/plugin/componentScope.d.ts +26 -0
- package/dist/babel/plugin/componentScope.ts +87 -0
- package/dist/babel/plugin/state.d.ts +123 -0
- package/dist/babel/plugin/state.ts +185 -0
- package/dist/babel/plugin/visitors/className.d.ts +11 -0
- package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
- package/dist/babel/plugin/visitors/className.ts +652 -0
- package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/dist/babel/plugin/visitors/imports.d.ts +11 -0
- package/dist/babel/plugin/visitors/imports.test.ts +88 -0
- package/dist/babel/plugin/visitors/imports.ts +116 -0
- package/dist/babel/plugin/visitors/program.d.ts +15 -0
- package/dist/babel/plugin/visitors/program.test.ts +325 -0
- package/dist/babel/plugin/visitors/program.ts +116 -0
- package/dist/babel/plugin/visitors/tw.d.ts +16 -0
- package/dist/babel/plugin/visitors/tw.test.ts +771 -0
- package/dist/babel/plugin/visitors/tw.ts +148 -0
- package/dist/babel/plugin.d.ts +3 -96
- package/dist/babel/plugin.test.ts +470 -0
- package/dist/babel/plugin.ts +28 -963
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/dist/babel/utils/componentSupport.test.ts +20 -7
- package/dist/babel/utils/componentSupport.ts +2 -0
- package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
- package/dist/babel/utils/modifierProcessing.ts +21 -0
- package/dist/babel/utils/platformModifierProcessing.ts +11 -0
- package/dist/babel/utils/styleInjection.d.ts +31 -0
- package/dist/babel/utils/styleInjection.ts +253 -7
- package/dist/babel/utils/twProcessing.d.ts +2 -0
- package/dist/babel/utils/twProcessing.ts +103 -3
- package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
- package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/dist/components/TouchableOpacity.d.ts +35 -0
- package/dist/components/TouchableOpacity.js +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +1 -0
- package/dist/config/markers.d.ts +5 -0
- package/dist/config/markers.js +1 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +1 -1
- package/dist/parser/borders.d.ts +3 -1
- package/dist/parser/borders.js +1 -1
- package/dist/parser/borders.test.js +1 -1
- package/dist/parser/colors.js +1 -1
- package/dist/parser/colors.test.js +1 -1
- package/dist/parser/index.d.ts +2 -2
- 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/modifiers.d.ts +32 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/parser/modifiers.test.js +1 -1
- package/dist/parser/sizing.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.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +4 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +4 -4
- package/package.json +6 -6
- package/src/babel/plugin/componentScope.ts +87 -0
- package/src/babel/plugin/state.ts +185 -0
- package/src/babel/plugin/visitors/className.test.ts +1625 -0
- package/src/babel/plugin/visitors/className.ts +652 -0
- package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/src/babel/plugin/visitors/imports.test.ts +88 -0
- package/src/babel/plugin/visitors/imports.ts +116 -0
- package/src/babel/plugin/visitors/program.test.ts +325 -0
- package/src/babel/plugin/visitors/program.ts +116 -0
- package/src/babel/plugin/visitors/tw.test.ts +771 -0
- package/src/babel/plugin/visitors/tw.ts +148 -0
- package/src/babel/plugin.ts +28 -963
- package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/src/babel/utils/componentSupport.test.ts +20 -7
- package/src/babel/utils/componentSupport.ts +2 -0
- package/src/babel/utils/directionalModifierProcessing.ts +99 -0
- package/src/babel/utils/modifierProcessing.ts +21 -0
- package/src/babel/utils/platformModifierProcessing.ts +11 -0
- package/src/babel/utils/styleInjection.ts +253 -7
- package/src/babel/utils/twProcessing.ts +103 -3
- package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/src/components/TouchableOpacity.tsx +71 -0
- package/src/components/index.ts +3 -0
- package/src/config/markers.ts +5 -0
- package/src/index.ts +4 -5
- package/src/parser/borders.test.ts +162 -0
- package/src/parser/borders.ts +67 -9
- package/src/parser/colors.test.ts +249 -0
- package/src/parser/colors.ts +38 -0
- package/src/parser/index.ts +4 -2
- package/src/parser/layout.test.ts +74 -0
- package/src/parser/layout.ts +94 -0
- package/src/parser/modifiers.test.ts +206 -0
- package/src/parser/modifiers.ts +62 -3
- package/src/parser/sizing.ts +11 -0
- package/src/parser/spacing.test.ts +66 -0
- package/src/parser/spacing.ts +15 -5
- package/src/parser/typography.test.ts +8 -0
- package/src/parser/typography.ts +4 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing window dimensions (w-screen, h-screen)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import { RUNTIME_DIMENSIONS_MARKER } from "../../config/markers.js";
|
|
7
|
+
import type { StyleObject } from "../../types/core.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin state interface (subset needed for window dimensions processing)
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
|
+
export interface WindowDimensionsProcessingState {
|
|
14
|
+
needsWindowDimensionsImport: boolean;
|
|
15
|
+
windowDimensionsVariableName: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a style object contains runtime dimension markers
|
|
20
|
+
*
|
|
21
|
+
* @param styleObject - Style object to check
|
|
22
|
+
* @returns true if the style object contains runtime dimension markers
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* hasRuntimeDimensions({ width: "{{RUNTIME:dimensions.width}}" }) // true
|
|
26
|
+
* hasRuntimeDimensions({ width: 100 }) // false
|
|
27
|
+
*/
|
|
28
|
+
export function hasRuntimeDimensions(styleObject: StyleObject): boolean {
|
|
29
|
+
return Object.values(styleObject).some(
|
|
30
|
+
(value) => typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create an inline style object with runtime dimension access
|
|
36
|
+
*
|
|
37
|
+
* Converts runtime markers like "{{RUNTIME:dimensions.width}}" to
|
|
38
|
+
* AST nodes like: { width: _twDimensions.width }
|
|
39
|
+
*
|
|
40
|
+
* @param styleObject - Style object with runtime markers
|
|
41
|
+
* @param state - Plugin state
|
|
42
|
+
* @param t - Babel types
|
|
43
|
+
* @returns AST object expression for inline style
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* Input: { width: "{{RUNTIME:dimensions.width}}", height: "{{RUNTIME:dimensions.height}}" }
|
|
47
|
+
* Output: { width: _twDimensions.width, height: _twDimensions.height }
|
|
48
|
+
*/
|
|
49
|
+
export function createRuntimeDimensionObject(
|
|
50
|
+
styleObject: StyleObject,
|
|
51
|
+
state: WindowDimensionsProcessingState,
|
|
52
|
+
t: typeof BabelTypes,
|
|
53
|
+
): BabelTypes.ObjectExpression {
|
|
54
|
+
// Mark that we need useWindowDimensions import and hook injection
|
|
55
|
+
state.needsWindowDimensionsImport = true;
|
|
56
|
+
|
|
57
|
+
const properties: BabelTypes.ObjectProperty[] = [];
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(styleObject)) {
|
|
60
|
+
let valueNode: BabelTypes.Expression;
|
|
61
|
+
|
|
62
|
+
if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
|
|
63
|
+
// Extract property name: "{{RUNTIME:dimensions.width}}" -> "width"
|
|
64
|
+
const match = value.match(/dimensions\.(\w+)/);
|
|
65
|
+
const prop = match?.[1];
|
|
66
|
+
|
|
67
|
+
if (prop) {
|
|
68
|
+
// Generate: _twDimensions.width or _twDimensions.height
|
|
69
|
+
valueNode = t.memberExpression(t.identifier(state.windowDimensionsVariableName), t.identifier(prop));
|
|
70
|
+
} else {
|
|
71
|
+
// Fallback: shouldn't happen, but handle gracefully
|
|
72
|
+
valueNode = t.stringLiteral(value);
|
|
73
|
+
}
|
|
74
|
+
} else if (typeof value === "number") {
|
|
75
|
+
valueNode = t.numericLiteral(value);
|
|
76
|
+
} else if (typeof value === "string") {
|
|
77
|
+
valueNode = t.stringLiteral(value);
|
|
78
|
+
} else if (typeof value === "object" && value !== null) {
|
|
79
|
+
// Handle nested objects (e.g., transform arrays)
|
|
80
|
+
valueNode = t.valueToNode(value);
|
|
81
|
+
} else {
|
|
82
|
+
// Handle other types
|
|
83
|
+
valueNode = t.valueToNode(value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
properties.push(t.objectProperty(t.identifier(key), valueNode));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return t.objectExpression(properties);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Split a style object into static and runtime parts
|
|
94
|
+
*
|
|
95
|
+
* @param styleObject - Style object to split
|
|
96
|
+
* @returns Object with static and runtime style objects
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* Input: { width: "{{RUNTIME:dimensions.width}}", padding: 16, backgroundColor: "#fff" }
|
|
100
|
+
* Output: {
|
|
101
|
+
* static: { padding: 16, backgroundColor: "#fff" },
|
|
102
|
+
* runtime: { width: "{{RUNTIME:dimensions.width}}" }
|
|
103
|
+
* }
|
|
104
|
+
*/
|
|
105
|
+
export function splitStaticAndRuntimeStyles(styleObject: StyleObject): {
|
|
106
|
+
static: StyleObject;
|
|
107
|
+
runtime: StyleObject;
|
|
108
|
+
} {
|
|
109
|
+
const staticStyles: StyleObject = {};
|
|
110
|
+
const runtimeStyles: StyleObject = {};
|
|
111
|
+
|
|
112
|
+
for (const [key, value] of Object.entries(styleObject)) {
|
|
113
|
+
if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
|
|
114
|
+
runtimeStyles[key] = value;
|
|
115
|
+
} else {
|
|
116
|
+
staticStyles[key] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { static: staticStyles, runtime: runtimeStyles };
|
|
121
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced TouchableOpacity component with modifier support
|
|
3
|
+
* Adds active state support for active: modifier via onPressIn/onPressOut
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentRef } from "react";
|
|
7
|
+
import { forwardRef, useCallback, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
TouchableOpacity as RNTouchableOpacity,
|
|
10
|
+
type TouchableOpacityProps as RNTouchableOpacityProps,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
type ViewStyle,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
|
|
15
|
+
// TouchableOpacity state for style function
|
|
16
|
+
type TouchableOpacityState = { active: boolean; disabled: boolean | null | undefined };
|
|
17
|
+
|
|
18
|
+
export type TouchableOpacityProps = Omit<RNTouchableOpacityProps, "style"> & {
|
|
19
|
+
/**
|
|
20
|
+
* Style can be a static style object/array or a function that receives TouchableOpacity state
|
|
21
|
+
*/
|
|
22
|
+
style?: StyleProp<ViewStyle> | ((state: TouchableOpacityState) => StyleProp<ViewStyle>);
|
|
23
|
+
className?: string; // compile-time only
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Enhanced TouchableOpacity that supports active: and disabled: modifiers
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* <TouchableOpacity
|
|
31
|
+
* disabled={isLoading}
|
|
32
|
+
* className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400"
|
|
33
|
+
* >
|
|
34
|
+
* <Text>Submit</Text>
|
|
35
|
+
* </TouchableOpacity>
|
|
36
|
+
*/
|
|
37
|
+
export const TouchableOpacity = forwardRef<ComponentRef<typeof RNTouchableOpacity>, TouchableOpacityProps>(
|
|
38
|
+
function TouchableOpacity({ style, disabled = false, onPressIn, onPressOut, ...props }, ref) {
|
|
39
|
+
const [isActive, setIsActive] = useState(false);
|
|
40
|
+
|
|
41
|
+
const handlePressIn = useCallback(
|
|
42
|
+
(event: Parameters<NonNullable<RNTouchableOpacityProps["onPressIn"]>>[0]) => {
|
|
43
|
+
setIsActive(true);
|
|
44
|
+
onPressIn?.(event);
|
|
45
|
+
},
|
|
46
|
+
[onPressIn],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const handlePressOut = useCallback(
|
|
50
|
+
(event: Parameters<NonNullable<RNTouchableOpacityProps["onPressOut"]>>[0]) => {
|
|
51
|
+
setIsActive(false);
|
|
52
|
+
onPressOut?.(event);
|
|
53
|
+
},
|
|
54
|
+
[onPressOut],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Inject active and disabled state into style function context
|
|
58
|
+
const resolvedStyle = typeof style === "function" ? style({ active: isActive, disabled }) : style;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RNTouchableOpacity
|
|
62
|
+
ref={ref}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
style={resolvedStyle}
|
|
65
|
+
onPressIn={handlePressIn}
|
|
66
|
+
onPressOut={handlePressOut}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ export { generateStyleKey } from "./utils/styleKey";
|
|
|
15
15
|
export type { StyleObject } from "./types/core";
|
|
16
16
|
export type { NativeStyle, TwStyle } from "./types/runtime";
|
|
17
17
|
|
|
18
|
+
// Re-export colors
|
|
19
|
+
export { TAILWIND_COLORS } from "./config/tailwind";
|
|
20
|
+
|
|
18
21
|
// Re-export individual parsers for advanced usage
|
|
19
22
|
export {
|
|
20
23
|
parseAspectRatio,
|
|
@@ -39,8 +42,4 @@ export { SPACING_SCALE } from "./parser/spacing";
|
|
|
39
42
|
export { FONT_SIZES, LETTER_SPACING_SCALE } from "./parser/typography";
|
|
40
43
|
|
|
41
44
|
// Re-export enhanced components with modifier support
|
|
42
|
-
export
|
|
43
|
-
export type { PressableProps } from "./components/Pressable";
|
|
44
|
-
export { TextInput } from "./components/TextInput";
|
|
45
|
-
export type { TextInputProps } from "./components/TextInput";
|
|
46
|
-
export { TAILWIND_COLORS } from "./config/tailwind";
|
|
45
|
+
export * from "./components";
|
|
@@ -327,3 +327,165 @@ describe("parseBorder - comprehensive coverage", () => {
|
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
329
|
});
|
|
330
|
+
|
|
331
|
+
describe("parseBorder - color pattern detection", () => {
|
|
332
|
+
it("should return null for directional border colors with preset values", () => {
|
|
333
|
+
// These should be handled by parseColor
|
|
334
|
+
expect(parseBorder("border-t-red-500")).toBeNull();
|
|
335
|
+
expect(parseBorder("border-r-blue-500")).toBeNull();
|
|
336
|
+
expect(parseBorder("border-b-green-500")).toBeNull();
|
|
337
|
+
expect(parseBorder("border-l-yellow-500")).toBeNull();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should return null for directional border colors with basic values", () => {
|
|
341
|
+
// These should be handled by parseColor
|
|
342
|
+
expect(parseBorder("border-t-white")).toBeNull();
|
|
343
|
+
expect(parseBorder("border-r-black")).toBeNull();
|
|
344
|
+
expect(parseBorder("border-b-transparent")).toBeNull();
|
|
345
|
+
expect(parseBorder("border-l-white")).toBeNull();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("should return null for directional border colors with arbitrary hex values", () => {
|
|
349
|
+
// These should be handled by parseColor
|
|
350
|
+
expect(parseBorder("border-t-[#ff0000]")).toBeNull();
|
|
351
|
+
expect(parseBorder("border-r-[#3B82F6]")).toBeNull();
|
|
352
|
+
expect(parseBorder("border-b-[#abc]")).toBeNull();
|
|
353
|
+
expect(parseBorder("border-l-[#00FF00AA]")).toBeNull();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should return null for directional border colors with opacity", () => {
|
|
357
|
+
// These should be handled by parseColor
|
|
358
|
+
expect(parseBorder("border-t-red-500/50")).toBeNull();
|
|
359
|
+
expect(parseBorder("border-r-blue-500/80")).toBeNull();
|
|
360
|
+
expect(parseBorder("border-b-[#ff0000]/60")).toBeNull();
|
|
361
|
+
expect(parseBorder("border-l-black/25")).toBeNull();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should return null for directional border colors with custom colors", () => {
|
|
365
|
+
// These should be handled by parseColor (assuming brand-primary is a custom color)
|
|
366
|
+
expect(parseBorder("border-t-brand-primary")).toBeNull();
|
|
367
|
+
expect(parseBorder("border-r-accent")).toBeNull();
|
|
368
|
+
expect(parseBorder("border-b-brand-secondary")).toBeNull();
|
|
369
|
+
expect(parseBorder("border-l-custom")).toBeNull();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should still handle directional border widths correctly", () => {
|
|
373
|
+
// These should NOT be detected as color patterns
|
|
374
|
+
expect(parseBorder("border-t-2")).toEqual({ borderTopWidth: 2 });
|
|
375
|
+
expect(parseBorder("border-r-4")).toEqual({ borderRightWidth: 4 });
|
|
376
|
+
expect(parseBorder("border-b-8")).toEqual({ borderBottomWidth: 8 });
|
|
377
|
+
expect(parseBorder("border-l-0")).toEqual({ borderLeftWidth: 0 });
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("should still handle directional border width arbitrary values", () => {
|
|
381
|
+
// These should NOT be detected as color patterns
|
|
382
|
+
expect(parseBorder("border-t-[3px]")).toEqual({ borderTopWidth: 3 });
|
|
383
|
+
expect(parseBorder("border-r-[5px]")).toEqual({ borderRightWidth: 5 });
|
|
384
|
+
expect(parseBorder("border-b-[10]")).toEqual({ borderBottomWidth: 10 });
|
|
385
|
+
expect(parseBorder("border-l-[8px]")).toEqual({ borderLeftWidth: 8 });
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("parseBorder - logical border width (RTL-aware)", () => {
|
|
390
|
+
it("should parse border start width", () => {
|
|
391
|
+
expect(parseBorder("border-s")).toEqual({ borderStartWidth: 1 });
|
|
392
|
+
expect(parseBorder("border-s-0")).toEqual({ borderStartWidth: 0 });
|
|
393
|
+
expect(parseBorder("border-s-2")).toEqual({ borderStartWidth: 2 });
|
|
394
|
+
expect(parseBorder("border-s-4")).toEqual({ borderStartWidth: 4 });
|
|
395
|
+
expect(parseBorder("border-s-8")).toEqual({ borderStartWidth: 8 });
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should parse border end width", () => {
|
|
399
|
+
expect(parseBorder("border-e")).toEqual({ borderEndWidth: 1 });
|
|
400
|
+
expect(parseBorder("border-e-0")).toEqual({ borderEndWidth: 0 });
|
|
401
|
+
expect(parseBorder("border-e-2")).toEqual({ borderEndWidth: 2 });
|
|
402
|
+
expect(parseBorder("border-e-4")).toEqual({ borderEndWidth: 4 });
|
|
403
|
+
expect(parseBorder("border-e-8")).toEqual({ borderEndWidth: 8 });
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should parse border start/end with arbitrary values", () => {
|
|
407
|
+
expect(parseBorder("border-s-[3px]")).toEqual({ borderStartWidth: 3 });
|
|
408
|
+
expect(parseBorder("border-s-[5]")).toEqual({ borderStartWidth: 5 });
|
|
409
|
+
expect(parseBorder("border-e-[3px]")).toEqual({ borderEndWidth: 3 });
|
|
410
|
+
expect(parseBorder("border-e-[5]")).toEqual({ borderEndWidth: 5 });
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("parseBorder - logical border radius sides (RTL-aware)", () => {
|
|
415
|
+
it("should parse rounded start (both top and bottom start corners)", () => {
|
|
416
|
+
expect(parseBorder("rounded-s")).toEqual({
|
|
417
|
+
borderTopStartRadius: 4,
|
|
418
|
+
borderBottomStartRadius: 4,
|
|
419
|
+
});
|
|
420
|
+
expect(parseBorder("rounded-s-lg")).toEqual({
|
|
421
|
+
borderTopStartRadius: 8,
|
|
422
|
+
borderBottomStartRadius: 8,
|
|
423
|
+
});
|
|
424
|
+
expect(parseBorder("rounded-s-[12px]")).toEqual({
|
|
425
|
+
borderTopStartRadius: 12,
|
|
426
|
+
borderBottomStartRadius: 12,
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("should parse rounded end (both top and bottom end corners)", () => {
|
|
431
|
+
expect(parseBorder("rounded-e")).toEqual({
|
|
432
|
+
borderTopEndRadius: 4,
|
|
433
|
+
borderBottomEndRadius: 4,
|
|
434
|
+
});
|
|
435
|
+
expect(parseBorder("rounded-e-lg")).toEqual({
|
|
436
|
+
borderTopEndRadius: 8,
|
|
437
|
+
borderBottomEndRadius: 8,
|
|
438
|
+
});
|
|
439
|
+
expect(parseBorder("rounded-e-[12px]")).toEqual({
|
|
440
|
+
borderTopEndRadius: 12,
|
|
441
|
+
borderBottomEndRadius: 12,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe("parseBorder - logical border radius corners (RTL-aware)", () => {
|
|
447
|
+
it("should parse rounded start-start (top-start corner)", () => {
|
|
448
|
+
expect(parseBorder("rounded-ss")).toEqual({ borderTopStartRadius: 4 });
|
|
449
|
+
expect(parseBorder("rounded-ss-lg")).toEqual({ borderTopStartRadius: 8 });
|
|
450
|
+
expect(parseBorder("rounded-ss-[12px]")).toEqual({
|
|
451
|
+
borderTopStartRadius: 12,
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("should parse rounded start-end (top-end corner)", () => {
|
|
456
|
+
expect(parseBorder("rounded-se")).toEqual({ borderTopEndRadius: 4 });
|
|
457
|
+
expect(parseBorder("rounded-se-lg")).toEqual({ borderTopEndRadius: 8 });
|
|
458
|
+
expect(parseBorder("rounded-se-[12px]")).toEqual({
|
|
459
|
+
borderTopEndRadius: 12,
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("should parse rounded end-start (bottom-start corner)", () => {
|
|
464
|
+
expect(parseBorder("rounded-es")).toEqual({ borderBottomStartRadius: 4 });
|
|
465
|
+
expect(parseBorder("rounded-es-lg")).toEqual({ borderBottomStartRadius: 8 });
|
|
466
|
+
expect(parseBorder("rounded-es-[12px]")).toEqual({
|
|
467
|
+
borderBottomStartRadius: 12,
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("should parse rounded end-end (bottom-end corner)", () => {
|
|
472
|
+
expect(parseBorder("rounded-ee")).toEqual({ borderBottomEndRadius: 4 });
|
|
473
|
+
expect(parseBorder("rounded-ee-lg")).toEqual({ borderBottomEndRadius: 8 });
|
|
474
|
+
expect(parseBorder("rounded-ee-[12px]")).toEqual({
|
|
475
|
+
borderBottomEndRadius: 12,
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("should parse all logical corners with different sizes", () => {
|
|
480
|
+
// Using full scale to verify all sizes work
|
|
481
|
+
expect(parseBorder("rounded-ss-none")).toEqual({ borderTopStartRadius: 0 });
|
|
482
|
+
expect(parseBorder("rounded-se-sm")).toEqual({ borderTopEndRadius: 2 });
|
|
483
|
+
expect(parseBorder("rounded-es-md")).toEqual({ borderBottomStartRadius: 6 });
|
|
484
|
+
expect(parseBorder("rounded-ee-xl")).toEqual({ borderBottomEndRadius: 12 });
|
|
485
|
+
expect(parseBorder("rounded-ss-2xl")).toEqual({ borderTopStartRadius: 16 });
|
|
486
|
+
expect(parseBorder("rounded-se-3xl")).toEqual({ borderTopEndRadius: 24 });
|
|
487
|
+
expect(parseBorder("rounded-es-full")).toEqual({
|
|
488
|
+
borderBottomStartRadius: 9999,
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
package/src/parser/borders.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { StyleObject } from "../types";
|
|
6
|
+
import { parseColor } from "./colors";
|
|
6
7
|
|
|
7
8
|
// Border width scale
|
|
8
9
|
export const BORDER_WIDTH_SCALE: Record<string, number> = {
|
|
@@ -34,10 +35,12 @@ const BORDER_WIDTH_PROP_MAP: Record<string, string> = {
|
|
|
34
35
|
r: "borderRightWidth",
|
|
35
36
|
b: "borderBottomWidth",
|
|
36
37
|
l: "borderLeftWidth",
|
|
38
|
+
s: "borderStartWidth",
|
|
39
|
+
e: "borderEndWidth",
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
|
-
* Property mapping for border radius corners
|
|
43
|
+
* Property mapping for border radius corners (physical)
|
|
41
44
|
*/
|
|
42
45
|
const BORDER_RADIUS_CORNER_MAP: Record<string, string> = {
|
|
43
46
|
tl: "borderTopLeftRadius",
|
|
@@ -46,6 +49,18 @@ const BORDER_RADIUS_CORNER_MAP: Record<string, string> = {
|
|
|
46
49
|
br: "borderBottomRightRadius",
|
|
47
50
|
};
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Property mapping for border radius corners (logical/RTL-aware)
|
|
54
|
+
* ss = start-start (top-start), se = start-end (top-end)
|
|
55
|
+
* es = end-start (bottom-start), ee = end-end (bottom-end)
|
|
56
|
+
*/
|
|
57
|
+
const BORDER_RADIUS_LOGICAL_CORNER_MAP: Record<string, string> = {
|
|
58
|
+
ss: "borderTopStartRadius",
|
|
59
|
+
se: "borderTopEndRadius",
|
|
60
|
+
es: "borderBottomStartRadius",
|
|
61
|
+
ee: "borderBottomEndRadius",
|
|
62
|
+
};
|
|
63
|
+
|
|
49
64
|
/**
|
|
50
65
|
* Property mapping for border radius sides (returns array of properties)
|
|
51
66
|
*/
|
|
@@ -54,6 +69,8 @@ const BORDER_RADIUS_SIDE_MAP: Record<string, string[]> = {
|
|
|
54
69
|
r: ["borderTopRightRadius", "borderBottomRightRadius"],
|
|
55
70
|
b: ["borderBottomLeftRadius", "borderBottomRightRadius"],
|
|
56
71
|
l: ["borderTopLeftRadius", "borderBottomLeftRadius"],
|
|
72
|
+
s: ["borderTopStartRadius", "borderBottomStartRadius"],
|
|
73
|
+
e: ["borderTopEndRadius", "borderBottomEndRadius"],
|
|
57
74
|
};
|
|
58
75
|
|
|
59
76
|
/**
|
|
@@ -108,8 +125,10 @@ function parseArbitraryBorderRadius(value: string): number | null {
|
|
|
108
125
|
|
|
109
126
|
/**
|
|
110
127
|
* Parse border classes
|
|
128
|
+
* @param cls - The class name to parse
|
|
129
|
+
* @param customColors - Optional custom colors from tailwind.config (used to detect color patterns)
|
|
111
130
|
*/
|
|
112
|
-
export function parseBorder(cls: string): StyleObject | null {
|
|
131
|
+
export function parseBorder(cls: string, customColors?: Record<string, string>): StyleObject | null {
|
|
113
132
|
// Border style (must come before parseBorderWidth)
|
|
114
133
|
if (cls === "border-solid") return { borderStyle: "solid" };
|
|
115
134
|
if (cls === "border-dotted") return { borderStyle: "dotted" };
|
|
@@ -117,7 +136,7 @@ export function parseBorder(cls: string): StyleObject | null {
|
|
|
117
136
|
|
|
118
137
|
// Border width (border-0, border-t, border-[8px], etc.)
|
|
119
138
|
if (cls.startsWith("border-")) {
|
|
120
|
-
return parseBorderWidth(cls);
|
|
139
|
+
return parseBorderWidth(cls, customColors);
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
if (cls === "border") {
|
|
@@ -134,14 +153,27 @@ export function parseBorder(cls: string): StyleObject | null {
|
|
|
134
153
|
|
|
135
154
|
/**
|
|
136
155
|
* Parse border width classes
|
|
156
|
+
* @param cls - The class name to parse
|
|
157
|
+
* @param customColors - Optional custom colors (passed to parseColor for pattern detection)
|
|
137
158
|
*/
|
|
138
|
-
function parseBorderWidth(cls: string): StyleObject | null {
|
|
139
|
-
// Directional borders: border-t, border-t-2, border-t-[8px]
|
|
140
|
-
|
|
159
|
+
function parseBorderWidth(cls: string, customColors?: Record<string, string>): StyleObject | null {
|
|
160
|
+
// Directional borders: border-t, border-t-2, border-t-[8px], border-s, border-e (RTL-aware)
|
|
161
|
+
// Note: border-x and border-y are handled by parseColor for colors only
|
|
162
|
+
const dirMatch = cls.match(/^border-([trblse])(?:-(.+))?$/);
|
|
141
163
|
if (dirMatch) {
|
|
142
164
|
const dir = dirMatch[1];
|
|
143
165
|
const valueStr = dirMatch[2] || ""; // empty string for border-t
|
|
144
166
|
|
|
167
|
+
// If it's a color pattern, let parseColor handle it
|
|
168
|
+
// Try to parse as color - if it succeeds, return null (let parseColor handle it)
|
|
169
|
+
// Note: We skip color check for s/e since React Native doesn't support borderStartColor/borderEndColor
|
|
170
|
+
if (valueStr && dir !== "s" && dir !== "e") {
|
|
171
|
+
const colorResult = parseColor(cls, customColors);
|
|
172
|
+
if (colorResult !== null) {
|
|
173
|
+
return null; // It's a color, let parseColor handle it
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
145
177
|
// Try arbitrary value first (if it starts with [)
|
|
146
178
|
if (valueStr.startsWith("[")) {
|
|
147
179
|
const arbitraryValue = parseArbitraryBorderWidth(valueStr);
|
|
@@ -205,7 +237,7 @@ function parseBorderRadius(cls: string): StyleObject | null {
|
|
|
205
237
|
return null;
|
|
206
238
|
}
|
|
207
239
|
|
|
208
|
-
// Specific corners: rounded-tl, rounded-tl-lg, rounded-tl-[8px]
|
|
240
|
+
// Specific physical corners: rounded-tl, rounded-tl-lg, rounded-tl-[8px]
|
|
209
241
|
const cornerMatch = rest.match(/^(tl|tr|bl|br)(?:-(.+))?$/);
|
|
210
242
|
if (cornerMatch) {
|
|
211
243
|
const corner = cornerMatch[1];
|
|
@@ -229,8 +261,34 @@ function parseBorderRadius(cls: string): StyleObject | null {
|
|
|
229
261
|
return null;
|
|
230
262
|
}
|
|
231
263
|
|
|
232
|
-
//
|
|
233
|
-
|
|
264
|
+
// Logical corners (RTL-aware): rounded-ss, rounded-se, rounded-es, rounded-ee
|
|
265
|
+
// ss = start-start (top-start), se = start-end (top-end)
|
|
266
|
+
// es = end-start (bottom-start), ee = end-end (bottom-end)
|
|
267
|
+
const logicalCornerMatch = rest.match(/^(ss|se|es|ee)(?:-(.+))?$/);
|
|
268
|
+
if (logicalCornerMatch) {
|
|
269
|
+
const corner = logicalCornerMatch[1];
|
|
270
|
+
const valueStr = logicalCornerMatch[2] || ""; // empty string for rounded-ss
|
|
271
|
+
|
|
272
|
+
// Try arbitrary value first
|
|
273
|
+
if (valueStr.startsWith("[")) {
|
|
274
|
+
const arbitraryValue = parseArbitraryBorderRadius(valueStr);
|
|
275
|
+
if (arbitraryValue !== null) {
|
|
276
|
+
return { [BORDER_RADIUS_LOGICAL_CORNER_MAP[corner]]: arbitraryValue };
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Try preset scale
|
|
282
|
+
const scaleValue = BORDER_RADIUS_SCALE[valueStr];
|
|
283
|
+
if (scaleValue !== undefined) {
|
|
284
|
+
return { [BORDER_RADIUS_LOGICAL_CORNER_MAP[corner]]: scaleValue };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Sides: rounded-t, rounded-t-lg, rounded-t-[8px], rounded-s, rounded-e (RTL-aware)
|
|
291
|
+
const sideMatch = rest.match(/^([trblse])(?:-(.+))?$/);
|
|
234
292
|
if (sideMatch) {
|
|
235
293
|
const side = sideMatch[1];
|
|
236
294
|
const valueStr = sideMatch[2] || ""; // empty string for rounded-t
|