license import { ControlMetaType , ControlThemedStyleType , ThemedStyleType , } from '@eva-design/dss' ; import { StyledComponentProps } from './styled' ; import { Interaction , StyleService , StyleType , } from './style.service' ; import { ThemeType } from '../theme/theme.service' ; const SEPARATOR_MAPPING_ENTRY = '.' ; const DOC_ROOT = 'https://akveo.github.io/react-native-ui-kitten/docs' ; interface StyleInfo { appearance : string ; variants : string [ ] ; states : string [ ] ; } export class StyleConsumerService { private readonly name : string ; private readonly meta : ControlMetaType ; constructor ( name , style ) { this . name = name ; this . meta = this . safe ( style [ name ] , ( generatedConfig ) : ControlMetaType => { return generatedConfig . meta ; } ) ; if ( ! this . meta ) { const message = [ `\n${ this . name }: unsupported configuration.` , 'Using UI Kitten components is only possible with configuring ApplicationProvider.' , `📖 Documentation: ${ DOC_ROOT }/guides/getting-started#manual-installation` , '\nIn case you have all in place, there might be an incorrect usage of a "styled" function.' , `📖 Documentation: ${ DOC_ROOT }/design-system/custom-component-mapping` , ] . join ( '\n' ) ; console . error ( message ) ; } } public createDefaultProps < P extends object > ( ) { const appearance = this . getDefaultAppearance ( ) ; const variants = this . getDefaultVariants ( ) ; const states = this . getDefaultStates ( ) ; return { appearance , ... variants , ... states } ; } public createStyleProp < P extends object > ( source , style , theme , interaction ) { const styleInfo = this . getStyleInfo ( source , this . withValidInteraction ( interaction ) ) ; const generatedMapping = this . getGeneratedStyleMapping ( style , styleInfo ) ; if ( ! generatedMapping ) { const message = [ `${ this . name }: unsupported configuration.` , `Check one of the following prop values: ${ JSON . stringify ( styleInfo , null , 2 ) }` , `📖 Documentation: ${ DOC_ROOT }/components/${ this . name . toLowerCase ( ) }/api` , ] . join ( '\n' ) ; console . warn ( message ) ; return this . createStyleProp ( { ... source , ... this . createDefaultProps ( ) } , style , theme , interaction ) ; } const mapping = this . withValidParameters ( generatedMapping ) ; return StyleService . createThemedEntry ( mapping , theme ) ; } private getGeneratedStyleMapping < P extends StyledComponentProps > ( style , info ) { return this . safe ( style [ this . name ] , ( componentStyles ) : ThemedStyleType => { const styleKeys = Object . keys ( componentStyles . styles ) ; const query = this . findGeneratedQuery ( info , styleKeys ) ; return componentStyles . styles [ query ] ; } ) ; } private withValidInteraction ( interaction ) { const validInteractions = interaction . filter ( ( key ) => { return Object . keys ( this . meta . states ) . includes ( key ) ; } ) ; if ( validInteractions . length < interaction . length ) { const message = [ `${ this . name }: unsupported configuration.` , `Check one of the following dispatched interactions: ${ interaction }` , `📖 Documentation: ${ DOC_ROOT }/design-system/custom-component-mapping` , ] . join ( '\n' ) ; console . warn ( message ) ; } return validInteractions ; } private withValidParameters ( mapping ) { const invalidParameters = [ ] ; Object . keys ( mapping ) . forEach ( ( key ) => { if ( ! Object . keys ( this . meta . parameters ) . includes ( key ) ) { invalidParameters . push ( key ) ; delete mapping [ key ] ; } } ) ; if ( invalidParameters . length !== 0 ) { const message = [ `${ this . name }: unsupported configuration.` , `Unable to apply ${ invalidParameters }` , 'There might be an incorrect usage of mapping' , `📖 Documentation: ${ DOC_ROOT }/design-system/custom-component-mapping` , ] . join ( '\n' ) ; console . warn ( message ) ; } return mapping ; } private getStyleInfo < P extends StyledComponentProps > ( props , interaction ) { const variantProps = this . getDerivedVariants ( this . meta , props ) ; const stateProps = this . getDerivedStates ( this . meta , props ) ; const variants = Object . keys ( variantProps ) . map ( ( variant ) : string => { return variantProps [ variant ] ; } ) ; const states = Object . keys ( stateProps ) ; return { appearance : props . appearance , variants : variants , states : [ ... states , ... interaction ] , } ; } private getDefaultAppearance ( ) { const matches = Object . keys ( this . meta . appearances ) . filter ( ( appearance ) : boolean => { return this . meta . appearances [ appearance ] . default === true ; } ) ; return matches [ matches . length - 1 ] ; } private getDefaultVariants ( ) { return this . transformObject ( this . meta . variantGroups , ( variants , group ) : string | undefined => { return Object . keys ( variants [ group ] ) . find ( ( variant ) : boolean => { return variants [ group ] [ variant ] . default === true ; } ) ; } ) ; } private getDefaultStates ( ) { return this . transformObject ( this . meta . states , ( states , state ) : boolean | undefined => { const isDefault = states [ state ] . default === true ; return isDefault ? isDefault : undefined ; } ) ; } private getDerivedVariants < P extends StyledComponentProps > ( meta , props ) { return this . transformObject ( props , ( p , prop ) : string | undefined => { const isVariant = Object . keys ( meta . variantGroups ) . includes ( prop ) ; return isVariant ? p [ prop ] : undefined ; } ) ; } private getDerivedStates < P extends StyledComponentProps > ( meta , props ) { return this . transformObject ( props , ( p , prop ) : boolean => { const isState = Object . keys ( meta . states ) . includes ( prop ) ; const isAssigned = p [ prop ] === true ; return isState && isAssigned ; } ) ; } param value param transform private transformObject < V extends object > ( value , transform ) { return Object . keys ( value ) . reduce ( ( acc , key ) : any => { const nextValue = transform ( value , key ) ; return nextValue ? { ... acc , [ key ] : nextValue , } : acc ; } , { } ) ; } param info param source return private findGeneratedQuery ( info , source ) { const query = [ info . appearance , ... info . variants , ... info . states , ] ; return source . find ( ( value ) => this . compareArrays ( query , value . split ( SEPARATOR_MAPPING_ENTRY ) ) ) ; } param lhs param rhs return private compareArrays ( lhs , rhs ) { if ( lhs . length !== rhs . length ) { return false ; } return lhs . reduce ( ( acc , current ) : boolean => acc && rhs . includes ( current ) , true ) ; } param value param reducer param fallback return private safe < T , R > ( value , reducer , fallback ? ) { if ( value ) { return reducer ( value ) ; } return fallback ; } }