clases 1.0.2 → 1.1.1

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 CHANGED
@@ -63,8 +63,71 @@ cl({
63
63
  });
64
64
  // Result: "md:hover:scale-105 md:hover:after:content-['*']"
65
65
  ```
66
+ ---
67
+
68
+ ## 🌈 Beautiful Syntax & Variants
69
+
70
+ The most powerful feature of this utility is **Transparent Logical Nesting**. It allows you to organize your design system using nested objects that represent your business logic (variants, states, or themes) without polluting the final CSS output.
71
+
72
+ #### How it Works
73
+
74
+ The engine distinguishes between **Registered Prefixes** (modifiers like `md`, `hover`, or `ui`) and **Logical Keys** (your own organizational names like `variants`, `primary`, or `[state]`):
75
+
76
+ * **Registered Keys**: Concatenate to form the final CSS prefix.
77
+ * **Unregistered Keys**: Act as transparent wrappers. They are ignored in the final string but pass the current prefix down to their children.
78
+
79
+ #### Component Variants Example
66
80
 
81
+ This structure allows you to colocate base styles, responsive modifiers, and interaction states within a single logical branch:
67
82
 
83
+ ```typescript
84
+ const variant = 'primary';
85
+ const theme = 'dark';
86
+
87
+ const className = cl({
88
+ md: {
89
+ // 'variants' is NOT in the registry, so it is transparent
90
+ variants: {
91
+ // We select the active branch using standard JS
92
+ [variant]: {
93
+ base: 'rounded-lg px-4 py-2 transition',
94
+ // 'dark' is a registered prefix, so it will be mapped
95
+ dark: 'border-white text-white',
96
+ hover: 'opacity-80'
97
+ },
98
+ secondary: 'bg-gray-200 text-black'
99
+ }[variant]
100
+ }
101
+ });
102
+
103
+ /**
104
+ * Output (for variant 'primary'):
105
+ * "md:rounded-lg md:px-4 md:py-2 md:transition md:dark:border-white md:dark:text-white md:hover:opacity-80"
106
+ */
107
+ ```
108
+
109
+ #### Why this is superior:
110
+
111
+ 1. **Clean DOM**: You won't see "ghost" prefixes like `variants:primary:bg-blue-500` in your HTML.
112
+ 2. **Zero Boilerplate**: You don't have to repeat `md:dark:...` for every single class; the engine handles the chain automatically.
113
+ 3. **Type-Safe Organization**: Use your own naming conventions to group styles while keeping the output perfectly compatible with Tailwind CSS.
114
+
115
+ #### Best Practice: Selection Logic
116
+
117
+ To keep the output optimized and prevent class collisions, handle the selection at the logical level so the engine only processes the "winning" branch:
118
+
119
+ ```typescript
120
+ cl({
121
+ ui: {
122
+ [status]: {
123
+ success: 'text-green-600',
124
+ error: 'text-red-600',
125
+ pending: 'text-yellow-600'
126
+ }[status]
127
+ }
128
+ });
129
+ ```
130
+ ---
68
131
 
69
132
  ### 🛠️ Custom Plugin Management
70
133
  You can stack the Tailwind plugin with your own semantic aliases or project-specific configs.
@@ -100,7 +163,6 @@ cl({
100
163
  lg: 'grid-cols-3 gap-8'
101
164
  });
102
165
  ```
103
-
104
166
  ---
105
167
 
106
168
  ## ⌨️ Why Objects?
package/dist/index.cjs CHANGED
@@ -17,13 +17,18 @@ function createCl(...plugins) {
17
17
  }
18
18
  if (typeof value === "object") {
19
19
  return Object.entries(value).map(([nestedKey, nestedValue]) => {
20
- const combinedKey = key === "base" ? nestedKey : nestedKey === "base" ? key : `${key}:${nestedKey}`;
21
- return process(combinedKey, nestedValue);
20
+ const isRegistered = registry[nestedKey] !== void 0;
21
+ const nextKey = key === "base" ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;
22
+ return process(nextKey, nestedValue);
22
23
  }).join(" ");
23
24
  }
