UNPKG

postcss-ordered-values

Version:

Ensure values are ordered consistently in your CSS.

138 lines (124 loc) • 3.48 kB
import cssnanoUtils from 'cssnano-utils'; import vendorUnprefixed from '../lib/vendorUnprefixed.js'; import { classifyMathLength, isLength } from '../lib/isLength.js'; import { isFunction, isHash, isIdent, isUrl, name, reservedIdentKeywords, serializeArguments, } from '../lib/tokenize.js'; const { mathFunctions } = cssnanoUtils; // Color-producing functions per CSS Color 4/5. Anything else is not a // <color> value and must not be reordered as one. const colorFunctions = new Set([ 'color', 'color-mix', 'device-cmyk', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'light-dark', 'oklab', 'oklch', 'rgb', 'rgba', ]); /** @param {import('../lib/tokenize.js').Term} term @param {string} lower */ const isColor = (term, lower) => { if (isIdent(term)) { return ( lower !== 'none' && lower !== 'auto' && !reservedIdentKeywords.has(lower) ); } if (isFunction(term)) { return !isUrl(term) && colorFunctions.has(vendorUnprefixed(lower)); } return isHash(term); }; /** * A literal zero <length>. * * @param {import('../lib/tokenize.js').Term} term */ const isZeroLength = (term) => isLength(term) && /** @type {{value: number}} */ (term.tokens[0][4])?.value === 0; /** * Blur radius and spread distance default to 0, and the spread cannot be * written without the blur, so only trailing zeros are omissible. * * @param {import('../lib/tokenize.js').Term[]} lengths */ function omitTrailingZeros(lengths) { while (lengths.length > 2 && isZeroLength(lengths[lengths.length - 1])) { lengths.pop(); } } /** * @param {import('../lib/tokenize.js').Term[][]} args * @return {import('../lib/tokenize.js').Term[][] | null} */ function normalize(args) { const list = []; let hasNone = false; for (const arg of args) { if (arg.length === 1 && isIdent(arg[0]) && name(arg[0]) === 'none') { hasNone = true; list.push(arg); continue; } /** @type {import('../lib/tokenize.js').Term[]} */ const val = []; /** @type {Record<'inset'|'color', import('../lib/tokenize.js').Term[]>} */ const state = { inset: [], color: [], }; for (const node of arg) { const value = name(node); if (isFunction(node) && vendorUnprefixed(value) === 'inset') { return null; } if (isLength(node)) { val.push(node); } else if ( isFunction(node) && mathFunctions.has(vendorUnprefixed(value)) ) { // Math is only usable as a length here; anything else fails closed // rather than being reclassified as a color. if (classifyMathLength(node) === null) return null; val.push(node); } else if (isIdent(node) && value === 'inset') { state.inset.push(node); } else if (isColor(node, value)) { state.color.push(node); } else { return null; } } if (val.length < 2 || val.length > 4 || state.color.length > 1) { return null; } omitTrailingZeros(val); list.push([...state.inset, ...val, ...state.color]); } if (args.length > 1 && hasNone) { return null; } return list; } /** * @param {{ arguments: import('../lib/tokenize.js').Term[][], value: string }} parsed * @return {string | null} */ function normalizeBoxShadow(parsed) { const normalized = normalize(parsed.arguments); return normalized === null ? null : serializeArguments(normalized); } export default normalizeBoxShadow;