'use strict' ; import * as nodes from '../parser/cssNodes' ; import { includes } from '../utils/arrays' ; export class Element { public readonly fullPropertyName : string ; public readonly node : nodes . Declaration ; constructor ( decl ) { this . fullPropertyName = decl . getFullPropertyName ( ) . toLowerCase ( ) ; this . node = decl ; } } interface SideState { value : boolean ; properties : Element [ ] ; } interface BoxModel { width ? : Element ; height ? : Element ; top : SideState ; right : SideState ; bottom : SideState ; left : SideState ; } function setSide < K extends 'top' | 'right' | 'bottom' | 'left' > ( model , side , value , property ) { const state = model [ side ] ; state . value = value ; if ( value ) { if ( ! includes ( state . properties , property ) ) { state . properties . push ( property ) ; } } } function setAllSides ( model , value , property ) { setSide ( model , 'top' , value , property ) ; setSide ( model , 'right' , value , property ) ; setSide ( model , 'bottom' , value , property ) ; setSide ( model , 'left' , value , property ) ; } function updateModelWithValue ( model , side , value , property ) { if ( side === 'top' || side === 'right' || side === 'bottom' || side === 'left' ) { setSide ( model , side , value , property ) ; } else { setAllSides ( model , value , property ) ; } } function updateModelWithList ( model , values , property ) { switch ( values . length ) { case 1 : updateModelWithValue ( model , undefined , values [ 0 ] , property ) ; break ; case 2 : updateModelWithValue ( model , 'top' , values [ 0 ] , property ) ; updateModelWithValue ( model , 'bottom' , values [ 0 ] , property ) ; updateModelWithValue ( model , 'right' , values [ 1 ] , property ) ; updateModelWithValue ( model , 'left' , values [ 1 ] , property ) ; break ; case 3 : updateModelWithValue ( model , 'top' , values [ 0 ] , property ) ; updateModelWithValue ( model , 'right' , values [ 1 ] , property ) ; updateModelWithValue ( model , 'left' , values [ 1 ] , property ) ; updateModelWithValue ( model , 'bottom' , values [ 2 ] , property ) ; break ; case 4 : updateModelWithValue ( model , 'top' , values [ 0 ] , property ) ; updateModelWithValue ( model , 'right' , values [ 1 ] , property ) ; updateModelWithValue ( model , 'bottom' , values [ 2 ] , property ) ; updateModelWithValue ( model , 'left' , values [ 3 ] , property ) ; break ; } } function matches ( value , candidates ) { for ( let candidate of candidates ) { if ( value . matches ( candidate ) ) { return true ; } } return false ; } param allowsKeywords return function checkLineWidth ( value , allowsKeywords = true ) { if ( allowsKeywords && matches ( value , [ 'initial' , 'unset' ] ) ) { return false ; } return parseFloat ( value . getText ( ) ) !== 0 ; } function checkLineWidthList ( nodes , allowsKeywords = true ) { return nodes . map ( node => checkLineWidth ( node , allowsKeywords ) ) ; } param allowsKeywords return function checkLineStyle ( valueNode , allowsKeywords = true ) { if ( matches ( valueNode , [ 'none' , 'hidden' ] ) ) { return false ; } if ( allowsKeywords && matches ( valueNode , [ 'initial' , 'unset' ] ) ) { return false ; } return true ; } function checkLineStyleList ( nodes , allowsKeywords = true ) { return nodes . map ( node => checkLineStyle ( node , allowsKeywords ) ) ; } function checkBorderShorthand ( node ) { const children = node . getChildren ( ) ; if ( children . length === 1 ) { const value = children [ 0 ] ; return checkLineWidth ( value ) && checkLineStyle ( value ) ; } for ( const child of children ) { const value = child ; if ( ! checkLineWidth ( value , false ) || ! checkLineStyle ( value , false ) ) { return false ; } } return true ; } export default function calculateBoxModel ( propertyTable ) { const model = { top : { value : false , properties : [ ] } , right : { value : false , properties : [ ] } , bottom : { value : false , properties : [ ] } , left : { value : false , properties : [ ] } , } ; for ( const property of propertyTable ) { const value = property . node . value ; if ( typeof value === 'undefined' ) { continue ; } switch ( property . fullPropertyName ) { case 'box-sizing' : return { top : { value : false , properties : [ ] } , right : { value : false , properties : [ ] } , bottom : { value : false , properties : [ ] } , left : { value : false , properties : [ ] } , } ; case 'width' : model . width = property ; break ; case 'height' : model . height = property ; break ; default : const segments = property . fullPropertyName . split ( '-' ) ; switch ( segments [ 0 ] ) { case 'border' : switch ( segments [ 1 ] ) { case undefined : case 'top' : case 'right' : case 'bottom' : case 'left' : switch ( segments [ 2 ] ) { case undefined : updateModelWithValue ( model , segments [ 1 ] , checkBorderShorthand ( value ) , property ) ; break ; case 'width' : updateModelWithValue ( model , segments [ 1 ] , checkLineWidth ( value , false ) , property ) ; break ; case 'style' : updateModelWithValue ( model , segments [ 1 ] , checkLineStyle ( value , true ) , property ) ; break ; } break ; case 'width' : updateModelWithList ( model , checkLineWidthList ( value . getChildren ( ) , false ) , property ) ; break ; case 'style' : updateModelWithList ( model , checkLineStyleList ( value . getChildren ( ) , true ) , property ) ; break ; } break ; case 'padding' : if ( segments . length === 1 ) { updateModelWithList ( model , checkLineWidthList ( value . getChildren ( ) , true ) , property ) ; } else { updateModelWithValue ( model , segments [ 1 ] , checkLineWidth ( value , true ) , property ) ; } break ; } break ; } } return model ; }