121 lines
3.1 KiB
JavaScript
121 lines
3.1 KiB
JavaScript
const parser = require('postcss-selector-parser')
|
|
const parseSelector = parser()
|
|
|
|
function isObject(value) {
|
|
return typeof value === 'object' && value !== null
|
|
}
|
|
|
|
function isPlainObject(value) {
|
|
if (typeof value !== 'object' || value === null) {
|
|
return false
|
|
}
|
|
|
|
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
|
return false
|
|
}
|
|
|
|
if (Object.getPrototypeOf(value) === null) {
|
|
return true
|
|
}
|
|
|
|
let proto = value
|
|
while (Object.getPrototypeOf(proto) !== null) {
|
|
proto = Object.getPrototypeOf(proto)
|
|
}
|
|
|
|
return Object.getPrototypeOf(value) === proto
|
|
}
|
|
|
|
function merge(target, ...sources) {
|
|
if (!sources.length) return target
|
|
const source = sources.shift()
|
|
|
|
if (isObject(target) && isObject(source)) {
|
|
for (const key in source) {
|
|
if (Array.isArray(source[key])) {
|
|
if (!target[key]) target[key] = []
|
|
source[key].forEach((item, index) => {
|
|
if (isPlainObject(item) && isPlainObject(target[key][index])) {
|
|
target[key][index] = merge(target[key][index], item)
|
|
} else {
|
|
target[key][index] = item
|
|
}
|
|
})
|
|
} else if (isPlainObject(source[key])) {
|
|
if (!target[key]) target[key] = {}
|
|
merge(target[key], source[key])
|
|
} else {
|
|
target[key] = source[key]
|
|
}
|
|
}
|
|
}
|
|
|
|
return merge(target, ...sources)
|
|
}
|
|
|
|
function castArray(value) {
|
|
return Array.isArray(value) ? value : [value]
|
|
}
|
|
|
|
module.exports = {
|
|
isObject,
|
|
isPlainObject,
|
|
merge,
|
|
castArray,
|
|
isUsableColor(color, values) {
|
|
return isPlainObject(values) && color !== 'gray' && values[600]
|
|
},
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
commonTrailingPseudos(selector) {
|
|
let ast = parseSelector.astSync(selector)
|
|
|
|
/** @type {import('postcss-selector-parser').Pseudo[][]} */
|
|
let matrix = []
|
|
|
|
// Put the pseudo elements in reverse order in a sparse, column-major 2D array
|
|
for (let [i, sel] of ast.nodes.entries()) {
|
|
for (const [j, child] of [...sel.nodes].reverse().entries()) {
|
|
// We only care about pseudo elements
|
|
if (child.type !== 'pseudo' || !child.value.startsWith('::')) {
|
|
break
|
|
}
|
|
|
|
matrix[j] = matrix[j] || []
|
|
matrix[j][i] = child
|
|
}
|
|
}
|
|
|
|
let trailingPseudos = parser.selector()
|
|
|
|
// At this point the pseudo elements are in a column-major 2D array
|
|
// This means each row contains one "column" of pseudo elements from each selector
|
|
// We can compare all the pseudo elements in a row to see if they are the same
|
|
for (const pseudos of matrix) {
|
|
// It's a sparse 2D array so there are going to be holes in the rows
|
|
// We skip those
|
|
if (!pseudos) {
|
|
continue
|
|
}
|
|
|
|
let values = new Set(pseudos.map((p) => p.value))
|
|
|
|
// The pseudo elements are not the same
|
|
if (values.size > 1) {
|
|
break
|
|
}
|
|
|
|
pseudos.forEach((pseudo) => pseudo.remove())
|
|
trailingPseudos.prepend(pseudos[0])
|
|
}
|
|
|
|
if (trailingPseudos.nodes.length) {
|
|
return [trailingPseudos.toString(), ast.toString()]
|
|
}
|
|
|
|
return [null, selector]
|
|
},
|
|
}
|