@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.
Files changed (104) hide show
  1. package/README.md +45 -2031
  2. package/dist/babel/index.cjs +1726 -1094
  3. package/dist/babel/plugin/componentScope.d.ts +26 -0
  4. package/dist/babel/plugin/componentScope.ts +87 -0
  5. package/dist/babel/plugin/state.d.ts +123 -0
  6. package/dist/babel/plugin/state.ts +185 -0
  7. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  8. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
  9. package/dist/babel/plugin/visitors/className.ts +652 -0
  10. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  11. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  12. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  13. package/dist/babel/plugin/visitors/imports.ts +116 -0
  14. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  15. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  16. package/dist/babel/plugin/visitors/program.ts +116 -0
  17. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  18. package/dist/babel/plugin/visitors/tw.test.ts +771 -0
  19. package/dist/babel/plugin/visitors/tw.ts +148 -0
  20. package/dist/babel/plugin.d.ts +3 -96
  21. package/dist/babel/plugin.test.ts +470 -0
  22. package/dist/babel/plugin.ts +28 -963
  23. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  24. package/dist/babel/utils/componentSupport.test.ts +20 -7
  25. package/dist/babel/utils/componentSupport.ts +2 -0
  26. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  27. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  28. package/dist/babel/utils/modifierProcessing.ts +21 -0
  29. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  30. package/dist/babel/utils/styleInjection.d.ts +31 -0
  31. package/dist/babel/utils/styleInjection.ts +253 -7
  32. package/dist/babel/utils/twProcessing.d.ts +2 -0
  33. package/dist/babel/utils/twProcessing.ts +103 -3
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +2 -2
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/modifiers.d.ts +32 -2
  54. package/dist/parser/modifiers.js +1 -1
  55. package/dist/parser/modifiers.test.js +1 -1
  56. package/dist/parser/sizing.js +1 -1
  57. package/dist/parser/spacing.d.ts +1 -1
  58. package/dist/parser/spacing.js +1 -1
  59. package/dist/parser/spacing.test.js +1 -1
  60. package/dist/parser/typography.test.js +1 -1
  61. package/dist/runtime.cjs +1 -1
  62. package/dist/runtime.cjs.map +4 -4
  63. package/dist/runtime.js +1 -1
  64. package/dist/runtime.js.map +4 -4
  65. package/package.json +6 -6
  66. package/src/babel/plugin/componentScope.ts +87 -0
  67. package/src/babel/plugin/state.ts +185 -0
  68. package/src/babel/plugin/visitors/className.test.ts +1625 -0
  69. package/src/babel/plugin/visitors/className.ts +652 -0
  70. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  71. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  72. package/src/babel/plugin/visitors/imports.ts +116 -0
  73. package/src/babel/plugin/visitors/program.test.ts +325 -0
  74. package/src/babel/plugin/visitors/program.ts +116 -0
  75. package/src/babel/plugin/visitors/tw.test.ts +771 -0
  76. package/src/babel/plugin/visitors/tw.ts +148 -0
  77. package/src/babel/plugin.ts +28 -963
  78. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  79. package/src/babel/utils/componentSupport.test.ts +20 -7
  80. package/src/babel/utils/componentSupport.ts +2 -0
  81. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  82. package/src/babel/utils/modifierProcessing.ts +21 -0
  83. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  84. package/src/babel/utils/styleInjection.ts +253 -7
  85. package/src/babel/utils/twProcessing.ts +103 -3
  86. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  87. package/src/components/TouchableOpacity.tsx +71 -0
  88. package/src/components/index.ts +3 -0
  89. package/src/config/markers.ts +5 -0
  90. package/src/index.ts +4 -5
  91. package/src/parser/borders.test.ts +162 -0
  92. package/src/parser/borders.ts +67 -9
  93. package/src/parser/colors.test.ts +249 -0
  94. package/src/parser/colors.ts +38 -0
  95. package/src/parser/index.ts +4 -2
  96. package/src/parser/layout.test.ts +74 -0
  97. package/src/parser/layout.ts +94 -0
  98. package/src/parser/modifiers.test.ts +206 -0
  99. package/src/parser/modifiers.ts +62 -3
  100. package/src/parser/sizing.ts +11 -0
  101. package/src/parser/spacing.test.ts +66 -0
  102. package/src/parser/spacing.ts +15 -5
  103. package/src/parser/typography.test.ts +8 -0
  104. package/src/parser/typography.ts +4 -0
