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