UNPKG

postcss-merge-longhand

Version:

Merge longhand properties into shorthand with PostCSS.

155 lines (135 loc) • 3.82 kB
import cssnanoUtils from 'cssnano-utils'; import { discardOverriddenInLanes, discardOverriddenInList, } from './overriddenDeclarations.js'; import stylehacks from 'stylehacks'; import canExplode from '../canExplode.js'; import { isFallback } from '../isFallback.js'; import { SlotLane, commitShorthand, flushableSlots } from './slotVector.js'; import { hasNonAll, isAll } from './importanceLanes.js'; import { allColumnProps, columns, columnSlots, isInvalid, isValidColumns, normalize, parseColumns, parsedValue, } from './columnsValue.js'; const { asciiLowerCase } = cssnanoUtils; /** @import {Container, Declaration} from 'postcss'; */ export { allColumnProps, setsOtherColumnProperty } from './columnsValue.js'; /** * @param {Container} rule * @param {Declaration[]} laneDecls * @param {boolean} important */ function processLane(rule, laneDecls, important) { /** @type {Map<Declaration, Declaration>} */ const inserted = new Map(); const lane = new SlotLane(2, (slots, contributing, fallbacks) => { const full = flushableSlots(slots); if (!full) return; commitShorthand(rule, full, contributing, fallbacks, { prop: columns, value: normalize([full[0].value, full[1].value]), important, inserted, }); }); for (const decl of laneDecls) { const p = asciiLowerCase(decl.prop); const isShort = p === columns; if ( isAll(decl) || stylehacks.detect(decl) || (isShort && !canExplode(decl)) ) { lane.reset(); continue; } const idx = isShort ? -1 : (columnSlots.get(p) ?? -1); const wasFull = lane.begin(Math.max(idx, 0), idx === -1 ? 2 : 1, decl); if (isShort) { const parsed = parseColumns(parsedValue(decl)); if (!parsed) { lane.reset(); continue; } lane.assign(0, parsed[0], decl, wasFull); lane.assign(1, parsed[1], decl, wasFull); } else { lane.assign(idx, decl.value, decl, wasFull); } } lane.reset(); /** @type {Declaration[]} */ const remaining = []; for (const d of laneDecls) { const repl = inserted.get(d); if (repl) { remaining.push(repl); } else if (d.parent === rule) { remaining.push(d); } } if (remaining.length > 1) { discardOverriddenInList(remaining, allColumnProps, columnsPrecedence); } } /** * A later valid `columns` shorthand resets both longhands. * * @type {import('./overriddenDeclarations.js').CrossPropertyRule} */ const columnsPrecedence = { overrides: (node, lastNode) => asciiLowerCase(lastNode.prop) === columns && asciiLowerCase(node.prop) !== columns && isValidColumns(lastNode) && !isFallback(node, lastNode), }; /** @param {Declaration | undefined} s */ function normalizeSingleton(s) { if ( !s || asciiLowerCase(s.prop) !== columns || stylehacks.detect(s) || !canExplode(s) ) { return; } const parsed = parseColumns(parsedValue(s)); if (!parsed) return; const norm = normalize(parsed); if (s.value !== norm || s.prop !== columns) { s.prop = columns; s.value = norm; delete s.raws?.value; } } /** * @param {Container} rule * @param {Declaration[]} decls * @param {[Declaration[], Declaration[]]} familyLanes */ export function reduceColumns(rule, decls, familyLanes) { if (!rule.nodes) return; if (decls.length === 0 || decls.some(isInvalid)) return; discardOverriddenInLanes(familyLanes, allColumnProps); const live = decls.filter((d) => d.parent); if (live.length <= 1) { normalizeSingleton(live[0]); return; } for (const lane of [false, true]) { const laneDecls = familyLanes[lane ? 1 : 0].filter( (d) => d.parent === rule ); if (hasNonAll(laneDecls)) { processLane(rule, laneDecls, lane); } } }