@@ -391,3 +391,252 @@ describe("parseColor - opacity modifiers", () => {
391
391
  });
392
392
  });
393
393
  });
394
+
395
+ describe("parseColor - directional border colors", () => {
396
+ it("should parse directional border colors with preset values", () => {
397
+ expect(parseColor("border-t-red-500")).toEqual({ borderTopColor: COLORS["red-500"] });
398
+ expect(parseColor("border-r-blue-500")).toEqual({ borderRightColor: COLORS["blue-500"] });
399
+ expect(parseColor("border-b-green-500")).toEqual({ borderBottomColor: COLORS["green-500"] });
400
+ expect(parseColor("border-l-yellow-500")).toEqual({ borderLeftColor: COLORS["yellow-500"] });
401
+ });
402
+
403
+ it("should parse directional border colors with basic values", () => {
404
+ expect(parseColor("border-t-white")).toEqual({ borderTopColor: "#FFFFFF" });
405
+ expect(parseColor("border-r-black")).toEqual({ borderRightColor: "#000000" });
406
+ expect(parseColor("border-b-transparent")).toEqual({ borderBottomColor: "transparent" });
407
+ expect(parseColor("border-l-white")).toEqual({ borderLeftColor: "#FFFFFF" });
408
+ });
409
+
410
+ it("should parse directional border colors with arbitrary 6-digit hex values", () => {
411
+ expect(parseColor("border-t-[#ff0000]")).toEqual({ borderTopColor: "#ff0000" });
412
+ expect(parseColor("border-r-[#3B82F6]")).toEqual({ borderRightColor: "#3B82F6" });
413
+ expect(parseColor("border-b-[#00ff00]")).toEqual({ borderBottomColor: "#00ff00" });
414
+ expect(parseColor("border-l-[#cccccc]")).toEqual({ borderLeftColor: "#cccccc" });
415
+ });
416
+
417
+ it("should parse directional border colors with arbitrary 3-digit hex values", () => {
418
+ expect(parseColor("border-t-[#f00]")).toEqual({ borderTopColor: "#ff0000" });
419
+ expect(parseColor("border-r-[#abc]")).toEqual({ borderRightColor: "#aabbcc" });
420
+ expect(parseColor("border-b-[#123]")).toEqual({ borderBottomColor: "#112233" });
421
+ expect(parseColor("border-l-[#999]")).toEqual({ borderLeftColor: "#999999" });
422
+ });
423
+
424
+ it("should parse directional border colors with arbitrary 8-digit hex values (with alpha)", () => {
425
+ expect(parseColor("border-t-[#ff0000aa]")).toEqual({ borderTopColor: "#ff0000aa" });
426
+ expect(parseColor("border-r-[#0000FF50]")).toEqual({ borderRightColor: "#0000FF50" });
427
+ expect(parseColor("border-b-[#00FF0080]")).toEqual({ borderBottomColor: "#00FF0080" });
428
+ expect(parseColor("border-l-[#FFFFFF00]")).toEqual({ borderLeftColor: "#FFFFFF00" });
429
+ });
430
+
431
+ it("should parse directional border colors with opacity modifiers", () => {
432
+ expect(parseColor("border-t-red-500/50")).toEqual({
433
+ borderTopColor: applyOpacity(COLORS["red-500"], 50),
434
+ });
435
+ expect(parseColor("border-r-blue-500/80")).toEqual({
436
+ borderRightColor: applyOpacity(COLORS["blue-500"], 80),
437
+ });
438
+ expect(parseColor("border-b-green-500/30")).toEqual({
439
+ borderBottomColor: applyOpacity(COLORS["green-500"], 30),
440
+ });
441
+ expect(parseColor("border-l-black/25")).toEqual({
442
+ borderLeftColor: applyOpacity(COLORS.black, 25),
443
+ });
444
+ });
445
+
446
+ it("should parse directional border colors with arbitrary hex and opacity", () => {
447
+ expect(parseColor("border-t-[#ff0000]/50")).toEqual({ borderTopColor: "#FF000080" });
448
+ expect(parseColor("border-r-[#3B82F6]/80")).toEqual({ borderRightColor: "#3B82F6CC" });
449
+ expect(parseColor("border-b-[#abc]/60")).toEqual({ borderBottomColor: "#AABBCC99" });
450
+ expect(parseColor("border-l-[#000]/100")).toEqual({ borderLeftColor: "#000000FF" });
451
+ });
452
+
453
+ it("should parse directional border colors with custom colors", () => {
454
+ const customColors = {
455
+ "brand-primary": "#FF6B6B",
456
+ "brand-secondary": "#4ECDC4",
457
+ accent: "#FFE66D",
458
+ };
459
+
460
+ expect(parseColor("border-t-brand-primary", customColors)).toEqual({
461
+ borderTopColor: "#FF6B6B",
462
+ });
463
+ expect(parseColor("border-r-brand-secondary", customColors)).toEqual({
464
+ borderRightColor: "#4ECDC4",
465
+ });
466
+ expect(parseColor("border-b-accent", customColors)).toEqual({ borderBottomColor: "#FFE66D" });
467
+ expect(parseColor("border-l-brand-primary", customColors)).toEqual({
468
+ borderLeftColor: "#FF6B6B",
469
+ });
470
+ });
471
+
472
+ it("should parse directional border colors with custom colors and opacity", () => {
473
+ const customColors = { "brand-primary": "#FF6B6B" };
474
+ expect(parseColor("border-t-brand-primary/50", customColors)).toEqual({
475
+ borderTopColor: "#FF6B6B80",
476
+ });
477
+ expect(parseColor("border-l-brand-primary/75", customColors)).toEqual({
478
+ borderLeftColor: "#FF6B6BBF",
479
+ });
480
+ });
481
+
482
+ it("should not match border width classes", () => {
483
+ // These should be handled by parseBorder
484
+ expect(parseColor("border-t-2")).toBeNull();
485
+ expect(parseColor("border-r-4")).toBeNull();
486
+ expect(parseColor("border-b-8")).toBeNull();
487
+ expect(parseColor("border-l-0")).toBeNull();
488
+ });
489
+
490
+ it("should not match border width arbitrary values", () => {
491
+ // These should be handled by parseBorder
492
+ expect(parseColor("border-t-[3px]")).toBeNull();
493
+ expect(parseColor("border-r-[8px]")).toBeNull();
494
+ expect(parseColor("border-b-[5]")).toBeNull();
495
+ expect(parseColor("border-l-[10px]")).toBeNull();
496
+ });
497
+
498
+ it("should return null for invalid directional border color values", () => {
499
+ expect(parseColor("border-t-invalid")).toBeNull();
500
+ expect(parseColor("border-r-notacolor")).toBeNull();
501
+ expect(parseColor("border-b-xyz")).toBeNull();
502
+ expect(parseColor("border-l-wrongcolor")).toBeNull();
503
+ });
504
+
505
+ it("should handle all directions with same color", () => {
506
+ const color = COLORS["blue-500"];
507
+ expect(parseColor("border-t-blue-500")).toEqual({ borderTopColor: color });
508
+ expect(parseColor("border-r-blue-500")).toEqual({ borderRightColor: color });
509
+ expect(parseColor("border-b-blue-500")).toEqual({ borderBottomColor: color });
510
+ expect(parseColor("border-l-blue-500")).toEqual({ borderLeftColor: color });
511
+ });
512
+
513
+ it("should handle all color families for directional borders", () => {
514
+ const families = ["gray", "red", "blue", "green", "yellow", "purple", "pink", "orange", "indigo"];
515
+ families.forEach((family) => {
516
+ expect(parseColor(`border-t-${family}-500`)).toBeTruthy();
517
+ expect(parseColor(`border-r-${family}-500`)).toBeTruthy();
518
+ expect(parseColor(`border-b-${family}-500`)).toBeTruthy();
519
+ expect(parseColor(`border-l-${family}-500`)).toBeTruthy();
520
+ });
521
+ });
522
+
523
+ it("should handle directional borders with all opacity levels", () => {
524
+ expect(parseColor("border-t-black/0")).toEqual({ borderTopColor: "#00000000" });
525
+ expect(parseColor("border-t-black/25")).toEqual({ borderTopColor: "#00000040" });
526
+ expect(parseColor("border-t-black/50")).toEqual({ borderTopColor: "#00000080" });
527
+ expect(parseColor("border-t-black/75")).toEqual({ borderTopColor: "#000000BF" });
528
+ expect(parseColor("border-t-black/100")).toEqual({ borderTopColor: "#000000FF" });
529
+ });
530
+ });
531
+
532
+ describe("parseColor - border-x and border-y colors", () => {
533
+ it("should parse border-x colors (horizontal: left + right)", () => {
534
+ expect(parseColor("border-x-red-500")).toEqual({
535
+ borderLeftColor: COLORS["red-500"],
536
+ borderRightColor: COLORS["red-500"],
537
+ });
538
+ expect(parseColor("border-x-blue-500")).toEqual({
539
+ borderLeftColor: COLORS["blue-500"],
540
+ borderRightColor: COLORS["blue-500"],
541
+ });
542
+ });
543
+
544
+ it("should parse border-y colors (vertical: top + bottom)", () => {
545
+ expect(parseColor("border-y-green-500")).toEqual({
546
+ borderTopColor: COLORS["green-500"],
547
+ borderBottomColor: COLORS["green-500"],
548
+ });
549
+ expect(parseColor("border-y-yellow-500")).toEqual({
550
+ borderTopColor: COLORS["yellow-500"],
551
+ borderBottomColor: COLORS["yellow-500"],
552
+ });
553
+ });
554
+
555
+ it("should parse border-x with basic colors", () => {
556
+ expect(parseColor("border-x-white")).toEqual({
557
+ borderLeftColor: "#FFFFFF",
558
+ borderRightColor: "#FFFFFF",
559
+ });
560
+ expect(parseColor("border-x-black")).toEqual({
561
+ borderLeftColor: "#000000",
562
+ borderRightColor: "#000000",
563
+ });
564
+ });
565
+
566
+ it("should parse border-y with basic colors", () => {
567
+ expect(parseColor("border-y-white")).toEqual({
568
+ borderTopColor: "#FFFFFF",
569
+ borderBottomColor: "#FFFFFF",
570
+ });
571
+ expect(parseColor("border-y-transparent")).toEqual({
572
+ borderTopColor: "transparent",
573
+ borderBottomColor: "transparent",
574
+ });
575
+ });
576
+
577
+ it("should parse border-x with opacity", () => {
578
+ expect(parseColor("border-x-red-500/50")).toEqual({
579
+ borderLeftColor: applyOpacity(COLORS["red-500"], 50),
580
+ borderRightColor: applyOpacity(COLORS["red-500"], 50),
581
+ });
582
+ });
583
+
584
+ it("should parse border-y with opacity", () => {
585
+ expect(parseColor("border-y-blue-500/80")).toEqual({
586
+ borderTopColor: applyOpacity(COLORS["blue-500"], 80),
587
+ borderBottomColor: applyOpacity(COLORS["blue-500"], 80),
588
+ });
589
+ });
590
+
591
+ it("should parse border-x with arbitrary hex colors", () => {
592
+ expect(parseColor("border-x-[#ff0000]")).toEqual({
593
+ borderLeftColor: "#ff0000",
594
+ borderRightColor: "#ff0000",
595
+ });
596
+ expect(parseColor("border-x-[#abc]")).toEqual({
597
+ borderLeftColor: "#aabbcc",
598
+ borderRightColor: "#aabbcc",
599
+ });
600
+ });
601
+
602
+ it("should parse border-y with arbitrary hex colors", () => {
603
+ expect(parseColor("border-y-[#00ff00]")).toEqual({
604
+ borderTopColor: "#00ff00",
605
+ borderBottomColor: "#00ff00",
606
+ });
607
+ expect(parseColor("border-y-[#123]")).toEqual({
608
+ borderTopColor: "#112233",
609
+ borderBottomColor: "#112233",
610
+ });
611
+ });
612
+
613
+ it("should parse border-x with custom colors", () => {
614
+ const customColors = { "brand-primary": "#FF6B6B" };
615
+ expect(parseColor("border-x-brand-primary", customColors)).toEqual({
616
+ borderLeftColor: "#FF6B6B",
617
+ borderRightColor: "#FF6B6B",
618
+ });
619
+ });
620
+
621
+ it("should parse border-y with custom colors", () => {
622
+ const customColors = { accent: "#FFE66D" };
623
+ expect(parseColor("border-y-accent", customColors)).toEqual({
624
+ borderTopColor: "#FFE66D",
625
+ borderBottomColor: "#FFE66D",
626
+ });
627
+ });
628
+
629
+ it("should not match border-x/border-y width classes", () => {
630
+ expect(parseColor("border-x-2")).toBeNull();
631
+ expect(parseColor("border-y-4")).toBeNull();
632
+ expect(parseColor("border-x-0")).toBeNull();
633
+ expect(parseColor("border-y-8")).toBeNull();
634
+ });
635
+
636
+ it("should not match border-x/border-y arbitrary width values", () => {
637
+ expect(parseColor("border-x-[3px]")).toBeNull();
638
+ expect(parseColor("border-y-[5px]")).toBeNull();
639
+ expect(parseColor("border-x-[10]")).toBeNull();
640
+ expect(parseColor("border-y-[8px]")).toBeNull();
641
+ });
642
+ });
@@ -178,5 +178,43 @@ export function parseColor(cls: string, customColors?: Record<string, string>):
178
178
  }
