module import * as CSSSelect from "@opticss/css-select" ; import * as propParser from "css-property-parser" ; import * as parse5 from "parse5" ; import * as postcss from "postcss" ; import * as specificity from "specificity" ; import { bodyElement , parseStylesheet , walkRules } from "./util" ; export interface PseudoStates { /**
     * Maps the pseudo-state's name to the elements for which
     * that state can affect the computed style of an element.
     */ [ pseudoState ] : Array < parse5 . Element > ; } export interface StyledPseudoElements { /**
     * Maps the pseudo-element's name to its computed style
     * The name does not include the preceding colon(s).
     */ [ pseudoElement ] : ElementStyle ; } /**
 * The styles computed from the cascade for an element in a particular state.
 */ export interface ComputedStyle { [ property ] : string ; } export interface MatchedSelector { selector : string ; rule : postcss . Rule ; specificity : specificity . Specificity ; } export class ElementStyle { matchedSelectors : Array < MatchedSelector > ; /**
     * Track whether the selectors are out of order and need to be re-sorted;
     */ private dirty : boolean ; constructor ( ) { this . matchedSelectors = new Array ( ) ; this . dirty = false ; } returns pseudoStates ( ) { return { } ; } returns styledPseudoElements ( ) { return { } ; } pseudoElementStyle ( _name ) { return new ElementStyle ( ) ; } add ( selector , rule , specificity ) { this . dirty = true ; this . matchedSelectors . push ( { selector , rule , specificity , } ) ; } private clean ( ) { if ( this . dirty ) { this . dirty = false ; let indexMap = new Map < MatchedSelector , number > ( ) ; this . matchedSelectors . forEach ( ( m , i ) => { indexMap . set ( m , i ) ; } ) ; this . matchedSelectors . sort ( ( a , b ) => { let cmp = specificity . compare ( a . specificity . specificityArray , b . specificity . specificityArray ) ; if ( cmp === 0 ) { return ( indexMap . get ( a ) ! < indexMap . get ( b ) ! ) ? - 1 : 1 ; } else { return cmp ; } } ) ; } } compute ( ) { this . clean ( ) ; let style = { } ; let importantDeclarations = new Array < postcss . Declaration > ( ) ; this . matchedSelectors . forEach ( match => { match . rule . walkDecls ( ( decl ) => { if ( decl . important ) { importantDeclarations . push ( decl ) ; } else { Object . assign ( style , stylesForDeclaration ( decl ) ) ; } } ) ; } ) ; importantDeclarations . forEach ( decl => { Object . assign ( style , stylesForDeclaration ( decl ) ) ; } ) ; return style ; } debug ( ) { this . clean ( ) ; let ruleSets = new Array < string > ( ) ; this . matchedSelectors . forEach ( m => { let declarations = new Array < string > ( `${ m . selector } {` ) ; m . rule . walkDecls ( decl => { declarations . push ( "  " + decl . toString ( ) ) ; } ) ; declarations . push ( "}" ) ; ruleSets . push ( declarations . join ( "\n" ) ) ; } ) ; return ruleSets . join ( "\n\n" ) ; } } function stylesForDeclaration ( decl ) { if ( propParser . isShorthandProperty ( decl . prop ) ) { let expandedValues = < ComputedStyle > propParser . expandShorthandProperty ( decl . prop , decl . value , true , true ) ; let expandedProps = Object . keys ( expandedValues ) ; for ( let prop of expandedProps ) { if ( propParser . isShorthandProperty ( prop ) ) { delete expandedValues [ prop ] ; } } return expandedValues ; } else { return { [ decl . prop ] : decl . value , } ; } } export type FullCascade = Map < parse5 . Element , ElementStyle > ; export class Cascade { stylesheet : string ; html : parse5 . DefaultTreeDocument ; constructor ( stylesheet , html ) { this . stylesheet = stylesheet ; this . html = html ; } perform ( ) { let map = new Map < parse5 . Element , ElementStyle > ( ) ; let bodyEl = bodyElement ( this . html ) ! ; let selectOpts ; return parseStylesheet ( this . stylesheet ) . then ( result => { walkRules ( result . root ! , ( rule , scope ) => { if ( scope . length > 0 ) return ; if ( rule . selectors ) { rule . selectors . forEach ( selector => { let s = specificity . calculate ( selector ) [ 0 ] ; try { let matchedElements = CSSSelect ( selector , bodyEl , selectOpts ) ; matchedElements . forEach ( e => { let style = map . get ( e ) ; if ( ! style ) { style = new ElementStyle ( ) ; map . set ( e , style ) ; } style . add ( selector , rule , s ) ; } ) ; } catch ( e ) { if ( e . message && e . message . match ( /unmatched pseudo-(class|element)/ ) ) { } else { throw e ; } } } ) ; } } ) ; return map ; } ) ; } }