import { CommonStyle , FontFace , Globals , Keyframes , ResolvedDeclarationList , ResolvedDeclarations , ResolvedValue , Style , Theme , } from '../style' ; import InjectorClient from '../client/InjectorClient' ; import InjectorServer from '../server/InjectorServer' ; import { Transformer } from '../options' ; import { issueFormatter } from '../utils/debugging' ; import { ANIMATION_NAME , FONT_FAMILY } from './Injector' ; import { combineConditions , formatMediaRule , formatSupportsRule } from '../utils/format' ; type ResolvedStyle = { [ key ] : ResolvedValue | ResolvedDeclarations ; } ; type Resolver < TReturn > = ( style : CommonStyle , theme : Theme , resolved ? : ResolvedStyle , media ? : string , condition ? : string , selector ? : string ) => TReturn ; type Cache = { [ key ] : string | Cache ; } ; const GROUPING_IDENTIFIER = '@' . charCodeAt ( 0 ) ; const MEDIA_IDENTIFIER = 'm' . charCodeAt ( 0 ) ; const SUPPORTS_IDENTIFIER = 's' . charCodeAt ( 0 ) ; const PSEUDO_IDENTIFIER = ':' . charCodeAt ( 0 ) ; const ATTRIBUTE_IDENTIFIER = '[' . charCodeAt ( 0 ) ; export interface Base < TStyle extends Style > { injectStyle : ( styles : TStyle | readonly TStyle [ ] , theme ? : Theme ) => string ; injectGlobals : ( styles : Globals , theme ? : Theme ) => void ; } export function createStyleInjectors < TStyle extends Style > ( getInjector , transformer ) { const cache = { } ; const createResolver = ( inject ) => { const resolver = ( style , theme , styleIndex = { } , media , condition , selector ) => { let className = '' ; const properties = Object . keys ( style ) ; for ( let i = properties . length ; i > 0 ; i -- ) { let property = properties [ i - 1 ] ; let value = style [ property ] ; if ( typeof value === 'function' ) { value = value ( theme ) ; } if ( value === null ) { value = void 0 ; } if ( process . env . NODE_ENV !== 'production' ) { const issueTransformer = ( object ) => { if ( selector ) { object = { [ selector ] : object } ; } if ( media ) { object = { [ media ] : object } ; } return object ; } ; if ( [ 'undefined' , 'string' , 'number' , 'object' ] . indexOf ( typeof value ) === - 1 ) { console . error ( ... issueFormatter ( `Value from property \`${ property }\` has to be a string, number, plain object or array in:` , style , { [ property ] : value } , null , issueTransformer ) ) ; } if ( value === '' ) { console . error ( ... issueFormatter ( `Value from property \`${ property }\` is an empty string and may cause some unexpected behavior in:` , style , { [ property ] : value } , null , issueTransformer ) ) ; } if ( typeof value === 'number' && Number . isNaN ( value ) ) { console . error ( ... issueFormatter ( `Value from property \`${ property }\` is a NaN and may cause some unexpected behavior in:` , style , { [ property ] : value } , null , issueTransformer ) ) ; } if ( typeof value === 'number' && ! Number . isFinite ( value ) ) { console . error ( ... issueFormatter ( `Value from property \`${ property }\` is an infinite number and may cause some unexpected behavior in:` , style , { [ property ] : value } , null , issueTransformer ) ) ; } if ( typeof value === 'object' && Object . keys ( value ) . length === 0 ) { console . warn ( ... issueFormatter ( `Value from property \`${ property }\` is an empty object and can be removed in:` , style , { [ property ] : value } , null , issueTransformer ) ) ; } } if ( property === '@keyframes' ) { property = ANIMATION_NAME ; } if ( property === '@font-face' ) { property = FONT_FAMILY ; } if ( property === ANIMATION_NAME || property === FONT_FAMILY || typeof value !== 'object' || Array . isArray ( value ) ) { const declarations = getIndex ( styleIndex , media , condition , selector ) ; if ( ! ( property in declarations ) ) { if ( typeof value === 'object' ) { if ( property === ANIMATION_NAME ) { const keyframes = ( [ ] as Keyframes [ ] ) . concat ( value as Keyframes | Keyframes [ ] ) ; const names = [ ] ; for ( let j = 0 ; j < keyframes . length ; j ++ ) { const keyframe = keyframes [ j ] ; if ( typeof keyframe === 'object' ) { const list = { } ; for ( const key in keyframe ) { const block = resolveStyle ( keyframe [ key ] as CommonStyle , theme ) as ResolvedDeclarations ; list [ key ] = transformer ? transformer ( block ) : block ; } names [ j ] = getInjector ( ) . injectKeyframes ( list ) ; } } value = names . length === 1 ? names [ 0 ] : names ; } if ( property === FONT_FAMILY ) { const fonts = ( [ ] as ( string | FontFace ) [ ] ) . concat ( value as string | FontFace | ( string | FontFace ) [ ] ) ; const names = [ ] ; for ( const font of fonts ) { if ( typeof font === 'object' ) { const fontFace = resolveStyle ( font as CommonStyle , theme ) as ResolvedDeclarations ; const name = getInjector ( ) . injectFontFace ( transformer ? transformer ( fontFace ) : fontFace ) ; if ( names . indexOf ( name ) === - 1 ) { names . push ( name ) ; } } else { names . push ( font ) ; } } value = names . join ( ',' ) ; } } const declaration = { } ; declarations [ property ] = declaration [ property ] = value as ResolvedValue ; if ( typeof value !== 'undefined' && inject ) { let values ; let key ; if ( typeof value === 'string' || typeof value === 'number' ) { const cacheIndex = getIndex ( cache , media , condition , selector ) ; values = ( cacheIndex [ property ] ??= { } ) as Cache ; key = value ; if ( key in values ) { className += ' ' + values [ value ] ; continue ; } } let declarationClassName ; className += ' ' + ( declarationClassName = getInjector ( media ) . injectClassName ( transformer ? transformer ( declaration ) : declaration , selector , condition ) ) ; if ( values && key ) { values [ key ] = declarationClassName ; } } } continue ; } const identifier = property . charCodeAt ( 0 ) ; let nextMedia = '' ; let nextCondition = '' ; if ( identifier === GROUPING_IDENTIFIER ) { const nextIdentifier = property . charCodeAt ( 1 ) ; if ( nextIdentifier === MEDIA_IDENTIFIER ) { nextMedia = property . slice ( 6 ) . trim ( ) ; } else if ( nextIdentifier === SUPPORTS_IDENTIFIER ) { nextCondition = property . slice ( 9 ) . trim ( ) ; } } if ( nextMedia || nextCondition || identifier === PSEUDO_IDENTIFIER || identifier === ATTRIBUTE_IDENTIFIER ) { const result = resolver ( value , theme , styleIndex , nextMedia ? ( media ? combineConditions ( media , nextMedia ) : nextMedia ) : media , nextCondition ? ( condition ? combineConditions ( condition , nextCondition ) : nextCondition ) : condition , identifier === PSEUDO_IDENTIFIER || identifier === ATTRIBUTE_IDENTIFIER ? ( selector ?? '' ) + property : selector ) ; if ( inject ) { className += result as string ; } continue ; } const extensions = Object . keys ( value ) ; for ( let j = extensions . length ; j > 0 ; j -- ) { const extension = extensions [ j - 1 ] ; const longhandValue = value [ extension ] ; let alias = false ; if ( extension === 'y' || extension === 'xy' ) { alias = true ; const result = resolver ( { [ property + 'Top' ] : longhandValue , [ property + 'Bottom' ] : longhandValue } , theme , styleIndex , media , condition , selector ) ; if ( inject ) { className += result as string ; } } if ( extension === 'x' || extension === 'xy' ) { alias = true ; const result = resolver ( { [ property + 'Left' ] : longhandValue , [ property + 'Right' ] : longhandValue } , theme , styleIndex , media , condition , selector ) ; if ( inject ) { className += result as string ; } } if ( ! alias ) { const result = resolver ( { [ property + extension [ 0 ] . toUpperCase ( ) + extension . slice ( 1 ) ] : longhandValue } , theme , styleIndex , media , condition , selector ) ; if ( inject ) { className += result as string ; } } } } return inject ? className : styleIndex ; } ; return resolver as any ; } ; const resolveStyle = createResolver ( false ) ; const injectStyle = createResolver ( true ) ; function injectGlobals ( selector , result , media ? ) { const properties = Object . keys ( result ) ; const declarations = { } ; let hasDeclarations = false ; for ( let i = properties . length - 1 ; i >= 0 ; i -- ) { const property = properties [ i ] ; const identifier = property . charCodeAt ( 0 ) ; if ( identifier !== GROUPING_IDENTIFIER && identifier !== PSEUDO_IDENTIFIER && identifier !== ATTRIBUTE_IDENTIFIER ) { declarations [ property ] = result [ property ] as ResolvedValue ; hasDeclarations = true ; } } if ( hasDeclarations ) { getInjector ( media ) . injectGlobals ( transformer ? transformer ( declarations ) : declarations , selector ) ; } for ( let i = properties . length - 1 ; i >= 0 ; i -- ) { const property = properties [ i ] ; const identifier = property . charCodeAt ( 0 ) ; if ( identifier === GROUPING_IDENTIFIER && property . charCodeAt ( 1 ) === MEDIA_IDENTIFIER ) { injectGlobals ( selector , result [ property ] as ResolvedStyle , property . slice ( 7 ) ) ; } else if ( identifier === PSEUDO_IDENTIFIER || identifier === ATTRIBUTE_IDENTIFIER ) { injectGlobals ( selector + property , result [ property ] as ResolvedStyle , media ) ; } } } return [ ( styles , theme = { } ) => { styles = Array . isArray ( styles ) ? styles : [ styles ] ; const index = { } ; let classNames = '' ; for ( let i = styles . length - 1 ; i >= 0 ; i -- ) { classNames += injectStyle ( styles [ i ] as unknown as CommonStyle , theme , index ) ; } return classNames . slice ( 1 ) ; } , ( styles , theme = { } ) => { for ( const property in styles ) { injectGlobals ( property , resolveStyle ( styles [ property ] as CommonStyle , theme ) ) ; } } , ] as const ; } function getIndex < TIndex extends { [ key ] : any ; } > ( indexes , media , condition , selector ) { let index = indexes ; if ( media ) { index = ( index [ formatMediaRule ( media ) ] as TIndex ) ??= { } as TIndex ; } if ( condition ) { index = ( index [ formatSupportsRule ( condition ) ] as TIndex ) ??= { } as TIndex ; } if ( selector ) { return ( ( index [ selector ] as TIndex ) ??= { } as TIndex ) ; } else { return index ; } }