24
- const prefix = registry[key] || key;
25
+ const resolvedPrefix = key.split(":").map((part) => {
26
+ if (part === "base") return null;
27
+ if (registry[part]) return registry[part];
28
+ return null;
29
+ }).filter(Boolean).join(":");
25
30
  if (typeof value === "string") {
26
- return value.split(/[,\s\n]+/).filter(Boolean).map((cls) => prefix === "base" ? cls : `${prefix}:${cls}`).join(" ");
31
+ return value.split(/[,\s\n]+/).filter(Boolean).map((cls) => !resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`).join(" ");
27
32
  }
28
33
  return "";
29
34
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["twMerge","clsx"],"mappings":";;;;;;;;;;AAsBO,SAAS,YAAuD,OAAA,EAAmB;AAEtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAOnF,EAAA,MAAM,OAAA,GAAU,CAAC,GAAA,EAAa,KAAA,KAAuB;AACjD,IAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AAEnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtB,MAAA,OAAO,KAAA,CACF,GAAA,CAAI,CAAC,CAAA,KAAM,OAAA,CAAQ,GAAA,EAAK,CAAC,CAAC,CAAA,CAC1B,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAAA,IACjB;AAEA,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,MAAA,CAAO,QAAQ,KAAK,CAAA,CACtB,IAAI,CAAC,CAAC,SAAA,EAAW,WAAW,CAAA,KAAM;AAE/B,QAAA,MAAM,WAAA,GACF,GAAA,KAAQ,MAAA,GACF,SAAA,GACA,SAAA,KAAc,SACd,GAAA,GACA,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AAE7B,QAAA,OAAO,OAAA,CAAQ,aAAa,WAAW,CAAA;AAAA,MAC3C,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACjB;AAEA,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAG,CAAA,IAAK,GAAA;AAEhC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,KAAA,CACF,MAAM,UAAU,CAAA,CAChB,OAAO,OAAO,CAAA,CACd,IAAI,CAAC,GAAA,KAAS,WAAW,MAAA,GAAS,GAAA,GAAM,GAAG,MAAM,CAAA,CAAA,EAAI,GAAG,CAAA,CAAG,CAAA,CAC3D,KAAK,GAAG,CAAA;AAAA,IACjB;AACA,IAAA,OAAO,EAAA;AAAA,EACX,CAAA;AASA,EAAA,OAAO,IAAI,MAAA,KAAoF;AAC3F,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AACpC,MAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtE,QAAA,OAAO,OAAO,OAAA,CAAQ,KAAK,EACtB,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAO,CAAA,KAAM,IAAA,GAAO,IAAI,OAAA,CAAQ,CAAA,EAAG,CAAC,CAAE,CAAA,CAChD,KAAK,GAAG,CAAA;AAAA,MACjB;AACA,MAAA,OAAO,KAAA;AAAA,IACX,CAAC,CAAA;AACD,IAAA,OAAOA,qBAAA,CAAQC,qBAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EAClC,CAAA;AACJ","file":"index.cjs","sourcesContent":["import { twMerge } from 'tailwind-merge';\r\nimport clsx, { type ClassValue } from 'clsx';\r\n\r\n/**\r\n * Helper type to merge an array of objects into one single type.\r\n * This provides the user with full autocomplete for ALL combined plugins.\r\n */\r\ntype MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest]\r\n ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {})\r\n : {};\r\n\r\n/**\r\n * Creates a customized class utility instance with plugin support.\r\n * * This factory function merges multiple prefix plugins and returns a scoped\r\n * `cl` function that handles recursive prefixing, tailwind-merge, and clsx logic.\r\n * * @param plugins - One or more objects defining prefix maps (e.g., { btn: 'button' }).\r\n * @returns A specialized `cl` function with autocompletion for the provided plugins.\r\n * * @example\r\n * const myCl = createCl({ ui: 'prefix' });\r\n * // Autocomplete will suggest 'ui' or 'base'\r\n * myCl({ ui: { primary: true } }); // 'prefix:primary'\r\n */\r\nexport function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {\r\n // Core merges all plugins into a single registry automatically\r\n const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);\r\n\r\n type CombinedKeys = keyof MergePlugins<TPlugins>;\r\n\r\n /**\r\n * Internal processor to handle recursive key chaining and prefix mapping.\r\n */\r\n const process = (key: string, value: any): string => {\r\n if (!value) return '';\r\n\r\n if (Array.isArray(value)) {\r\n return value\r\n .map((v) => process(key, v))\r\n .filter(Boolean)\r\n .join(' ');\r\n }\r\n\r\n if (typeof value === 'object') {\r\n return Object.entries(value)\r\n .map(([nestedKey, nestedValue]) => {\r\n // FIX: If the nested key is 'base', it doesn't add to the prefix chain\r\n const combinedKey =\r\n key === 'base'\r\n ? nestedKey\r\n : nestedKey === 'base'\r\n ? key // If nested is base, keep the parent key\r\n : `${key}:${nestedKey}`;\r\n\r\n return process(combinedKey, nestedValue);\r\n })\r\n .join(' ');\r\n }\r\n\r\n const prefix = registry[key] || key;\r\n\r\n if (typeof value === 'string') {\r\n return value\r\n .split(/[,\\s\\n]+/)\r\n .filter(Boolean)\r\n .map((cls) => (prefix === 'base' ? cls : `${prefix}:${cls}`))\r\n .join(' ');\r\n }\r\n return '';\r\n };\r\n\r\n /**\r\n * Optimized class utility function.\r\n * * Combines multiple arguments into a single string, resolving Tailwind conflicts\r\n * and applying the plugin prefixing logic defined during creation.\r\n * * @param inputs - Arguments can be strings, objects with plugin keys, or standard ClassValues.\r\n * @returns A merged string of CSS classes.\r\n */\r\n return (...inputs: (ClassValue | { [K in CombinedKeys | 'base' | (string & {})]?: any })[]) => {\r\n const processed = inputs.map((input) => {\r\n if (input !== null && typeof input === 'object' && !Array.isArray(input)) {\r\n return Object.entries(input)\r\n .map(([k, v]) => (v === true ? k : process(k, v)))\r\n .join(' ');\r\n }\r\n return input;\r\n });\r\n return twMerge(clsx(processed));\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["twMerge","clsx"],"mappings":";;;;;;;;;;AASO,SAAS,YAAuD,OAAA,EAAmB;AAMtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAQnF,EAAA,MAAM,OAAA,GAAU,CAAC,GAAA,EAAa,KAAA,KAAuB;AACjD,IAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AAGnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtB,MAAA,OAAO,KAAA,CACF,GAAA,CAAI,CAAC,CAAA,KAAM,OAAA,CAAQ,GAAA,EAAK,CAAC,CAAC,CAAA,CAC1B,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAAA,IACjB;AAGA,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,MAAA,CAAO,QAAQ,KAAK,CAAA,CACtB,IAAI,CAAC,CAAC,SAAA,EAAW,WAAW,CAAA,KAAM;AAM/B,QAAA,MAAM,YAAA,GAAe,QAAA,CAAS,SAAS,CAAA,KAAM,MAAA;AAC7C,QAAA,MAAM,OAAA,GACF,QAAQ,MAAA,GAAS,SAAA,GAAY,eAAe,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,GAAK,GAAA;AAExE,QAAA,OAAO,OAAA,CAAQ,SAAS,WAAW,CAAA;AAAA,MACvC,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACjB;AAOA,IAAA,MAAM,iBAAiB,GAAA,CAClB,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,CAAC,IAAA,KAAS;AACX,MAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,IAAA;AAE5B,MAAA,IAAI,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,SAAS,IAAI,CAAA;AAExC,MAAA,OAAO,IAAA;AAAA,IACX,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAGb,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,KAAA,CACF,MAAM,UAAU,CAAA,CAChB,OAAO,OAAO,CAAA,CACd,IAAI,CAAC,GAAA,KAAS,CAAC,cAAA,GAAiB,GAAA,GAAM,GAAG,cAAc,CAAA,CAAA,EAAI,GAAG,CAAA,CAAG,CAAA,CACjE,KAAK,GAAG,CAAA;AAAA,IACjB;AACA,IAAA,OAAO,EAAA;AAAA,EACX,CAAA;AAQA,EAAA,OAAO,IAAI,MAAA,KAAkB;AACzB,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AACpC,MAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtE,QAAA,OAAO,OAAO,OAAA,CAAQ,KAAK,EACtB,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAO,CAAA,KAAM,IAAA,GAAO,IAAI,OAAA,CAAQ,CAAA,EAAG,CAAC,CAAE,CAAA,CAChD,KAAK,GAAG,CAAA;AAAA,MACjB;AACA,MAAA,OAAO,KAAA;AAAA,IACX,CAAC,CAAA;AACD,IAAA,OAAOA,qBAAA,CAAQC,qBAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EAClC,CAAA;AACJ","file":"index.cjs","sourcesContent":["import { twMerge } from 'tailwind-merge';\r\nimport clsx from 'clsx';\r\n\r\n/**\r\n * Creates a specialized utility for managing CSS classes with prefix support,\r\n * plugin mapping, and transparent logical nesting.\r\n * * @param plugins - An array of objects mapping custom aliases to real CSS prefixes.\r\n * @returns A function that processes class values, objects, and nested structures.\r\n */\r\nexport function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {\r\n /**\r\n * Internal registry that stores all official prefixes.\r\n * Any key not found here will be treated as a \"transparent\" logical container\r\n * and will be discarded in the final string resolution.\r\n */\r\n const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);\r\n\r\n /**\r\n * Recursively processes keys and values to build the prefixed class string.\r\n * * @param key - The current accumulated prefix path.\r\n * @param value - The class value, array, or nested object to process.\r\n * @returns A space-separated string of prefixed classes.\r\n */\r\n const process = (key: string, value: any): string => {\r\n if (!value) return '';\r\n\r\n // Handle Arrays: Process each element with the current key\r\n if (Array.isArray(value)) {\r\n return value\r\n .map((v) => process(key, v))\r\n .filter(Boolean)\r\n .join(' ');\r\n }\r\n\r\n // Handle Objects: Manage nesting and logical transparency\r\n if (typeof value === 'object') {\r\n return Object.entries(value)\r\n .map(([nestedKey, nestedValue]) => {\r\n /**\r\n * Rule: If the child key is registered, we concatenate it.\r\n * If it's not registered, it's a \"logical\" key (transparent),\r\n * so we inherit the parent's prefix to keep the path clean.\r\n */\r\n const isRegistered = registry[nestedKey] !== undefined;\r\n const nextKey =\r\n key === 'base' ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;\r\n\r\n return process(nextKey, nestedValue);\r\n })\r\n .join(' ');\r\n }\r\n\r\n /**\r\n * FINAL RESOLUTION\r\n * Maps aliases (e.g., 'ui' -> 'prefix') and filters out any part\r\n * of the path that is not explicitly registered in the registry.\r\n */\r\n const resolvedPrefix = key\r\n .split(':')\r\n .map((part) => {\r\n if (part === 'base') return null;\r\n // Only return the part if it's found in our registry\r\n if (registry[part]) return registry[part];\r\n // Otherwise, it's a logical container and should be ignored\r\n return null;\r\n })\r\n .filter(Boolean)\r\n .join(':');\r\n\r\n // Apply the resolved prefix to each class in the string\r\n if (typeof value === 'string') {\r\n return value\r\n .split(/[,\\s\\n]+/)\r\n .filter(Boolean)\r\n .map((cls) => (!resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`))\r\n .join(' ');\r\n }\r\n return '';\r\n };\r\n\r\n /**\r\n * The final utility function.\r\n * Processes inputs through the prefix engine and cleans them using tailwind-merge.\r\n * * @param inputs - Variadic arguments including strings, objects, arrays, or booleans.\r\n * @returns A merged and optimized string of Tailwind CSS classes.\r\n */\r\n return (...inputs: any[]) => {\r\n const processed = inputs.map((input) => {\r\n if (input !== null && typeof input === 'object' && !Array.isArray(input)) {\r\n return Object.entries(input)\r\n .map(([k, v]) => (v === true ? k : process(k, v)))\r\n .join(' ');\r\n }\r\n return input;\r\n });\r\n return twMerge(clsx(processed));\r\n };\r\n}\r\n"]}
package/dist/index.d.cts CHANGED
@@ -1,21 +1,9 @@
1
- import { ClassValue } from 'clsx';
2
-
3
- /**
4
- * Helper type to merge an array of objects into one single type.
5
- * This provides the user with full autocomplete for ALL combined plugins.
6
- */
7
- type MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest] ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {}) : {};
8
1
  /**
9
- * Creates a customized class utility instance with plugin support.
10
- * * This factory function merges multiple prefix plugins and returns a scoped
11
- * `cl` function that handles recursive prefixing, tailwind-merge, and clsx logic.
12
- * * @param plugins - One or more objects defining prefix maps (e.g., { btn: 'button' }).
13
- * @returns A specialized `cl` function with autocompletion for the provided plugins.
14
- * * @example
15
- * const myCl = createCl({ ui: 'prefix' });
16
- * // Autocomplete will suggest 'ui' or 'base'
17
- * myCl({ ui: { primary: true } }); // 'prefix:primary'
2
+ * Creates a specialized utility for managing CSS classes with prefix support,
3
+ * plugin mapping, and transparent logical nesting.
4
+ * * @param plugins - An array of objects mapping custom aliases to real CSS prefixes.
5
+ * @returns A function that processes class values, objects, and nested structures.
18
6
  */
