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
package/dist/cx.cjs
CHANGED
package/dist/cx.esm.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -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
|
|
@@ -9614,7 +9614,7 @@ function processClass(cls, selector, styles) {
|
|
|
9614
9614
|
|
|
9615
9615
|
// Get cssObject from singleton cache
|
|
9616
9616
|
const cssObject = tailwindCache.getOrGenerate(generateTailwindCssString, convertCssToObject);
|
|
9617
|
-
let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "
|
|
9617
|
+
let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\/")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
|
|
9618
9618
|
if (!declarations && baseClassName.includes("[")) {
|
|
9619
9619
|
const match = CUSTOM_VALUE_FULL_REGEX.exec(baseClassName);
|
|
9620
9620
|
if (match) {
|
|
@@ -9836,11 +9836,26 @@ function flattenStyleObject(obj) {
|
|
|
9836
9836
|
flatArray.push(item);
|
|
9837
9837
|
} else if (isSelectorObject(item)) {
|
|
9838
9838
|
const nested = flattenStyleObject(item, currentSelector);
|
|
9839
|
-
|
|
9839
|
+
// Merge nested results, handling & that resolves to same selector
|
|
9840
|
+
for (const ns in nested) {
|
|
9841
|
+
if (ns === currentSelector) {
|
|
9842
|
+
// & resolved to same selector — include in this array
|
|
9843
|
+
if (Array.isArray(nested[ns])) {
|
|
9844
|
+
flatArray.push(...nested[ns]);
|
|
9845
|
+
} else {
|
|
9846
|
+
flatArray.push(nested[ns]);
|
|
9847
|
+
}
|
|
9848
|
+
} else {
|
|
9849
|
+
result[ns] = nested[ns];
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9840
9852
|
}
|
|
9841
9853
|
}
|
|
9842
9854
|
if (flatArray.length > 0) {
|
|
9843
9855
|
result[currentSelector] = result[currentSelector] || [];
|
|
9856
|
+
if (!Array.isArray(result[currentSelector])) {
|
|
9857
|
+
result[currentSelector] = [result[currentSelector]];
|
|
9858
|
+
}
|
|
9844
9859
|
result[currentSelector].push(...flatArray);
|
|
9845
9860
|
}
|
|
9846
9861
|
} else if (isSelectorObject(val)) {
|
|
@@ -9972,6 +9987,20 @@ function twsxNoCache(obj) {
|
|
|
9972
9987
|
// Process each selector within the media query
|
|
9973
9988
|
for (const innerSelector in val) {
|
|
9974
9989
|
const innerVal = val[innerSelector];
|
|
9990
|
+
|
|
9991
|
+
// Handle @css string directive inside media queries
|
|
9992
|
+
if (typeof innerVal === "string") {
|
|
9993
|
+
const trimmedInner = innerVal.trim();
|
|
9994
|
+
if (trimmedInner.startsWith('@css')) {
|
|
9995
|
+
const cssMatch = trimmedInner.match(/^@css\s*\{([\s\S]*)\}\s*$/);
|
|
9996
|
+
if (cssMatch) {
|
|
9997
|
+
const rawCss = cssMatch[1].trim();
|
|
9998
|
+
styles[selector][innerSelector] = styles[selector][innerSelector] || '';
|
|
9999
|
+
styles[selector][innerSelector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
|
|
10000
|
+
continue;
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
9975
10004
|
const baseClass = typeof innerVal === "string" ? expandGroupedClass(innerVal) : "";
|
|
9976
10005
|
|
|
9977
10006
|
// Process Tailwind classes for this selector
|
|
@@ -10011,6 +10040,17 @@ function twsxNoCache(obj) {
|
|
|
10011
10040
|
} else if (Array.isArray(val)) {
|
|
10012
10041
|
for (const item of val) {
|
|
10013
10042
|
if (typeof item === "string") {
|
|
10043
|
+
// Handle @css strings inside arrays
|
|
10044
|
+
const trimmedItem = item.trim();
|
|
10045
|
+
if (trimmedItem.startsWith('@css')) {
|
|
10046
|
+
const cssMatch = trimmedItem.match(/^@css\s*\{([\s\S]*)\}\s*$/);
|
|
10047
|
+
if (cssMatch) {
|
|
10048
|
+
const rawCss = cssMatch[1].trim();
|
|
10049
|
+
styles[selector] = styles[selector] || '';
|
|
10050
|
+
styles[selector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
|
|
10051
|
+
continue;
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10014
10054
|
baseClass += (baseClass ? " " : "") + expandGroupedClass(item);
|
|
10015
10055
|
} else if (typeof item === "object" && item !== null) {
|
|
10016
10056
|
Object.assign(nested, item);
|
|
@@ -10565,6 +10605,9 @@ function twsxVariantsNoCache(className) {
|
|
|
10565
10605
|
// Skip if it's the default value
|
|
10566
10606
|
if (value === defaultVariants[key]) continue;
|
|
10567
10607
|
|
|
10608
|
+
// Skip if the variant value doesn't exist in the variant options
|
|
10609
|
+
if (!variants[key].hasOwnProperty(value) && value !== true && value !== "true" && value !== false && value !== "false") continue;
|
|
10610
|
+
|
|
10568
10611
|
// Handle boolean variants
|
|
10569
10612
|
if (value === true || value === "true") {
|
|
10570
10613
|
variantParts.push(key);
|
|
@@ -10677,27 +10720,40 @@ function fastObjectHash(obj) {
|
|
|
10677
10720
|
*/
|
|
10678
10721
|
function twsx(obj) {
|
|
10679
10722
|
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
10680
|
-
// Create fast hash key from input (100x faster than JSON.stringify)
|
|
10723
|
+
// Create fast hash key from input content (100x faster than JSON.stringify)
|
|
10681
10724
|
const cacheKey = fastObjectHash(obj, options);
|
|
10725
|
+
const {
|
|
10726
|
+
inject = true
|
|
10727
|
+
} = options;
|
|
10728
|
+
|
|
10729
|
+
// Derive a STABLE registry key from the object's top-level selector KEYS only.
|
|
10730
|
+
// This key is independent of the CSS class values, so when styles are edited
|
|
10731
|
+
// during HMR (same selectors, different Tailwind classes) the old slot is
|
|
10732
|
+
// replaced rather than a new slot being created — preventing CSS accumulation.
|
|
10733
|
+
const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
|
|
10682
10734
|
|
|
10683
10735
|
// Check cache first
|
|
10684
10736
|
if (_twsxInputCache.has(cacheKey)) {
|
|
10685
10737
|
const cached = _twsxInputCache.get(cacheKey);
|
|
10686
10738
|
|
|
10687
|
-
//
|
|
10688
|
-
const {
|
|
10689
|
-
inject = true
|
|
10690
|
-
} = options;
|
|
10739
|
+
// Re-inject with registryKey so the slot stays registered (no-op when unchanged).
|
|
10691
10740
|
if (inject && (IS_BROWSER || _ssrCollecting)) {
|
|
10692
|
-
autoInjectCss(cached);
|
|
10741
|
+
autoInjectCss(cached, registryKey);
|
|
10693
10742
|
}
|
|
10694
10743
|
return cached;
|
|
10695
10744
|
}
|
|
10696
10745
|
|
|
10697
|
-
// Cache miss:
|
|
10698
|
-
|
|
10746
|
+
// Cache miss: generate CSS without internal auto-injection so that twsx owns
|
|
10747
|
+
// the injection and can pass the selector-based registryKey for slot replacement.
|
|
10748
|
+
const result = twsxNoCache(obj, {
|
|
10749
|
+
...options,
|
|
10750
|
+
inject: false
|
|
10751
|
+
});
|
|
10699
10752
|
_twsxInputCache.set(cacheKey, result);
|
|
10700
10753
|
evictMap(_twsxInputCache);
|
|
10754
|
+
if (inject && (IS_BROWSER || _ssrCollecting)) {
|
|
10755
|
+
autoInjectCss(result, registryKey);
|
|
10756
|
+
}
|
|
10701
10757
|
return result;
|
|
10702
10758
|
}
|
|
10703
10759
|
|
|
@@ -10753,7 +10809,27 @@ function getCssHash(str) {
|
|
|
10753
10809
|
|
|
10754
10810
|
// Enhanced auto-inject CSS with performance monitoring & SSR support
|
|
10755
10811
|
const injectedCssHashSet = new Set();
|
|
10812
|
+
|
|
10813
|
+
// Registry of sourceKey → cssBlock for smart slot-based replacement.
|
|
10814
|
+
// Prevents stale CSS chunks from accumulating across HMR cycles.
|
|
10815
|
+
const _cssBlockRegistry = new Map();
|
|
10816
|
+
|
|
10817
|
+
/**
|
|
10818
|
+
* Rebuild the single twsx style tag from the full CSS block registry.
|
|
10819
|
+
* Called whenever a block is added or updated.
|
|
10820
|
+
*/
|
|
10821
|
+
function rebuildStyleTag() {
|
|
10822
|
+
let styleTag = document.getElementById("twsx-auto-style");
|
|
10823
|
+
if (!styleTag) {
|
|
10824
|
+
styleTag = document.createElement("style");
|
|
10825
|
+
styleTag.id = "twsx-auto-style";
|
|
10826
|
+
styleTag.setAttribute("data-twsx", "");
|
|
10827
|
+
document.head.appendChild(styleTag);
|
|
10828
|
+
}
|
|
10829
|
+
styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
|
|
10830
|
+
}
|
|
10756
10831
|
function autoInjectCss(cssString) {
|
|
10832
|
+
let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
10757
10833
|
const marker = performanceMonitor.start("css:inject");
|
|
10758
10834
|
try {
|
|
10759
10835
|
// SSR mode: collect CSS strings instead of DOM injection
|
|
@@ -10770,6 +10846,28 @@ function autoInjectCss(cssString) {
|
|
|
10770
10846
|
return;
|
|
10771
10847
|
}
|
|
10772
10848
|
if (IS_BROWSER) {
|
|
10849
|
+
if (sourceKey) {
|
|
10850
|
+
// Slot-based update: each unique twsx(obj) call owns its own CSS block.
|
|
10851
|
+
// When styles change (new content, same logical call site via cacheKey)
|
|
10852
|
+
// the old slot is replaced and the style tag is fully rebuilt, so no
|
|
10853
|
+
// stale rules from previous HMR cycles can pile up.
|
|
10854
|
+
const existing = _cssBlockRegistry.get(sourceKey);
|
|
10855
|
+
if (existing === cssString) {
|
|
10856
|
+
// Identical content – nothing to do.
|
|
10857
|
+
performanceMonitor.end(marker);
|
|
10858
|
+
return;
|
|
10859
|
+
}
|
|
10860
|
+
_cssBlockRegistry.set(sourceKey, cssString);
|
|
10861
|
+
rebuildStyleTag();
|
|
10862
|
+
if (_cssBlockRegistry.size % 10 === 0) {
|
|
10863
|
+
logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
|
|
10864
|
+
}
|
|
10865
|
+
performanceMonitor.end(marker);
|
|
10866
|
+
return;
|
|
10867
|
+
}
|
|
10868
|
+
|
|
10869
|
+
// Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
|
|
10870
|
+
// keep the original hash-based dedup + append behaviour.
|
|
10773
10871
|
const cssHash = getCssHash(cssString);
|
|
10774
10872
|
if (injectedCssHashSet.has(cssHash)) {
|
|
10775
10873
|
performanceMonitor.end(marker);
|
|
@@ -10785,30 +10883,11 @@ function autoInjectCss(cssString) {
|
|
|
10785
10883
|
document.head.appendChild(styleTag);
|
|
10786
10884
|
}
|
|
10787
10885
|
|
|
10788
|
-
//
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
const rules = cssString.split('}').filter(r => r.trim());
|
|
10794
|
-
for (const rule of rules) {
|
|
10795
|
-
const trimmed = rule.trim();
|
|
10796
|
-
if (trimmed) {
|
|
10797
|
-
try {
|
|
10798
|
-
sheet.insertRule(trimmed + '}', sheet.cssRules.length);
|
|
10799
|
-
} catch (e) {
|
|
10800
|
-
// Fallback for complex rules (e.g., @keyframes, @media)
|
|
10801
|
-
styleTag.textContent += `\n${trimmed}}`;
|
|
10802
|
-
}
|
|
10803
|
-
}
|
|
10804
|
-
}
|
|
10805
|
-
} else {
|
|
10806
|
-
styleTag.textContent += `\n${cssString}`;
|
|
10807
|
-
}
|
|
10808
|
-
} catch (e) {
|
|
10809
|
-
// Ultimate fallback
|
|
10810
|
-
styleTag.textContent += `\n${cssString}`;
|
|
10811
|
-
}
|
|
10886
|
+
// Append CSS to style tag using textContent for reliability
|
|
10887
|
+
// Note: insertRule + textContent mixing destroys CSSOM rules,
|
|
10888
|
+
// so we use textContent exclusively for consistent behavior
|
|
10889
|
+
// with @keyframes, @media, and other nested CSS blocks.
|
|
10890
|
+
styleTag.textContent += `\n${cssString}`;
|
|
10812
10891
|
|
|
10813
10892
|
// Log injection stats periodically
|
|
10814
10893
|
if (injectedCssHashSet.size % 10 === 0) {
|
|
@@ -10854,6 +10933,7 @@ const performanceUtils = {
|
|
|
10854
10933
|
},
|
|
10855
10934
|
injectionStats: {
|
|
10856
10935
|
uniqueStylesheets: injectedCssHashSet.size,
|
|
10936
|
+
cssBlocks: _cssBlockRegistry.size,
|
|
10857
10937
|
keyframes: _injectedKeyframes.size
|
|
10858
10938
|
}
|
|
10859
10939
|
};
|
|
@@ -10868,6 +10948,14 @@ const performanceUtils = {
|
|
|
10868
10948
|
// Clear new input-level caches
|
|
10869
10949
|
_twsxInputCache.clear();
|
|
10870
10950
|
_twsxVariantsResultCache.clear();
|
|
10951
|
+
|
|
10952
|
+
// Clear CSS block registry and rebuild (empty) the style tag
|
|
10953
|
+
_cssBlockRegistry.clear();
|
|
10954
|
+
injectedCssHashSet.clear();
|
|
10955
|
+
if (IS_BROWSER) {
|
|
10956
|
+
const styleTag = document.getElementById("twsx-auto-style");
|
|
10957
|
+
if (styleTag) styleTag.textContent = "";
|
|
10958
|
+
}
|
|
10871
10959
|
logger.info("All caches cleared");
|
|
10872
10960
|
performanceMonitor.end(marker);
|
|
10873
10961
|
},
|
package/dist/index.esm.js
CHANGED
|
@@ -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
|
|
@@ -9612,7 +9612,7 @@ function processClass(cls, selector, styles) {
|
|
|
9612
9612
|
|
|
9613
9613
|
// Get cssObject from singleton cache
|
|
9614
9614
|
const cssObject = tailwindCache.getOrGenerate(generateTailwindCssString, convertCssToObject);
|
|
9615
|
-
let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "
|
|
9615
|
+
let declarations = cssObject[baseClassName] || cssObject[baseClassName.replace(ESCAPE_SLASH_REGEX, "\\/")] || cssObject[baseClassName.replace(ESCAPE_DOT_REGEX, "\\.")];
|
|
9616
9616
|
if (!declarations && baseClassName.includes("[")) {
|
|
9617
9617
|
const match = CUSTOM_VALUE_FULL_REGEX.exec(baseClassName);
|
|
9618
9618
|
if (match) {
|
|
@@ -9834,11 +9834,26 @@ function flattenStyleObject(obj) {
|
|
|
9834
9834
|
flatArray.push(item);
|
|
9835
9835
|
} else if (isSelectorObject(item)) {
|
|
9836
9836
|
const nested = flattenStyleObject(item, currentSelector);
|
|
9837
|
-
|
|
9837
|
+
// Merge nested results, handling & that resolves to same selector
|
|
9838
|
+
for (const ns in nested) {
|
|
9839
|
+
if (ns === currentSelector) {
|
|
9840
|
+
// & resolved to same selector — include in this array
|
|
9841
|
+
if (Array.isArray(nested[ns])) {
|
|
9842
|
+
flatArray.push(...nested[ns]);
|
|
9843
|
+
} else {
|
|
9844
|
+
flatArray.push(nested[ns]);
|
|
9845
|
+
}
|
|
9846
|
+
} else {
|
|
9847
|
+
result[ns] = nested[ns];
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9838
9850
|
}
|
|
9839
9851
|
}
|
|
9840
9852
|
if (flatArray.length > 0) {
|
|
9841
9853
|
result[currentSelector] = result[currentSelector] || [];
|
|
9854
|
+
if (!Array.isArray(result[currentSelector])) {
|
|
9855
|
+
result[currentSelector] = [result[currentSelector]];
|
|
9856
|
+
}
|
|
9842
9857
|
result[currentSelector].push(...flatArray);
|
|
9843
9858
|
}
|
|
9844
9859
|
} else if (isSelectorObject(val)) {
|
|
@@ -9970,6 +9985,20 @@ function twsxNoCache(obj) {
|
|
|
9970
9985
|
// Process each selector within the media query
|
|
9971
9986
|
for (const innerSelector in val) {
|
|
9972
9987
|
const innerVal = val[innerSelector];
|
|
9988
|
+
|
|
9989
|
+
// Handle @css string directive inside media queries
|
|
9990
|
+
if (typeof innerVal === "string") {
|
|
9991
|
+
const trimmedInner = innerVal.trim();
|
|
9992
|
+
if (trimmedInner.startsWith('@css')) {
|
|
9993
|
+
const cssMatch = trimmedInner.match(/^@css\s*\{([\s\S]*)\}\s*$/);
|
|
9994
|
+
if (cssMatch) {
|
|
9995
|
+
const rawCss = cssMatch[1].trim();
|
|
9996
|
+
styles[selector][innerSelector] = styles[selector][innerSelector] || '';
|
|
9997
|
+
styles[selector][innerSelector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
|
|
9998
|
+
continue;
|
|
9999
|
+
}
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
9973
10002
|
const baseClass = typeof innerVal === "string" ? expandGroupedClass(innerVal) : "";
|
|
9974
10003
|
|
|
9975
10004
|
// Process Tailwind classes for this selector
|
|
@@ -10009,6 +10038,17 @@ function twsxNoCache(obj) {
|
|
|
10009
10038
|
} else if (Array.isArray(val)) {
|
|
10010
10039
|
for (const item of val) {
|
|
10011
10040
|
if (typeof item === "string") {
|
|
10041
|
+
// Handle @css strings inside arrays
|
|
10042
|
+
const trimmedItem = item.trim();
|
|
10043
|
+
if (trimmedItem.startsWith('@css')) {
|
|
10044
|
+
const cssMatch = trimmedItem.match(/^@css\s*\{([\s\S]*)\}\s*$/);
|
|
10045
|
+
if (cssMatch) {
|
|
10046
|
+
const rawCss = cssMatch[1].trim();
|
|
10047
|
+
styles[selector] = styles[selector] || '';
|
|
10048
|
+
styles[selector] += rawCss.split(';').filter(d => d.trim()).map(d => d.trim() + ';').join(' ') + '\n';
|
|
10049
|
+
continue;
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10012
10052
|
baseClass += (baseClass ? " " : "") + expandGroupedClass(item);
|
|
10013
10053
|
} else if (typeof item === "object" && item !== null) {
|
|
10014
10054
|
Object.assign(nested, item);
|
|
@@ -10563,6 +10603,9 @@ function twsxVariantsNoCache(className) {
|
|
|
10563
10603
|
// Skip if it's the default value
|
|
10564
10604
|
if (value === defaultVariants[key]) continue;
|
|
10565
10605
|
|
|
10606
|
+
// Skip if the variant value doesn't exist in the variant options
|
|
10607
|
+
if (!variants[key].hasOwnProperty(value) && value !== true && value !== "true" && value !== false && value !== "false") continue;
|
|
10608
|
+
|
|
10566
10609
|
// Handle boolean variants
|
|
10567
10610
|
if (value === true || value === "true") {
|
|
10568
10611
|
variantParts.push(key);
|
|
@@ -10675,27 +10718,40 @@ function fastObjectHash(obj) {
|
|
|
10675
10718
|
*/
|
|
10676
10719
|
function twsx(obj) {
|
|
10677
10720
|
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
10678
|
-
// Create fast hash key from input (100x faster than JSON.stringify)
|
|
10721
|
+
// Create fast hash key from input content (100x faster than JSON.stringify)
|
|
10679
10722
|
const cacheKey = fastObjectHash(obj, options);
|
|
10723
|
+
const {
|
|
10724
|
+
inject = true
|
|
10725
|
+
} = options;
|
|
10726
|
+
|
|
10727
|
+
// Derive a STABLE registry key from the object's top-level selector KEYS only.
|
|
10728
|
+
// This key is independent of the CSS class values, so when styles are edited
|
|
10729
|
+
// during HMR (same selectors, different Tailwind classes) the old slot is
|
|
10730
|
+
// replaced rather than a new slot being created — preventing CSS accumulation.
|
|
10731
|
+
const registryKey = obj && typeof obj === "object" && !Array.isArray(obj) ? Object.keys(obj).sort().join("|") : cacheKey; // fallback for edge cases
|
|
10680
10732
|
|
|
10681
10733
|
// Check cache first
|
|
10682
10734
|
if (_twsxInputCache.has(cacheKey)) {
|
|
10683
10735
|
const cached = _twsxInputCache.get(cacheKey);
|
|
10684
10736
|
|
|
10685
|
-
//
|
|
10686
|
-
const {
|
|
10687
|
-
inject = true
|
|
10688
|
-
} = options;
|
|
10737
|
+
// Re-inject with registryKey so the slot stays registered (no-op when unchanged).
|
|
10689
10738
|
if (inject && (IS_BROWSER || _ssrCollecting)) {
|
|
10690
|
-
autoInjectCss(cached);
|
|
10739
|
+
autoInjectCss(cached, registryKey);
|
|
10691
10740
|
}
|
|
10692
10741
|
return cached;
|
|
10693
10742
|
}
|
|
10694
10743
|
|
|
10695
|
-
// Cache miss:
|
|
10696
|
-
|
|
10744
|
+
// Cache miss: generate CSS without internal auto-injection so that twsx owns
|
|
10745
|
+
// the injection and can pass the selector-based registryKey for slot replacement.
|
|
10746
|
+
const result = twsxNoCache(obj, {
|
|
10747
|
+
...options,
|
|
10748
|
+
inject: false
|
|
10749
|
+
});
|
|
10697
10750
|
_twsxInputCache.set(cacheKey, result);
|
|
10698
10751
|
evictMap(_twsxInputCache);
|
|
10752
|
+
if (inject && (IS_BROWSER || _ssrCollecting)) {
|
|
10753
|
+
autoInjectCss(result, registryKey);
|
|
10754
|
+
}
|
|
10699
10755
|
return result;
|
|
10700
10756
|
}
|
|
10701
10757
|
|
|
@@ -10751,7 +10807,27 @@ function getCssHash(str) {
|
|
|
10751
10807
|
|
|
10752
10808
|
// Enhanced auto-inject CSS with performance monitoring & SSR support
|
|
10753
10809
|
const injectedCssHashSet = new Set();
|
|
10810
|
+
|
|
10811
|
+
// Registry of sourceKey → cssBlock for smart slot-based replacement.
|
|
10812
|
+
// Prevents stale CSS chunks from accumulating across HMR cycles.
|
|
10813
|
+
const _cssBlockRegistry = new Map();
|
|
10814
|
+
|
|
10815
|
+
/**
|
|
10816
|
+
* Rebuild the single twsx style tag from the full CSS block registry.
|
|
10817
|
+
* Called whenever a block is added or updated.
|
|
10818
|
+
*/
|
|
10819
|
+
function rebuildStyleTag() {
|
|
10820
|
+
let styleTag = document.getElementById("twsx-auto-style");
|
|
10821
|
+
if (!styleTag) {
|
|
10822
|
+
styleTag = document.createElement("style");
|
|
10823
|
+
styleTag.id = "twsx-auto-style";
|
|
10824
|
+
styleTag.setAttribute("data-twsx", "");
|
|
10825
|
+
document.head.appendChild(styleTag);
|
|
10826
|
+
}
|
|
10827
|
+
styleTag.textContent = [..._cssBlockRegistry.values()].join("\n");
|
|
10828
|
+
}
|
|
10754
10829
|
function autoInjectCss(cssString) {
|
|
10830
|
+
let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
10755
10831
|
const marker = performanceMonitor.start("css:inject");
|
|
10756
10832
|
try {
|
|
10757
10833
|
// SSR mode: collect CSS strings instead of DOM injection
|
|
@@ -10768,6 +10844,28 @@ function autoInjectCss(cssString) {
|
|
|
10768
10844
|
return;
|
|
10769
10845
|
}
|
|
10770
10846
|
if (IS_BROWSER) {
|
|
10847
|
+
if (sourceKey) {
|
|
10848
|
+
// Slot-based update: each unique twsx(obj) call owns its own CSS block.
|
|
10849
|
+
// When styles change (new content, same logical call site via cacheKey)
|
|
10850
|
+
// the old slot is replaced and the style tag is fully rebuilt, so no
|
|
10851
|
+
// stale rules from previous HMR cycles can pile up.
|
|
10852
|
+
const existing = _cssBlockRegistry.get(sourceKey);
|
|
10853
|
+
if (existing === cssString) {
|
|
10854
|
+
// Identical content – nothing to do.
|
|
10855
|
+
performanceMonitor.end(marker);
|
|
10856
|
+
return;
|
|
10857
|
+
}
|
|
10858
|
+
_cssBlockRegistry.set(sourceKey, cssString);
|
|
10859
|
+
rebuildStyleTag();
|
|
10860
|
+
if (_cssBlockRegistry.size % 10 === 0) {
|
|
10861
|
+
logger.debug(`CSS registry stats: ${_cssBlockRegistry.size} blocks registered`);
|
|
10862
|
+
}
|
|
10863
|
+
performanceMonitor.end(marker);
|
|
10864
|
+
return;
|
|
10865
|
+
}
|
|
10866
|
+
|
|
10867
|
+
// Fallback path (e.g. direct autoInjectCss calls without a sourceKey):
|
|
10868
|
+
// keep the original hash-based dedup + append behaviour.
|
|
10771
10869
|
const cssHash = getCssHash(cssString);
|
|
10772
10870
|
if (injectedCssHashSet.has(cssHash)) {
|
|
10773
10871
|
performanceMonitor.end(marker);
|
|
@@ -10783,30 +10881,11 @@ function autoInjectCss(cssString) {
|
|
|
10783
10881
|
document.head.appendChild(styleTag);
|
|
10784
10882
|
}
|
|
10785
10883
|
|
|
10786
|
-
//
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
const rules = cssString.split('}').filter(r => r.trim());
|
|
10792
|
-
for (const rule of rules) {
|
|
10793
|
-
const trimmed = rule.trim();
|
|
10794
|
-
if (trimmed) {
|
|
10795
|
-
try {
|
|
10796
|
-
sheet.insertRule(trimmed + '}', sheet.cssRules.length);
|
|
10797
|
-
} catch (e) {
|
|
10798
|
-
// Fallback for complex rules (e.g., @keyframes, @media)
|
|
10799
|
-
styleTag.textContent += `\n${trimmed}}`;
|
|
10800
|
-
}
|
|
10801
|
-
}
|
|
10802
|
-
}
|
|
10803
|
-
} else {
|
|
10804
|
-
styleTag.textContent += `\n${cssString}`;
|
|
10805
|
-
}
|
|
10806
|
-
} catch (e) {
|
|
10807
|
-
// Ultimate fallback
|
|
10808
|
-
styleTag.textContent += `\n${cssString}`;
|
|
10809
|
-
}
|
|
10884
|
+
// Append CSS to style tag using textContent for reliability
|
|
10885
|
+
// Note: insertRule + textContent mixing destroys CSSOM rules,
|
|
10886
|
+
// so we use textContent exclusively for consistent behavior
|
|
10887
|
+
// with @keyframes, @media, and other nested CSS blocks.
|
|
10888
|
+
styleTag.textContent += `\n${cssString}`;
|
|
10810
10889
|
|
|
10811
10890
|
// Log injection stats periodically
|
|
10812
10891
|
if (injectedCssHashSet.size % 10 === 0) {
|
|
@@ -10852,6 +10931,7 @@ const performanceUtils = {
|
|
|
10852
10931
|
},
|
|
10853
10932
|
injectionStats: {
|
|
10854
10933
|
uniqueStylesheets: injectedCssHashSet.size,
|
|
10934
|
+
cssBlocks: _cssBlockRegistry.size,
|
|
10855
10935
|
keyframes: _injectedKeyframes.size
|
|
10856
10936
|
}
|
|
10857
10937
|
};
|
|
@@ -10866,6 +10946,14 @@ const performanceUtils = {
|
|
|
10866
10946
|
// Clear new input-level caches
|
|
10867
10947
|
_twsxInputCache.clear();
|
|
10868
10948
|
_twsxVariantsResultCache.clear();
|
|
10949
|
+
|
|
10950
|
+
// Clear CSS block registry and rebuild (empty) the style tag
|
|
10951
|
+
_cssBlockRegistry.clear();
|
|
10952
|
+
injectedCssHashSet.clear();
|
|
10953
|
+
if (IS_BROWSER) {
|
|
10954
|
+
const styleTag = document.getElementById("twsx-auto-style");
|
|
10955
|
+
if (styleTag) styleTag.textContent = "";
|
|
10956
|
+
}
|
|
10869
10957
|
logger.info("All caches cleared");
|
|
10870
10958
|
performanceMonitor.end(marker);
|
|
10871
10959
|
},
|