@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.
- package/README.md +459 -39
- package/dist/babel/index.cjs +810 -279
- package/dist/babel/index.d.ts +2 -1
- package/dist/babel/index.ts +328 -22
- package/dist/components/Pressable.d.ts +32 -0
- package/dist/components/Pressable.js +1 -0
- package/dist/components/TextInput.d.ts +56 -0
- package/dist/components/TextInput.js +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +1 -1
- package/dist/parser/aspectRatio.d.ts +16 -0
- package/dist/parser/aspectRatio.js +1 -0
- package/dist/parser/aspectRatio.test.d.ts +1 -0
- package/dist/parser/aspectRatio.test.js +1 -0
- package/dist/parser/borders.js +1 -1
- package/dist/parser/borders.test.d.ts +1 -0
- package/dist/parser/borders.test.js +1 -0
- package/dist/parser/colors.d.ts +1 -0
- package/dist/parser/colors.js +1 -1
- package/dist/parser/colors.test.d.ts +1 -0
- package/dist/parser/colors.test.js +1 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +1 -1
- package/dist/parser/layout.d.ts +2 -0
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.d.ts +1 -0
- package/dist/parser/layout.test.js +1 -0
- package/dist/parser/modifiers.d.ts +47 -0
- package/dist/parser/modifiers.js +1 -0
- package/dist/parser/modifiers.test.d.ts +1 -0
- package/dist/parser/modifiers.test.js +1 -0
- package/dist/parser/shadows.d.ts +26 -0
- package/dist/parser/shadows.js +1 -0
- package/dist/parser/shadows.test.d.ts +1 -0
- package/dist/parser/shadows.test.js +1 -0
- package/dist/parser/sizing.test.d.ts +1 -0
- package/dist/parser/sizing.test.js +1 -0
- package/dist/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.d.ts +1 -0
- package/dist/parser/spacing.test.js +1 -0
- package/dist/parser/typography.d.ts +2 -1
- package/dist/parser/typography.js +1 -1
- package/dist/parser/typography.test.d.ts +1 -0
- package/dist/parser/typography.test.js +1 -0
- package/dist/types.d.ts +5 -2
- package/package.json +7 -6
- package/src/babel/index.ts +328 -22
- package/src/components/Pressable.tsx +46 -0
- package/src/components/TextInput.tsx +90 -0
- package/src/index.ts +20 -2
- package/src/parser/aspectRatio.test.ts +191 -0
- package/src/parser/aspectRatio.ts +73 -0
- package/src/parser/borders.test.ts +329 -0
- package/src/parser/borders.ts +187 -108
- package/src/parser/colors.test.ts +335 -0
- package/src/parser/colors.ts +117 -6
- package/src/parser/index.ts +13 -2
- package/src/parser/layout.test.ts +459 -0
- package/src/parser/layout.ts +128 -0
- package/src/parser/modifiers.test.ts +375 -0
- package/src/parser/modifiers.ts +104 -0
- package/src/parser/shadows.test.ts +201 -0
- package/src/parser/shadows.ts +133 -0
- package/src/parser/sizing.test.ts +256 -0
- package/src/parser/spacing.test.ts +226 -0
- package/src/parser/spacing.ts +93 -138
- package/src/parser/typography.test.ts +221 -0
- package/src/parser/typography.ts +143 -112
- package/src/types.ts +2 -2
- package/dist/react-native.d.js +0 -1
package/dist/babel/index.d.ts
CHANGED
|
@@ -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,
|
|
9
|
+
styleRegistry: Map<string, StyleObject>;
|
|
9
10
|
hasClassNames: boolean;
|
|
10
11
|
hasStyleSheetImport: boolean;
|
|
11
12
|
customColors: Record<string, string>;
|
package/dist/babel/index.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
327
|
-
const
|
|
490
|
+
// Check if className contains modifiers (active:, hover:, focus:)
|
|
491
|
+
const { baseClasses, modifierClasses } = splitModifierClasses(className);
|
|
328
492
|
|
|
329
|
-
//
|
|
330
|
-
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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
|
-
*
|
|
755
|
+
* Replace className with style function attribute (for Pressable with modifiers)
|
|
506
756
|
*/
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
|
|
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
|
|
843
|
+
// Create: const _tailwindStyles = StyleSheet.create({ ... })
|
|
535
844
|
const styleSheet = t.variableDeclaration("const", [
|
|
536
845
|
t.variableDeclarator(
|
|
537
|
-
t.identifier(
|
|
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);});});
|