19
- declare function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins): (...inputs: (ClassValue | { [K in keyof MergePlugins<TPlugins> | "base" | (string & {})]?: any; })[]) => string;
7
+ declare function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins): (...inputs: any[]) => string;
20
8
 
21
9
  export { createCl };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,9 @@
1
- import { ClassValue } from 'clsx';
2
-
3
- /**
4
- * Helper type to merge an array of objects into one single type.
5
- * This provides the user with full autocomplete for ALL combined plugins.
6
- */
7
- type MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest] ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {}) : {};
8
1
  /**
9
- * Creates a customized class utility instance with plugin support.
10
- * * This factory function merges multiple prefix plugins and returns a scoped
11
- * `cl` function that handles recursive prefixing, tailwind-merge, and clsx logic.
12
- * * @param plugins - One or more objects defining prefix maps (e.g., { btn: 'button' }).
13
- * @returns A specialized `cl` function with autocompletion for the provided plugins.
14
- * * @example
15
- * const myCl = createCl({ ui: 'prefix' });
16
- * // Autocomplete will suggest 'ui' or 'base'
17
- * myCl({ ui: { primary: true } }); // 'prefix:primary'
2
+ * Creates a specialized utility for managing CSS classes with prefix support,
3
+ * plugin mapping, and transparent logical nesting.
4
+ * * @param plugins - An array of objects mapping custom aliases to real CSS prefixes.
5
+ * @returns A function that processes class values, objects, and nested structures.
18
6
  */