179
179
  }
180
180
 
181
+ // Directional border colors: border-t-red-500, border-l-blue-500/50, border-r-[#ff0000]
182
+ const dirBorderMatch = cls.match(/^border-([trblxy])-(.+)$/);
183
+ if (dirBorderMatch) {
184
+ const dir = dirBorderMatch[1];
185
+ const colorKey = dirBorderMatch[2];
186
+
187
+ // Skip arbitrary values that don't look like colors (e.g., border-t-[3px] is width)
188
+ if (colorKey.startsWith("[") && !colorKey.startsWith("[#")) {
189
+ return null;
190
+ }
191
+
192
+ const color = parseColorWithOpacity(colorKey);
193
+ if (color) {
194
+ // Map direction to React Native property/properties
195
+ // x and y apply to multiple sides (RN doesn't have borderHorizontalColor/borderVerticalColor)
196
+ if (dir === "x") {
197
+ return {
198
+ borderLeftColor: color,
199
+ borderRightColor: color,
200
+ };
201
+ }
202
+ if (dir === "y") {
203
+ return {
204
+ borderTopColor: color,
205
+ borderBottomColor: color,
206
+ };
207
+ }
208
+
209
+ const propMap: Record<string, string> = {
210
+ t: "borderTopColor",
211
+ r: "borderRightColor",
212
+ b: "borderBottomColor",
213
+ l: "borderLeftColor",
214
+ };
215
+ return { [propMap[dir]]: color };
216
+ }
217
+ }
218
+
181
219
  return null;
