tailwind-to-style 3.1.2 → 3.2.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 +535 -282
- package/dist/core/tws.cjs +984 -0
- package/dist/core/tws.d.ts +14 -0
- package/dist/core/tws.esm.js +981 -0
- package/dist/core/tws.esm.js.map +1 -0
- package/dist/core/twsx.cjs +9068 -0
- package/dist/core/twsx.d.ts +26 -0
- package/dist/core/twsx.esm.js +9066 -0
- package/dist/core/twsx.esm.js.map +1 -0
- package/dist/core/twsxVariants.cjs +9542 -0
- package/dist/core/twsxVariants.d.ts +85 -0
- package/dist/core/twsxVariants.esm.js +9541 -0
- package/dist/core/twsxVariants.esm.js.map +1 -0
- package/dist/cx.cjs +115 -0
- package/dist/cx.d.ts +41 -0
- package/dist/cx.esm.js +111 -0
- package/dist/cx.esm.js.map +1 -0
- package/dist/index.cjs +623 -242
- package/dist/index.d.ts +103 -20
- package/dist/index.esm.js +619 -243
- package/dist/index.esm.js.map +1 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/utils/index.cjs +1797 -0
- package/dist/utils/index.d.ts +44 -0
- package/dist/utils/index.esm.js +1795 -0
- package/dist/utils/index.esm.js.map +1 -0
- package/package.json +51 -7
- package/types/core/tws.d.ts +14 -0
- package/types/core/twsx.d.ts +26 -0
- package/types/core/twsxVariants.d.ts +85 -0
- package/types/cx.d.ts +41 -0
- package/types/index.d.ts +382 -0
- package/types/utils/index.d.ts +44 -0
- package/README.v2-backup.md +0 -2456
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailwind-to-style v3.2.0
|
|
3
|
+
* Runtime Tailwind CSS to inline styles converter
|
|
4
|
+
*
|
|
5
|
+
* @author Bigetion
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* TailwindCache singleton for managing generated Tailwind CSS
|
|
10
|
+
* Replaces global state with proper encapsulation
|
|
11
|
+
*/
|
|
12
|
+
class TailwindCache {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.twString = null;
|
|
15
|
+
this.cssObject = null;
|
|
16
|
+
this.initialized = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get or generate the CSS object
|
|
21
|
+
* @param {Function} generateFn - Function to generate CSS string
|
|
22
|
+
* @param {Function} convertFn - Function to convert CSS string to object
|
|
23
|
+
* @returns {Object} CSS object lookup
|
|
24
|
+
*/
|
|
25
|
+
getOrGenerate(generateFn, convertFn) {
|
|
26
|
+
if (!this.initialized) {
|
|
27
|
+
this.twString = generateFn().replace(/\s\s+/g, " ");
|
|
28
|
+
this.cssObject = convertFn(this.twString);
|
|
29
|
+
this.initialized = true;
|
|
30
|
+
}
|
|
31
|
+
return this.cssObject;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the CSS string
|
|
36
|
+
*/
|
|
37
|
+
getCssString() {
|
|
38
|
+
return this.twString;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the CSS object
|
|
43
|
+
*/
|
|
44
|
+
getCssObject() {
|
|
45
|
+
return this.cssObject;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if cache is initialized
|
|
50
|
+
*/
|
|
51
|
+
isInitialized() {
|
|
52
|
+
return this.initialized;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Reset the cache (useful for testing)
|
|
57
|
+
*/
|
|
58
|
+
reset() {
|
|
59
|
+
this.twString = null;
|
|
60
|
+
this.cssObject = null;
|
|
61
|
+
this.initialized = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Singleton instance
|
|
66
|
+
let instance = null;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the TailwindCache singleton instance
|
|
70
|
+
* @returns {TailwindCache} The cache instance
|
|
71
|
+
*/
|
|
72
|
+
function getTailwindCache() {
|
|
73
|
+
if (!instance) {
|
|
74
|
+
instance = new TailwindCache();
|
|
75
|
+
}
|
|
76
|
+
return instance;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Logger class with configurable log levels
|
|
81
|
+
* Prevents console spam in production
|
|
82
|
+
*/
|
|
83
|
+
class Logger {
|
|
84
|
+
constructor() {
|
|
85
|
+
let level = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "warn";
|
|
86
|
+
this.level = level;
|
|
87
|
+
this.levels = {
|
|
88
|
+
debug: 0,
|
|
89
|
+
info: 1,
|
|
90
|
+
warn: 2,
|
|
91
|
+
error: 3,
|
|
92
|
+
silent: 4
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set the log level
|
|
98
|
+
* @param {string} level - One of 'debug', 'info', 'warn', 'error', 'silent'
|
|
99
|
+
*/
|
|
100
|
+
setLevel(level) {
|
|
101
|
+
if (this.levels[level] !== undefined) {
|
|
102
|
+
this.level = level;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get current log level
|
|
108
|
+
*/
|
|
109
|
+
getLevel() {
|
|
110
|
+
return this.level;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if a message should be logged
|
|
115
|
+
*/
|
|
116
|
+
shouldLog(level) {
|
|
117
|
+
return this.levels[level] >= this.levels[this.level];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Log debug message
|
|
122
|
+
*/
|
|
123
|
+
debug(message) {
|
|
124
|
+
if (this.shouldLog("debug")) {
|
|
125
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
126
|
+
args[_key - 1] = arguments[_key];
|
|
127
|
+
}
|
|
128
|
+
console.debug(`[twsx:debug] ${message}`, ...args);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Log info message
|
|
134
|
+
*/
|
|
135
|
+
info(message) {
|
|
136
|
+
if (this.shouldLog("info")) {
|
|
137
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
138
|
+
args[_key2 - 1] = arguments[_key2];
|
|
139
|
+
}
|
|
140
|
+
console.info(`[twsx:info] ${message}`, ...args);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Log warning message
|
|
146
|
+
*/
|
|
147
|
+
warn(message) {
|
|
148
|
+
if (this.shouldLog("warn")) {
|
|
149
|
+
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
150
|
+
args[_key3 - 1] = arguments[_key3];
|
|
151
|
+
}
|
|
152
|
+
console.warn(`[twsx:warn] ${message}`, ...args);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Log error message
|
|
158
|
+
*/
|
|
159
|
+
error(message) {
|
|
160
|
+
if (this.shouldLog("error")) {
|
|
161
|
+
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
|
162
|
+
args[_key4 - 1] = arguments[_key4];
|
|
163
|
+
}
|
|
164
|
+
console.error(`[twsx:error] ${message}`, ...args);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Create singleton instance with silent defaults
|
|
170
|
+
// Can be enabled via TWSX_LOG_LEVEL environment variable
|
|
171
|
+
let logLevel = "silent";
|
|
172
|
+
try {
|
|
173
|
+
if (typeof process !== "undefined" && process && process.env) {
|
|
174
|
+
// Allow explicit log level override via environment variable
|
|
175
|
+
// e.g., TWSX_LOG_LEVEL=debug or TWSX_LOG_LEVEL=warn
|
|
176
|
+
logLevel = process.env.TWSX_LOG_LEVEL || "silent";
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Silently fail - in browser environment, default to silent
|
|
180
|
+
logLevel = "silent";
|
|
181
|
+
}
|
|
182
|
+
const logger = new Logger(logLevel);
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Custom error class for tailwind-to-style
|
|
186
|
+
*/
|
|
187
|
+
class TwsError extends Error {
|
|
188
|
+
constructor(message) {
|
|
189
|
+
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
190
|
+
super(message);
|
|
191
|
+
this.name = "TwsError";
|
|
192
|
+
this.context = context;
|
|
193
|
+
this.timestamp = new Date().toISOString();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Error event handlers
|
|
199
|
+
*/
|
|
200
|
+
const errorHandlers = new Set();
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle and broadcast errors
|
|
204
|
+
* @param {Error} error - The error that occurred
|
|
205
|
+
* @param {Object} context - Additional context about the error
|
|
206
|
+
*/
|
|
207
|
+
function handleError(error) {
|
|
208
|
+
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
209
|
+
const twsError = error instanceof TwsError ? error : new TwsError(error.message, context);
|
|
210
|
+
|
|
211
|
+
// Log the error
|
|
212
|
+
logger.error(twsError.message, twsError.context);
|
|
213
|
+
|
|
214
|
+
// Notify all registered handlers
|
|
215
|
+
errorHandlers.forEach(handler => {
|
|
216
|
+
try {
|
|
217
|
+
handler(twsError);
|
|
218
|
+
} catch (handlerError) {
|
|
219
|
+
logger.error("Error in error handler:", handlerError);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
return twsError;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Performance Monitoring Utilities
|
|
227
|
+
*
|
|
228
|
+
* Tracks and logs performance metrics for optimization analysis.
|
|
229
|
+
*
|
|
230
|
+
* @module utils/performanceMonitor
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Performance monitoring system
|
|
236
|
+
*
|
|
237
|
+
* Provides start/end timing and automatic slow operation logging.
|
|
238
|
+
*/
|
|
239
|
+
const performanceMonitor = {
|
|
240
|
+
enabled: typeof performance !== "undefined",
|
|
241
|
+
/**
|
|
242
|
+
* Start performance measurement
|
|
243
|
+
*
|
|
244
|
+
* @param {string} label - Label for the measurement
|
|
245
|
+
* @returns {Object|null} Marker object with label and start time
|
|
246
|
+
*/
|
|
247
|
+
start(label) {
|
|
248
|
+
if (!this.enabled) return null;
|
|
249
|
+
return {
|
|
250
|
+
label,
|
|
251
|
+
startTime: performance.now()
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* End performance measurement and log if slow
|
|
256
|
+
*
|
|
257
|
+
* @param {Object} marker - Marker from start()
|
|
258
|
+
*/
|
|
259
|
+
end(marker) {
|
|
260
|
+
if (!this.enabled || !marker) return;
|
|
261
|
+
const duration = performance.now() - marker.startTime;
|
|
262
|
+
if (duration > 5) {
|
|
263
|
+
// Only log if > 5ms
|
|
264
|
+
logger.warn(`Slow ${marker.label}: ${duration.toFixed(2)}ms`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
/**
|
|
268
|
+
* Measure function execution time
|
|
269
|
+
*
|
|
270
|
+
* @param {Function} fn - Function to measure
|
|
271
|
+
* @param {string} label - Label for measurement
|
|
272
|
+
* @returns {*} Function result
|
|
273
|
+
*/
|
|
274
|
+
measure(fn, label) {
|
|
275
|
+
const marker = this.start(label);
|
|
276
|
+
try {
|
|
277
|
+
const result = fn();
|
|
278
|
+
this.end(marker);
|
|
279
|
+
return result;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
this.end(marker);
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Proper LRU (Least Recently Used) Cache implementation
|
|
289
|
+
* Efficiently manages memory by removing least recently used items
|
|
290
|
+
*/
|
|
291
|
+
class LRUCache {
|
|
292
|
+
constructor() {
|
|
293
|
+
let maxSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000;
|
|
294
|
+
this.maxSize = maxSize;
|
|
295
|
+
this.cache = new Map();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get value from cache
|
|
300
|
+
* Updates the item as most recently used
|
|
301
|
+
*/
|
|
302
|
+
get(key) {
|
|
303
|
+
if (!this.cache.has(key)) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const value = this.cache.get(key);
|
|
307
|
+
// Move to end (most recently used)
|
|
308
|
+
this.cache.delete(key);
|
|
309
|
+
this.cache.set(key, value);
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Set value in cache
|
|
315
|
+
* Removes least recently used item if cache is full
|
|
316
|
+
*/
|
|
317
|
+
set(key, value) {
|
|
318
|
+
// If key exists, delete it first to update position
|
|
319
|
+
if (this.cache.has(key)) {
|
|
320
|
+
this.cache.delete(key);
|
|
321
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
322
|
+
// Remove least recently used (first item)
|
|
323
|
+
const firstKey = this.cache.keys().next().value;
|
|
324
|
+
this.cache.delete(firstKey);
|
|
325
|
+
}
|
|
326
|
+
this.cache.set(key, value);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Check if key exists in cache
|
|
331
|
+
*/
|
|
332
|
+
has(key) {
|
|
333
|
+
return this.cache.has(key);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Clear all cache entries
|
|
338
|
+
*/
|
|
339
|
+
clear() {
|
|
340
|
+
this.cache.clear();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get current cache size
|
|
345
|
+
*/
|
|
346
|
+
get size() {
|
|
347
|
+
return this.cache.size;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Delete specific key
|
|
352
|
+
*/
|
|
353
|
+
delete(key) {
|
|
354
|
+
return this.cache.delete(key);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Pre-compiled Regex Constants and Configuration
|
|
360
|
+
*
|
|
361
|
+
* Pre-compiling regex patterns provides 50-100x performance improvement
|
|
362
|
+
* by avoiding repeated regex object creation in hot code paths.
|
|
363
|
+
*
|
|
364
|
+
* @module core/constants
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
// ============================================================================
|
|
368
|
+
// CLASS PARSING
|
|
369
|
+
// ============================================================================
|
|
370
|
+
|
|
371
|
+
/** Regex for parsing Tailwind class names (includes . for decimal values like p-0.5) */
|
|
372
|
+
const CLASS_PARSER_REGEX = /[\w.\-\/]+(?:\/\d+)?(?:\[[^\]]+\])?/g;
|
|
373
|
+
|
|
374
|
+
// ============================================================================
|
|
375
|
+
// OPACITY MODIFIERS
|
|
376
|
+
// ============================================================================
|
|
377
|
+
|
|
378
|
+
/** Regex for extracting opacity modifiers (e.g., /50 in text-red-500/50) */
|
|
379
|
+
const OPACITY_MODIFIER_REGEX = /\/(\d+)$/;
|
|
380
|
+
|
|
381
|
+
/** Pre-compiled regex patterns for opacity CSS custom properties */
|
|
382
|
+
const OPACITY_PROP_REGEXES = {
|
|
383
|
+
"--text-opacity": /--text-opacity\s*:\s*[\d.]+/gi,
|
|
384
|
+
"--bg-opacity": /--bg-opacity\s*:\s*[\d.]+/gi,
|
|
385
|
+
"--border-opacity": /--border-opacity\s*:\s*[\d.]+/gi,
|
|
386
|
+
"--ring-opacity": /--ring-opacity\s*:\s*[\d.]+/gi,
|
|
387
|
+
"--divide-opacity": /--divide-opacity\s*:\s*[\d.]+/gi,
|
|
388
|
+
"--placeholder-opacity": /--placeholder-opacity\s*:\s*[\d.]+/gi,
|
|
389
|
+
"--text-decoration-opacity": /--text-decoration-opacity\s*:\s*[\d.]+/gi,
|
|
390
|
+
"--outline-opacity": /--outline-opacity\s*:\s*[\d.]+/gi,
|
|
391
|
+
"--accent-opacity": /--accent-opacity\s*:\s*[\d.]+/gi,
|
|
392
|
+
"--caret-opacity": /--caret-opacity\s*:\s*[\d.]+/gi
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/** Regex for CSS declarations */
|
|
396
|
+
const CSS_SEMICOLON_SPLIT_REGEX = /;/;
|
|
397
|
+
const CSS_COLON_SPLIT_REGEX = /:/;
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// CSS VARIABLE RESOLUTION
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
/** Regex for CSS custom property (var) resolution — supports nested parens in fallback (e.g. rgba(...)) */
|
|
404
|
+
const CSS_VAR_REGEX = /var\((--[\w-]+)(?:,\s*((?:[^()]+|\([^()]*\))*))?\)/g;
|
|
405
|
+
const CAMEL_CASE_REGEX = /-([a-z])/g;
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// CUSTOM CLASS DETECTION
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
/** Regex for arbitrary value detection (e.g., w-[123px]) */
|
|
412
|
+
const CUSTOM_VALUE_BRACKET_REGEX = /\[([^\]]+)\]/;
|
|
413
|
+
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// COLOR PROPERTIES
|
|
416
|
+
// ============================================================================
|
|
417
|
+
|
|
418
|
+
/** List of CSS properties that accept color values */
|
|
419
|
+
const COLOR_PROPERTIES = ["color", "background-color", "border-color", "text-decoration-color", "outline-color", "fill", "stroke", "caret-color", "accent-color"];
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Pre-compiled regex patterns for each color property
|
|
423
|
+
* Used in opacity modifier processing for 50-100x performance improvement
|
|
424
|
+
*/
|
|
425
|
+
const COLOR_REGEX_PATTERNS = new Map();
|
|
426
|
+
for (const prop of COLOR_PROPERTIES) {
|
|
427
|
+
const escapedProp = prop.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
428
|
+
COLOR_REGEX_PATTERNS.set(prop, {
|
|
429
|
+
rgb: new RegExp(`(${escapedProp}\\s*:\\s*)rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)`, "gi"),
|
|
430
|
+
rgba: new RegExp(`(${escapedProp}\\s*:\\s*)rgba\\((\\d+),\\s*(\\d+),\\s*(\\d+),\\s*[\\d.]+\\)`, "gi"),
|
|
431
|
+
hsl: new RegExp(`(${escapedProp}\\s*:\\s*)hsl\\((\\d+),\\s*([\\d.]+%),\\s*([\\d.]+%)\\)`, "gi"),
|
|
432
|
+
hsla: new RegExp(`(${escapedProp}\\s*:\\s*)hsla\\((\\d+),\\s*([\\d.]+%),\\s*([\\d.]+%),\\s*[\\d.]+\\)`, "gi"),
|
|
433
|
+
hex: new RegExp(`(${escapedProp}\\s*:\\s*)(#[0-9a-fA-F]{3,6})`, "gi")
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// TAILWIND CONFIGURATION
|
|
439
|
+
// ============================================================================
|
|
440
|
+
|
|
441
|
+
/** Fraction denominators supported in Tailwind (for w-1/2, h-2/3, etc.) */
|
|
442
|
+
const FRACTION_DENOMINATORS = [2, 3, 4, 5, 6, 12];
|
|
443
|
+
|
|
444
|
+
/** Prefixes that support fractional values */
|
|
445
|
+
const FRACTION_PREFIXES = ["w-", "h-", "max-w-", "max-h-", "min-w-", "min-h-", "top-", "bottom-", "left-", "right-", "inset-", "inset-x-", "inset-y-", "translate-x-", "translate-y-", "rounded-t-", "rounded-b-", "rounded-l-", "rounded-r-", "rounded-bl-", "rounded-br-", "rounded-tl-", "rounded-tr-", "flex-basis-", "z-"];
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* CSS Resolution Utilities
|
|
449
|
+
*
|
|
450
|
+
* Handles CSS custom property (var) resolution, CSS declaration parsing,
|
|
451
|
+
* and conversion between different CSS formats.
|
|
452
|
+
*
|
|
453
|
+
* @module css/resolver
|
|
454
|
+
*/
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
// Cache for CSS resolution
|
|
458
|
+
const cssResolutionCache = new LRUCache(1000);
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Resolve all CSS custom properties (var) in a CSS string
|
|
462
|
+
*
|
|
463
|
+
* Optimized with for loops and indexOf for 2-3x better performance.
|
|
464
|
+
* Converts CSS with custom properties to clear CSS with resolved values.
|
|
465
|
+
*
|
|
466
|
+
* @param {string} cssString - CSS string with potential var() references
|
|
467
|
+
* @returns {string} Resolved CSS string (e.g., 'color: rgba(255,255,255,1); background: #fff;')
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* resolveCssToClearCss('color: var(--text-color); --text-color: red;')
|
|
471
|
+
* // Returns: 'color: red;'
|
|
472
|
+
*/
|
|
473
|
+
function resolveCssToClearCss(cssString) {
|
|
474
|
+
const customVars = {};
|
|
475
|
+
const props = {};
|
|
476
|
+
|
|
477
|
+
// Split by semicolon and process declarations
|
|
478
|
+
const declarations = cssString.split(CSS_SEMICOLON_SPLIT_REGEX);
|
|
479
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
480
|
+
const decl = declarations[i];
|
|
481
|
+
if (!decl) continue;
|
|
482
|
+
const colonIndex = decl.indexOf(":");
|
|
483
|
+
if (colonIndex === -1) continue;
|
|
484
|
+
const key = decl.substring(0, colonIndex).trim();
|
|
485
|
+
const value = decl.substring(colonIndex + 1).trim();
|
|
486
|
+
if (!key || !value) continue;
|
|
487
|
+
if (key.startsWith("--")) {
|
|
488
|
+
customVars[key] = value;
|
|
489
|
+
} else {
|
|
490
|
+
props[key] = value;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Replace var(--foo) in all values using pre-compiled regex
|
|
495
|
+
const propKeys = Object.keys(props);
|
|
496
|
+
for (let i = 0; i < propKeys.length; i++) {
|
|
497
|
+
const key = propKeys[i];
|
|
498
|
+
let val = props[key];
|
|
499
|
+
if (val.includes("var(")) {
|
|
500
|
+
CSS_VAR_REGEX.lastIndex = 0;
|
|
501
|
+
val = val.replace(CSS_VAR_REGEX, (m, varName) => customVars[varName] !== undefined ? customVars[varName] : m);
|
|
502
|
+
props[key] = val;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Build CSS string - INCLUDE CSS variables so they can be resolved later
|
|
507
|
+
let result = "";
|
|
508
|
+
const varKeys = Object.keys(customVars);
|
|
509
|
+
for (let i = 0; i < varKeys.length; i++) {
|
|
510
|
+
const key = varKeys[i];
|
|
511
|
+
result += `${key}: ${customVars[key]}; `;
|
|
512
|
+
}
|
|
513
|
+
for (let i = 0; i < propKeys.length; i++) {
|
|
514
|
+
const key = propKeys[i];
|
|
515
|
+
result += `${key}: ${props[key]}; `;
|
|
516
|
+
}
|
|
517
|
+
return result.trim();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Separate and resolve CSS declarations with custom property resolution
|
|
522
|
+
*
|
|
523
|
+
* Enhanced with caching and better error handling for production use.
|
|
524
|
+
*
|
|
525
|
+
* @param {string[]} arr - Array of CSS declaration strings
|
|
526
|
+
* @returns {string} Resolved CSS string with all variables replaced
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* separateAndResolveCSS(['color: var(--text); --text: blue;', 'margin: 1rem;'])
|
|
530
|
+
* // Returns: 'color: blue; margin: 1rem;'
|
|
531
|
+
*/
|
|
532
|
+
function separateAndResolveCSS(arr) {
|
|
533
|
+
try {
|
|
534
|
+
// Fix: cacheKey must be unique for each input array
|
|
535
|
+
const cacheKey = Array.isArray(arr) ? arr.join("|") : String(arr);
|
|
536
|
+
if (cssResolutionCache.has(cacheKey)) {
|
|
537
|
+
return cssResolutionCache.get(cacheKey);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Process CSS resolution
|
|
541
|
+
const cssProperties = {};
|
|
542
|
+
for (let i = 0; i < arr.length; i++) {
|
|
543
|
+
const item = arr[i];
|
|
544
|
+
if (!item) continue;
|
|
545
|
+
try {
|
|
546
|
+
const declarations = item.split(CSS_SEMICOLON_SPLIT_REGEX);
|
|
547
|
+
for (let j = 0; j < declarations.length; j++) {
|
|
548
|
+
const decl = declarations[j].trim();
|
|
549
|
+
if (!decl) continue;
|
|
550
|
+
const colonIndex = decl.indexOf(":");
|
|
551
|
+
if (colonIndex === -1) continue;
|
|
552
|
+
const prop = decl.substring(0, colonIndex).trim();
|
|
553
|
+
const value = decl.substring(colonIndex + 1).trim();
|
|
554
|
+
if (!prop || !value) continue;
|
|
555
|
+
|
|
556
|
+
// Keep 3-stop gradient (with via) — don't let from/to overwrite it
|
|
557
|
+
if (prop === "--gradient-color-stops" && cssProperties[prop] && cssProperties[prop].includes("--gradient-via-color") && !value.includes("--gradient-via-color")) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
cssProperties[prop] = value;
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logger.warn("Error processing CSS declaration:", item, error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const resolvedProperties = {
|
|
567
|
+
...cssProperties
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Optimized CSS variable resolution using regex-based approach
|
|
572
|
+
* 2-3x faster than manual char-by-char parsing
|
|
573
|
+
* Handles nested var() with fallback values
|
|
574
|
+
*/
|
|
575
|
+
const resolveValue = function (value, variables) {
|
|
576
|
+
let maxDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 10;
|
|
577
|
+
if (!value || !value.includes("var(") || maxDepth <= 0) return value;
|
|
578
|
+
try {
|
|
579
|
+
let resolved = value;
|
|
580
|
+
|
|
581
|
+
// Iteratively resolve variables until no more changes or max depth reached
|
|
582
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
583
|
+
let changed = false;
|
|
584
|
+
CSS_VAR_REGEX.lastIndex = 0;
|
|
585
|
+
resolved = resolved.replace(CSS_VAR_REGEX, (match, varName, fallback) => {
|
|
586
|
+
// Check if variable exists in our resolved properties
|
|
587
|
+
if (variables[varName] !== undefined) {
|
|
588
|
+
changed = true;
|
|
589
|
+
return variables[varName];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// If fallback is provided, use it
|
|
593
|
+
if (fallback !== undefined) {
|
|
594
|
+
changed = true;
|
|
595
|
+
return fallback.trim();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Keep the var() reference if not found
|
|
599
|
+
return match;
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// If nothing changed, we're done
|
|
603
|
+
if (!changed) break;
|
|
604
|
+
}
|
|
605
|
+
return resolved;
|
|
606
|
+
} catch (error) {
|
|
607
|
+
logger.warn("Error resolving CSS variable:", value, error);
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// Resolve variables recursively - multiple passes with optimized loop
|
|
613
|
+
let maxPasses = 5;
|
|
614
|
+
let hasUnresolved = true;
|
|
615
|
+
while (hasUnresolved && maxPasses-- > 0) {
|
|
616
|
+
hasUnresolved = false;
|
|
617
|
+
const propKeys = Object.keys(resolvedProperties);
|
|
618
|
+
for (let i = 0; i < propKeys.length; i++) {
|
|
619
|
+
const key = propKeys[i];
|
|
620
|
+
const resolved = resolveValue(resolvedProperties[key], resolvedProperties);
|
|
621
|
+
if (resolved !== resolvedProperties[key]) {
|
|
622
|
+
resolvedProperties[key] = resolved;
|
|
623
|
+
hasUnresolved = true;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Remove CSS variables after resolution using optimized loop
|
|
629
|
+
const allKeys = Object.keys(resolvedProperties);
|
|
630
|
+
for (let i = 0; i < allKeys.length; i++) {
|
|
631
|
+
const key = allKeys[i];
|
|
632
|
+
if (key.startsWith("--")) {
|
|
633
|
+
delete resolvedProperties[key];
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Build result string with optimized loop (faster than map/join)
|
|
638
|
+
let result = "";
|
|
639
|
+
const finalKeys = Object.keys(resolvedProperties);
|
|
640
|
+
for (let i = 0; i < finalKeys.length; i++) {
|
|
641
|
+
const key = finalKeys[i];
|
|
642
|
+
result += `${key}: ${resolvedProperties[key]}; `;
|
|
643
|
+
}
|
|
644
|
+
result = result.trim();
|
|
645
|
+
cssResolutionCache.set(cacheKey, result);
|
|
646
|
+
return result;
|
|
647
|
+
} catch (error) {
|
|
648
|
+
logger.error("Critical error in CSS resolution:", error);
|
|
649
|
+
return "";
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Convert inline CSS string to JavaScript object with camelCase properties
|
|
655
|
+
*
|
|
656
|
+
* Resolves CSS custom properties recursively before conversion.
|
|
657
|
+
*
|
|
658
|
+
* @param {string} styleString - Inline CSS string (e.g., 'color: red; margin: 1rem;')
|
|
659
|
+
* @returns {Object} Style object with camelCase keys
|
|
660
|
+
*
|
|
661
|
+
* @example
|
|
662
|
+
* inlineStyleToJson('color: red; font-size: 16px;')
|
|
663
|
+
* // Returns: { color: 'red', fontSize: '16px' }
|
|
664
|
+
*/
|
|
665
|
+
function inlineStyleToJson(styleString) {
|
|
666
|
+
const styles = styleString.split(CSS_SEMICOLON_SPLIT_REGEX).filter(style => style.trim() !== "");
|
|
667
|
+
const styleObject = {};
|
|
668
|
+
const cssVariables = {};
|
|
669
|
+
|
|
670
|
+
// First pass: collect CSS variables
|
|
671
|
+
for (let i = 0; i < styles.length; i++) {
|
|
672
|
+
const parts = styles[i].split(CSS_COLON_SPLIT_REGEX, 2);
|
|
673
|
+
if (parts.length !== 2) continue;
|
|
674
|
+
const key = parts[0].trim();
|
|
675
|
+
const value = parts[1].trim();
|
|
676
|
+
if (key && key.startsWith("--")) {
|
|
677
|
+
cssVariables[key] = value;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Helper to resolve CSS variables recursively
|
|
682
|
+
const resolveVariables = value => {
|
|
683
|
+
if (!value || !value.includes("var(")) return value;
|
|
684
|
+
let resolved = value;
|
|
685
|
+
let maxIterations = 10; // Prevent infinite loops
|
|
686
|
+
|
|
687
|
+
while (resolved.includes("var(") && maxIterations-- > 0) {
|
|
688
|
+
CSS_VAR_REGEX.lastIndex = 0; // Reset global regex
|
|
689
|
+
resolved = resolved.replace(CSS_VAR_REGEX, (match, variable, fallback) => {
|
|
690
|
+
return cssVariables[variable] || fallback || match;
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return resolved;
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// Second pass: create style object with resolved values
|
|
697
|
+
for (let i = 0; i < styles.length; i++) {
|
|
698
|
+
const parts = styles[i].split(CSS_COLON_SPLIT_REGEX, 2);
|
|
699
|
+
if (parts.length !== 2) continue;
|
|
700
|
+
const key = parts[0].trim();
|
|
701
|
+
const value = parts[1].trim();
|
|
702
|
+
if (key && value && !key.startsWith("--")) {
|
|
703
|
+
const camelCaseKey = key.replace(CAMEL_CASE_REGEX, (_, letter) => letter.toUpperCase());
|
|
704
|
+
styleObject[camelCaseKey] = resolveVariables(value);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return styleObject;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* CSS Parser Utilities
|
|
712
|
+
*
|
|
713
|
+
* Handles parsing of Tailwind class names, opacity modifiers,
|
|
714
|
+
* bracket value encoding/decoding, and custom value extraction.
|
|
715
|
+
*
|
|
716
|
+
* @module css/parser
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Process opacity modifier from class name
|
|
722
|
+
*
|
|
723
|
+
* Converts opacity modifiers (e.g., text-red-500/50) to actual CSS opacity values.
|
|
724
|
+
* Handles rgb, rgba, hsl, hsla, and hex color formats.
|
|
725
|
+
*
|
|
726
|
+
* @param {string} className - Class name with potential opacity modifier
|
|
727
|
+
* @param {string} cssDeclaration - CSS declaration to modify
|
|
728
|
+
* @returns {string} Modified CSS declaration with opacity applied
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* processOpacityModifier('text-red-500/50', 'color: rgb(239, 68, 68);')
|
|
732
|
+
* // Returns: 'color: rgba(239, 68, 68, 0.5);'
|
|
733
|
+
*/
|
|
734
|
+
function processOpacityModifier(className, cssDeclaration) {
|
|
735
|
+
const opacityMatch = OPACITY_MODIFIER_REGEX.exec(className);
|
|
736
|
+
if (!opacityMatch) return cssDeclaration;
|
|
737
|
+
const opacityValue = parseInt(opacityMatch[1], 10);
|
|
738
|
+
if (opacityValue < 0 || opacityValue > 100) return cssDeclaration;
|
|
739
|
+
const alphaValue = (opacityValue / 100).toString();
|
|
740
|
+
|
|
741
|
+
// Handle Tailwind's CSS custom property pattern
|
|
742
|
+
let modifiedDeclaration = cssDeclaration;
|
|
743
|
+
|
|
744
|
+
// Replace opacity custom properties using pre-compiled regexes
|
|
745
|
+
for (const prop in OPACITY_PROP_REGEXES) {
|
|
746
|
+
const regex = OPACITY_PROP_REGEXES[prop];
|
|
747
|
+
regex.lastIndex = 0; // Reset global regex
|
|
748
|
+
modifiedDeclaration = modifiedDeclaration.replace(regex, `${prop}: ${alphaValue}`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Also handle direct color values using pre-compiled regex patterns
|
|
752
|
+
for (const prop of COLOR_PROPERTIES) {
|
|
753
|
+
const patterns = COLOR_REGEX_PATTERNS.get(prop);
|
|
754
|
+
if (!patterns) continue;
|
|
755
|
+
|
|
756
|
+
// Reset all regex lastIndex for reuse
|
|
757
|
+
patterns.rgb.lastIndex = 0;
|
|
758
|
+
patterns.rgba.lastIndex = 0;
|
|
759
|
+
patterns.hsl.lastIndex = 0;
|
|
760
|
+
patterns.hsla.lastIndex = 0;
|
|
761
|
+
patterns.hex.lastIndex = 0;
|
|
762
|
+
|
|
763
|
+
// Convert rgb to rgba with opacity
|
|
764
|
+
modifiedDeclaration = modifiedDeclaration.replace(patterns.rgb, `$1rgba($2, $3, $4, ${alphaValue})`);
|
|
765
|
+
|
|
766
|
+
// Update existing rgba opacity
|
|
767
|
+
modifiedDeclaration = modifiedDeclaration.replace(patterns.rgba, `$1rgba($2, $3, $4, ${alphaValue})`);
|
|
768
|
+
|
|
769
|
+
// Convert hsl to hsla with opacity
|
|
770
|
+
modifiedDeclaration = modifiedDeclaration.replace(patterns.hsl, `$1hsla($2, $3, $4, ${alphaValue})`);
|
|
771
|
+
|
|
772
|
+
// Update existing hsla opacity
|
|
773
|
+
modifiedDeclaration = modifiedDeclaration.replace(patterns.hsla, `$1hsla($2, $3, $4, ${alphaValue})`);
|
|
774
|
+
|
|
775
|
+
// Handle hex colors - convert to rgba
|
|
776
|
+
modifiedDeclaration = modifiedDeclaration.replace(patterns.hex, (match, propPart, hexColor) => {
|
|
777
|
+
// Convert hex to rgba
|
|
778
|
+
const hex = hexColor.replace("#", "");
|
|
779
|
+
let r, g, b;
|
|
780
|
+
if (hex.length === 3) {
|
|
781
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
782
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
783
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
784
|
+
} else {
|
|
785
|
+
r = parseInt(hex.substring(0, 2), 16);
|
|
786
|
+
g = parseInt(hex.substring(2, 4), 16);
|
|
787
|
+
b = parseInt(hex.substring(4, 6), 16);
|
|
788
|
+
}
|
|
789
|
+
return `${propPart}rgba(${r}, ${g}, ${b}, ${alphaValue})`;
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
return modifiedDeclaration;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* TWS Core - Tailwind to Style Converter
|
|
797
|
+
*
|
|
798
|
+
* Main function for converting Tailwind class names to inline styles or JSON objects.
|
|
799
|
+
*
|
|
800
|
+
* @module core/tws
|
|
801
|
+
*/
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Convert Tailwind class string to inline CSS styles or JSON object
|
|
806
|
+
*
|
|
807
|
+
* Supports all Tailwind utilities including:
|
|
808
|
+
* - Responsive variants (sm:, md:, lg:, xl:, 2xl:)
|
|
809
|
+
* - Pseudo-state variants (hover:, focus:, active:, etc.)
|
|
810
|
+
* - Opacity modifiers (text-red-500/50)
|
|
811
|
+
* - Arbitrary values (w-[123px], text-[#abc])
|
|
812
|
+
* - Important modifier (!bg-red-500)
|
|
813
|
+
*
|
|
814
|
+
* @param {string} classNames - String containing Tailwind classes to convert
|
|
815
|
+
* @param {boolean} convertToJson - If true, returns JSON object; if false, returns CSS string
|
|
816
|
+
* @returns {string|Object} Inline CSS string or style JSON object
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* // CSS string output
|
|
820
|
+
* tws('bg-blue-500 text-white p-4')
|
|
821
|
+
* // Returns: 'background-color: rgb(59, 130, 246); color: rgb(255, 255, 255); padding: 1rem;'
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* // JSON object output
|
|
825
|
+
* tws('flex items-center gap-4', true)
|
|
826
|
+
* // Returns: { display: 'flex', alignItems: 'center', gap: '1rem' }
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* // Opacity modifiers
|
|
830
|
+
* tws('text-red-500/50')
|
|
831
|
+
* // Returns: 'color: rgba(239, 68, 68, 0.5);'
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* // Arbitrary values
|
|
835
|
+
* tws('w-[123px] text-[#abc]')
|
|
836
|
+
* // Returns: 'width: 123px; color: #abc;'
|
|
837
|
+
*/
|
|
838
|
+
function tws(classNames) {
|
|
839
|
+
let convertToJson = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
840
|
+
const totalMarker = performanceMonitor.start("tws:total");
|
|
841
|
+
try {
|
|
842
|
+
// Get CSS object from singleton cache (auto-generates if needed)
|
|
843
|
+
const tailwindCache = getTailwindCache();
|
|
844
|
+
const cssObject = tailwindCache.getOrGenerate(
|
|
845
|
+
// generateFn and convertFn arehandled by tailwindCache
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
// Validate input
|
|
849
|
+
if (!classNames || typeof classNames !== "string" || classNames.trim() === "") {
|
|
850
|
+
performanceMonitor.end(totalMarker);
|
|
851
|
+
return convertToJson ? {} : "";
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Parse class names
|
|
855
|
+
let classes;
|
|
856
|
+
try {
|
|
857
|
+
const parseMarker = performanceMonitor.start("tws:parse");
|
|
858
|
+
CLASS_PARSER_REGEX.lastIndex = 0; // Reset global regex
|
|
859
|
+
classes = classNames.match(CLASS_PARSER_REGEX);
|
|
860
|
+
performanceMonitor.end(parseMarker);
|
|
861
|
+
|
|
862
|
+
// If no valid classes are found
|
|
863
|
+
if (!classes || classes.length === 0) {
|
|
864
|
+
logger.warn(`No valid Tailwind classes found in input: "${classNames}"`);
|
|
865
|
+
performanceMonitor.end(totalMarker);
|
|
866
|
+
return convertToJson ? {} : "";
|
|
867
|
+
}
|
|
868
|
+
} catch (error) {
|
|
869
|
+
logger.error(`Error parsing Tailwind classes: ${error.message}`);
|
|
870
|
+
performanceMonitor.end(totalMarker);
|
|
871
|
+
return convertToJson ? {} : "";
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Process classes with performance monitoring
|
|
875
|
+
const processMarker = performanceMonitor.start("tws:process");
|
|
876
|
+
let cssResult = classes.map(className => {
|
|
877
|
+
// Extract base class name without opacity modifier
|
|
878
|
+
// Only remove /digits if it's an opacity modifier (not a fraction like w-2/3)
|
|
879
|
+
// Opacity modifiers are typically /0-100, fractions are /2, /3, /4, /5, /6, /12
|
|
880
|
+
const opacityMatch = OPACITY_MODIFIER_REGEX.exec(className);
|
|
881
|
+
let baseClassName = className;
|
|
882
|
+
let hasOpacityModifier = false;
|
|
883
|
+
if (opacityMatch) {
|
|
884
|
+
const opacityValue = parseInt(opacityMatch[1], 10);
|
|
885
|
+
// If it's a valid opacity value (0-100), treat it as opacity modifier
|
|
886
|
+
if (opacityValue >= 0 && opacityValue <= 100) {
|
|
887
|
+
// Check if this could be a fraction (e.g., w-2/3, h-1/2)
|
|
888
|
+
// Fractions typically have denominators of 2, 3, 4, 5, 6, 12
|
|
889
|
+
const couldBeFraction = FRACTION_DENOMINATORS.includes(opacityValue) && FRACTION_PREFIXES.some(prefix => className.startsWith(prefix) || className.startsWith(`-${prefix}`));
|
|
890
|
+
if (!couldBeFraction) {
|
|
891
|
+
baseClassName = className.replace(/\/\d+$/, "");
|
|
892
|
+
hasOpacityModifier = true;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
let result = cssObject[baseClassName] || cssObject[baseClassName.replace(/\//g, "\\/")] || cssObject[baseClassName.replace(/\./g, "\\.")];
|
|
897
|
+
if (result) {
|
|
898
|
+
// Apply opacity modifier if present
|
|
899
|
+
if (hasOpacityModifier && className.includes("/") && /\/\d+$/.test(className)) {
|
|
900
|
+
result = processOpacityModifier(className, result);
|
|
901
|
+
}
|
|
902
|
+
return resolveCssToClearCss(result);
|
|
903
|
+
} else if (baseClassName.includes("[")) {
|
|
904
|
+
const match = CUSTOM_VALUE_BRACKET_REGEX.exec(baseClassName);
|
|
905
|
+
if (match) {
|
|
906
|
+
const customValue = match[1];
|
|
907
|
+
const baseKey = baseClassName.split("[")[0];
|
|
908
|
+
if (cssObject[`${baseKey}custom`]) {
|
|
909
|
+
let customResult = cssObject[`${baseKey}custom`].replace(/custom_value/g, customValue);
|
|
910
|
+
// Apply opacity modifier to custom values too
|
|
911
|
+
if (hasOpacityModifier && className.includes("/") && /\/\d+$/.test(className)) {
|
|
912
|
+
customResult = processOpacityModifier(className, customResult);
|
|
913
|
+
}
|
|
914
|
+
return customResult;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return "";
|
|
919
|
+
});
|
|
920
|
+
performanceMonitor.end(processMarker);
|
|
921
|
+
|
|
922
|
+
// Resolve CSS
|
|
923
|
+
cssResult = performanceMonitor.measure(() => separateAndResolveCSS(cssResult), "tws:resolve");
|
|
924
|
+
|
|
925
|
+
// Convert to JSON if needed
|
|
926
|
+
if (convertToJson) {
|
|
927
|
+
cssResult = performanceMonitor.measure(() => inlineStyleToJson(cssResult), "tws:json");
|
|
928
|
+
}
|
|
929
|
+
performanceMonitor.end(totalMarker);
|
|
930
|
+
return cssResult;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
performanceMonitor.end(totalMarker);
|
|
933
|
+
handleError(error, {
|
|
934
|
+
classNames,
|
|
935
|
+
convertToJson
|
|
936
|
+
});
|
|
937
|
+
return convertToJson ? {} : "";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Debounced version of tws function
|
|
943
|
+
*
|
|
944
|
+
* Useful for real-time class name updates in UI.
|
|
945
|
+
*
|
|
946
|
+
* @param {string} classNames - String containing Tailwind classes
|
|
947
|
+
* @param {boolean} convertToJson - If true, returns JSON object
|
|
948
|
+
* @param {number} wait - Debounce delay in milliseconds (default: 50ms)
|
|
949
|
+
* @returns {Function} Debounced tws function
|
|
950
|
+
*/
|
|
951
|
+
function debounce(func) {
|
|
952
|
+
let wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
|
|
953
|
+
let timeout;
|
|
954
|
+
let callCount = 0;
|
|
955
|
+
return function () {
|
|
956
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
957
|
+
args[_key] = arguments[_key];
|
|
958
|
+
}
|
|
959
|
+
const context = this;
|
|
960
|
+
callCount++;
|
|
961
|
+
clearTimeout(timeout);
|
|
962
|
+
timeout = setTimeout(() => {
|
|
963
|
+
const marker = performanceMonitor.start(`debounced:${func.name || "anonymous"}`);
|
|
964
|
+
try {
|
|
965
|
+
const result = func.apply(context, args);
|
|
966
|
+
performanceMonitor.end(marker);
|
|
967
|
+
return result;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
performanceMonitor.end(marker);
|
|
970
|
+
logger.error(`Debounced function error (call #${callCount}):`, error);
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
973
|
+
}, wait);
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/** Debounced version of tws with 50ms delay */
|
|
978
|
+
const debouncedTws = debounce(tws, 50);
|
|
979
|
+
|
|
980
|
+
export { debounce, debouncedTws, tws };
|
|
981
|
+
//# sourceMappingURL=tws.esm.js.map
|