19
- declare function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins): (...inputs: (ClassValue | { [K in keyof MergePlugins<TPlugins> | "base" | (string & {})]?: any; })[]) => string;
7
+ declare function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins): (...inputs: any[]) => string;
20
8
 
21
9
  export { createCl };
package/dist/index.js CHANGED
@@ -11,13 +11,18 @@ function createCl(...plugins) {
11
11
  }
12
12
  if (typeof value === "object") {
13
13
  return Object.entries(value).map(([nestedKey, nestedValue]) => {
14
- const combinedKey = key === "base" ? nestedKey : nestedKey === "base" ? key : `${key}:${nestedKey}`;
15
- return process(combinedKey, nestedValue);
14
+ const isRegistered = registry[nestedKey] !== void 0;
15
+ const nextKey = key === "base" ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;
16
+ return process(nextKey, nestedValue);
16
17
  }).join(" ");
17
18
  }
18
- const prefix = registry[key] || key;
19
+ const resolvedPrefix = key.split(":").map((part) => {
20
+ if (part === "base") return null;
21
+ if (registry[part]) return registry[part];
22
+ return null;
23
+ }).filter(Boolean).join(":");
19
24
  if (typeof value === "string") {
20
- return value.split(/[,\s\n]+/).filter(Boolean).map((cls) => prefix === "base" ? cls : `${prefix}:${cls}`).join(" ");
25
+ return value.split(/[,\s\n]+/).filter(Boolean).map((cls) => !resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`).join(" ");
21
26
  }
22
27
  return "";
23
28
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;AAsBO,SAAS,YAAuD,OAAA,EAAmB;AAEtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAOnF,EAAA,MAAM,OAAA,GAAU,CAAC,GAAA,EAAa,KAAA,KAAuB;AACjD,IAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AAEnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtB,MAAA,OAAO,KAAA,CACF,GAAA,CAAI,CAAC,CAAA,KAAM,OAAA,CAAQ,GAAA,EAAK,CAAC,CAAC,CAAA,CAC1B,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAAA,IACjB;AAEA,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,MAAA,CAAO,QAAQ,KAAK,CAAA,CACtB,IAAI,CAAC,CAAC,SAAA,EAAW,WAAW,CAAA,KAAM;AAE/B,QAAA,MAAM,WAAA,GACF,GAAA,KAAQ,MAAA,GACF,SAAA,GACA,SAAA,KAAc,SACd,GAAA,GACA,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AAE7B,QAAA,OAAO,OAAA,CAAQ,aAAa,WAAW,CAAA;AAAA,MAC3C,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACjB;AAEA,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAG,CAAA,IAAK,GAAA;AAEhC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,KAAA,CACF,MAAM,UAAU,CAAA,CAChB,OAAO,OAAO,CAAA,CACd,IAAI,CAAC,GAAA,KAAS,WAAW,MAAA,GAAS,GAAA,GAAM,GAAG,MAAM,CAAA,CAAA,EAAI,GAAG,CAAA,CAAG,CAAA,CAC3D,KAAK,GAAG,CAAA;AAAA,IACjB;AACA,IAAA,OAAO,EAAA;AAAA,EACX,CAAA;AASA,EAAA,OAAO,IAAI,MAAA,KAAoF;AAC3F,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AACpC,MAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtE,QAAA,OAAO,OAAO,OAAA,CAAQ,KAAK,EACtB,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAO,CAAA,KAAM,IAAA,GAAO,IAAI,OAAA,CAAQ,CAAA,EAAG,CAAC,CAAE,CAAA,CAChD,KAAK,GAAG,CAAA;AAAA,MACjB;AACA,MAAA,OAAO,KAAA;AAAA,IACX,CAAC,CAAA;AACD,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EAClC,CAAA;AACJ","file":"index.js","sourcesContent":["import { twMerge } from 'tailwind-merge';\r\nimport clsx, { type ClassValue } from 'clsx';\r\n\r\n/**\r\n * Helper type to merge an array of objects into one single type.\r\n * This provides the user with full autocomplete for ALL combined plugins.\r\n */\r\ntype MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest]\r\n ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {})\r\n : {};\r\n\r\n/**\r\n * Creates a customized class utility instance with plugin support.\r\n * * This factory function merges multiple prefix plugins and returns a scoped\r\n * `cl` function that handles recursive prefixing, tailwind-merge, and clsx logic.\r\n * * @param plugins - One or more objects defining prefix maps (e.g., { btn: 'button' }).\r\n * @returns A specialized `cl` function with autocompletion for the provided plugins.\r\n * * @example\r\n * const myCl = createCl({ ui: 'prefix' });\r\n * // Autocomplete will suggest 'ui' or 'base'\r\n * myCl({ ui: { primary: true } }); // 'prefix:primary'\r\n */\r\nexport function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {\r\n // Core merges all plugins into a single registry automatically\r\n const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);\r\n\r\n type CombinedKeys = keyof MergePlugins<TPlugins>;\r\n\r\n /**\r\n * Internal processor to handle recursive key chaining and prefix mapping.\r\n */\r\n const process = (key: string, value: any): string => {\r\n if (!value) return '';\r\n\r\n if (Array.isArray(value)) {\r\n return value\r\n .map((v) => process(key, v))\r\n .filter(Boolean)\r\n .join(' ');\r\n }\r\n\r\n if (typeof value === 'object') {\r\n return Object.entries(value)\r\n .map(([nestedKey, nestedValue]) => {\r\n // FIX: If the nested key is 'base', it doesn't add to the prefix chain\r\n const combinedKey =\r\n key === 'base'\r\n ? nestedKey\r\n : nestedKey === 'base'\r\n ? key // If nested is base, keep the parent key\r\n : `${key}:${nestedKey}`;\r\n\r\n return process(combinedKey, nestedValue);\r\n })\r\n .join(' ');\r\n }\r\n\r\n const prefix = registry[key] || key;\r\n\r\n if (typeof value === 'string') {\r\n return value\r\n .split(/[,\\s\\n]+/)\r\n .filter(Boolean)\r\n .map((cls) => (prefix === 'base' ? cls : `${prefix}:${cls}`))\r\n .join(' ');\r\n }\r\n return '';\r\n };\r\n\r\n /**\r\n * Optimized class utility function.\r\n * * Combines multiple arguments into a single string, resolving Tailwind conflicts\r\n * and applying the plugin prefixing logic defined during creation.\r\n * * @param inputs - Arguments can be strings, objects with plugin keys, or standard ClassValues.\r\n * @returns A merged string of CSS classes.\r\n */\r\n return (...inputs: (ClassValue | { [K in CombinedKeys | 'base' | (string & {})]?: any })[]) => {\r\n const processed = inputs.map((input) => {\r\n if (input !== null && typeof input === 'object' && !Array.isArray(input)) {\r\n return Object.entries(input)\r\n .map(([k, v]) => (v === true ? k : process(k, v)))\r\n .join(' ');\r\n }\r\n return input;\r\n });\r\n return twMerge(clsx(processed));\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;AASO,SAAS,YAAuD,OAAA,EAAmB;AAMtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAQnF,EAAA,MAAM,OAAA,GAAU,CAAC,GAAA,EAAa,KAAA,KAAuB;AACjD,IAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AAGnB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtB,MAAA,OAAO,KAAA,CACF,GAAA,CAAI,CAAC,CAAA,KAAM,OAAA,CAAQ,GAAA,EAAK,CAAC,CAAC,CAAA,CAC1B,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAAA,IACjB;AAGA,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,MAAA,CAAO,QAAQ,KAAK,CAAA,CACtB,IAAI,CAAC,CAAC,SAAA,EAAW,WAAW,CAAA,KAAM;AAM/B,QAAA,MAAM,YAAA,GAAe,QAAA,CAAS,SAAS,CAAA,KAAM,MAAA;AAC7C,QAAA,MAAM,OAAA,GACF,QAAQ,MAAA,GAAS,SAAA,GAAY,eAAe,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,GAAK,GAAA;AAExE,QAAA,OAAO,OAAA,CAAQ,SAAS,WAAW,CAAA;AAAA,MACvC,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACjB;AAOA,IAAA,MAAM,iBAAiB,GAAA,CAClB,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,CAAC,IAAA,KAAS;AACX,MAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,IAAA;AAE5B,MAAA,IAAI,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,SAAS,IAAI,CAAA;AAExC,MAAA,OAAO,IAAA;AAAA,IACX,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAGb,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC3B,MAAA,OAAO,KAAA,CACF,MAAM,UAAU,CAAA,CAChB,OAAO,OAAO,CAAA,CACd,IAAI,CAAC,GAAA,KAAS,CAAC,cAAA,GAAiB,GAAA,GAAM,GAAG,cAAc,CAAA,CAAA,EAAI,GAAG,CAAA,CAAG,CAAA,CACjE,KAAK,GAAG,CAAA;AAAA,IACjB;AACA,IAAA,OAAO,EAAA;AAAA,EACX,CAAA;AAQA,EAAA,OAAO,IAAI,MAAA,KAAkB;AACzB,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AACpC,MAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtE,QAAA,OAAO,OAAO,OAAA,CAAQ,KAAK,EACtB,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAO,CAAA,KAAM,IAAA,GAAO,IAAI,OAAA,CAAQ,CAAA,EAAG,CAAC,CAAE,CAAA,CAChD,KAAK,GAAG,CAAA;AAAA,MACjB;AACA,MAAA,OAAO,KAAA;AAAA,IACX,CAAC,CAAA;AACD,IAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EAClC,CAAA;AACJ","file":"index.js","sourcesContent":["import { twMerge } from 'tailwind-merge';\r\nimport clsx from 'clsx';\r\n\r\n/**\r\n * Creates a specialized utility for managing CSS classes with prefix support,\r\n * plugin mapping, and transparent logical nesting.\r\n * * @param plugins - An array of objects mapping custom aliases to real CSS prefixes.\r\n * @returns A function that processes class values, objects, and nested structures.\r\n */\r\nexport function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {\r\n /**\r\n * Internal registry that stores all official prefixes.\r\n * Any key not found here will be treated as a \"transparent\" logical container\r\n * and will be discarded in the final string resolution.\r\n */\r\n const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);\r\n\r\n /**\r\n * Recursively processes keys and values to build the prefixed class string.\r\n * * @param key - The current accumulated prefix path.\r\n * @param value - The class value, array, or nested object to process.\r\n * @returns A space-separated string of prefixed classes.\r\n */\r\n const process = (key: string, value: any): string => {\r\n if (!value) return '';\r\n\r\n // Handle Arrays: Process each element with the current key\r\n if (Array.isArray(value)) {\r\n return value\r\n .map((v) => process(key, v))\r\n .filter(Boolean)\r\n .join(' ');\r\n }\r\n\r\n // Handle Objects: Manage nesting and logical transparency\r\n if (typeof value === 'object') {\r\n return Object.entries(value)\r\n .map(([nestedKey, nestedValue]) => {\r\n /**\r\n * Rule: If the child key is registered, we concatenate it.\r\n * If it's not registered, it's a \"logical\" key (transparent),\r\n * so we inherit the parent's prefix to keep the path clean.\r\n */\r\n const isRegistered = registry[nestedKey] !== undefined;\r\n const nextKey =\r\n key === 'base' ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;\r\n\r\n return process(nextKey, nestedValue);\r\n })\r\n .join(' ');\r\n }\r\n\r\n /**\r\n * FINAL RESOLUTION\r\n * Maps aliases (e.g., 'ui' -> 'prefix') and filters out any part\r\n * of the path that is not explicitly registered in the registry.\r\n */\r\n const resolvedPrefix = key\r\n .split(':')\r\n .map((part) => {\r\n if (part === 'base') return null;\r\n // Only return the part if it's found in our registry\r\n if (registry[part]) return registry[part];\r\n // Otherwise, it's a logical container and should be ignored\r\n return null;\r\n })\r\n .filter(Boolean)\r\n .join(':');\r\n\r\n // Apply the resolved prefix to each class in the string\r\n if (typeof value === 'string') {\r\n return value\r\n .split(/[,\\s\\n]+/)\r\n .filter(Boolean)\r\n .map((cls) => (!resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`))\r\n .join(' ');\r\n }\r\n return '';\r\n };\r\n\r\n /**\r\n * The final utility function.\r\n * Processes inputs through the prefix engine and cleans them using tailwind-merge.\r\n * * @param inputs - Variadic arguments including strings, objects, arrays, or booleans.\r\n * @returns A merged and optimized string of Tailwind CSS classes.\r\n */\r\n return (...inputs: any[]) => {\r\n const processed = inputs.map((input) => {\r\n if (input !== null && typeof input === 'object' && !Array.isArray(input)) {\r\n return Object.entries(input)\r\n .map(([k, v]) => (v === true ? k : process(k, v)))\r\n .join(' ');\r\n }\r\n return input;\r\n });\r\n return twMerge(clsx(processed));\r\n };\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clases",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,37 +1,30 @@
1
1
  import { twMerge } from 'tailwind-merge';
