clases 1.0.1 → 1.1.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 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":";;;;;;;;;;AAWO,SAAS,YAAuD,OAAA,EAAmB;AAEtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAInF,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;AAER,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;AAEQ,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;AAEA,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 is what gives the user autocomplete for ALL plugins combined.\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\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 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\nif (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 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;AAKtF,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 \"transparent\" logic.\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.\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') to real prefixes (e.g., 'prefix')\r\n * and removes any non-registered logical parts.\r\n */\r\n const resolvedPrefix = key\r\n .split(':')\r\n .map((part) => {\r\n if (part === 'base') return null;\r\n // Return mapped value from registry if it exists\r\n if (registry[part]) return registry[part];\r\n // Otherwise, discard the part (Total Transparency)\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,10 +1,9 @@
1
- import { ClassValue } from 'clsx';
2
-
3
1
  /**
4
- * Helper type to merge an array of objects into one single type
5
- * This is what gives the user autocomplete for ALL plugins combined.
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.
6
6
  */
7
- type MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest] ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {}) : {};
8
- 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;
9
8
 
10
9
  export { createCl };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { ClassValue } from 'clsx';
2
-
3
1
  /**
4
- * Helper type to merge an array of objects into one single type
5
- * This is what gives the user autocomplete for ALL plugins combined.
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.
6
6
  */
7
- type MergePlugins<T extends Record<string, string>[]> = T extends [infer First, ...infer Rest] ? First & (Rest extends Record<string, string>[] ? MergePlugins<Rest> : {}) : {};
8
- 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;
9
8
 
