/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { includes } from '../utils/arrays'; export class Element { constructor(decl) { this.fullPropertyName = decl.getFullPropertyName().toLowerCase(); this.node = decl; } } function setSide(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 whether the initial value of property is zero, so keywords `initial` and `unset` count as zero * @return `true` if this node represents a non-zero border; otherwise, `false` */ function checkLineWidth(value, allowsKeywords = true) { if (allowsKeywords && matches(value, ['initial', 'unset'])) { return false; } // a is a value and a unit // so use `parseFloat` to strip the unit return parseFloat(value.getText()) !== 0; } function checkLineWidthList(nodes, allowsKeywords = true) { return nodes.map(node => checkLineWidth(node, allowsKeywords)); } /** * @param allowsKeywords whether keywords `initial` and `unset` count as zero * @return `true` if this node represents a non-zero border; otherwise, `false` */ 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(); // the only child can be a keyword, a , or a // if either check returns false, the result is no border if (children.length === 1) { const value = children[0]; return checkLineWidth(value) && checkLineStyle(value); } // multiple children can't contain keywords // if any child means no border, the result is no border for (const child of children) { const value = child; if (!checkLineWidth(value, /* allowsKeywords: */ false) || !checkLineStyle(value, /* allowsKeywords: */ 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': // has `box-sizing`, bail out 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': // the initial value of `border-width` is `medium`, not zero updateModelWithValue(model, segments[1], checkLineWidth(value, false), property); break; case 'style': // the initial value of `border-style` is `none` updateModelWithValue(model, segments[1], checkLineStyle(value, true), property); break; } break; case 'width': // the initial value of `border-width` is `medium`, not zero updateModelWithList(model, checkLineWidthList(value.getChildren(), false), property); break; case 'style': // the initial value of `border-style` is `none` updateModelWithList(model, checkLineStyleList(value.getChildren(), true), property); break; } break; case 'padding': if (segments.length === 1) { // the initial value of `padding` is zero updateModelWithList(model, checkLineWidthList(value.getChildren(), true), property); } else { // the initial value of `padding` is zero updateModelWithValue(model, segments[1], checkLineWidth(value, true), property); } break; } break; } } return model; }