2
- import clsx, { type ClassValue } from 'clsx';
2
+ import clsx from 'clsx';
3
3
 
4
4
  /**
5
- * Helper type to merge an array of objects into one single type.
6
- * This provides the user with full autocomplete for ALL combined plugins.
7
- */
8
- type MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest]
9
- ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {})
10
- : {};
11
-
12
- /**
13
- * Creates a customized class utility instance with plugin support.
14
- * * This factory function merges multiple prefix plugins and returns a scoped
15
- * `cl` function that handles recursive prefixing, tailwind-merge, and clsx logic.
16
- * * @param plugins - One or more objects defining prefix maps (e.g., { btn: 'button' }).
17
- * @returns A specialized `cl` function with autocompletion for the provided plugins.
18
- * * @example
19
- * const myCl = createCl({ ui: 'prefix' });
20
- * // Autocomplete will suggest 'ui' or 'base'
21
- * myCl({ ui: { primary: true } }); // 'prefix:primary'
5
+ * Creates a specialized utility for managing CSS classes with prefix support,
6
+ * plugin mapping, and transparent logical nesting.
7
+ * * @param plugins - An array of objects mapping custom aliases to real CSS prefixes.
8
+ * @returns A function that processes class values, objects, and nested structures.
22
9
  */
23
10
  export function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {
24
- // Core merges all plugins into a single registry automatically
11
+ /**
12
+ * Internal registry that stores all official prefixes.
13
+ * Any key not found here will be treated as a "transparent" logical container
14
+ * and will be discarded in the final string resolution.
15
+ */
25
16
  const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);
