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,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