@chassis/core
Version:
CSS4 pre-processor and responsive framework for modern UI development
228 lines (173 loc) • 5.21 kB
JavaScript
import parseValue from 'postcss-value-parser'
import StringUtils from '../../utilities/StringUtils.js'
import ViewportUtils from '../../utilities/ViewportUtils.js'
export default class Expression {
#parent = null
#nodes = null
#dimension = null
#min = null
#max = null
#comparisonOperators = ['<', '<=', '=', '>=', '>']
#operatorError = `\nInvalid media operator\nExpected one of the following: ${this.#comparisonOperators.join(', ')}`
constructor (parent, expression) {
this.#parent = parent
this.#nodes = parseValue(expression).nodes.filter(node => node.type === 'word').map(node => node.value)
if (this.#comparisonOperators.some(operator => this.#nodes.includes(operator))) {
return this.#processComparison()
}
this.#processComputation()
}
get min () {
return this.#min
}
get max () {
return this.#max
}
get dimension () {
return this.#dimension
}
error () {
return this.#parent.error(...arguments)
}
#processComparison = () => {
switch (this.#nodes.length) {
case 3: return this.#processSingleComparison()
case 5: return this.#processDoubleComparison()
default: throw this.error('\nInvalid expression', { index: 1 })
}
}
#processSingleComparison = () => {
let first = 'dimension'
const props = this.#nodes.reduce((props, node, index) => {
if (['height', 'width'].includes(node)) {
if (props.value) {
first = 'value'
}
this.#dimension = node
} else if (this.#comparisonOperators.includes(node)) {
props.operator = node
} else {
props.value = node
}
return props
}, {})
if (!this.#dimension) {
throw this.error('\nInvalid media dimension\nExpected "width" or "height"', {
word: this.#nodes[first === 'dimension' ? 0 : 2]
})
}
if (!props.operator) {
throw this.error(this.#operatorError, { word: this.#nodes[1] })
}
if (!props.value) {
throw this.error('\nInvalid media value\nExpected pixel (px) value or viewport name prefixed with "--"', {
word: this.#nodes[first === 'value' ? 0 : 2]
})
}
const numeric = parseInt(props.value)
if (isNaN(numeric)) {
return this.#processSingleViewportComparison(props, first)
}
const units = StringUtils.getUnits(props.value)
if (units !== 'px') {
throw this.error('\nMedia query constraints must be specified in px', { word: units })
}
if (props.operator === '=') {
this.#min = numeric
this.#max = numeric
}
if (first === 'value') {
this.#processValueFirstComparison(numeric, props.operator)
} else {
this.#processDimensionFirstComparison(numeric, props.operator)
}
}
#processValueFirstComparison = (value, operator) => {
switch (operator) {
case '<=':
this.#min = value
return
case '>=':
this.#max = value
return
case '<':
this.#min = value + 1
return
case '>':
this.#max = value - 1
return
default: throw this.error(this.#operatorError, { word: operator })
}
}
#processDimensionFirstComparison = (value, operator) => {
switch (operator) {
case '<=':
this.#max = value
return
case '>=':
this.#min = value
return
case '<':
this.#max = value - 1
return
case '>':
this.#min = value + 1
return
default: this.error(this.#operatorError, { word: operator })
}
}
#processDoubleComparison = () => {
}
#processSingleViewportComparison = (props, first) => {
const name = props.value.replace('--', '')
const viewport = ViewportUtils.get(name)
if (!viewport) {
throw this.error(`\nViewport "${name}" not found`, { word: name })
}
const dimensions = {
min: NGN.coalesce(viewport.bounds.min, ViewportUtils.getPreviousBound(viewport, 'max')),
max: NGN.coalesce(viewport.bounds.max, ViewportUtils.getNextBound(viewport, 'min'))
}
if (props.operator === '=') {
this.#min = dimensions.min
this.#max = dimensions.max - 1
return
}
if (first === 'value') {
switch (props.operator) {
case '<=':
this.#min = dimensions.min
return
case '<':
this.#min = dimensions.max + 1
return
case '>=':
this.#max = dimensions.max - 1
return
case '>':
this.#max = Math.max(dimensions.min - 1, 0)
return
default: this.error(this.#operatorError, { word: props.operator })
}
}
switch (props.operator) {
case '<=':
this.#max = Math.max(dimensions.max - 1, 0)
return
case '<':
this.#max = Math.max(dimensions.min - 1, 0)
return
case '>=':
this.#min = dimensions.min
return
case '>':
this.#min = dimensions.max + 1
return
default: this.error(this.#operatorError, { word: props.operator })
}
}
#processDoubleViewportComparison = props => {
}
#processComputation = () => {
}
}