182
220
  }
@@ -50,10 +50,10 @@ export function parseClassName(className: string, customTheme?: CustomTheme): St
50
50
  export function parseClass(cls: string, customTheme?: CustomTheme): StyleObject {
51
51
  // Try each parser in order
52
52
  // Note: parseBorder must come before parseColor to avoid border-[3px] being parsed as a color
53
- // parseColor and parseTypography get custom theme, others don't need it
53
+ // parseBorder, parseColor and parseTypography get custom theme
54
54
  const parsers: Array<(cls: string) => StyleObject | null> = [
55
55
  parseSpacing,
56
- parseBorder,
56
+ (cls: string) => parseBorder(cls, customTheme?.colors),
57
57
  (cls: string) => parseColor(cls, customTheme?.colors),
58
58
  parseLayout,
59
59
  (cls: string) => parseTypography(cls, customTheme?.fontFamily, customTheme?.fontSize),
@@ -97,6 +97,7 @@ export {
97
97
  hasModifier,
98
98
  isColorClass,
99
99
  isColorSchemeModifier,
100
+ isDirectionalModifier,
100
101
  isPlatformModifier,
101
102
  isSchemeModifier,
102
103
  isStateModifier,
@@ -105,6 +106,7 @@ export {
105
106
  } from "./modifiers";
106
107
  export type {
107
108
  ColorSchemeModifierType,
109
+ DirectionalModifierType,
108
110
  ModifierType,
109
111
  ParsedModifier,
110
112
  PlatformModifierType,
@@ -645,3 +645,77 @@ describe("parseLayout - specific property coverage", () => {
645
645
  expect(insetY).not.toHaveProperty("right");
646
646
  });
647
647
  });
648
+
649
+ describe("parseLayout - logical positioning (RTL-aware)", () => {
650
+ it("should parse start positioning with preset values", () => {
651
+ expect(parseLayout("start-0")).toEqual({ start: 0 });
652
+ expect(parseLayout("start-4")).toEqual({ start: 16 });
653
+ expect(parseLayout("start-8")).toEqual({ start: 32 });
654
+ expect(parseLayout("start-0.5")).toEqual({ start: 2 });
655
+ expect(parseLayout("start-2.5")).toEqual({ start: 10 });
656
+ });
657
+
658
+ it("should parse end positioning with preset values", () => {
659
+ expect(parseLayout("end-0")).toEqual({ end: 0 });
660
+ expect(parseLayout("end-4")).toEqual({ end: 16 });
661
+ expect(parseLayout("end-8")).toEqual({ end: 32 });
662
+ expect(parseLayout("end-0.5")).toEqual({ end: 2 });
663
+ expect(parseLayout("end-2.5")).toEqual({ end: 10 });
664
+ });
665
+
666
+ it("should parse start/end with arbitrary pixel values", () => {
667
+ expect(parseLayout("start-[10px]")).toEqual({ start: 10 });
668
+ expect(parseLayout("start-[50]")).toEqual({ start: 50 });
669
+ expect(parseLayout("end-[10px]")).toEqual({ end: 10 });
670
+ expect(parseLayout("end-[50]")).toEqual({ end: 50 });
671
+ });
672
+
673
+ it("should parse start/end with arbitrary percentage values", () => {
674
+ expect(parseLayout("start-[10%]")).toEqual({ start: "10%" });
675
+ expect(parseLayout("start-[50%]")).toEqual({ start: "50%" });
676
+ expect(parseLayout("end-[10%]")).toEqual({ end: "10%" });
677
+ expect(parseLayout("end-[50%]")).toEqual({ end: "50%" });
678
+ });
679
+
680
+ it("should parse negative start/end with preset values", () => {
681
+ expect(parseLayout("-start-4")).toEqual({ start: -16 });
682
+ expect(parseLayout("-start-8")).toEqual({ start: -32 });
683
+ expect(parseLayout("-end-4")).toEqual({ end: -16 });
684
+ expect(parseLayout("-end-8")).toEqual({ end: -32 });
685
+ });
686
+
687
+ it("should parse negative start/end with arbitrary values", () => {
688
+ expect(parseLayout("-start-[10px]")).toEqual({ start: -10 });
689
+ expect(parseLayout("-start-[50]")).toEqual({ start: -50 });
690
+ expect(parseLayout("-end-[10px]")).toEqual({ end: -10 });
691
+ expect(parseLayout("-end-[50]")).toEqual({ end: -50 });
692
+ expect(parseLayout("-start-[10%]")).toEqual({ start: "-10%" });
693
+ expect(parseLayout("-end-[25%]")).toEqual({ end: "-25%" });
694
+ });
695
+
696
+ it("should handle auto value for start/end", () => {
697
+ expect(parseLayout("start-auto")).toEqual({});
698
+ expect(parseLayout("end-auto")).toEqual({});
699
+ });
700
+ });
701
+
702
+ describe("parseLayout - logical inset (RTL-aware)", () => {
703
+ it("should parse inset-s (start) with preset values", () => {
704
+ expect(parseLayout("inset-s-0")).toEqual({ start: 0 });
705
+ expect(parseLayout("inset-s-4")).toEqual({ start: 16 });
706
+ expect(parseLayout("inset-s-8")).toEqual({ start: 32 });
707
+ });
708
+
709
+ it("should parse inset-e (end) with preset values", () => {
710
+ expect(parseLayout("inset-e-0")).toEqual({ end: 0 });
711
+ expect(parseLayout("inset-e-4")).toEqual({ end: 16 });
712
+ expect(parseLayout("inset-e-8")).toEqual({ end: 32 });
713
+ });
714
+
715
+ it("should parse inset-s/inset-e with arbitrary values", () => {
716
+ expect(parseLayout("inset-s-[10px]")).toEqual({ start: 10 });
717
+ expect(parseLayout("inset-s-[20%]")).toEqual({ start: "20%" });
718
+ expect(parseLayout("inset-e-[10px]")).toEqual({ end: 10 });
719
+ expect(parseLayout("inset-e-[20%]")).toEqual({ end: "20%" });
720
+ });
721
+ });
@@ -339,6 +339,68 @@ export function parseLayout(cls: string): StyleObject | null {
339
339
  }
340
340
  }
341
341
 
342
+ // Start positioning (RTL-aware): start-0, start-4, start-[10px], -start-4, etc.
343
+ const startMatch = cls.match(/^(-?)start-(.+)$/);
344
+ if (startMatch) {
345
+ const [, negPrefix, startKey] = startMatch;
346
+ const isNegative = negPrefix === "-";
347
+
348
+ // Auto value - return empty object (no-op, removes the property)
349
+ if (startKey === "auto") {
350
+ return {};
351
+ }
352
+
353
+ // Arbitrary values: start-[123px], start-[50%], -start-[10px]
354
+ const arbitraryStart = parseArbitraryInset(startKey);
355
+ if (arbitraryStart !== null) {
356
+ if (typeof arbitraryStart === "number") {
357
+ return { start: isNegative ? -arbitraryStart : arbitraryStart };
358
+ }
359
+ // Percentage values with negative prefix
360
+ if (isNegative && arbitraryStart.endsWith("%")) {
361
+ const numValue = parseFloat(arbitraryStart);
362
+ return { start: `${-numValue}%` };
363
+ }
364
+ return { start: arbitraryStart };
365
+ }
366
+
367
+ const startValue = INSET_SCALE[startKey];
368
+ if (startValue !== undefined) {
369
+ return { start: isNegative ? -startValue : startValue };
370
+ }
371
+ }
372
+
373
+ // End positioning (RTL-aware): end-0, end-4, end-[10px], -end-4, etc.
374
+ const endMatch = cls.match(/^(-?)end-(.+)$/);
375
+ if (endMatch) {
376
+ const [, negPrefix, endKey] = endMatch;
377
+ const isNegative = negPrefix === "-";
378
+
379
+ // Auto value - return empty object (no-op, removes the property)
380
+ if (endKey === "auto") {
381
+ return {};
382
+ }
383
+
384
+ // Arbitrary values: end-[123px], end-[50%], -end-[10px]
385
+ const arbitraryEnd = parseArbitraryInset(endKey);
386
+ if (arbitraryEnd !== null) {
387
+ if (typeof arbitraryEnd === "number") {
388
+ return { end: isNegative ? -arbitraryEnd : arbitraryEnd };
389
+ }
390
+ // Percentage values with negative prefix
391
+ if (isNegative && arbitraryEnd.endsWith("%")) {
392
+ const numValue = parseFloat(arbitraryEnd);
393
+ return { end: `${-numValue}%` };
394
+ }
395
+ return { end: arbitraryEnd };
396
+ }
397
+
398
+ const endValue = INSET_SCALE[endKey];
399
+ if (endValue !== undefined) {
400
+ return { end: isNegative ? -endValue : endValue };
401
+ }
402
+ }
403
+
342
404
  // Inset X (left and right): inset-x-0, inset-x-4, inset-x-[10px], etc.
343
405
  if (cls.startsWith("inset-x-")) {
344
406
  const insetKey = cls.substring(8);
@@ -371,6 +433,38 @@ export function parseLayout(cls: string): StyleObject | null {
371
433
  }
372
434
  }
373
435
 
436
+ // Inset S (start, RTL-aware): inset-s-0, inset-s-4, inset-s-[10px], etc.
437
+ if (cls.startsWith("inset-s-")) {
438
+ const insetKey = cls.substring(8);
439
+
440
+ // Arbitrary values: inset-s-[123px], inset-s-[50%]
441
+ const arbitraryInset = parseArbitraryInset(insetKey);
442
+ if (arbitraryInset !== null) {
443
+ return { start: arbitraryInset };
444
+ }
445
+
446
+ const insetValue = INSET_SCALE[insetKey];
447
+ if (insetValue !== undefined) {
448
+ return { start: insetValue };
449
+ }
450
+ }
451
+
452
+ // Inset E (end, RTL-aware): inset-e-0, inset-e-4, inset-e-[10px], etc.
453
+ if (cls.startsWith("inset-e-")) {
454
+ const insetKey = cls.substring(8);
455
+
456
+ // Arbitrary values: inset-e-[123px], inset-e-[50%]
457
+ const arbitraryInset = parseArbitraryInset(insetKey);
458
+ if (arbitraryInset !== null) {
459
+ return { end: arbitraryInset };
460
+ }
461
+
462
+ const insetValue = INSET_SCALE[insetKey];
463
+ if (insetValue !== undefined) {
464
+ return { end: insetValue };
465
+ }
466
+ }
467
+
374
468
  // Inset (all sides): inset-0, inset-4, inset-[10px], etc.
375
469
  if (cls.startsWith("inset-")) {
376
470
  const insetKey = cls.substring(6);