26
17
 
27
- type CombinedKeys = keyof MergePlugins<TPlugins>;
28
-
29
18
  /**
30
- * Internal processor to handle recursive key chaining and prefix mapping.
19
+ * Recursively processes keys and values to build the prefixed class string.
20
+ * * @param key - The current accumulated prefix path.
21
+ * @param value - The class value, array, or nested object to process.
22
+ * @returns A space-separated string of prefixed classes.
31
23
  */
32
24
  const process = (key: string, value: any): string => {
33
25
  if (!value) return '';
34
26
 
27
+ // Handle Arrays: Process each element with the current key
35
28
  if (Array.isArray(value)) {
36
29
  return value
37
30
  .map((v) => process(key, v))
@@ -39,42 +32,59 @@ export function createCl<TPlugins extends Record<string, string>[]>(...plugins:
39
32
  .join(' ');
40
33
  }
41
34
 
35
+ // Handle Objects: Manage nesting and logical transparency
42
36
  if (typeof value === 'object') {
43
37
  return Object.entries(value)
44
38
  .map(([nestedKey, nestedValue]) => {
45
- // FIX: If the nested key is 'base', it doesn't add to the prefix chain
46
- const combinedKey =
47
- key === 'base'
48
- ? nestedKey
49
- : nestedKey === 'base'
50
- ? key // If nested is base, keep the parent key
51
- : `${key}:${nestedKey}`;
39
+ /**
40
+ * Rule: If the child key is registered, we concatenate it.
41
+ * If it's not registered, it's a "logical" key (transparent),
42
+ * so we inherit the parent's prefix to keep the path clean.
43
+ */
44
+ const isRegistered = registry[nestedKey] !== undefined;
45
+ const nextKey =
46
+ key === 'base' ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;
52
47
 
53
- return process(combinedKey, nestedValue);
48
+ return process(nextKey, nestedValue);
54
49
  })
55
50
  .join(' ');
56
51
  }
57
52
 
58
- const prefix = registry[key] || key;
53
+ /**
54
+ * FINAL RESOLUTION
55
+ * Maps aliases (e.g., 'ui' -> 'prefix') and filters out any part
56
+ * of the path that is not explicitly registered in the registry.
57
+ */
58
+ const resolvedPrefix = key
59
+ .split(':')
60
+ .map((part) => {
61
+ if (part === 'base') return null;
62
+ // Only return the part if it's found in our registry
63
+ if (registry[part]) return registry[part];
64
+ // Otherwise, it's a logical container and should be ignored
65
+ return null;
66
+ })
67
+ .filter(Boolean)
68
+ .join(':');
59
69
 
70
+ // Apply the resolved prefix to each class in the string
60
71
  if (typeof value === 'string') {
61
72
  return value
62
73
  .split(/[,\s\n]+/)
63
74
  .filter(Boolean)
64
- .map((cls) => (prefix === 'base' ? cls : `${prefix}:${cls}`))
75
+ .map((cls) => (!resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`))
65
76
  .join(' ');
66
77
  }
67
78
  return '';
68
79
  };
69
80
 
70
81
  /**
71
- * Optimized class utility function.
72
- * * Combines multiple arguments into a single string, resolving Tailwind conflicts
73
- * and applying the plugin prefixing logic defined during creation.
74
- * * @param inputs - Arguments can be strings, objects with plugin keys, or standard ClassValues.
75
- * @returns A merged string of CSS classes.
82
+ * The final utility function.
83
+ * Processes inputs through the prefix engine and cleans them using tailwind-merge.
84
+ * * @param inputs - Variadic arguments including strings, objects, arrays, or booleans.
85
+ * @returns A merged and optimized string of Tailwind CSS classes.
76
86
  */
77
- return (...inputs: (ClassValue | { [K in CombinedKeys | 'base' | (string & {})]?: any })[]) => {
87
+ return (...inputs: any[]) => {
78
88
  const processed = inputs.map((input) => {
79
89
  if (input !== null && typeof input === 'object' && !Array.isArray(input)) {
80
90
  return Object.entries(input)
@@ -1,60 +1,125 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { createCl } from '../index';
3
3
 
4
- describe('Core cl utility', () => {
5
- // 1. Test basic plugin merging
6
- it('should merge multiple plugins and apply prefixes', () => {
7
- const tailwind = { md: 'md', hover: 'hover' };
8
- const custom = { hocus: 'hover:focus' };
4
+ describe('cl - Escala de Complejidad (Registry-Based)', () => {
5
+ // 1. Setup con Prefijos y Plugins registrados
6
+ const cl = createCl(
7
+ { md: 'md', hover: 'hover', focus: 'focus' }, // Tailwind
8
+ { ui: 'prefix', btn: 'button' } // Plugins
9
+ );
9
10
 
10
- const cl = createCl(tailwind, custom);
11
+ it('Nivel 1: Uso básico (Strings y Arrays)', () => {
12
+ expect(cl('p-4', ['m-2', 'flex'])).toBe('p-4 m-2 flex');
13
+ });
14
+
15
+ it('Nivel 2: Objetos Simples y Registro', () => {
16
+ // 'md' está registrado, 'p-4' es el valor
17
+ expect(cl({ md: 'p-4' })).toBe('md:p-4');
18
+ // 'ui' se mapea a 'prefix'
19
+ expect(cl({ ui: 'p-4' })).toBe('prefix:p-4');
20
+ });
11
21
 
22
+ it('Nivel 3: Transparencia Automática (Sintaxis Hermosa)', () => {
23
+ // 'custom' NO está registrado -> Invisible
24
+ // 'inner' NO está registrado -> Invisible
12
25
  const result = cl({
13
- md: 'p-4',
14
- hocus: 'scale-110'
26
+ custom: {
27
+ inner: 'text-center'
28
+ }
15
29
  });
16
-
17
- expect(result).toBe('md:p-4 hover:focus:scale-110');
30
+ expect(result).toBe('text-center');
18
31
  });
19
32
 
20
- // 2. Test Recursion (Nesting)
21
- it('should stack prefixes when objects are nested', () => {
22
- const cl = createCl({ md: 'md', hover: 'hover', dark: 'dark' });
23
-
33
+ it('Nivel 4: Nesting Híbrido (Registrado + Transparente)', () => {
24
34
  const result = cl({
25
35
  md: {
26
- hover: 'bg-blue-500',
27
- base: 'text-black',
28
- dark: {
29
- base: 'text-white',
30
- focus: 'ring-2'
36
+ // Registrado
37
+ card: {
38
+ // Lógico (Transparente)
39
+ hover: {
40
+ // Registrado
41
+ base: 'shadow-lg'
42
+ }
31
43
  }
32
44
  }
33
45
  });
46
+ // Debe saltar 'card' y conectar md con hover
47
+ expect(result).toBe('md:hover:shadow-lg');
48
+ });
49
+
50
+ it('Nivel 5: El "Inception" (Lógica anidada, condicionales y plugins)', () => {
51
+ const isEnabled = true;
52
+ const variant = 'primary';
53
+ const theme = 'dark';
54
+
55
+ const result = cl({
56
+ md: {
57
+ [isEnabled ? 'active' : 'idle']: {
58
+ ui: {
59
+ [`[${variant}]`]: {
60
+ base: 'bg-blue-500',
61
+ // Seleccionamos el valor ANTES de que el motor lo aplane
62
+ theme: theme === 'dark' ? 'text-white' : 'text-black'
63
+ }
64
+ }
65
+ }
66
+ }
67
+ });
34
68
 
35
- expect(result).toContain('md:hover:bg-blue-500');
36
- expect(result).toContain('md:text-black');
37
- expect(result).toContain('md:dark:text-white'); // Deep base check
38
- expect(result).toContain('md:dark:focus:ring-2'); // Deep stack check
69
+ expect(result).toContain('md:prefix:bg-blue-500');
70
+ expect(result).toContain('md:prefix:text-white'); // Ahora sí pasará
71
+ expect(result).not.toContain('md:prefix:text-black');
72
+ });
73
+
74
+ it('Nivel 6: Resolución de Conflictos Final (twMerge)', () => {
75
+ // En este nivel probamos que tras toda la recursión, twMerge limpie el resultado
76
+ const result = cl({ md: 'p-4' }, { md: { logic: 'p-8' } });
77
+ // md:p-8 debe ganar a md:p-4
78
+ expect(result).toBe('md:p-8');
39
79
  });
40
80
 
41
- // 3. Test Recursive Arrays
42
- it('should flatten and prefix arrays deep in the tree', () => {
43
- const cl = createCl({ lg: 'lg' });
81
+ it('Nivel 7: Selectores Dinámicos (Sintaxis de Componente)', () => {
82
+ const variant = 'primary';
83
+ const onHover = 'scale';
44
84
 
45
85
  const result = cl({
46
- lg: ['flex', ['items-center', 'gap-2']]
86
+ // 'variants' no está registrado -> Desaparece
87
+ variants: {
88
+ primary: 'bg-action text-white',
89
+ secondary: 'bg-neutral-50 text-black'
90
+ }[variant],
91
+
92
+ // 'effects' no está registrado -> Desaparece
93
+ effects: {
94
+ scale: 'elevate-hover',
95
+ dim: 'hover:opacity-80'
96
+ }[onHover]
47
97
  });
48
98
 
49
- expect(result).toBe('lg:flex lg:items-center lg:gap-2');
99
+ // El resultado no debe contener ni 'variants:' ni 'effects:'
100
+ expect(result).toBe('bg-action text-white elevate-hover');
101
+ expect(result).not.toContain('variants:');
102
+ expect(result).not.toContain('effects:');
50
103
  });
51
104
 
52
- // 4. Test tailwind-merge logic
53
- it('should resolve conflicts using tailwind-merge', () => {
54
- const cl = createCl({ md: 'md' });
105
+ it('Nivel 8: Combinación de Selectores con Prefijos Registrados', () => {
106
+ const variant = 'secondary';
107
+ const isDark = true;
108
+
109
+ const result = cl({
110
+ md: {
111
+ // Selector dinámico dentro de un prefijo registrado
112
+ container: {
113
+ [variant]: {
114
+ base: 'p-4',
115
+ // Prefijo registrado dentro de un selector dinámico
116
+ hover: 'shadow-xl'
117
+ }
118
+ }[variant]
119
+ }
120
+ });
55
121
 
56
- // p-8 should override p-4 even inside the object
57
- const result = cl('p-4', { base: 'p-8' });
58
- expect(result).toBe('p-8');
122
+ // 'md' y 'hover' se mantienen, 'container' y 'secondary' desaparecen
123
+ expect(result).toBe('md:p-4 md:hover:shadow-xl');
59
124
  });
60
125
  });