@winglet/style-utils
Version:
Comprehensive CSS and style management utilities including className manipulation, CSS compression, and universal style manager for TypeScript projects
171 lines (168 loc) • 6.39 kB
JavaScript
import { compressCss } from '../../utils/compressCss/compressCss.mjs';
import { getUniqueId } from './type.mjs';
class StyleManager {
constructor(scopeId, instanceKey, config) {
this.scopeId = scopeId;
this.__processedStyles__ = new Map();
this.__element__ = null;
this.__sheet__ = null;
this.__dirty__ = false;
this.__frameId__ = 0;
this.__scope__ = scopeId;
this.__root__ = config?.shadowRoot || document;
this.__isShadowDOM__ = !!config?.shadowRoot;
this.__instanceKey__ = instanceKey;
}
static get(scopeId, config) {
let instanceKey;
if (config?.shadowRoot) {
const shadowRoot = config.shadowRoot;
if (!shadowRoot[this.__SHADOW_ID_KEY__])
shadowRoot[this.__SHADOW_ID_KEY__] = getUniqueId();
instanceKey = `${scopeId}:shadow:${shadowRoot[this.__SHADOW_ID_KEY__]}`;
}
else
instanceKey = scopeId;
let manager = this.__SHEETS__.get(instanceKey);
if (!manager) {
manager = new StyleManager(scopeId, instanceKey, config);
this.__SHEETS__.set(instanceKey, manager);
}
return manager;
}
add(id, css, compressed = false) {
if (!css.trim())
return;
const previousProcessedCSS = this.__processedStyles__.get(id);
const scopedCSS = this.__scopeCSS__(css);
const compressedCSS = compressed ? scopedCSS : compressCss(scopedCSS);
if (previousProcessedCSS !== compressedCSS) {
this.__processedStyles__.set(id, compressedCSS);
this.__scheduleDOMUpdate__();
}
}
remove(id) {
if (this.__processedStyles__.delete(id))
this.__scheduleDOMUpdate__();
}
destroy() {
if (this.__frameId__) {
cancelAnimationFrame(this.__frameId__);
this.__frameId__ = 0;
}
if (this.__sheet__ && 'adoptedStyleSheets' in this.__root__) {
const sheets = this.__root__.adoptedStyleSheets;
const targetSheet = this.__sheet__;
let foundIndex = -1;
for (let i = 0, l = sheets.length; i < l; i++) {
if (sheets[i] === targetSheet) {
foundIndex = i;
break;
}
}
if (foundIndex !== -1) {
const newSheets = new Array(sheets.length - 1);
for (let i = 0, l = foundIndex; i < l; i++)
newSheets[i] = sheets[i];
for (let i = foundIndex + 1, l = sheets.length; i < l; i++)
newSheets[i - 1] = sheets[i];
this.__root__.adoptedStyleSheets = newSheets;
}
this.__sheet__ = null;
}
if (this.__element__) {
this.__element__.remove();
this.__element__ = null;
}
this.__processedStyles__.clear();
this.__dirty__ = false;
StyleManager.__SHEETS__.delete(this.__instanceKey__);
}
__scheduleDOMUpdate__() {
if (!this.__dirty__) {
this.__dirty__ = true;
this.__frameId__ = requestAnimationFrame(() => this.__flush__());
}
}
__scopeCSS__(css) {
if (this.__isShadowDOM__)
return css;
const scope = '.' + this.__scope__;
let result = '';
let currentIndex = 0;
const cssLength = css.length;
while (currentIndex < cssLength) {
const ruleEnd = css.indexOf('}', currentIndex);
if (ruleEnd === -1)
break;
const rule = css.slice(currentIndex, ruleEnd + 1);
const braceIndex = rule.indexOf('{');
if (braceIndex === -1) {
currentIndex = ruleEnd + 1;
continue;
}
const selector = rule.slice(0, braceIndex).trim();
const declarations = rule.slice(braceIndex);
if (!selector) {
currentIndex = ruleEnd + 1;
continue;
}
if (selector[0] === '@' || selector === ':root' || selector === ':host')
result += rule;
else
result += scope + ' ' + selector + declarations;
currentIndex = ruleEnd + 1;
}
return result;
}
__flush__() {
this.__dirty__ = false;
this.__frameId__ = 0;
if (this.__processedStyles__.size === 0) {
this.__applyCSS__('');
return;
}
const styleCount = this.__processedStyles__.size;
const styles = new Array(styleCount);
let index = 0;
for (const css of this.__processedStyles__.values())
styles[index++] = css;
const result = styles.join('\n');
this.__applyCSS__(result);
}
__applyCSS__(css) {
if (typeof CSSStyleSheet !== 'undefined' &&
'replaceSync' in CSSStyleSheet.prototype &&
'adoptedStyleSheets' in this.__root__) {
if (!this.__sheet__) {
this.__sheet__ = new CSSStyleSheet();
const currentSheets = this.__root__.adoptedStyleSheets;
const newSheets = new Array(currentSheets.length + 1);
for (let i = 0, l = currentSheets.length; i < l; i++)
newSheets[i] = currentSheets[i];
newSheets[currentSheets.length] = this.__sheet__;
this.__root__.adoptedStyleSheets = newSheets;
}
try {
this.__sheet__.replaceSync(css);
}
catch (error) {
console.warn(`StyleManager: Failed to apply CSS for scope "${this.scopeId}":`, error);
}
}
else {
if (!this.__element__) {
this.__element__ = document.createElement('style');
this.__element__.className = this.__scope__;
if (this.__isShadowDOM__)
this.__root__.appendChild(this.__element__);
else
document.head.appendChild(this.__element__);
}
this.__element__.textContent = css;
}
}
}
StyleManager.__SHEETS__ = new Map();
StyleManager.__SHADOW_ID_KEY__ = Symbol('__styleManagerId__');
export { StyleManager };