/**
 * 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
 */ 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 ( nonce ? ) { const el = document . createElement ( 'style' ) ; el . dataset . glaze = '' ; el . nonce = nonce ; return document . head . appendChild ( el ) ; } function getSheet ( nonce ? ) { 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 ( nonce ) ; styleEl . appendChild ( document . createTextNode ( '' ) ) ; return ( styleEl . sheet as CSSStyleSheet | undefined ) || getSheet ( nonce ) ; } function getInitialRuleIndexes ( rules ) { const ruleIndexesByClassName = new Map < ClassRef , number > ( ) ; for ( let i = 0 , { length } = rules ; i < length ; ++ i ) { const rule = rules [ i ] ; if ( rule . type === CSSRule . STYLE_RULE ) { const className = ( rule as CSSStyleRule ) . selectorText . slice ( 1 ) ; ruleIndexesByClassName . set ( className , i ) ; } } return ruleIndexesByClassName ; } export interface StyleInjector { readonly ruleManager : RuleManager ; addRule ( cssText ) : number ; nullifyRule ( index ) : void ; } export class NullStyleInjector implements StyleInjector { ruleManager : RuleManager = new NullRuleManager ( ) ; addRule ( ) { return 0 ; } nullifyRule ( ) { } } export class VirtualStyleInjector implements StyleInjector { ruleManager : RuleManager = new OptimizedRuleManager ( this , new Map ( ) ) ; private cssTexts : string [ ] = [ ] ; private nonce ? : string ; setNonce ( nonce ? ) { this . nonce = nonce ; } getStyleElement ( ) { return ( < style data - glaze = "" nonce = { this . nonce } dangerouslySetInnerHTML = { { __html : this . cssTexts . join ( '' ) } } />); } addRule ( cssText ) { return this . cssTexts . push ( cssText ) - 1 ; } nullifyRule ( ) { } } export class OptimizedStyleInjector implements StyleInjector { private sheet : CSSStyleSheet ; private ruleCount = 0 ; private freeIndexes : number [ ] = [ ] ; ruleManager : RuleManager ; constructor ( nonce ? ) { this . sheet = getSheet ( nonce ) ; this . ruleManager = new OptimizedRuleManager ( this , getInitialRuleIndexes ( this . sheet . rules ) ) ; } addRule ( cssText ) { if ( this . freeIndexes . length ) { return this . replaceRule ( this . freeIndexes . pop ( ) ! , cssText ) ; } return this . sheet . insertRule ( cssText , this . ruleCount ++ ) ; } nullifyRule ( index ) { if ( index === this . ruleCount - 1 ) { this . sheet . deleteRule ( -- this . ruleCount ) ; } else { const dummyRule = '#_{}' ; this . freeIndexes . push ( this . replaceRule ( index , dummyRule ) ) ; } } replaceRule ( index , cssText ) { this . sheet . deleteRule ( index ) ; return this . sheet . insertRule ( cssText , index ) ; } } export class DebuggableStyleInjector implements StyleInjector { ruleManager : RuleManager = new OptimizedRuleManager ( this , new Map ( ) ) ; private styleEl : HTMLStyleElement ; private nodes : Text [ ] = [ ] ; private freeIndexes : number [ ] = [ ] ; constructor ( nonce ? ) { this . styleEl = createAndMountStyleElement ( nonce ) ; } addRule ( cssText ) { if ( this . freeIndexes . length ) { return this . replaceRule ( this . freeIndexes . pop ( ) ! , cssText ) ; } const index = this . nodes . length ; if ( index === MAX_RULE_COUNT ) { warnOnce ( 'An excessive amount of styling rules have been injected at runtime. Consider using inline styles or CSS variables instead.' ) ; } const node = document . createTextNode ( cssText ) ; this . styleEl . appendChild ( node ) ; this . nodes [ index ] = node ; return index ; } nullifyRule ( index ) { if ( index === this . nodes . length - 1 ) { this . styleEl . removeChild ( this . nodes . pop ( ) ! ) ; } else { const dummyRule = '' ; this . freeIndexes . push ( this . replaceRule ( index , dummyRule ) ) ; } } replaceRule ( index , cssText ) { this . nodes [ index ] . textContent = cssText ; return index ; } }