@mgcrea/react-native-tailwind 0.3.0 → 0.5.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 (71) hide show
  1. package/README.md +459 -39
  2. package/dist/babel/index.cjs +810 -279
  3. package/dist/babel/index.d.ts +2 -1
  4. package/dist/babel/index.ts +328 -22
  5. package/dist/components/Pressable.d.ts +32 -0
  6. package/dist/components/Pressable.js +1 -0
  7. package/dist/components/TextInput.d.ts +56 -0
  8. package/dist/components/TextInput.js +1 -0
  9. package/dist/index.d.ts +9 -2
  10. package/dist/index.js +1 -1
  11. package/dist/parser/aspectRatio.d.ts +16 -0
  12. package/dist/parser/aspectRatio.js +1 -0
  13. package/dist/parser/aspectRatio.test.d.ts +1 -0
  14. package/dist/parser/aspectRatio.test.js +1 -0
  15. package/dist/parser/borders.js +1 -1
  16. package/dist/parser/borders.test.d.ts +1 -0
  17. package/dist/parser/borders.test.js +1 -0
  18. package/dist/parser/colors.d.ts +1 -0
  19. package/dist/parser/colors.js +1 -1
  20. package/dist/parser/colors.test.d.ts +1 -0
  21. package/dist/parser/colors.test.js +1 -0
  22. package/dist/parser/index.d.ts +4 -0
  23. package/dist/parser/index.js +1 -1
  24. package/dist/parser/layout.d.ts +2 -0
  25. package/dist/parser/layout.js +1 -1
  26. package/dist/parser/layout.test.d.ts +1 -0
  27. package/dist/parser/layout.test.js +1 -0
  28. package/dist/parser/modifiers.d.ts +47 -0
  29. package/dist/parser/modifiers.js +1 -0
  30. package/dist/parser/modifiers.test.d.ts +1 -0
  31. package/dist/parser/modifiers.test.js +1 -0
  32. package/dist/parser/shadows.d.ts +26 -0
  33. package/dist/parser/shadows.js +1 -0
  34. package/dist/parser/shadows.test.d.ts +1 -0
  35. package/dist/parser/shadows.test.js +1 -0
  36. package/dist/parser/sizing.test.d.ts +1 -0
  37. package/dist/parser/sizing.test.js +1 -0
  38. package/dist/parser/spacing.d.ts +1 -1
  39. package/dist/parser/spacing.js +1 -1
  40. package/dist/parser/spacing.test.d.ts +1 -0
  41. package/dist/parser/spacing.test.js +1 -0
  42. package/dist/parser/typography.d.ts +2 -1
  43. package/dist/parser/typography.js +1 -1
  44. package/dist/parser/typography.test.d.ts +1 -0
  45. package/dist/parser/typography.test.js +1 -0
  46. package/dist/types.d.ts +5 -2
  47. package/package.json +7 -6
  48. package/src/babel/index.ts +328 -22
  49. package/src/components/Pressable.tsx +46 -0
  50. package/src/components/TextInput.tsx +90 -0
  51. package/src/index.ts +20 -2
  52. package/src/parser/aspectRatio.test.ts +191 -0
  53. package/src/parser/aspectRatio.ts +73 -0
  54. package/src/parser/borders.test.ts +329 -0
  55. package/src/parser/borders.ts +187 -108
  56. package/src/parser/colors.test.ts +335 -0
  57. package/src/parser/colors.ts +117 -6
  58. package/src/parser/index.ts +13 -2
  59. package/src/parser/layout.test.ts +459 -0
  60. package/src/parser/layout.ts +128 -0
  61. package/src/parser/modifiers.test.ts +375 -0
  62. package/src/parser/modifiers.ts +104 -0
  63. package/src/parser/shadows.test.ts +201 -0
  64. package/src/parser/shadows.ts +133 -0
  65. package/src/parser/sizing.test.ts +256 -0
  66. package/src/parser/spacing.test.ts +226 -0
  67. package/src/parser/spacing.ts +93 -138
  68. package/src/parser/typography.test.ts +221 -0
  69. package/src/parser/typography.ts +143 -112
  70. package/src/types.ts +2 -2
  71. package/dist/react-native.d.js +0 -1
@@ -4,8 +4,9 @@
4
4
  */
5
5
  import type { PluginObj, PluginPass } from "@babel/core";
6
6
  import * as BabelTypes from "@babel/types";
