@mgcrea/react-native-tailwind 0.13.0 → 0.15.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 +33 -30
- package/dist/babel/config-loader.d.ts +10 -0
- package/dist/babel/config-loader.test.ts +75 -21
- package/dist/babel/config-loader.ts +100 -2
- package/dist/babel/index.cjs +439 -46
- package/dist/babel/plugin/state.d.ts +4 -0
- package/dist/babel/plugin/state.ts +8 -0
- package/dist/babel/plugin/visitors/className.test.ts +313 -0
- package/dist/babel/plugin/visitors/className.ts +36 -8
- package/dist/babel/plugin/visitors/imports.ts +16 -1
- package/dist/babel/plugin/visitors/program.ts +19 -2
- package/dist/babel/plugin/visitors/tw.test.ts +151 -0
- package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
- package/dist/babel/utils/styleInjection.d.ts +16 -0
- package/dist/babel/utils/styleInjection.ts +138 -7
- package/dist/babel/utils/twProcessing.d.ts +2 -0
- package/dist/babel/utils/twProcessing.ts +92 -3
- package/dist/parser/borders.js +1 -1
- package/dist/parser/borders.test.js +1 -1
- package/dist/parser/index.d.ts +3 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/layout.d.ts +3 -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.d.ts +3 -1
- package/dist/parser/sizing.js +1 -1
- package/dist/parser/sizing.test.js +1 -1
- package/dist/parser/spacing.d.ts +4 -2
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.js +1 -1
- package/dist/parser/transforms.d.ts +3 -1
- package/dist/parser/transforms.js +1 -1
- package/dist/parser/transforms.test.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.d.ts +2 -0
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/runtime.test.js +1 -1
- package/package.json +6 -6
- package/src/babel/config-loader.test.ts +75 -21
- package/src/babel/config-loader.ts +100 -2
- package/src/babel/plugin/state.ts +8 -0
- package/src/babel/plugin/visitors/className.test.ts +313 -0
- package/src/babel/plugin/visitors/className.ts +36 -8
- package/src/babel/plugin/visitors/imports.ts +16 -1
- package/src/babel/plugin/visitors/program.ts +19 -2
- package/src/babel/plugin/visitors/tw.test.ts +151 -0
- package/src/babel/utils/directionalModifierProcessing.ts +99 -0
- package/src/babel/utils/styleInjection.ts +138 -7
- package/src/babel/utils/twProcessing.ts +92 -3
- package/src/parser/borders.test.ts +104 -0
- package/src/parser/borders.ts +50 -7
- package/src/parser/index.ts +8 -5
- package/src/parser/layout.test.ts +168 -0
- package/src/parser/layout.ts +107 -8
- package/src/parser/modifiers.test.ts +206 -0
- package/src/parser/modifiers.ts +62 -3
- package/src/parser/sizing.test.ts +56 -0
- package/src/parser/sizing.ts +20 -15
- package/src/parser/spacing.test.ts +123 -0
- package/src/parser/spacing.ts +30 -15
- package/src/parser/transforms.test.ts +57 -0
- package/src/parser/transforms.ts +7 -3
- package/src/parser/typography.test.ts +8 -0
- package/src/parser/typography.ts +4 -0
- package/src/runtime.test.ts +149 -0
- package/src/runtime.ts +53 -1
|
@@ -645,3 +645,171 @@ 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
|
+
});
|
|
722
|
+
|
|
723
|
+
describe("parseLayout - custom spacing", () => {
|
|
724
|
+
const customSpacing = {
|
|
725
|
+
xs: 4,
|
|
726
|
+
sm: 8,
|
|
727
|
+
md: 16,
|
|
728
|
+
lg: 32,
|
|
729
|
+
xl: 64,
|
|
730
|
+
"4": 20, // Override default (16)
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
it("should support custom spacing values for top/right/bottom/left", () => {
|
|
734
|
+
expect(parseLayout("top-xs", customSpacing)).toEqual({ top: 4 });
|
|
735
|
+
expect(parseLayout("top-sm", customSpacing)).toEqual({ top: 8 });
|
|
736
|
+
expect(parseLayout("top-md", customSpacing)).toEqual({ top: 16 });
|
|
737
|
+
expect(parseLayout("top-lg", customSpacing)).toEqual({ top: 32 });
|
|
738
|
+
expect(parseLayout("top-xl", customSpacing)).toEqual({ top: 64 });
|
|
739
|
+
|
|
740
|
+
expect(parseLayout("right-xs", customSpacing)).toEqual({ right: 4 });
|
|
741
|
+
expect(parseLayout("bottom-sm", customSpacing)).toEqual({ bottom: 8 });
|
|
742
|
+
expect(parseLayout("left-md", customSpacing)).toEqual({ left: 16 });
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it("should override default spacing values", () => {
|
|
746
|
+
// Without custom spacing, top-4 is 16
|
|
747
|
+
expect(parseLayout("top-4")).toEqual({ top: 16 });
|
|
748
|
+
|
|
749
|
+
// With custom spacing, top-4 is 20 (overridden)
|
|
750
|
+
expect(parseLayout("top-4", customSpacing)).toEqual({ top: 20 });
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it("should merge custom spacing with defaults", () => {
|
|
754
|
+
// Custom spacing should merge with defaults
|
|
755
|
+
// top-0 should still work (from defaults)
|
|
756
|
+
expect(parseLayout("top-0", customSpacing)).toEqual({ top: 0 });
|
|
757
|
+
|
|
758
|
+
// top-8 should still work (from defaults, not overridden)
|
|
759
|
+
expect(parseLayout("top-8", customSpacing)).toEqual({ top: 32 });
|
|
760
|
+
|
|
761
|
+
// top-xs should work (from custom)
|
|
762
|
+
expect(parseLayout("top-xs", customSpacing)).toEqual({ top: 4 });
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it("should support custom spacing for start/end (RTL-aware)", () => {
|
|
766
|
+
expect(parseLayout("start-xs", customSpacing)).toEqual({ start: 4 });
|
|
767
|
+
expect(parseLayout("end-lg", customSpacing)).toEqual({ end: 32 });
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it("should support custom spacing for inset utilities", () => {
|
|
771
|
+
expect(parseLayout("inset-xs", customSpacing)).toEqual({
|
|
772
|
+
top: 4,
|
|
773
|
+
right: 4,
|
|
774
|
+
bottom: 4,
|
|
775
|
+
left: 4,
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
expect(parseLayout("inset-x-sm", customSpacing)).toEqual({
|
|
779
|
+
left: 8,
|
|
780
|
+
right: 8,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
expect(parseLayout("inset-y-md", customSpacing)).toEqual({
|
|
784
|
+
top: 16,
|
|
785
|
+
bottom: 16,
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it("should support custom spacing for inset-s/inset-e", () => {
|
|
790
|
+
expect(parseLayout("inset-s-lg", customSpacing)).toEqual({ start: 32 });
|
|
791
|
+
expect(parseLayout("inset-e-xl", customSpacing)).toEqual({ end: 64 });
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("should support negative values with custom spacing for start/end", () => {
|
|
795
|
+
// Note: -top-*, -left-*, -right-*, -bottom-* negative prefixes are not supported
|
|
796
|
+
// Use arbitrary values like top-[-10px] for negative positioning
|
|
797
|
+
expect(parseLayout("-start-sm", customSpacing)).toEqual({ start: -8 });
|
|
798
|
+
expect(parseLayout("-end-md", customSpacing)).toEqual({ end: -16 });
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it("should still support arbitrary values with custom spacing", () => {
|
|
802
|
+
expect(parseLayout("top-[100px]", customSpacing)).toEqual({ top: 100 });
|
|
803
|
+
expect(parseLayout("inset-[50%]", customSpacing)).toEqual({
|
|
804
|
+
top: "50%",
|
|
805
|
+
right: "50%",
|
|
806
|
+
bottom: "50%",
|
|
807
|
+
left: "50%",
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it("should handle non-layout classes as null", () => {
|
|
812
|
+
expect(parseLayout("text-xl", customSpacing)).toBeNull();
|
|
813
|
+
expect(parseLayout("bg-blue-500", customSpacing)).toBeNull();
|
|
814
|
+
});
|
|
815
|
+
});
|
package/src/parser/layout.ts
CHANGED
|
@@ -237,8 +237,13 @@ export const INSET_SCALE: Record<string, number> = {
|
|
|
237
237
|
|
|
238
238
|
/**
|
|
239
239
|
* Parse layout classes
|
|
240
|
+
* @param cls - The class name to parse
|
|
241
|
+
* @param customSpacing - Optional custom spacing values from tailwind.config (for inset utilities)
|
|
240
242
|
*/
|
|
241
|
-
export function parseLayout(cls: string): StyleObject | null {
|
|
243
|
+
export function parseLayout(cls: string, customSpacing?: Record<string, number>): StyleObject | null {
|
|
244
|
+
// Merge custom spacing with defaults for inset utilities
|
|
245
|
+
const insetMap = customSpacing ? { ...INSET_SCALE, ...customSpacing } : INSET_SCALE;
|
|
246
|
+
|
|
242
247
|
// Z-index: z-0, z-10, z-20, z-[999], etc.
|
|
243
248
|
if (cls.startsWith("z-")) {
|
|
244
249
|
const zKey = cls.substring(2);
|
|
@@ -270,7 +275,7 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
270
275
|
return { top: arbitraryTop };
|
|
271
276
|
}
|
|
272
277
|
|
|
273
|
-
const topValue =
|
|
278
|
+
const topValue = insetMap[topKey];
|
|
274
279
|
if (topValue !== undefined) {
|
|
275
280
|
return { top: topValue };
|
|
276
281
|
}
|
|
@@ -291,7 +296,7 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
291
296
|
return { right: arbitraryRight };
|
|
292
297
|
}
|
|
293
298
|
|
|
294
|
-
const rightValue =
|
|
299
|
+
const rightValue = insetMap[rightKey];
|
|
295
300
|
if (rightValue !== undefined) {
|
|
296
301
|
return { right: rightValue };
|
|
297
302
|
}
|
|
@@ -312,7 +317,7 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
312
317
|
return { bottom: arbitraryBottom };
|
|
313
318
|
}
|
|
314
319
|
|
|
315
|
-
const bottomValue =
|
|
320
|
+
const bottomValue = insetMap[bottomKey];
|
|
316
321
|
if (bottomValue !== undefined) {
|
|
317
322
|
return { bottom: bottomValue };
|
|
318
323
|
}
|
|
@@ -333,12 +338,74 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
333
338
|
return { left: arbitraryLeft };
|
|
334
339
|
}
|
|
335
340
|
|
|
336
|
-
const leftValue =
|
|
341
|
+
const leftValue = insetMap[leftKey];
|
|
337
342
|
if (leftValue !== undefined) {
|
|
338
343
|
return { left: leftValue };
|
|
339
344
|
}
|
|
340
345
|
}
|
|
341
346
|
|
|
347
|
+
// Start positioning (RTL-aware): start-0, start-4, start-[10px], -start-4, etc.
|
|
348
|
+
const startMatch = cls.match(/^(-?)start-(.+)$/);
|
|
349
|
+
if (startMatch) {
|
|
350
|
+
const [, negPrefix, startKey] = startMatch;
|
|
351
|
+
const isNegative = negPrefix === "-";
|
|
352
|
+
|
|
353
|
+
// Auto value - return empty object (no-op, removes the property)
|
|
354
|
+
if (startKey === "auto") {
|
|
355
|
+
return {};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Arbitrary values: start-[123px], start-[50%], -start-[10px]
|
|
359
|
+
const arbitraryStart = parseArbitraryInset(startKey);
|
|
360
|
+
if (arbitraryStart !== null) {
|
|
361
|
+
if (typeof arbitraryStart === "number") {
|
|
362
|
+
return { start: isNegative ? -arbitraryStart : arbitraryStart };
|
|
363
|
+
}
|
|
364
|
+
// Percentage values with negative prefix
|
|
365
|
+
if (isNegative && arbitraryStart.endsWith("%")) {
|
|
366
|
+
const numValue = parseFloat(arbitraryStart);
|
|
367
|
+
return { start: `${-numValue}%` };
|
|
368
|
+
}
|
|
369
|
+
return { start: arbitraryStart };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const startValue = insetMap[startKey];
|
|
373
|
+
if (startValue !== undefined) {
|
|
374
|
+
return { start: isNegative ? -startValue : startValue };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// End positioning (RTL-aware): end-0, end-4, end-[10px], -end-4, etc.
|
|
379
|
+
const endMatch = cls.match(/^(-?)end-(.+)$/);
|
|
380
|
+
if (endMatch) {
|
|
381
|
+
const [, negPrefix, endKey] = endMatch;
|
|
382
|
+
const isNegative = negPrefix === "-";
|
|
383
|
+
|
|
384
|
+
// Auto value - return empty object (no-op, removes the property)
|
|
385
|
+
if (endKey === "auto") {
|
|
386
|
+
return {};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Arbitrary values: end-[123px], end-[50%], -end-[10px]
|
|
390
|
+
const arbitraryEnd = parseArbitraryInset(endKey);
|
|
391
|
+
if (arbitraryEnd !== null) {
|
|
392
|
+
if (typeof arbitraryEnd === "number") {
|
|
393
|
+
return { end: isNegative ? -arbitraryEnd : arbitraryEnd };
|
|
394
|
+
}
|
|
395
|
+
// Percentage values with negative prefix
|
|
396
|
+
if (isNegative && arbitraryEnd.endsWith("%")) {
|
|
397
|
+
const numValue = parseFloat(arbitraryEnd);
|
|
398
|
+
return { end: `${-numValue}%` };
|
|
399
|
+
}
|
|
400
|
+
return { end: arbitraryEnd };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const endValue = insetMap[endKey];
|
|
404
|
+
if (endValue !== undefined) {
|
|
405
|
+
return { end: isNegative ? -endValue : endValue };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
342
409
|
// Inset X (left and right): inset-x-0, inset-x-4, inset-x-[10px], etc.
|
|
343
410
|
if (cls.startsWith("inset-x-")) {
|
|
344
411
|
const insetKey = cls.substring(8);
|
|
@@ -349,7 +416,7 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
349
416
|
return { left: arbitraryInset, right: arbitraryInset };
|
|
350
417
|
}
|
|
351
418
|
|
|
352
|
-
const insetValue =
|
|
419
|
+
const insetValue = insetMap[insetKey];
|
|
353
420
|
if (insetValue !== undefined) {
|
|
354
421
|
return { left: insetValue, right: insetValue };
|
|
355
422
|
}
|
|
@@ -365,12 +432,44 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
365
432
|
return { top: arbitraryInset, bottom: arbitraryInset };
|
|
366
433
|
}
|
|
367
434
|
|
|
368
|
-
const insetValue =
|
|
435
|
+
const insetValue = insetMap[insetKey];
|
|
369
436
|
if (insetValue !== undefined) {
|
|
370
437
|
return { top: insetValue, bottom: insetValue };
|
|
371
438
|
}
|
|
372
439
|
}
|
|
373
440
|
|
|
441
|
+
// Inset S (start, RTL-aware): inset-s-0, inset-s-4, inset-s-[10px], etc.
|
|
442
|
+
if (cls.startsWith("inset-s-")) {
|
|
443
|
+
const insetKey = cls.substring(8);
|
|
444
|
+
|
|
445
|
+
// Arbitrary values: inset-s-[123px], inset-s-[50%]
|
|
446
|
+
const arbitraryInset = parseArbitraryInset(insetKey);
|
|
447
|
+
if (arbitraryInset !== null) {
|
|
448
|
+
return { start: arbitraryInset };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const insetValue = insetMap[insetKey];
|
|
452
|
+
if (insetValue !== undefined) {
|
|
453
|
+
return { start: insetValue };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Inset E (end, RTL-aware): inset-e-0, inset-e-4, inset-e-[10px], etc.
|
|
458
|
+
if (cls.startsWith("inset-e-")) {
|
|
459
|
+
const insetKey = cls.substring(8);
|
|
460
|
+
|
|
461
|
+
// Arbitrary values: inset-e-[123px], inset-e-[50%]
|
|
462
|
+
const arbitraryInset = parseArbitraryInset(insetKey);
|
|
463
|
+
if (arbitraryInset !== null) {
|
|
464
|
+
return { end: arbitraryInset };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const insetValue = insetMap[insetKey];
|
|
468
|
+
if (insetValue !== undefined) {
|
|
469
|
+
return { end: insetValue };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
374
473
|
// Inset (all sides): inset-0, inset-4, inset-[10px], etc.
|
|
375
474
|
if (cls.startsWith("inset-")) {
|
|
376
475
|
const insetKey = cls.substring(6);
|
|
@@ -381,7 +480,7 @@ export function parseLayout(cls: string): StyleObject | null {
|
|
|
381
480
|
return { top: arbitraryInset, right: arbitraryInset, bottom: arbitraryInset, left: arbitraryInset };
|
|
382
481
|
}
|
|
383
482
|
|
|
384
|
-
const insetValue =
|
|
483
|
+
const insetValue = insetMap[insetKey];
|
|
385
484
|
if (insetValue !== undefined) {
|
|
386
485
|
return { top: insetValue, right: insetValue, bottom: insetValue, left: insetValue };
|
|
387
486
|
}
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
expandSchemeModifier,
|
|
5
5
|
hasModifier,
|
|
6
6
|
isColorClass,
|
|
7
|
+
isColorSchemeModifier,
|
|
8
|
+
isDirectionalModifier,
|
|
9
|
+
isPlatformModifier,
|
|
7
10
|
isSchemeModifier,
|
|
11
|
+
isStateModifier,
|
|
8
12
|
parseModifier,
|
|
9
13
|
splitModifierClasses,
|
|
10
14
|
} from "./modifiers";
|
|
@@ -523,3 +527,205 @@ describe("expandSchemeModifier", () => {
|
|
|
523
527
|
expect(result).toEqual([]);
|
|
524
528
|
});
|
|
525
529
|
});
|
|
530
|
+
|
|
531
|
+
describe("parseModifier - directional modifiers (rtl/ltr)", () => {
|
|
532
|
+
it("should parse rtl modifier", () => {
|
|
533
|
+
const result = parseModifier("rtl:text-right");
|
|
534
|
+
expect(result).toEqual({
|
|
535
|
+
modifier: "rtl",
|
|
536
|
+
baseClass: "text-right",
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("should parse ltr modifier", () => {
|
|
541
|
+
const result = parseModifier("ltr:text-left");
|
|
542
|
+
expect(result).toEqual({
|
|
543
|
+
modifier: "ltr",
|
|
544
|
+
baseClass: "text-left",
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("should parse directional modifiers with various base classes", () => {
|
|
549
|
+
expect(parseModifier("rtl:ms-4")).toEqual({
|
|
550
|
+
modifier: "rtl",
|
|
551
|
+
baseClass: "ms-4",
|
|
552
|
+
});
|
|
553
|
+
expect(parseModifier("ltr:me-4")).toEqual({
|
|
554
|
+
modifier: "ltr",
|
|
555
|
+
baseClass: "me-4",
|
|
556
|
+
});
|
|
557
|
+
expect(parseModifier("rtl:flex-row-reverse")).toEqual({
|
|
558
|
+
modifier: "rtl",
|
|
559
|
+
baseClass: "flex-row-reverse",
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe("modifier type check functions", () => {
|
|
565
|
+
it("should identify state modifiers", () => {
|
|
566
|
+
expect(isStateModifier("active")).toBe(true);
|
|
567
|
+
expect(isStateModifier("hover")).toBe(true);
|
|
568
|
+
expect(isStateModifier("focus")).toBe(true);
|
|
569
|
+
expect(isStateModifier("disabled")).toBe(true);
|
|
570
|
+
expect(isStateModifier("placeholder")).toBe(true);
|
|
571
|
+
expect(isStateModifier("ios")).toBe(false);
|
|
572
|
+
expect(isStateModifier("dark")).toBe(false);
|
|
573
|
+
expect(isStateModifier("rtl")).toBe(false);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("should identify platform modifiers", () => {
|
|
577
|
+
expect(isPlatformModifier("ios")).toBe(true);
|
|
578
|
+
expect(isPlatformModifier("android")).toBe(true);
|
|
579
|
+
expect(isPlatformModifier("web")).toBe(true);
|
|
580
|
+
expect(isPlatformModifier("active")).toBe(false);
|
|
581
|
+
expect(isPlatformModifier("dark")).toBe(false);
|
|
582
|
+
expect(isPlatformModifier("rtl")).toBe(false);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("should identify color scheme modifiers", () => {
|
|
586
|
+
expect(isColorSchemeModifier("dark")).toBe(true);
|
|
587
|
+
expect(isColorSchemeModifier("light")).toBe(true);
|
|
588
|
+
expect(isColorSchemeModifier("active")).toBe(false);
|
|
589
|
+
expect(isColorSchemeModifier("ios")).toBe(false);
|
|
590
|
+
expect(isColorSchemeModifier("rtl")).toBe(false);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("should identify directional modifiers", () => {
|
|
594
|
+
expect(isDirectionalModifier("rtl")).toBe(true);
|
|
595
|
+
expect(isDirectionalModifier("ltr")).toBe(true);
|
|
596
|
+
expect(isDirectionalModifier("active")).toBe(false);
|
|
597
|
+
expect(isDirectionalModifier("ios")).toBe(false);
|
|
598
|
+
expect(isDirectionalModifier("dark")).toBe(false);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("should identify scheme modifier", () => {
|
|
602
|
+
expect(isSchemeModifier("scheme")).toBe(true);
|
|
603
|
+
expect(isSchemeModifier("active")).toBe(false);
|
|
604
|
+
expect(isSchemeModifier("dark")).toBe(false);
|
|
605
|
+
expect(isSchemeModifier("rtl")).toBe(false);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
describe("splitModifierClasses - directional modifiers", () => {
|
|
610
|
+
it("should split directional modifier classes", () => {
|
|
611
|
+
const result = splitModifierClasses("bg-white rtl:bg-gray-100 ltr:bg-gray-50");
|
|
612
|
+
expect(result.baseClasses).toEqual(["bg-white"]);
|
|
613
|
+
expect(result.modifierClasses).toHaveLength(2);
|
|
614
|
+
expect(result.modifierClasses[0]).toEqual({
|
|
615
|
+
modifier: "rtl",
|
|
616
|
+
baseClass: "bg-gray-100",
|
|
617
|
+
});
|
|
618
|
+
expect(result.modifierClasses[1]).toEqual({
|
|
619
|
+
modifier: "ltr",
|
|
620
|
+
baseClass: "bg-gray-50",
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("should handle mixed modifiers including directional", () => {
|
|
625
|
+
const result = splitModifierClasses("p-4 rtl:ps-6 ios:p-8 dark:bg-gray-900");
|
|
626
|
+
expect(result.baseClasses).toEqual(["p-4"]);
|
|
627
|
+
expect(result.modifierClasses).toHaveLength(3);
|
|
628
|
+
expect(result.modifierClasses.map((m) => m.modifier)).toContain("rtl");
|
|
629
|
+
expect(result.modifierClasses.map((m) => m.modifier)).toContain("ios");
|
|
630
|
+
expect(result.modifierClasses.map((m) => m.modifier)).toContain("dark");
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
describe("splitModifierClasses - text-start/text-end expansion", () => {
|
|
635
|
+
it("should expand text-start to ltr:text-left rtl:text-right", () => {
|
|
636
|
+
const result = splitModifierClasses("text-start");
|
|
637
|
+
expect(result.baseClasses).toEqual([]);
|
|
638
|
+
expect(result.modifierClasses).toHaveLength(2);
|
|
639
|
+
expect(result.modifierClasses).toContainEqual({
|
|
640
|
+
modifier: "ltr",
|
|
641
|
+
baseClass: "text-left",
|
|
642
|
+
});
|
|
643
|
+
expect(result.modifierClasses).toContainEqual({
|
|
644
|
+
modifier: "rtl",
|
|
645
|
+
baseClass: "text-right",
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it("should expand text-end to ltr:text-right rtl:text-left", () => {
|
|
650
|
+
const result = splitModifierClasses("text-end");
|
|
651
|
+
expect(result.baseClasses).toEqual([]);
|
|
652
|
+
expect(result.modifierClasses).toHaveLength(2);
|
|
653
|
+
expect(result.modifierClasses).toContainEqual({
|
|
654
|
+
modifier: "ltr",
|
|
655
|
+
baseClass: "text-right",
|
|
656
|
+
});
|
|
657
|
+
expect(result.modifierClasses).toContainEqual({
|
|
658
|
+
modifier: "rtl",
|
|
659
|
+
baseClass: "text-left",
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("should handle text-start with other classes", () => {
|
|
664
|
+
const result = splitModifierClasses("p-4 text-start bg-white");
|
|
665
|
+
expect(result.baseClasses).toEqual(["p-4", "bg-white"]);
|
|
666
|
+
expect(result.modifierClasses).toHaveLength(2);
|
|
667
|
+
expect(result.modifierClasses).toContainEqual({
|
|
668
|
+
modifier: "ltr",
|
|
669
|
+
baseClass: "text-left",
|
|
670
|
+
});
|
|
671
|
+
expect(result.modifierClasses).toContainEqual({
|
|
672
|
+
modifier: "rtl",
|
|
673
|
+
baseClass: "text-right",
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("should handle both text-start and text-end in same className", () => {
|
|
678
|
+
// This is unusual but should work
|
|
679
|
+
const result = splitModifierClasses("text-start text-end");
|
|
680
|
+
expect(result.baseClasses).toEqual([]);
|
|
681
|
+
expect(result.modifierClasses).toHaveLength(4);
|
|
682
|
+
// text-start expands
|
|
683
|
+
expect(result.modifierClasses).toContainEqual({
|
|
684
|
+
modifier: "ltr",
|
|
685
|
+
baseClass: "text-left",
|
|
686
|
+
});
|
|
687
|
+
expect(result.modifierClasses).toContainEqual({
|
|
688
|
+
modifier: "rtl",
|
|
689
|
+
baseClass: "text-right",
|
|
690
|
+
});
|
|
691
|
+
// text-end expands
|
|
692
|
+
expect(result.modifierClasses).toContainEqual({
|
|
693
|
+
modifier: "ltr",
|
|
694
|
+
baseClass: "text-right",
|
|
695
|
+
});
|
|
696
|
+
expect(result.modifierClasses).toContainEqual({
|
|
697
|
+
modifier: "rtl",
|
|
698
|
+
baseClass: "text-left",
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it("should not affect text-left and text-right (no expansion)", () => {
|
|
703
|
+
const result = splitModifierClasses("text-left text-right text-center");
|
|
704
|
+
expect(result.baseClasses).toEqual(["text-left", "text-right", "text-center"]);
|
|
705
|
+
expect(result.modifierClasses).toEqual([]);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("should handle text-start/text-end with other modifiers", () => {
|
|
709
|
+
const result = splitModifierClasses("text-start active:bg-blue-500 rtl:pr-4");
|
|
710
|
+
expect(result.baseClasses).toEqual([]);
|
|
711
|
+
expect(result.modifierClasses).toHaveLength(4);
|
|
712
|
+
// text-start expansion
|
|
713
|
+
expect(result.modifierClasses).toContainEqual({
|
|
714
|
+
modifier: "ltr",
|
|
715
|
+
baseClass: "text-left",
|
|
716
|
+
});
|
|
717
|
+
expect(result.modifierClasses).toContainEqual({
|
|
718
|
+
modifier: "rtl",
|
|
719
|
+
baseClass: "text-right",
|
|
720
|
+
});
|
|
721
|
+
// explicit modifiers
|
|
722
|
+
expect(result.modifierClasses).toContainEqual({
|
|
723
|
+
modifier: "active",
|
|
724
|
+
baseClass: "bg-blue-500",
|
|
725
|
+
});
|
|
726
|
+
expect(result.modifierClasses).toContainEqual({
|
|
727
|
+
modifier: "rtl",
|
|
728
|
+
baseClass: "pr-4",
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
});
|
package/src/parser/modifiers.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Modifier parsing utilities for state-based, platform-specific,
|
|
2
|
+
* Modifier parsing utilities for state-based, platform-specific, color scheme, and directional class names
|
|
3
3
|
* - State modifiers: active:, hover:, focus:, disabled:, placeholder:
|
|
4
4
|
* - Platform modifiers: ios:, android:, web:
|
|
5
5
|
* - Color scheme modifiers: dark:, light:
|
|
6
|
+
* - Directional modifiers: rtl:, ltr: (RTL-aware styling)
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
export type StateModifierType = "active" | "hover" | "focus" | "disabled" | "placeholder";
|
|
9
10
|
export type PlatformModifierType = "ios" | "android" | "web";
|
|
10
11
|
export type ColorSchemeModifierType = "dark" | "light";
|
|
11
12
|
export type SchemeModifierType = "scheme";
|
|
13
|
+
export type DirectionalModifierType = "rtl" | "ltr";
|
|
12
14
|
export type ModifierType =
|
|
13
15
|
| StateModifierType
|
|
14
16
|
| PlatformModifierType
|
|
15
17
|
| ColorSchemeModifierType
|
|
16
|
-
| SchemeModifierType
|
|
18
|
+
| SchemeModifierType
|
|
19
|
+
| DirectionalModifierType;
|
|
17
20
|
|
|
18
21
|
export type ParsedModifier = {
|
|
19
22
|
modifier: ModifierType;
|
|
@@ -47,13 +50,19 @@ const COLOR_SCHEME_MODIFIERS: readonly ColorSchemeModifierType[] = ["dark", "lig
|
|
|
47
50
|
const SCHEME_MODIFIERS: readonly SchemeModifierType[] = ["scheme"] as const;
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
|
-
*
|
|
53
|
+
* Supported directional modifiers that map to I18nManager.isRTL values
|
|
54
|
+
*/
|
|
55
|
+
const DIRECTIONAL_MODIFIERS: readonly DirectionalModifierType[] = ["rtl", "ltr"] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* All supported modifiers (state + platform + color scheme + scheme + directional)
|
|
51
59
|
*/
|
|
52
60
|
const SUPPORTED_MODIFIERS: readonly ModifierType[] = [
|
|
53
61
|
...STATE_MODIFIERS,
|
|
54
62
|
...PLATFORM_MODIFIERS,
|
|
55
63
|
...COLOR_SCHEME_MODIFIERS,
|
|
56
64
|
...SCHEME_MODIFIERS,
|
|
65
|
+
...DIRECTIONAL_MODIFIERS,
|
|
57
66
|
] as const;
|
|
58
67
|
|
|
59
68
|
/**
|
|
@@ -149,6 +158,16 @@ export function isSchemeModifier(modifier: ModifierType): modifier is SchemeModi
|
|
|
149
158
|
return SCHEME_MODIFIERS.includes(modifier as SchemeModifierType);
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Check if a modifier is a directional modifier (rtl, ltr)
|
|
163
|
+
*
|
|
164
|
+
* @param modifier - Modifier type to check
|
|
165
|
+
* @returns true if modifier is a directional modifier
|
|
166
|
+
*/
|
|
167
|
+
export function isDirectionalModifier(modifier: ModifierType): modifier is DirectionalModifierType {
|
|
168
|
+
return DIRECTIONAL_MODIFIERS.includes(modifier as DirectionalModifierType);
|
|
169
|
+
}
|
|
170
|
+
|
|
152
171
|
/**
|
|
153
172
|
* Check if a class name is a color-based utility class
|
|
154
173
|
*
|
|
@@ -242,6 +261,26 @@ export function expandSchemeModifier(
|
|
|
242
261
|
];
|
|
243
262
|
}
|
|
244
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Classes that expand to directional modifiers for true RTL support
|
|
266
|
+
* text-start -> ltr:text-left rtl:text-right (start = left in LTR, right in RTL)
|
|
267
|
+
* text-end -> ltr:text-right rtl:text-left (end = right in LTR, left in RTL)
|
|
268
|
+
*/
|
|
269
|
+
const DIRECTIONAL_TEXT_ALIGN_EXPANSIONS: Record<string, { ltr: string; rtl: string }> = {
|
|
270
|
+
"text-start": { ltr: "text-left", rtl: "text-right" },
|
|
271
|
+
"text-end": { ltr: "text-right", rtl: "text-left" },
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if a class should be expanded to directional modifiers
|
|
276
|
+
*
|
|
277
|
+
* @param cls - Class name to check
|
|
278
|
+
* @returns Expansion object if class should expand, undefined otherwise
|
|
279
|
+
*/
|
|
280
|
+
export function getDirectionalExpansion(cls: string): { ltr: string; rtl: string } | undefined {
|
|
281
|
+
return DIRECTIONAL_TEXT_ALIGN_EXPANSIONS[cls];
|
|
282
|
+
}
|
|
283
|
+
|
|
245
284
|
/**
|
|
246
285
|
* Split a space-separated className string into base and modifier classes
|
|
247
286
|
*
|
|
@@ -257,6 +296,17 @@ export function expandSchemeModifier(
|
|
|
257
296
|
* // { modifier: "active", baseClass: "p-6" }
|
|
258
297
|
* // ]
|
|
259
298
|
* // }
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* // text-start/text-end auto-expand to directional modifiers for true RTL support
|
|
302
|
+
* splitModifierClasses("text-start p-4")
|
|
303
|
+
* // {
|
|
304
|
+
* // baseClasses: ["p-4"],
|
|
305
|
+
* // modifierClasses: [
|
|
306
|
+
* // { modifier: "ltr", baseClass: "text-left" },
|
|
307
|
+
* // { modifier: "rtl", baseClass: "text-right" }
|
|
308
|
+
* // ]
|
|
309
|
+
* // }
|
|
260
310
|
*/
|
|
261
311
|
export function splitModifierClasses(className: string): {
|
|
262
312
|
baseClasses: string[];
|
|
@@ -267,6 +317,15 @@ export function splitModifierClasses(className: string): {
|
|
|
267
317
|
const modifierClasses: ParsedModifier[] = [];
|
|
268
318
|
|
|
269
319
|
for (const cls of classes) {
|
|
320
|
+
// Check for directional text alignment expansion (text-start, text-end)
|
|
321
|
+
const directionalExpansion = getDirectionalExpansion(cls);
|
|
322
|
+
if (directionalExpansion) {
|
|
323
|
+
// Expand to ltr: and rtl: modifiers for true RTL support
|
|
324
|
+
modifierClasses.push({ modifier: "ltr", baseClass: directionalExpansion.ltr });
|
|
325
|
+
modifierClasses.push({ modifier: "rtl", baseClass: directionalExpansion.rtl });
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
270
329
|
const parsed = parseModifier(cls);
|
|
271
330
|
if (parsed) {
|
|
272
331
|
modifierClasses.push(parsed);
|