/** * Based on: * * - https://github.com/threepointone/glamor/blob/78ffd84bfba187f4037a29b389e5a349dbb6e9a8/packages/glamor/src/StyleSheet.ts * - https://github.com/styled-components/styled-components/blob/94df8bb57c8ba01cd33268b65df93499a7961cb2/packages/styled-components/src/sheet/Tag.js */ /* eslint-disable no-plusplus */ import * as React from 'react'; import { ClassRef } from 'treat'; import { warnOnce } from './logger'; import { NullRuleManager, OptimizedRuleManager, RuleManager, } from './RuleManager'; const MAX_RULE_COUNT = 0xffff; function createAndMountStyleElement(): HTMLStyleElement { const el = document.createElement('style'); el.dataset.glaze = ''; return document.head.appendChild(el); } function getSheet(): CSSStyleSheet { // Hydrate existing style node if available for (let i = 0, len = document.styleSheets.length; i < len; ++i) { const styleSheet = document.styleSheets[i]; if ((styleSheet.ownerNode as HTMLElement).dataset.glaze === '') { return styleSheet as CSSStyleSheet; } } const styleEl = createAndMountStyleElement(); // Avoid Edge bug where empty style elements don't create sheets styleEl.appendChild(document.createTextNode('')); // Avoid Firefox quirk where the style element might not have a sheet property return (styleEl.sheet as CSSStyleSheet | undefined) || getSheet(); } function getInitialRuleIndexes(rules: CSSRuleList): Map { const ruleIndexesByClassName = new Map(); for (let i = 0, { length } = rules; i < length; ++i) { const rule = rules[i]; if (rule.type === CSSRule.STYLE_RULE) { // Remove leading '.' from class selector const className = (rule as CSSStyleRule).selectorText.slice(1); ruleIndexesByClassName.set(className, i); } // TODO: Add support for `CSSMediaRule` } return ruleIndexesByClassName; } export interface StyleInjector { ruleManager: RuleManager; addRule(cssText: string): number; nullifyRule(index: number): void; } export class NullStyleInjector implements StyleInjector { ruleManager: RuleManager = new NullRuleManager(); // eslint-disable-next-line class-methods-use-this addRule(): number { return 0; } // eslint-disable-next-line class-methods-use-this nullifyRule(): void {} } export class VirtualStyleInjector implements StyleInjector { ruleManager: RuleManager = new OptimizedRuleManager(this, new Map()); private cssTexts: string[] = []; getStyleElement(): JSX.Element { return (