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.
- package/README.md +37 -2
- package/dist/core/tws.cjs +1 -1
- package/dist/core/tws.esm.js +1 -1
- package/dist/core/twsx.cjs +111 -35
- package/dist/core/twsx.esm.js +111 -35
- package/dist/core/twsx.esm.js.map +1 -1
- package/dist/core/twsxVariants.cjs +114 -35
- package/dist/core/twsxVariants.esm.js +114 -35
- package/dist/core/twsxVariants.esm.js.map +1 -1
- package/dist/cx.cjs +1 -1
- package/dist/cx.esm.js +1 -1
- package/dist/index.cjs +123 -35
- package/dist/index.esm.js +123 -35
- package/dist/index.esm.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.esm.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* tailwind-to-style v3.2.
|
|
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, "
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
9426
|
-
|
|
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
|
-
//
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
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.
|
|
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, "
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
9424
|
-
|
|
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
|
-
//
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
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) {
|