tailwind-to-style 3.2.0 → 3.2.2

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.0
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -8342,7 +8342,7 @@ function processClass(cls, selector, styles) {
8342
8342
 
8343
8343
  // Get cssObject from singleton cache
8344
8344
  const cssObject = tailwindCache.getOrGenerate(generateTailwindCssString, convertCssToObject);
8345
- let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\$1")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
8345
+ let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\/")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
8346
8346
  if (!declarations && baseClassName.includes("[")) {
8347
8347
  const match = CUSTOM_VALUE_FULL_REGEX.exec(baseClassName);
8348
8348
  if (match) {
@@ -8564,11 +8564,26 @@ function flattenStyleObject(obj) {
8564
8564
  flatArray.push(item);
8565
8565
  } else if (isSelectorObject(item)) {
8566
8566
  const nested = flattenStyleObject(item, currentSelector);
8567
- Object.assign(result, nested);
8567
+ // Merge nested results, handling & that resolves to same selector
8568
+ for (const ns in nested) {
8569
+ if (ns === currentSelector) {
8570
+ // & resolved to same selector — include in this array
8571
+ if (Array.isArray(nested[ns])) {
8572
+ flatArray.push(...nested[ns]);
8573
+ } else {
8574
+ flatArray.push(nested[ns]);
8575
+ }
8576
+ } else {
8577
+ result[ns] = nested[ns];
8578
+ }
8579
+ }
8568
8580
  }
8569
8581
  }
8570
8582
  if (flatArray.length > 0) {
8571
8583
  result[currentSelector] = result[currentSelector] || [];
8584
+ if (!Array.isArray(result[currentSelector])) {
8585
+ result[currentSelector] = [result[currentSelector]];
8586
+ }
8572
8587
  result[currentSelector].push(...flatArray);
8573
8588
  }
8574
8589
  } else if (isSelectorObject(val)) {
@@ -8700,6 +8715,20 @@ function twsxNoCache(obj) {
8700
8715
  // Process each selector within the media query
8701
8716
  for (const innerSelector in val) {
8702
8717
  const innerVal = val[innerSelector];
8718
+
8719
+ // Handle @css string directive inside media queries
8720
+ if (typeof innerVal === "string") {
8721
+ const trimmedInner = innerVal.trim();
8722
+ if (trimmedInner.startsWith('@css')) {
8723
+ const cssMatch = trimmedInner.match(/^@css\s*\{([\s\S]*)\}\s*$/);
8724
+ if (cssMatch) {
8725
+ const rawCss = cssMatch[1].trim();
8726
+ styles[selector][innerSelector] = styles[selector][innerSelector] || '';
8727
+ styles[selector][innerSelector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
8728
+ continue;
8729
+ }
8730
+ }
8731
+ }
8703
8732
  const baseClass = typeof innerVal === "string" ? expandGroupedClass(innerVal) : "";
8704
8733
 
8705
8734
  // Process Tailwind classes for this selector
@@ -8739,6 +8768,17 @@ function twsxNoCache(obj) {
8739
8768
  } else if (Array.isArray(val)) {
8740
8769
  for (const item of val) {
8741
8770
  if (typeof item === "string") {
8771
+ // Handle @css strings inside arrays
8772
+ const trimmedItem = item.trim();
8773
+ if (trimmedItem.startsWith('@css')) {
8774
+ const cssMatch = trimmedItem.match(/^@css\s*\{([\s\S]*)\}\s*$/);
8775
+ if (cssMatch) {
8776
+ const rawCss = cssMatch[1].trim();
8777
+ styles[selector] = styles[selector] || '';
8778
+ styles[selector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
8779
+ continue;
8780
+ }
8781
+ }
8742
8782
  baseClass += (baseClass ? " " : "") + expandGroupedClass(item);
8743
8783
  } else if (typeof item === "object" && item !== null) {
8744
8784
  Object.assign(nested, item);
@@ -9293,6 +9333,9 @@ function twsxVariantsNoCache(className) {
9293
9333
  // Skip if it's the default value
9294
9334
  if (value === defaultVariants[key]) continue;
9295
9335
 
9336
+ // Skip if the variant value doesn't exist in the variant options
9337
+ if (!variants[key].hasOwnProperty(value) && value !== true && value !== "true" && value !== false && value !== "false") continue;
9338
+
9296
9339
  // Handle boolean variants
9297
9340
  if (value === true || value === "true") {
9298
9341
  variantParts.push(key);
@@ -9405,27 +9448,40 @@ function fastObjectHash(obj) {
9405
9448
  */
9406
9449
  function twsx(obj) {
9407
9450
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9408
- // Create fast hash key from input (100x faster than JSON.stringify)
9451
+ // Create fast hash key from input content (100x faster than JSON.stringify)
9409
9452
  const cacheKey = fastObjectHash(obj, options);
9453
+ const {
9454
+ inject = true
9455
+ } = options;
9456
+
9457
+ // Derive a STABLE registry key from the object's top-level selector KEYS only.
9458
+ // This key is independent of the CSS class values, so when styles are edited
9459
+ // during HMR (same selectors, different Tailwind classes) the old slot is
9460
+ // replaced rather than a new slot being created — preventing CSS accumulation.
9461
+ const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
9410
9462
 
9411
9463
  // Check cache first
9412
9464
  if (_twsxInputCache.has(cacheKey)) {
9413
9465
  const cached = _twsxInputCache.get(cacheKey);
9414
9466
 
9415
- // Handle injection for cached result
9416
- const {
9417
- inject = true
9418
- } = options;
9467
+ // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
9419
9468
  if (inject && (IS_BROWSER || _ssrCollecting)) {
9420
- autoInjectCss(cached);
9469
+ autoInjectCss(cached, registryKey);
9421
9470
  }
9422
9471
  return cached;
9423
9472
  }
9424
9473
 
9425
- // Cache miss: call original twsxNoCache and cache result
9426
- const result = twsxNoCache(obj, options);
9474
+ // Cache miss: generate CSS without internal auto-injection so that twsx owns
9475
+ // the injection and can pass the selector-based registryKey for slot replacement.
9476
+ const result = twsxNoCache(obj, {
9477
+ ...options,
9478
+ inject: false
9479
+ });
9427
9480
  _twsxInputCache.set(cacheKey, result);
9428
9481
  evictMap(_twsxInputCache);
9482
+ if (inject && (IS_BROWSER || _ssrCollecting)) {
9483
+ autoInjectCss(result, registryKey);
9484
+ }
9429
9485
  return result;
9430
9486
  }
9431
9487
 
@@ -9481,12 +9537,54 @@ function getCssHash(str) {
9481
9537
 
9482
9538
  // Enhanced auto-inject CSS with performance monitoring & SSR support
9483
9539
  const injectedCssHashSet = new Set();
9540
+
9541
+ // Registry of sourceKey → cssBlock for smart slot-based replacement.
9542
+ // Prevents stale CSS chunks from accumulating across HMR cycles.
9543
+ const _cssBlockRegistry = new Map();
9544
+
9545
+ /**
9546
+ * Rebuild the single twsx style tag from the full CSS block registry.
9547
+ * Called whenever a block is added or updated.
9548
+ */
9549
+ function rebuildStyleTag() {
9550
+ let styleTag = document.getElementById("twsx-auto-style");
9551
+ if (!styleTag) {
9552
+ styleTag = document.createElement("style");
9553
+ styleTag.id = "twsx-auto-style";
9554
+ styleTag.setAttribute("data-twsx", "");
9555
+ document.head.appendChild(styleTag);
9556
+ }
9557
+ styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
9558
+ }
9484
9559
  function autoInjectCss(cssString) {
9560
+ let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
9485
9561
  const marker = performanceMonitor.start("css:inject");
9486
9562
  try {
9487
9563
  // SSR mode: collect CSS strings instead of DOM injection
9488
9564
  if (_ssrCollecting) ;
9489
9565
  if (IS_BROWSER) {
9566
+ if (sourceKey) {
9567
+ // Slot-based update: each unique twsx(obj) call owns its own CSS block.
9568
+ // When styles change (new content, same logical call site via cacheKey)
9569
+ // the old slot is replaced and the style tag is fully rebuilt, so no
9570
+ // stale rules from previous HMR cycles can pile up.
9571
+ const existing = _cssBlockRegistry.get(sourceKey);
9572
+ if (existing === cssString) {
9573
+ // Identical content – nothing to do.
9574
+ performanceMonitor.end(marker);
9575
+ return;
9576
+ }
9577
+ _cssBlockRegistry.set(sourceKey, cssString);
9578
+ rebuildStyleTag();
9579
+ if (_cssBlockRegistry.size % 10 === 0) {
9580
+ logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
9581
+ }
9582
+ performanceMonitor.end(marker);
9583
+ return;
9584
+ }
9585
+
9586
+ // Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
9587
+ // keep the original hash-based dedup + append behaviour.
9490
9588
  const cssHash = getCssHash(cssString);
9491
9589
  if (injectedCssHashSet.has(cssHash)) {
9492
9590
  performanceMonitor.end(marker);
@@ -9502,30 +9600,11 @@ function autoInjectCss(cssString) {
9502
9600
  document.head.appendChild(styleTag);
9503
9601
  }
9504
9602
 
9505
- // Use insertRule for better performance (avoids full stylesheet reparse)
9506
- try {
9507
- const sheet = styleTag.sheet;
9508
- if (sheet) {
9509
- // Split CSS by closing brace to insert individual rules
9510
- const rules = cssString.split('}').filter(r => r.trim());
9511
- for (const rule of rules) {
9512
- const trimmed = rule.trim();
9513
- if (trimmed) {
9514
- try {
9515
- sheet.insertRule(trimmed + '}', sheet.cssRules.length);
9516
- } catch (e) {
9517
- // Fallback for complex rules (e.g., @keyframes, @media)
9518
- styleTag.textContent += `\n${trimmed}}`;
9519
- }
9520
- }
9521
- }
9522
- } else {
9523
- styleTag.textContent += `\n${cssString}`;
9524
- }
9525
- } catch (e) {
9526
- // Ultimate fallback
9527
- styleTag.textContent += `\n${cssString}`;
9528
- }
9603
+ // Append CSS to style tag using textContent for reliability
9604
+ // Note: insertRule + textContent mixing destroys CSSOM rules,
9605
+ // so we use textContent exclusively for consistent behavior
9606
+ // with @keyframes, @media, and other nested CSS blocks.
9607
+ styleTag.textContent += `\n${cssString}`;
9529
9608
 
9530
9609
  // Log injection stats periodically
9531
9610
  if (injectedCssHashSet.size % 10 === 0) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v3.2.0
2
+ * tailwind-to-style v3.2.2
3
3
  * Runtime Tailwind CSS to inline styles converter
4
4
  *
5
5
  * @author Bigetion
@@ -8340,7 +8340,7 @@ function processClass(cls, selector, styles) {
8340
8340
 
8341
8341
  // Get cssObject from singleton cache
8342
8342
  const cssObject = tailwindCache.getOrGenerate(generateTailwindCssString, convertCssToObject);
8343
- let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\$1")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
8343
+ let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\/")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
8344
8344
  if (!declarations && baseClassName.includes("[")) {
8345
8345
  const match = CUSTOM_VALUE_FULL_REGEX.exec(baseClassName);
8346
8346
  if (match) {
@@ -8562,11 +8562,26 @@ function flattenStyleObject(obj) {
8562
8562
  flatArray.push(item);
8563
8563
  } else if (isSelectorObject(item)) {
8564
8564
  const nested = flattenStyleObject(item, currentSelector);
8565
- Object.assign(result, nested);
8565
+ // Merge nested results, handling & that resolves to same selector
8566
+ for (const ns in nested) {
8567
+ if (ns === currentSelector) {
8568
+ // & resolved to same selector — include in this array
8569
+ if (Array.isArray(nested[ns])) {
8570
+ flatArray.push(...nested[ns]);
8571
+ } else {
8572
+ flatArray.push(nested[ns]);
8573
+ }
8574
+ } else {
8575
+ result[ns] = nested[ns];
8576
+ }
8577
+ }
8566
8578
  }
8567
8579
  }
8568
8580
  if (flatArray.length > 0) {
8569
8581
  result[currentSelector] = result[currentSelector] || [];
8582
+ if (!Array.isArray(result[currentSelector])) {
8583
+ result[currentSelector] = [result[currentSelector]];
8584
+ }
8570
8585
  result[currentSelector].push(...flatArray);
8571
8586
  }
8572
8587
  } else if (isSelectorObject(val)) {
@@ -8698,6 +8713,20 @@ function twsxNoCache(obj) {
8698
8713
  // Process each selector within the media query
8699
8714
  for (const innerSelector in val) {
8700
8715
  const innerVal = val[innerSelector];
8716
+
8717
+ // Handle @css string directive inside media queries
8718
+ if (typeof innerVal === "string") {
8719
+ const trimmedInner = innerVal.trim();
8720
+ if (trimmedInner.startsWith('@css')) {
8721
+ const cssMatch = trimmedInner.match(/^@css\s*\{([\s\S]*)\}\s*$/);
8722
+ if (cssMatch) {
8723
+ const rawCss = cssMatch[1].trim();
8724
+ styles[selector][innerSelector] = styles[selector][innerSelector] || '';
8725
+ styles[selector][innerSelector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
8726
+ continue;
8727
+ }
8728
+ }
8729
+ }
8701
8730
  const baseClass = typeof innerVal === "string" ? expandGroupedClass(innerVal) : "";
8702
8731
 
8703
8732
  // Process Tailwind classes for this selector
@@ -8737,6 +8766,17 @@ function twsxNoCache(obj) {
8737
8766
  } else if (Array.isArray(val)) {
8738
8767
  for (const item of val) {
8739
8768
  if (typeof item === "string") {
8769
+ // Handle @css strings inside arrays
8770
+ const trimmedItem = item.trim();
8771
+ if (trimmedItem.startsWith('@css')) {
8772
+ const cssMatch = trimmedItem.match(/^@css\s*\{([\s\S]*)\}\s*$/);
8773
+ if (cssMatch) {
8774
+ const rawCss = cssMatch[1].trim();
8775
+ styles[selector] = styles[selector] || '';
8776
+ styles[selector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
8777
+ continue;
8778
+ }
8779
+ }
8740
8780
  baseClass += (baseClass ? " " : "") + expandGroupedClass(item);
8741
8781
  } else if (typeof item === "object" && item !== null) {
8742
8782
  Object.assign(nested, item);
@@ -9291,6 +9331,9 @@ function twsxVariantsNoCache(className) {
9291
9331
  // Skip if it's the default value
9292
9332
  if (value === defaultVariants[key]) continue;
9293
9333
 
9334
+ // Skip if the variant value doesn't exist in the variant options
9335
+ if (!variants[key].hasOwnProperty(value) && value !== true && value !== "true" && value !== false && value !== "false") continue;
9336
+
9294
9337
  // Handle boolean variants
9295
9338
  if (value === true || value === "true") {
9296
9339
  variantParts.push(key);
@@ -9403,27 +9446,40 @@ function fastObjectHash(obj) {
9403
9446
  */
9404
9447
  function twsx(obj) {
9405
9448
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9406
- // Create fast hash key from input (100x faster than JSON.stringify)
9449
+ // Create fast hash key from input content (100x faster than JSON.stringify)
9407
9450
  const cacheKey = fastObjectHash(obj, options);
9451
+ const {
9452
+ inject = true
9453
+ } = options;
9454
+
9455
+ // Derive a STABLE registry key from the object's top-level selector KEYS only.
9456
+ // This key is independent of the CSS class values, so when styles are edited
9457
+ // during HMR (same selectors, different Tailwind classes) the old slot is
9458
+ // replaced rather than a new slot being created — preventing CSS accumulation.
9459
+ const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
9408
9460
 
9409
9461
  // Check cache first
9410
9462
  if (_twsxInputCache.has(cacheKey)) {
9411
9463
  const cached = _twsxInputCache.get(cacheKey);
9412
9464
 
9413
- // Handle injection for cached result
9414
- const {
9415
- inject = true
9416
- } = options;
9465
+ // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
9417
9466
  if (inject && (IS_BROWSER || _ssrCollecting)) {
9418
- autoInjectCss(cached);
9467
+ autoInjectCss(cached, registryKey);
9419
9468
  }
9420
9469
  return cached;
9421
9470
  }
9422
9471
 
9423
- // Cache miss: call original twsxNoCache and cache result
9424
- const result = twsxNoCache(obj, options);
9472
+ // Cache miss: generate CSS without internal auto-injection so that twsx owns
9473
+ // the injection and can pass the selector-based registryKey for slot replacement.
9474
+ const result = twsxNoCache(obj, {
9475
+ ...options,
9476
+ inject: false
9477
+ });
9425
9478
  _twsxInputCache.set(cacheKey, result);
9426
9479
  evictMap(_twsxInputCache);
9480
+ if (inject && (IS_BROWSER || _ssrCollecting)) {
9481
+ autoInjectCss(result, registryKey);
9482
+ }
9427
9483
  return result;
9428
9484
  }
9429
9485
 
@@ -9479,12 +9535,54 @@ function getCssHash(str) {
9479
9535
 
9480
9536
  // Enhanced auto-inject CSS with performance monitoring & SSR support
9481
9537
  const injectedCssHashSet = new Set();
9538
+
9539
+ // Registry of sourceKey → cssBlock for smart slot-based replacement.
9540
+ // Prevents stale CSS chunks from accumulating across HMR cycles.
9541
+ const _cssBlockRegistry = new Map();
9542
+
9543
+ /**
9544
+ * Rebuild the single twsx style tag from the full CSS block registry.
9545
+ * Called whenever a block is added or updated.
9546
+ */
9547
+ function rebuildStyleTag() {
9548
+ let styleTag = document.getElementById("twsx-auto-style");
9549
+ if (!styleTag) {
9550
+ styleTag = document.createElement("style");
9551
+ styleTag.id = "twsx-auto-style";
9552
+ styleTag.setAttribute("data-twsx", "");
9553
+ document.head.appendChild(styleTag);
9554
+ }
9555
+ styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
9556
+ }
9482
9557
  function autoInjectCss(cssString) {
9558
+ let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
9483
9559
  const marker = performanceMonitor.start("css:inject");
9484
9560
  try {
9485
9561
  // SSR mode: collect CSS strings instead of DOM injection
9486
9562
  if (_ssrCollecting) ;
9487
9563
  if (IS_BROWSER) {
9564
+ if (sourceKey) {
9565
+ // Slot-based update: each unique twsx(obj) call owns its own CSS block.
9566
+ // When styles change (new content, same logical call site via cacheKey)
9567
+ // the old slot is replaced and the style tag is fully rebuilt, so no
9568
+ // stale rules from previous HMR cycles can pile up.
9569
+ const existing = _cssBlockRegistry.get(sourceKey);
9570
+ if (existing === cssString) {
9571
+ // Identical content – nothing to do.
9572
+ performanceMonitor.end(marker);
9573
+ return;
9574
+ }
9575
+ _cssBlockRegistry.set(sourceKey, cssString);
9576
+ rebuildStyleTag();
9577
+ if (_cssBlockRegistry.size % 10 === 0) {
9578
+ logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
9579
+ }
9580
+ performanceMonitor.end(marker);
9581
+ return;
9582
+ }
9583
+
9584
+ // Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
9585
+ // keep the original hash-based dedup + append behaviour.
9488
9586
  const cssHash = getCssHash(cssString);
9489
9587
  if (injectedCssHashSet.has(cssHash)) {
9490
9588
  performanceMonitor.end(marker);
@@ -9500,30 +9598,11 @@ function autoInjectCss(cssString) {
9500
9598
  document.head.appendChild(styleTag);
9501
9599
  }
9502
9600
 
9503
- // Use insertRule for better performance (avoids full stylesheet reparse)
9504
- try {
9505
- const sheet = styleTag.sheet;
9506
- if (sheet) {
9507
- // Split CSS by closing brace to insert individual rules
9508
- const rules = cssString.split('}').filter(r => r.trim());
9509
- for (const rule of rules) {
9510
- const trimmed = rule.trim();
9511
- if (trimmed) {
9512
- try {
9513
- sheet.insertRule(trimmed + '}', sheet.cssRules.length);
9514
- } catch (e) {
9515
- // Fallback for complex rules (e.g., @keyframes, @media)
9516
- styleTag.textContent += `\n${trimmed}}`;
9517
- }
9518
- }
9519
- }
9520
- } else {
9521
- styleTag.textContent += `\n${cssString}`;
9522
- }
9523
- } catch (e) {
9524
- // Ultimate fallback
9525
- styleTag.textContent += `\n${cssString}`;
9526
- }
9601
+ // Append CSS to style tag using textContent for reliability
9602
+ // Note: insertRule + textContent mixing destroys CSSOM rules,
9603
+ // so we use textContent exclusively for consistent behavior
9604
+ // with @keyframes, @media, and other nested CSS blocks.
9605
+ styleTag.textContent += `\n${cssString}`;
9527
9606
 
9528
9607
  // Log injection stats periodically
9529
9608
  if (injectedCssHashSet.size % 10 === 0) {