7
+ import { StyleObject } from "src/types.js";
7
8
  type PluginState = PluginPass & {
8
- styleRegistry: Map<string, Record<string, string | number>>;
9
+ styleRegistry: Map<string, StyleObject>;
9
10
  hasClassNames: boolean;
10
11
  hasStyleSheetImport: boolean;
11
12
  customColors: Record<string, string>;
@@ -11,22 +11,28 @@
11
11
 
12
12
  import type { NodePath, PluginObj, PluginPass } from "@babel/core";
13
13
  import * as BabelTypes from "@babel/types";
14
- import { parseClassName as parseClassNameFn } from "../parser/index.js";
14
+ import { StyleObject } from "src/types.js";
15
+ import type { ModifierType, ParsedModifier } from "../parser/index.js";
16
+ import { parseClassName as parseClassNameFn, splitModifierClasses } from "../parser/index.js";
15
17
  import { generateStyleKey as generateStyleKeyFn } from "../utils/styleKey.js";
16
18
  import { extractCustomColors } from "./config-loader.js";
17
19
 
18
20
  type PluginState = PluginPass & {
19
- styleRegistry: Map<string, Record<string, string | number>>;
21
+ styleRegistry: Map<string, StyleObject>;
20
22
  hasClassNames: boolean;
21
23
  hasStyleSheetImport: boolean;
22
24
  customColors: Record<string, string>;
23
25
  };
24
26
 
27
+ // Use a unique identifier to avoid conflicts with user's own styles
28
+ const STYLES_IDENTIFIER = "_twStyles";
29
+
25
30
  /**
26
31
  * Supported className-like attributes
27
32
  */
28
33
  const SUPPORTED_CLASS_ATTRIBUTES = [
29
34
  "className",
35
+ "containerClassName",
30
36
  "contentContainerClassName",
31
37
  "columnWrapperClassName",
32
38
  "ListHeaderComponentClassName",
@@ -37,6 +43,9 @@ const SUPPORTED_CLASS_ATTRIBUTES = [
37
43
  * Get the target style prop name based on the className attribute
38
44
  */
39
45
  function getTargetStyleProp(attributeName: string): string {
46
+ if (attributeName === "containerClassName") {
47
+ return "containerStyle";
48
+ }
40
49
  if (attributeName === "contentContainerClassName") {
41
50
  return "contentContainerStyle";
42
51
  }
@@ -52,6 +61,49 @@ function getTargetStyleProp(attributeName: string): string {
52
61
  return "style";
53
62
  }
54
63
 
64
+ /**
65
+ * Check if a JSX element supports modifiers and determine which modifiers are supported
66
+ * Returns an object with component info and supported modifiers
67
+ */
68
+ function getComponentModifierSupport(
69
+ jsxElement: any,
70
+ t: typeof BabelTypes,
71
+ ): { component: string; supportedModifiers: ModifierType[] } | null {
72
+ if (!t.isJSXOpeningElement(jsxElement)) {
73
+ return null;
74
+ }
75
+
76
+ const name = jsxElement.name;
77
+ let componentName: string | null = null;
78
+
79
+ // Handle simple identifier: <Pressable>
80
+ if (t.isJSXIdentifier(name)) {
81
+ componentName = name.name;
82
+ }
83
+
84
+ // Handle member expression: <ReactNative.Pressable>
85
+ if (t.isJSXMemberExpression(name)) {
86
+ const property = name.property;
87
+ if (t.isJSXIdentifier(property)) {
88
+ componentName = property.name;
89
+ }
90
+ }
91
+
92
+ if (!componentName) {
93
+ return null;
94
+ }
95
+
96
+ // Map components to their supported modifiers
97
+ switch (componentName) {
98
+ case "Pressable":
99
+ return { component: "Pressable", supportedModifiers: ["active", "hover", "focus", "disabled"] };
100
+ case "TextInput":
101
+ return { component: "TextInput", supportedModifiers: ["focus", "disabled"] };
102
+ default:
103
+ return null;
104
+ }
105
+ }
106
+
55
107
  /**
56
108
  * Result of processing a dynamic expression
57
109
  */
@@ -117,7 +169,7 @@ function processTemplateLiteral(
117
169
  staticParts.push(cls);
118
170
 
119
171
  // Add to parts array
120
- parts.push(t.memberExpression(t.identifier("styles"), t.identifier(styleKey)));
172
+ parts.push(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)));
121
173
  }
122
174
  }
123
175
 
@@ -216,7 +268,7 @@ function processStringOrExpression(node: any, state: PluginState, t: typeof Babe
216
268
  const styleKey = generateStyleKey(className);
217
269
  state.styleRegistry.set(styleKey, styleObject);
218
270
 
219
- return t.memberExpression(t.identifier("styles"), t.identifier(styleKey));
271
+ return t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey));
220
272
  }
221
273
 
222
274
  // Handle nested expressions recursively
@@ -239,6 +291,118 @@ function processStringOrExpression(node: any, state: PluginState, t: typeof Babe
239
291
  return null;
240
292
  }
241
293
 
