UNPKG

@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
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 };