import { Attribute as Attr , AttributeValue , attrValues } from "@opticss/element-analysis" ; import { assertNever } from "@opticss/util" ; import { isString } from "util" ; import { ATTR_PRESENT , AttrToken , ROOT_CLASS } from "../BlockSyntax" ; import { BlockPath } from "../BlockSyntax" ; import { OutputMode , ResolvedConfiguration } from "../configuration" ; import { unionInto } from "../util/unionInto" ; import { Attribute } from "./Attribute" ; import { AttrValue } from "./AttrValue" ; import { Block } from "./Block" ; import { RulesetContainer } from "./RulesetContainer" ; import { Style } from "./Style" ; import { Styles } from "./Styles" ; function ensureToken ( input ) { let token = input ; if ( isString ( token ) ) { token = new BlockPath ( token ) . attribute ; } if ( ! token ) { throw new Error ( "Block path is not a valid attribute selector." ) ; } return token ; } export interface Composition { style : Styles ; conditions : AttrValue [ ] ; path : string ; } /**
 * Represents a Class present in the Block.
 */ export class BlockClass extends Style < BlockClass , Block , Block , Attribute > { private _sourceAttribute : Attr | undefined ; private _composedStyles : Set < Composition > = new Set ( ) ; private _resolvedComposedStyles : Set < Composition > | undefined ; public readonly rulesets : RulesetContainer < BlockClass > ; constructor ( name , parent ) { super ( name , parent ) ; this . rulesets = new RulesetContainer ( this ) ; } protected get ChildConstructor ( ) : typeof Attribute { return Attribute ; } returns public get name ( ) : string { return this . uid ; } get isRoot ( ) : boolean { return this . name === ROOT_CLASS ; } public attributes ( ) { return this . children ( ) ; } public getAttributes ( ) { return this . children ( ) ; } public getAttribute ( token ) { return this . getChild ( ensureToken ( token ) ) ; } public resolveAttribute ( token ) { return this . resolveChild ( ensureToken ( token ) ) ; } param name returns public ensureAttribute ( token ) { return this . ensureChild ( ensureToken ( token ) ) ; } /**
     * Returns all concrete AttrValues defined on this class.
     * Does not take inheritance into account.
     */ public allAttributeValues ( ) { let result = [ ] ; for ( let attr of this . attributes ( ) ) { result . push ( ... attr . values ( ) ) ; } return result ; } public getAttributeValues ( token , filter ? ) { token = ensureToken ( token ) ; let attr = this . getAttribute ( token ) ; if ( ! attr ) { return [ ] ; } let values = attr . values ( ) ; return filter ? values . filter ( s => s . value === filter ) : values ; } param token public resolveAttributeValues ( token ? ) { token = token ? ensureToken ( token ) : undefined ; let resolved = new Map ( ) ; let chain = this . resolveInheritance ( ) ; chain . push ( this ) ; for ( let base of chain ) { let attributes = ! token ? base . getAttributes ( ) : [ base . getAttribute ( token ) ] ; for ( let attr of attributes ) { if ( attr && attr . values ( ) ) { resolved = new Map ( [ ... resolved , ... attr . valuesMap ( ) ] ) ; } } } return resolved ; } param token returns public getAttributeValue ( token ) { token = ensureToken ( token ) ; let attr = this . getAttribute ( token ) ; return attr ? attr . getValue ( token . value ) || null : null ; } param token returns public resolveAttributeValue ( token ) { token = ensureToken ( token ) ; let value = token . value || ATTR_PRESENT ; let parent = this . resolveAttribute ( token ) ; if ( parent ) { return parent . resolveValue ( value ) ; } return null ; } param token returns public ensureAttributeValue ( token ) { token = ensureToken ( token ) ; return this . ensureAttribute ( token ) . ensureValue ( token . value ) ; } returns public booleanAttributeValues ( ) { let res = [ ] ; for ( let attr of this . getAttributes ( ) ) { let val = attr . getValue ( ATTR_PRESENT ) ; if ( ! attr . hasValues && val ) { res . push ( val ) ; } } return res ; } param scope returns public asSource ( scope ? ) { let blockName = this . block . name ; if ( scope instanceof Block ) { blockName = scope . getReferencedBlockLocalName ( this . block ) || blockName ; } if ( scope && scope !== this . block ) { return this . isRoot ? blockName : `${ blockName }.${ this . name }` ; } return this . isRoot ? ROOT_CLASS : `.${ this . name }` ; } param optionalRoot public asSourceAttributes ( optionalRoot = false ) { if ( ! this . _sourceAttribute ) { let value = { constant : this . name } ; if ( optionalRoot && this . isRoot ) { value = attrValues . oneOf ( [ value , attrValues . absent ( ) ] ) ; } this . _sourceAttribute = new Attr ( "class" , value ) ; } return [ this . _sourceAttribute ] ; } param config returns public cssClass ( config , reservedClassNames ) { if ( this . presetCssClass ) { return this . presetCssClass ; } switch ( config . outputMode ) { case OutputMode . BEM : let bemName = this . isRoot ? `${ this . block . name }` : `${ this . block . name }__${ this . name }` ; if ( reservedClassNames . has ( bemName ) ) { return this . isRoot ? `${ this . block . name }_${ this . block . guid }` : `${ this . block . name }_${ this . block . guid }__${ this . name }` ; } else { return bemName ; } case OutputMode . BEM_UNIQUE : return this . isRoot ? `${ this . block . name }_${ this . block . guid }` : `${ this . block . name }_${ this . block . guid }__${ this . name }` ; default : return assertNever ( config . outputMode ) ; } } param shallow returns all ( shallow ? ) { let result = [ this ] ; if ( ! shallow ) { result = result . concat ( this . allAttributeValues ( ) ) ; } return result ; } getGroupsNames ( ) { return new Set < string > ( [ ... this . _children . keys ( ) ] ) ; } returns resolveComposedStyles ( ) { if ( this . _resolvedComposedStyles ) { return this . _resolvedComposedStyles ; } const composedStyles = new Set ( ) ; const resolvedStyles = this . resolveStyles ( ) ; for ( let style of resolvedStyles ) { unionInto ( composedStyles , style . composedStyles ( ) ) ; } return this . _resolvedComposedStyles = composedStyles ; } returns composedStyles ( ) { return new Set ( this . _composedStyles ) ; } /**
     * Adds a new Style for this Style to compose.
     * TODO: Currently, conditions are grouped exclusively by the 'and' operator.
     *       We can abstract boolean operators to keep an internal representation
     *       of logic between css and template files and only resolve them to the
     *       requested language interface at rewrite time.
     */ addComposedStyle ( style , conditions , path ) { this . _composedStyles . add ( { style , conditions , path } ) ; } /**
     * Checks whether this style composes another style.
     */ composes ( style , resolve = true ) { let compositions = resolve ? this . resolveComposedStyles ( ) : new Set ( this . _composedStyles ) ; for ( let comp of compositions ) { if ( comp . style === style ) { return true ; } } return false ; } param options return debug ( config ) { let result = [ ] ; const composed = [ ... this . _composedStyles ] ; if ( composed . length ) { result . push ( " composes:" ) ; } for ( let comp of composed ) { let isLast = composed . indexOf ( comp ) === composed . length - 1 ; let conditional = comp . conditions . length ? ` when ${ comp . conditions . map ( ( c ) => c . name ( ) ) . join ( " && " ) }` : "" ; result . push ( ` ${ isLast ? "└──" : "├──" } ${ comp . style . asSource ( true ) }${ conditional }` ) ; } const children = [ ... this . resolveAttributeValues ( ) . values ( ) ] . map ( s => s . asSource ( ) ) ; children . sort ( ) ; if ( children . length ) { result . push ( " states:" ) ; } for ( let n of children ) { let o = this . resolveAttributeValue ( n ) ; if ( ! o ) { continue ; } let isLast = children . indexOf ( n ) === children . length - 1 ; result . push ( ` ${ isLast ? "└──" : "├──" } ${ o . asDebug ( config ) }` ) ; } return result ; } } export function isBlockClass ( o ) { return o instanceof BlockClass ; }