294
+ /**
295
+ * Process a static className string that contains modifiers
296
+ * Returns a style function expression for Pressable components
297
+ */
298
+ function processStaticClassNameWithModifiers(
299
+ className: string,
300
+ state: PluginState,
301
+ t: typeof BabelTypes,
302
+ ): any {
303
+ const { baseClasses, modifierClasses } = splitModifierClasses(className);
304
+
305
+ // Parse and register base classes
306
+ let baseStyleExpression: any = null;
307
+ if (baseClasses.length > 0) {
308
+ const baseClassName = baseClasses.join(" ");
309
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
310
+ const baseStyleKey = generateStyleKey(baseClassName);
311
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
312
+ baseStyleExpression = t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(baseStyleKey));
313
+ }
314
+
315
+ // Parse and register modifier classes
316
+ // Group by modifier type for better organization
317
+ const modifiersByType = new Map<ModifierType, ParsedModifier[]>();
318
+ for (const mod of modifierClasses) {
319
+ if (!modifiersByType.has(mod.modifier)) {
320
+ modifiersByType.set(mod.modifier, []);
321
+ }
322
+ const modGroup = modifiersByType.get(mod.modifier);
323
+ if (modGroup) {
324
+ modGroup.push(mod);
325
+ }
326
+ }
327
+
328
+ // Build style function: ({ pressed }) => [baseStyle, pressed && modifierStyle]
329
+ const styleArrayElements: any[] = [];
330
+
331
+ // Add base style first
332
+ if (baseStyleExpression) {
333
+ styleArrayElements.push(baseStyleExpression);
334
+ }
335
+
336
+ // Add conditional styles for each modifier type
337
+ for (const [modifierType, modifiers] of modifiersByType) {
338
+ // Parse all modifier classes together
339
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
340
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customColors);
341
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
342
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
343
+
344
+ // Create conditional: pressed && styles._active_bg_blue_700
345
+ const stateProperty = getStatePropertyForModifier(modifierType);
346
+ const conditionalExpression = t.logicalExpression(
347
+ "&&",
348
+ t.identifier(stateProperty),
349
+ t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(modifierStyleKey)),
350
+ );
351
+
352
+ styleArrayElements.push(conditionalExpression);
353
+ }
354
+
355
+ // If only base style, return it directly; otherwise return array
356
+ if (styleArrayElements.length === 1) {
357
+ return styleArrayElements[0];
358
+ }
359
+
360
+ return t.arrayExpression(styleArrayElements);
361
+ }
362
+
363
+ /**
364
+ * Get the state property name for a modifier type
365
+ * Maps modifier types to component state parameter properties
366
+ */
367
+ function getStatePropertyForModifier(modifier: ModifierType): string {
368
+ switch (modifier) {
369
+ case "active":
370
+ return "pressed";
371
+ case "hover":
372
+ return "hovered";
373
+ case "focus":
374
+ return "focused";
375
+ case "disabled":
376
+ return "disabled";
377
+ default:
378
+ return "pressed"; // fallback
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Create a style function for Pressable: ({ pressed }) => styleExpression
384
+ */
385
+ function createStyleFunction(styleExpression: any, modifierTypes: ModifierType[], t: typeof BabelTypes): any {
386
+ // Build parameter object: { pressed, hovered, focused }
387
+ const paramProperties: any[] = [];
388
+ const usedStateProps = new Set<string>();
389
+
390
+ for (const modifierType of modifierTypes) {
391
+ const stateProperty = getStatePropertyForModifier(modifierType);
392
+ if (!usedStateProps.has(stateProperty)) {
393
+ usedStateProps.add(stateProperty);
394
+ paramProperties.push(
395
+ t.objectProperty(t.identifier(stateProperty), t.identifier(stateProperty), false, true),
396
+ );
397
+ }
398
+ }
399
+
400
+ const param = t.objectPattern(paramProperties);
401
+
402
+ // Create arrow function: ({ pressed }) => styleExpression
403
+ return t.arrowFunctionExpression([param], styleExpression);
404
+ }
405
+
242
406
  export default function reactNativeTailwindBabelPlugin({
243
407
  types: t,
244
408
  }: {
@@ -311,7 +475,7 @@ export default function reactNativeTailwindBabelPlugin({
311
475
  // Determine target style prop based on attribute name
312
476
  const targetStyleProp = getTargetStyleProp(attributeName);
313
477
 
314
- // Handle static string literals (original behavior)
478
+ // Handle static string literals
315
479
  if (t.isStringLiteral(value)) {
316
480
  const className = value.value.trim();
317
481
 
@@ -323,13 +487,99 @@ export default function reactNativeTailwindBabelPlugin({
323
487
 
324
488
  state.hasClassNames = true;
325
489
 
326
- // Parse className to React Native styles
327
- const styleObject = parseClassName(className, state.customColors);
490
+ // Check if className contains modifiers (active:, hover:, focus:)
491
+ const { baseClasses, modifierClasses } = splitModifierClasses(className);
328
492
 
329
- // Generate unique style key
330
- const styleKey = generateStyleKey(className);
493
+ // If there are modifiers, check if this component supports them
494
+ if (modifierClasses.length > 0) {
495
+ // Get the JSX opening element (the direct parent of the attribute)
496
+ const jsxOpeningElement = path.parent;
497
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
498
+
499
+ if (componentSupport) {
500
+ // Get modifier types used in className
501
+ const usedModifiers = Array.from(new Set(modifierClasses.map((m) => m.modifier)));
502
+
503
+ // Check if all modifiers are supported by this component
504
+ const unsupportedModifiers = usedModifiers.filter(
505
+ (mod) => !componentSupport.supportedModifiers.includes(mod),
506
+ );
507
+
508
+ if (unsupportedModifiers.length > 0) {
509
+ // Warn about unsupported modifiers
510
+ if (process.env.NODE_ENV !== "production") {
511
+ console.warn(
512
+ `[react-native-tailwind] Modifiers (${unsupportedModifiers.map((m) => `${m}:`).join(", ")}) are not supported on ${componentSupport.component} component at ${state.file.opts.filename ?? "unknown"}. ` +
513
+ `Supported modifiers: ${componentSupport.supportedModifiers.join(", ")}`,
514
+ );
515
+ }
516
+ // Filter out unsupported modifiers
517
+ const supportedModifierClasses = modifierClasses.filter((m) =>
518
+ componentSupport.supportedModifiers.includes(m.modifier),
519
+ );
520
+
521
+ // If no supported modifiers remain, fall through to normal processing
522
+ if (supportedModifierClasses.length === 0) {
523
+ // Continue to normal processing
524
+ } else {
525
+ // Process only supported modifiers
526
+ const filteredClassName =
527
+ baseClasses.join(" ") +
528
+ " " +
529
+ supportedModifierClasses.map((m) => `${m.modifier}:${m.baseClass}`).join(" ");
530
+ const styleExpression = processStaticClassNameWithModifiers(
531
+ filteredClassName.trim(),
532
+ state,
533
+ t,
534
+ );
535
+ const modifierTypes = Array.from(new Set(supportedModifierClasses.map((m) => m.modifier)));
536
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
537
+
538
+ const parent = path.parent as any;
539
+ const styleAttribute = parent.attributes.find(
540
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
541
+ );
542
+
543
+ if (styleAttribute) {
544
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
545
+ } else {
546
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
547
+ }
548
+ return;
549
+ }
550
+ } else {
551
+ // All modifiers are supported - process normally
552
+ const styleExpression = processStaticClassNameWithModifiers(className, state, t);
553
+ const modifierTypes = usedModifiers;
554
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
555
+
556
+ const parent = path.parent as any;
557
+ const styleAttribute = parent.attributes.find(
558
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
559
+ );
560
+
561
+ if (styleAttribute) {
562
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
563
+ } else {
564
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
565
+ }
566
+ return;
567
+ }
568
+ } else {
569
+ // Component doesn't support any modifiers
570
+ if (process.env.NODE_ENV !== "production") {
571
+ const usedModifiers = Array.from(new Set(modifierClasses.map((m) => m.modifier)));
572
+ console.warn(
573
+ `[react-native-tailwind] Modifiers (${usedModifiers.map((m) => `${m}:`).join(", ")}) can only be used on compatible components (Pressable, TextInput). Found on unsupported element at ${state.file.opts.filename ?? "unknown"}`,
574
+ );
575
+ }
576
+ // Fall through to normal processing (ignore modifiers)
577
+ }
578
+ }
331
579
 
332
- // Store in registry
580
+ // Normal processing without modifiers
581
+ const styleObject = parseClassName(className, state.customColors);
582
+ const styleKey = generateStyleKey(className);
333
583
  state.styleRegistry.set(styleKey, styleObject);
334
584
 
335
585
  // Check if there's already a style prop on this element
@@ -426,7 +676,7 @@ function replaceWithStyleAttribute(
426
676
  ) {
427
677
  const styleAttribute = t.jsxAttribute(
428
678
  t.jsxIdentifier(targetStyleProp),
429
- t.jsxExpressionContainer(t.memberExpression(t.identifier("styles"), t.identifier(styleKey))),
679
+ t.jsxExpressionContainer(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey))),
430
680
  );
431
681
 
432
682
  classNamePath.replaceWith(styleAttribute);
@@ -446,7 +696,7 @@ function mergeStyleAttribute(
446
696
  // Create array with className styles first, then existing styles
447
697
  // This allows existing styles to override className styles
448
698
  const styleArray = t.arrayExpression([
449
- t.memberExpression(t.identifier("styles"), t.identifier(styleKey)),
699
+ t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)),
450
700
  existingStyle,
451
701
  ]);
452
702
 
@@ -502,13 +752,72 @@ function mergeDynamicStyleAttribute(
502
752
  }
503
753
 
504
754
  /**
505
- * Inject StyleSheet.create with all collected styles
755
+ * Replace className with style function attribute (for Pressable with modifiers)
506
756
  */
507
- function injectStyles(
508
- path: NodePath,
509
- styleRegistry: Map<string, Record<string, string | number>>,
757
+ function replaceWithStyleFunctionAttribute(
758
+ classNamePath: NodePath,
759
+ styleFunctionExpression: any,
760
+ targetStyleProp: string,
510
761
  t: typeof BabelTypes,
511
762
  ) {
763
+ const styleAttribute = t.jsxAttribute(
764
+ t.jsxIdentifier(targetStyleProp),
765
+ t.jsxExpressionContainer(styleFunctionExpression),
766
+ );
767
+
768
+ classNamePath.replaceWith(styleAttribute);
769
+ }
770
+
771
+ /**
772
+ * Merge className style function with existing style prop (for Pressable with modifiers)
773
+ */
774
+ function mergeStyleFunctionAttribute(
775
+ classNamePath: NodePath,
776
+ styleAttribute: any,
777
+ styleFunctionExpression: any,
778
+ t: typeof BabelTypes,
779
+ ) {
780
+ const existingStyle = styleAttribute.value.expression;
781
+
782
+ // Create a wrapper function that merges both styles
783
+ // ({ pressed }) => [styleFunctionResult, existingStyle]
784
+ // We need to call the style function and merge results
785
+
786
+ // If existing is already a function, we need to handle it specially
787
+ if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
788
+ // Both are functions - create wrapper that calls both
789
+ // (_state) => [newStyleFn(_state), existingStyleFn(_state)]
790
+ // Create an identifier for the parameter to pass to the function calls
791
+ const paramIdentifier = t.identifier("_state");
792
+
793
+ const newFunctionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
794
+ const existingFunctionCall = t.callExpression(existingStyle, [paramIdentifier]);
795
+
796
+ const mergedArray = t.arrayExpression([newFunctionCall, existingFunctionCall]);
797
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
798
+
799
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
800
+ } else {
801
+ // Existing is static - create function that returns array
802
+ // (_state) => [styleFunctionResult, existingStyle]
803
+ // Create an identifier for the parameter to pass to the function call
804
+ const paramIdentifier = t.identifier("_state");
805
+
806
+ const functionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
807
+ const mergedArray = t.arrayExpression([functionCall, existingStyle]);
808
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
809
+
810
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
811
+ }
812
+
813
+ // Remove the className attribute
814
+ classNamePath.remove();
815
+ }
816
+
817
+ /**
818
+ * Inject StyleSheet.create with all collected styles
819
+ */
820
+ function injectStyles(path: NodePath, styleRegistry: Map<string, StyleObject>, t: typeof BabelTypes) {
512
821
  // Build style object properties
513
822
  const styleProperties: any[] = [];
514
823
 
@@ -531,10 +840,10 @@ function injectStyles(
531
840
  styleProperties.push(t.objectProperty(t.identifier(key), t.objectExpression(properties)));
532
841
  }
533
842
 
534
- // Create: const styles = StyleSheet.create({ ... })
843
+ // Create: const _tailwindStyles = StyleSheet.create({ ... })
535
844
  const styleSheet = t.variableDeclaration("const", [
536
845
  t.variableDeclarator(
537
- t.identifier("styles"),
846
+ t.identifier(STYLES_IDENTIFIER),
538
847
  t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
539
848
  t.objectExpression(styleProperties),
540
849
  ]),
@@ -546,10 +855,7 @@ function injectStyles(
546
855
  }
547
856
 
548
857
  // Helper functions that use the imported parser
549
- function parseClassName(
550
- className: string,
551
- customColors: Record<string, string>,
552
- ): Record<string, string | number> {
858
+ function parseClassName(className: string, customColors: Record<string, string>): StyleObject {
553
859
  return parseClassNameFn(className, customColors);
554
860
  }
555
861
 
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Enhanced Pressable component with modifier support
3
+ * Injects disabled state into style function for disabled: modifier support
4
+ */
5
+ import { type PressableStateCallbackType, type PressableProps as RNPressableProps, type StyleProp, type ViewStyle } from "react-native";
6
+ type EnhancedPressableState = PressableStateCallbackType & {
7
+ disabled: boolean | null | undefined;
8
+ };
9
+ export type PressableProps = Omit<RNPressableProps, "style"> & {
10
+ /**
11
+ * Style can be a static style object/array or a function that receives Pressable state + disabled
12
+ */
13
+ style?: StyleProp<ViewStyle> | ((state: EnhancedPressableState) => StyleProp<ViewStyle>);
14
+ };
15
+ /**
16
+ * Enhanced Pressable that supports the disabled: modifier
17
+ *
18
+ * @example
19
+ * <Pressable
20
+ * disabled={isLoading}
21
+ * className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400"
22
+ * >
23
+ * <Text>Submit</Text>
24
+ * </Pressable>
25
+ */
26
+ export declare const Pressable: import("react").ForwardRefExoticComponent<Omit<RNPressableProps, "style"> & {
27
+ /**
28
+ * Style can be a static style object/array or a function that receives Pressable state + disabled
29
+ */
30
+ style?: StyleProp<ViewStyle> | ((state: EnhancedPressableState) => StyleProp<ViewStyle>);
31
+ } & import("react").RefAttributes<import("react-native").View>>;
32
+ export {};
@@ -0,0 +1 @@
1
+ var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.Pressable=void 0;var _objectWithoutProperties2=_interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));var _react=require("react");var _reactNative=require("react-native");var _jsxRuntime=require("react/jsx-runtime");var _jsxFileName="/Users/olivier/Projects/github/react-native-tailwind/src/components/Pressable.tsx";var _excluded=["style","disabled"];var Pressable=exports.Pressable=(0,_react.forwardRef)(function Pressable(_ref,ref){var style=_ref.style,_ref$disabled=_ref.disabled,disabled=_ref$disabled===void 0?false:_ref$disabled,props=(0,_objectWithoutProperties2.default)(_ref,_excluded);var resolvedStyle=typeof style==="function"?function(state){return style(Object.assign({},state,{disabled:disabled}));}:style;return(0,_jsxRuntime.jsx)(_reactNative.Pressable,Object.assign({ref:ref,disabled:disabled,style:resolvedStyle},props));});
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Enhanced TextInput component with focus state support for focus: modifier
3
+ *
4
+ * This component wraps React Native's TextInput and manages focus state internally,
5
+ * allowing the style prop to be a function that receives { focused: boolean }.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { TextInput } from '@mgcrea/react-native-tailwind';
10
+ *
11
+ * <TextInput
12
+ * className="border-2 border-gray-300 focus:border-blue-500 p-3 rounded-lg"
13
+ * placeholder="Email"
14
+ * />
15
+ * ```
16
+ */
17
+ import { TextInput as RNTextInput, type TextInputProps as RNTextInputProps } from "react-native";
18
+ export type TextInputProps = Omit<RNTextInputProps, "style"> & {
19
+ /**
20
+ * Style can be a static style object/array or a function that receives focus and disabled state
21
+ */
22
+ style?: RNTextInputProps["style"] | ((state: {
23
+ focused: boolean;
24
+ disabled: boolean;
25
+ }) => RNTextInputProps["style"]);
26
+ /**
27
+ * Convenience prop for disabled state (overrides editable if provided)
28
+ * When true, sets editable to false
29
+ */
30
+ disabled?: boolean;
31
+ };
32
+ /**
33
+ * Enhanced TextInput with focus and disabled state support
34
+ *
35
+ * Manages focus state internally and passes it to style functions,
36
+ * enabling the use of focus: and disabled: modifiers in className.
37
+ *
38
+ * Note: TextInput uses `editable` prop internally. You can pass either:
39
+ * - `disabled={true}` - convenience prop (sets editable to false)
40
+ * - `editable={false}` - React Native's native prop
41
+ * If both are provided, `disabled` takes precedence.
42
+ */
43
+ export declare const TextInput: import("react").ForwardRefExoticComponent<Omit<RNTextInputProps, "style"> & {
44
+ /**
45
+ * Style can be a static style object/array or a function that receives focus and disabled state
46
+ */
47
+ style?: RNTextInputProps["style"] | ((state: {
48
+ focused: boolean;
49
+ disabled: boolean;
50
+ }) => RNTextInputProps["style"]);
51
+ /**
52
+ * Convenience prop for disabled state (overrides editable if provided)
53
+ * When true, sets editable to false
54
+ */
55
+ disabled?: boolean;
56
+ } & import("react").RefAttributes<RNTextInput>>;
@@ -0,0 +1 @@
1
+ var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.TextInput=void 0;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));var _objectWithoutProperties2=_interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));var _react=require("react");var _reactNative=require("react-native");var _jsxRuntime=require("react/jsx-runtime");var _jsxFileName="/Users/olivier/Projects/github/react-native-tailwind/src/components/TextInput.tsx";var _excluded=["style","onFocus","onBlur","disabled","editable"];var TextInput=exports.TextInput=(0,_react.forwardRef)(function TextInput(_ref,ref){var style=_ref.style,onFocus=_ref.onFocus,onBlur=_ref.onBlur,disabled=_ref.disabled,_ref$editable=_ref.editable,editable=_ref$editable===void 0?true:_ref$editable,props=(0,_objectWithoutProperties2.default)(_ref,_excluded);var _useState=(0,_react.useState)(false),_useState2=(0,_slicedToArray2.default)(_useState,2),focused=_useState2[0],setFocused=_useState2[1];var handleFocus=(0,_react.useCallback)(function(e){setFocused(true);onFocus==null||onFocus(e);},[onFocus]);var handleBlur=(0,_react.useCallback)(function(e){setFocused(false);onBlur==null||onBlur(e);},[onBlur]);var isEditable=disabled!==undefined?!disabled:editable;var isDisabled=!isEditable;var resolvedStyle=typeof style==="function"?style({focused:focused,disabled:isDisabled}):style;return(0,_jsxRuntime.jsx)(_reactNative.TextInput,Object.assign({ref:ref,style:resolvedStyle,editable:isEditable,onFocus:handleFocus,onBlur:handleBlur},props));});
package/dist/index.d.ts CHANGED
@@ -5,8 +5,15 @@
5
5
  export { parseClass, parseClassName } from "./parser";
6
6
  export { generateStyleKey } from "./utils/styleKey";
7
7
  export type { RNStyle, StyleObject } from "./types";
8
- export { parseBorder, parseColor, parseLayout, parseSizing, parseSpacing, parseTypography } from "./parser";
8
+ export { parseAspectRatio, parseBorder, parseColor, parseLayout, parseShadow, parseSizing, parseSpacing, parseTypography, } from "./parser";
9
+ export { ASPECT_RATIO_PRESETS } from "./parser/aspectRatio";
9
10
  export { COLORS } from "./parser/colors";
11
+ export { INSET_SCALE, Z_INDEX_SCALE } from "./parser/layout";
12
+ export { SHADOW_SCALE } from "./parser/shadows";
10
13
  export { SIZE_PERCENTAGES, SIZE_SCALE } from "./parser/sizing";
11
14
  export { SPACING_SCALE } from "./parser/spacing";
12
- export { FONT_SIZES } from "./parser/typography";
15
+ export { FONT_SIZES, LETTER_SPACING_SCALE } from "./parser/typography";
16
+ export { Pressable } from "./components/Pressable";
17
+ export type { PressableProps } from "./components/Pressable";
18
+ export { TextInput } from "./components/TextInput";
19
+ export type { TextInputProps } from "./components/TextInput";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"COLORS",{enumerable:true,get:function get(){return _colors.COLORS;}});Object.defineProperty(exports,"FONT_SIZES",{enumerable:true,get:function get(){return _typography.FONT_SIZES;}});Object.defineProperty(exports,"SIZE_PERCENTAGES",{enumerable:true,get:function get(){return _sizing.SIZE_PERCENTAGES;}});Object.defineProperty(exports,"SIZE_SCALE",{enumerable:true,get:function get(){return _sizing.SIZE_SCALE;}});Object.defineProperty(exports,"SPACING_SCALE",{enumerable:true,get:function get(){return _spacing.SPACING_SCALE;}});Object.defineProperty(exports,"generateStyleKey",{enumerable:true,get:function get(){return _styleKey.generateStyleKey;}});Object.defineProperty(exports,"parseBorder",{enumerable:true,get:function get(){return _parser.parseBorder;}});Object.defineProperty(exports,"parseClass",{enumerable:true,get:function get(){return _parser.parseClass;}});Object.defineProperty(exports,"parseClassName",{enumerable:true,get:function get(){return _parser.parseClassName;}});Object.defineProperty(exports,"parseColor",{enumerable:true,get:function get(){return _parser.parseColor;}});Object.defineProperty(exports,"parseLayout",{enumerable:true,get:function get(){return _parser.parseLayout;}});Object.defineProperty(exports,"parseSizing",{enumerable:true,get:function get(){return _parser.parseSizing;}});Object.defineProperty(exports,"parseSpacing",{enumerable:true,get:function get(){return _parser.parseSpacing;}});Object.defineProperty(exports,"parseTypography",{enumerable:true,get:function get(){return _parser.parseTypography;}});var _parser=require("./parser");var _styleKey=require("./utils/styleKey");var _colors=require("./parser/colors");var _sizing=require("./parser/sizing");var _spacing=require("./parser/spacing");var _typography=require("./parser/typography");
1
+ Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"ASPECT_RATIO_PRESETS",{enumerable:true,get:function get(){return _aspectRatio.ASPECT_RATIO_PRESETS;}});Object.defineProperty(exports,"COLORS",{enumerable:true,get:function get(){return _colors.COLORS;}});Object.defineProperty(exports,"FONT_SIZES",{enumerable:true,get:function get(){return _typography.FONT_SIZES;}});Object.defineProperty(exports,"INSET_SCALE",{enumerable:true,get:function get(){return _layout.INSET_SCALE;}});Object.defineProperty(exports,"LETTER_SPACING_SCALE",{enumerable:true,get:function get(){return _typography.LETTER_SPACING_SCALE;}});Object.defineProperty(exports,"Pressable",{enumerable:true,get:function get(){return _Pressable.Pressable;}});Object.defineProperty(exports,"SHADOW_SCALE",{enumerable:true,get:function get(){return _shadows.SHADOW_SCALE;}});Object.defineProperty(exports,"SIZE_PERCENTAGES",{enumerable:true,get:function get(){return _sizing.SIZE_PERCENTAGES;}});Object.defineProperty(exports,"SIZE_SCALE",{enumerable:true,get:function get(){return _sizing.SIZE_SCALE;}});Object.defineProperty(exports,"SPACING_SCALE",{enumerable:true,get:function get(){return _spacing.SPACING_SCALE;}});Object.defineProperty(exports,"TextInput",{enumerable:true,get:function get(){return _TextInput.TextInput;}});Object.defineProperty(exports,"Z_INDEX_SCALE",{enumerable:true,get:function get(){return _layout.Z_INDEX_SCALE;}});Object.defineProperty(exports,"generateStyleKey",{enumerable:true,get:function get(){return _styleKey.generateStyleKey;}});Object.defineProperty(exports,"parseAspectRatio",{enumerable:true,get:function get(){return _parser.parseAspectRatio;}});Object.defineProperty(exports,"parseBorder",{enumerable:true,get:function get(){return _parser.parseBorder;}});Object.defineProperty(exports,"parseClass",{enumerable:true,get:function get(){return _parser.parseClass;}});Object.defineProperty(exports,"parseClassName",{enumerable:true,get:function get(){return _parser.parseClassName;}});Object.defineProperty(exports,"parseColor",{enumerable:true,get:function get(){return _parser.parseColor;}});Object.defineProperty(exports,"parseLayout",{enumerable:true,get:function get(){return _parser.parseLayout;}});Object.defineProperty(exports,"parseShadow",{enumerable:true,get:function get(){return _parser.parseShadow;}});Object.defineProperty(exports,"parseSizing",{enumerable:true,get:function get(){return _parser.parseSizing;}});Object.defineProperty(exports,"parseSpacing",{enumerable:true,get:function get(){return _parser.parseSpacing;}});Object.defineProperty(exports,"parseTypography",{enumerable:true,get:function get(){return _parser.parseTypography;}});var _parser=require("./parser");var _styleKey=require("./utils/styleKey");var _aspectRatio=require("./parser/aspectRatio");var _colors=require("./parser/colors");var _layout=require("./parser/layout");var _shadows=require("./parser/shadows");var _sizing=require("./parser/sizing");var _spacing=require("./parser/spacing");var _typography=require("./parser/typography");var _Pressable=require("./components/Pressable");var _TextInput=require("./components/TextInput");
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Aspect ratio utilities for React Native
3
+ * Uses aspectRatio style property (React Native 0.71+)
4
+ */
5
+ import type { StyleObject } from "../types";
6
+ /**
7
+ * Preset aspect ratios
8
+ */
9
+ declare const ASPECT_RATIO_PRESETS: Record<string, number | undefined>;
10
+ /**
11
+ * Parse aspect ratio classes
12
+ * @param cls - Class name to parse
13
+ * @returns Style object or null if not an aspect ratio class
14
+ */
15
+ export declare function parseAspectRatio(cls: string): StyleObject | null;
16
+ export { ASPECT_RATIO_PRESETS };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,"__esModule",{value:true});exports.ASPECT_RATIO_PRESETS=void 0;exports.parseAspectRatio=parseAspectRatio;var ASPECT_RATIO_PRESETS=exports.ASPECT_RATIO_PRESETS={"aspect-auto":undefined,"aspect-square":1,"aspect-video":16/9};function parseArbitraryAspectRatio(value){var match=value.match(/^\[(\d+)\/(\d+)\]$/);if(match){var numerator=Number.parseInt(match[1],10);var denominator=Number.parseInt(match[2],10);if(denominator===0){if(process.env.NODE_ENV!=="production"){console.warn(`[react-native-tailwind] Invalid aspect ratio: ${value}. Denominator cannot be zero.`);}return null;}return numerator/denominator;}return null;}function parseAspectRatio(cls){if(!cls.startsWith("aspect-")){return null;}if(cls in ASPECT_RATIO_PRESETS){var _aspectRatio=ASPECT_RATIO_PRESETS[cls];if(_aspectRatio===undefined){return{};}return{aspectRatio:_aspectRatio};}var arbitraryValue=cls.substring(7);var aspectRatio=parseArbitraryAspectRatio(arbitraryValue);if(aspectRatio!==null){return{aspectRatio:aspectRatio};}return null;}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ var _vitest=require("vitest");var _aspectRatio=require("./aspectRatio");(0,_vitest.describe)("ASPECT_RATIO_PRESETS",function(){(0,_vitest.it)("should export aspect ratio presets",function(){(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS).toMatchSnapshot();});(0,_vitest.it)("should have all preset values",function(){(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS).toHaveProperty("aspect-auto");(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS).toHaveProperty("aspect-square");(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS).toHaveProperty("aspect-video");});(0,_vitest.it)("should have correct preset values",function(){(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS["aspect-auto"]).toBeUndefined();(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS["aspect-square"]).toBe(1);(0,_vitest.expect)(_aspectRatio.ASPECT_RATIO_PRESETS["aspect-video"]).toBe(16/9);});});(0,_vitest.describe)("parseAspectRatio - preset values",function(){(0,_vitest.it)("should parse aspect-square",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-square")).toEqual({aspectRatio:1});});(0,_vitest.it)("should parse aspect-video",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-video")).toEqual({aspectRatio:16/9});});(0,_vitest.it)("should parse aspect-auto",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-auto")).toEqual({});});});(0,_vitest.describe)("parseAspectRatio - arbitrary values",function(){(0,_vitest.it)("should parse arbitrary aspect ratio values",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4/3]")).toEqual({aspectRatio:4/3});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[16/9]")).toEqual({aspectRatio:16/9});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[21/9]")).toEqual({aspectRatio:21/9});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[1/1]")).toEqual({aspectRatio:1});});(0,_vitest.it)("should handle arbitrary ratios with different aspect values",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[2/1]")).toEqual({aspectRatio:2});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[3/2]")).toEqual({aspectRatio:1.5});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[9/16]")).toEqual({aspectRatio:9/16});});(0,_vitest.it)("should handle arbitrary ratios with large numbers",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[100/50]")).toEqual({aspectRatio:2});(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[1920/1080]")).toEqual({aspectRatio:1920/1080});});(0,_vitest.it)("should calculate correct aspect ratio values",function(){var result=(0,_aspectRatio.parseAspectRatio)("aspect-[4/3]");(0,_vitest.expect)(result==null?void 0:result.aspectRatio).toBeCloseTo(1.333,3);var result2=(0,_aspectRatio.parseAspectRatio)("aspect-[16/9]");(0,_vitest.expect)(result2==null?void 0:result2.aspectRatio).toBeCloseTo(1.778,3);});});(0,_vitest.describe)("parseAspectRatio - edge cases",function(){(0,_vitest.it)("should return null for division by zero",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4/0]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[16/0]")).toBeNull();});(0,_vitest.it)("should return null for invalid arbitrary values",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[/3]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4/3/2]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[abc/def]")).toBeNull();});(0,_vitest.it)("should return null for malformed brackets",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4/3")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-4/3]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-4/3")).toBeNull();});(0,_vitest.it)("should return null for invalid class prefixes",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("ratio-[4/3]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("ar-[4/3]")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspectRatio-[4/3]")).toBeNull();});});(0,_vitest.describe)("parseAspectRatio - invalid classes",function(){(0,_vitest.it)("should return null for non-aspect classes",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("bg-blue-500")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("p-4")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("text-white")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("w-full")).toBeNull();});(0,_vitest.it)("should return null for invalid aspect class names",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-invalid")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-16-9")).toBeNull();});(0,_vitest.it)("should return null for empty or whitespace input",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)(" ")).toBeNull();});});(0,_vitest.describe)("parseAspectRatio - type validation",function(){(0,_vitest.it)("should return objects with correct property types",function(){var square=(0,_aspectRatio.parseAspectRatio)("aspect-square");(0,_vitest.expect)(typeof(square==null?void 0:square.aspectRatio)).toBe("number");var video=(0,_aspectRatio.parseAspectRatio)("aspect-video");(0,_vitest.expect)(typeof(video==null?void 0:video.aspectRatio)).toBe("number");var arbitrary=(0,_aspectRatio.parseAspectRatio)("aspect-[4/3]");(0,_vitest.expect)(typeof(arbitrary==null?void 0:arbitrary.aspectRatio)).toBe("number");});(0,_vitest.it)("should return null or object, never undefined",function(){var valid=(0,_aspectRatio.parseAspectRatio)("aspect-square");(0,_vitest.expect)(valid).not.toBeUndefined();(0,_vitest.expect)(typeof valid).toBe("object");var invalid=(0,_aspectRatio.parseAspectRatio)("invalid");(0,_vitest.expect)(invalid).toBeNull();});});(0,_vitest.describe)("parseAspectRatio - comprehensive coverage",function(){(0,_vitest.it)("should parse all preset variants without errors",function(){var presets=["aspect-auto","aspect-square","aspect-video"];presets.forEach(function(preset){var result=(0,_aspectRatio.parseAspectRatio)(preset);(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(typeof result).toBe("object");});});(0,_vitest.it)("should return consistent results for same input",function(){var result1=(0,_aspectRatio.parseAspectRatio)("aspect-square");var result2=(0,_aspectRatio.parseAspectRatio)("aspect-square");(0,_vitest.expect)(result1).toEqual(result2);var result3=(0,_aspectRatio.parseAspectRatio)("aspect-[4/3]");var result4=(0,_aspectRatio.parseAspectRatio)("aspect-[4/3]");(0,_vitest.expect)(result3).toEqual(result4);});(0,_vitest.it)("should handle case-sensitive class names",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("ASPECT-square")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("Aspect-video")).toBeNull();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-SQUARE")).toBeNull();});(0,_vitest.it)("should handle common aspect ratios",function(){(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[1/1]")).toBeTruthy();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[4/3]")).toBeTruthy();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[16/9]")).toBeTruthy();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[21/9]")).toBeTruthy();(0,_vitest.expect)((0,_aspectRatio.parseAspectRatio)("aspect-[9/16]")).toBeTruthy();});(0,_vitest.it)("should differentiate between preset and arbitrary values",function(){var preset=(0,_aspectRatio.parseAspectRatio)("aspect-video");(0,_vitest.expect)(preset).toEqual({aspectRatio:16/9});var arbitrary=(0,_aspectRatio.parseAspectRatio)("aspect-[16/9]");(0,_vitest.expect)(arbitrary).toEqual({aspectRatio:16/9});(0,_vitest.expect)(preset).toEqual(arbitrary);});(0,_vitest.it)("should handle fractional results correctly",function(){var result=(0,_aspectRatio.parseAspectRatio)("aspect-[5/7]");(0,_vitest.expect)(result==null?void 0:result.aspectRatio).toBeCloseTo(0.714,3);var result2=(0,_aspectRatio.parseAspectRatio)("aspect-[3/4]");(0,_vitest.expect)(result2==null?void 0:result2.aspectRatio).toBe(0.75);});});