UNPKG

postcss-merge-longhand

Version:

Merge longhand properties into shorthand with PostCSS.

143 lines (129 loc) • 4.67 kB
import cssnanoUtils from 'cssnano-utils'; import { sharesShorthandKeyword } from '../isCssWideKeyword.js'; import stylehacks from 'stylehacks'; import canExplode from '../canExplode.js'; import isCustomProp from '../isCustomProp.js'; import { commitShorthand } from './slotVector.js'; import { hasNonAll, repeatsProperty } from './importanceLanes.js'; import { normalizeAlignment, parseAlignmentDeclaration, sharesKeywordSupport, } from './alignmentForms.js'; const { asciiLowerCase } = cssnanoUtils; /** @import {Container, Declaration} from 'postcss'; */ /** @import {AlignmentFamilyConfig} from './alignmentForms.js'; */ /** @typedef {Map<Declaration, [string | null, string | null]>} ParsedDeclarations */ /** * Replaces consecutive declarations of one family with a single shorthand * when both axes are set. The later declaration of an axis wins, as in the * cascade; the caller has already rejected repeated properties, so no * declaration serves as a fallback for another. * * @param {Container} rule * @param {AlignmentFamilyConfig} family * @param {Declaration[]} segment * @param {boolean} important * @param {ParsedDeclarations} parsedDecls * @return {void} */ function mergeSegment(rule, family, segment, important, parsedDecls) { /** @type {(string | null)[]} */ const axes = [null, null]; for (const decl of segment) { const parsed = /** @type {[string | null, string | null]} */ ( parsedDecls.get(decl) ); for (const [axis, value] of parsed.entries()) { if (value !== null) axes[axis] = value; } } const [align, justify] = axes; if (align === null || justify === null) return; if (segment.some(isCustomProp)) return; // A CSS-wide keyword cannot share a shorthand with another value. if (!sharesShorthandKeyword([align, justify])) return; const values = segment.map((decl) => /** @type {[string | null, string | null]} */ (parsedDecls.get(decl)) .filter((value) => value !== null) .join(' ') ); if (!sharesKeywordSupport(values)) return; commitShorthand(rule, [], new Set(segment), new Set(), { prop: family.shorthand, value: normalizeAlignment(family, [align, justify]), important, }); } /** * Reduces alignment declarations within a single importance lane. Hacks, * unparsed values and `all` end a segment. * * @param {Container} rule * @param {AlignmentFamilyConfig} family * @param {Declaration[]} laneDecls * @param {boolean} important * @param {ParsedDeclarations} parsedDecls * @return {void} */ function processLane(rule, family, laneDecls, important, parsedDecls) { /** @type {Declaration[]} */ let segment = []; for (const decl of laneDecls) { if (parsedDecls.has(decl)) { segment.push(decl); continue; } mergeSegment(rule, family, segment, important, parsedDecls); segment = []; } mergeSegment(rule, family, segment, important, parsedDecls); } /** * Whether the declarations can be reduced at all: both longhands, or a * shorthand and a longhand. * * @param {Declaration[]} declarations * @return {boolean} */ function hasMergeableProperties(declarations) { return new Set(declarations.map((d) => asciiLowerCase(d.prop))).size > 1; } /** * Reduces paired box alignment longhand declarations within a container into * shorthand. Existing shorthands are normalized by the shorthand folding pass. * * @param {Container} rule * @param {AlignmentFamilyConfig} family * @param {Declaration[]} declarations * @param {[Declaration[], Declaration[]]} lanes - the declarations and `all` * split by importance * @return {void} */ export function reduceAlignmentFamily(rule, family, declarations, lanes) { if ( declarations.some((d) => !family.allProps.has(asciiLowerCase(d.prop))) || !hasMergeableProperties(declarations) ) { return; } /* A repeated property is usually a fallback for browsers that reject the * later value; merging would leave those browsers with neither. */ if (lanes.some(repeatsProperty)) return; /** @type {ParsedDeclarations} */ const parsedDecls = new Map(); for (const decl of declarations) { const isShort = asciiLowerCase(decl.prop) === family.shorthand; // Hacks and substitution-backed shorthands only bound a segment. if (stylehacks.detect(decl) || (isShort && !canExplode(decl))) continue; const parsed = parseAlignmentDeclaration(family, decl); if (!parsed) return; parsedDecls.set(decl, parsed); } for (const lane of [false, true]) { const laneDecls = lanes[lane ? 1 : 0]; if (hasNonAll(laneDecls)) { processLane(rule, family, laneDecls, lane, parsedDecls); } } }