10
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":";;;;AAWO,SAAS,YAAuD,OAAA,EAAmB;AAEtF,EAAA,MAAM,QAAA,GAAmC,OAAO,MAAA,CAAO,EAAE,MAAM,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAInF,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;AAER,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;AAEQ,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;AAEA,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 is what gives the user autocomplete for ALL plugins combined.\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\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 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\nif (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 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;AAKtF,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 \"transparent\" logic.\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.\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') to real prefixes (e.g., 'prefix')\r\n * and removes any non-registered logical parts.\r\n */\r\n const resolvedPrefix = key\r\n .split(':')\r\n .map((part) => {\r\n if (part === 'base') return null;\r\n // Return mapped value from registry if it exists\r\n if (registry[part]) return registry[part];\r\n // Otherwise, discard the part (Total Transparency)\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.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,23 +1,29 @@
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 is what gives the user autocomplete for ALL plugins combined.
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.
7
9
  */
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
10
  export function createCl<TPlugins extends Record<string, string>[]>(...plugins: TPlugins) {
13
- // 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 "transparent" logic.
14
+ */
14
15
  const registry: Record<string, string> = Object.assign({ base: 'base' }, ...plugins);
15
16
 
16
- type CombinedKeys = keyof MergePlugins<TPlugins>;
17
-
17
+ /**
18
+ * Recursively processes keys and values to build the prefixed class string.
19
+ * * @param key - The current accumulated prefix path.
20
+ * @param value - The class value, array, or nested object to process.
21
+ * @returns A space-separated string of prefixed classes.
22
+ */
18
23
  const process = (key: string, value: any): string => {
19
24
  if (!value) return '';
20
25
 
26
+ // Handle Arrays: Process each element with the current key
21
27
  if (Array.isArray(value)) {
22
28
  return value
23
29
  .map((v) => process(key, v))
@@ -25,35 +31,59 @@ export function createCl<TPlugins extends Record<string, string>[]>(...plugins:
25
31
  .join(' ');
26
32
  }
27
33
 
28
- if (typeof value === 'object') {
29
- return Object.entries(value)
30
- .map(([nestedKey, nestedValue]) => {
31
- // FIX: If the nested key is 'base', it doesn't add to the prefix chain
32
- const combinedKey =
33
- key === 'base'
34
- ? nestedKey
35
- : nestedKey === 'base'
36
- ? key // If nested is base, keep the parent key
37
- : `${key}:${nestedKey}`;
34
+ // Handle Objects: Manage nesting and logical transparency
35
+ if (typeof value === 'object') {
36
+ return Object.entries(value)
37
+ .map(([nestedKey, nestedValue]) => {
38
+ /**
39
+ * Rule: If the child key is registered, we concatenate it.
40
+ * If it's not registered, it's a "logical" key (transparent),
41
+ * so we inherit the parent's prefix.
42
+ */
43
+ const isRegistered = registry[nestedKey] !== undefined;
44
+ const nextKey =
45
+ key === 'base' ? nestedKey : isRegistered ? `${key}:${nestedKey}` : key;
38
46
 
39
- return process(combinedKey, nestedValue);
40
- })
41
- .join(' ');
42
- }
47
+ return process(nextKey, nestedValue);
48
+ })
49
+ .join(' ');
50
+ }
43
51
 
44
- const prefix = registry[key] || key;
52
+ /**
53
+ * FINAL RESOLUTION
54
+ * Maps aliases (e.g., 'ui') to real prefixes (e.g., 'prefix')
55
+ * and removes any non-registered logical parts.
56
+ */
57
+ const resolvedPrefix = key
58
+ .split(':')
59
+ .map((part) => {
60
+ if (part === 'base') return null;
61
+ // Return mapped value from registry if it exists
62
+ if (registry[part]) return registry[part];
63
+ // Otherwise, discard the part (Total Transparency)
64
+ return null;
65
+ })
66
+ .filter(Boolean)
67
+ .join(':');
45
68
 
69
+ // Apply the resolved prefix to each class in the string
46
70
  if (typeof value === 'string') {
47
71
  return value
48
72
  .split(/[,\s\n]+/)
49
73
  .filter(Boolean)
50
- .map((cls) => (prefix === 'base' ? cls : `${prefix}:${cls}`))
74
+ .map((cls) => (!resolvedPrefix ? cls : `${resolvedPrefix}:${cls}`))
51
75
  .join(' ');
52
76
  }
53
77
  return '';
54
78
  };
55
79
 
56
- return (...inputs: (ClassValue | { [K in CombinedKeys | 'base' | (string & {})]?: any })[]) => {
80
+ /**
81
+ * The final utility function.
82
+ * Processes inputs through the prefix engine and cleans them using tailwind-merge.
83
+ * * @param inputs - Variadic arguments including strings, objects, arrays, or booleans.
84
+ * @returns A merged and optimized string of Tailwind CSS classes.
85
+ */
86
+ return (...inputs: any[]) => {
57
87
  const processed = inputs.map((input) => {
58
88
  if (input !== null && typeof input === 'object' && !Array.isArray(input)) {
59
89
  return Object.entries(input)
@@ -1,60 +1,80 @@
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
  });
34
-
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
46
+ // Debe saltar 'card' y conectar md con hover
47
+ expect(result).toBe('md:hover:shadow-lg');
39
48
  });
40
49
 
41
- // 3. Test Recursive Arrays
42
- it('should flatten and prefix arrays deep in the tree', () => {
43
- const cl = createCl({ lg: 'lg' });
50
+ it('Nivel 5: El "Inception" (Lógica anidada, condicionales y plugins)', () => {
51
+ const isEnabled = true;
52
+ const variant = 'primary';
53
+ const theme = 'dark';
44
54
 
45
- const result = cl({
46
- lg: ['flex', ['items-center', 'gap-2']]
47
- });
48
-
49
- expect(result).toBe('lg:flex lg:items-center lg:gap-2');
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
+ }
50
67
  });
51
68
 
52
- // 4. Test tailwind-merge logic
53
- it('should resolve conflicts using tailwind-merge', () => {
54
- const cl = createCl({ md: 'md' });
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
+ });
55
73
 
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');
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');
59
79
  });
60
80
  });