import styleToString from './styleToString' ; import { Rule } from './Rule' ; import { ClassNameGenerator } from './ClassNameGenerator' ; import { Nullable } from './utils/types' ; export type RuleRenderer = { index ( ) : number ; attach ( sheet ) : void ; connect ( sheet , styleSheetIndex ) : number ; renderToString ( ) : string ; setRuleOverrideCount ( overrideCount ) : boolean ; getRuleClassNames ( overrideCount ) : string [ ] ; serialize ( ) : RuleRendererState ; } ; export type RuleRendererState = { index : number ; classNames : string [ ] ; } ; export function createRuleRenderer ( rule , index , classNameGenerator , classNames = [ classNameGenerator . nextClassName ( ) ] ) { let attached = false ; let overrideCount = classNames . length - 1 ; const cssRules = [ ] ; function attach ( sheet ) { if ( attached ) { return ; } let currentMedia = null ; let currentMediaRule = null ; for ( const { selector , style , media } of rule . definitions ) { if ( currentMedia !== media ) { if ( media ) { const index = sheet . insertRule ( `${ media }{}` , sheet . cssRules . length ) ; currentMediaRule = sheet . cssRules [ index ] as CSSMediaRule ; } else { currentMediaRule = null ; } currentMedia = media ; } const ruleText = `${ createSelector ( classNames , selector ) }{${ styleToString ( style ) }}` ; const container = currentMediaRule ? currentMediaRule : sheet ; const index = container . insertRule ( ruleText , container . cssRules . length ) ; cssRules . push ( container . cssRules [ index ] as CSSStyleRule ) ; } attached = true ; } function connect ( sheet , styleSheetIndex ) { let currentMedia = null ; let currentMediaRule = null ; let currentMediaIndex = 0 ; for ( const { media } of rule . definitions ) { if ( currentMedia !== media ) { if ( media ) { currentMediaRule = sheet . cssRules [ styleSheetIndex ] as CSSMediaRule ; currentMediaIndex = 0 ; } else { currentMediaRule = null ; styleSheetIndex ++ ; } currentMedia = media ; } if ( currentMediaRule ) { cssRules . push ( currentMediaRule . cssRules [ currentMediaIndex ] as CSSStyleRule ) ; currentMediaIndex ++ ; } else { cssRules . push ( sheet . cssRules [ styleSheetIndex ] as CSSStyleRule ) ; styleSheetIndex ++ ; } } if ( currentMediaRule ) { styleSheetIndex ++ ; } attached = true ; return styleSheetIndex ; } function renderToString ( ) { let result = '' ; let currentMedia = null ; for ( const { selector , style , media } of rule . definitions ) { if ( currentMedia !== media ) { if ( currentMedia ) { result += '}' ; } if ( media ) { result += `${ media }{` ; } currentMedia = media ; } result += `${ createSelector ( classNames , selector ) }{${ styleToString ( style ) }}` ; } if ( currentMedia ) { result += '}' ; } return result ; } function setRuleOverrideCount ( count ) { if ( overrideCount < count ) { for ( let i = 0 ; i < ( count - overrideCount ) ; i ++ ) { classNames . push ( classNameGenerator . nextClassName ( ) ) ; } if ( attached ) { for ( let i = 0 ; i < rule . definitions . length ; i ++ ) { const cssRule = cssRules [ i ] ; const { selector } = rule . definitions [ i ] ; cssRule . selectorText = createSelector ( classNames , selector ) ; } } overrideCount = count ; return true ; } return false ; } function getRuleClassNames ( overrideCount ) { return classNames . slice ( 0 , overrideCount + 1 ) ; } return { index : ( ) => index , attach , connect , renderToString , setRuleOverrideCount , getRuleClassNames , serialize : ( ) => ( { index , classNames } ) , } ; } function createSelector ( classNames , selector ) { return classNames . map ( ( _ , i ) => selector . replace ( /&/g , `.${ classNames . slice ( 0 , i + 1 ) . join ( '.' ) }` ) ) . join ( ',' ) ; }