UNPKG

@nvl/postcss-egal

Version:

PostCSS plugin to simplify uniformity in color saturation.

177 lines (175 loc) 5.52 kB
import { egal } from '@nvl/egal'; import { regex } from 'regex'; import MagicString from 'magic-string'; const cssAngleUnitsToDegrees = { deg: 1, grad: 360 / 400, rad: 180 / Math.PI, turn: 360, }; const gamutRegexes = { srgb: /s?rgb/iu, p3: /p3/iu, rec2020: /rec\.?2020/iu, }; const falseMatchRegex = regex('gim') ` (?<![^\s:;,\(]) egal \( `; const realMatchRegex = regex('gim') ` (?<![^\s:;,\(]) egal \s* \( \s* (?<lightness> \g<float> | none ) (?<lightness_percent> (?<!none) % )? \s+ (?<chroma> \g<float> | none ) (?<chroma_percent> (?<!none) % )? \s+ (?<hue> \g<float> | none ) (?<hue_unit> (?<!none) ( deg | grad | rad | turn ) )? ( # optionally, alpha value \s*? / \s* (?<alpha> \g<float> | none ) (?<alpha_percent> (?<!none) % )? )? ( # optionally, target gamut \g<sep> (?<gamut> ${gamutRegexes.srgb} | ${gamutRegexes.p3} | ${gamutRegexes.rec2020} ) )? ( # optionally, json options \g<sep> ( '' | ' (?<json> \s* \{ \s* ( [^'] | \g<escaped_single_quote> )* \s* \} \s* ) \g<nonescaped_single_quote> ) )? \s* \) (?(DEFINE) (?<float> [\+\-]?\d+\.? | [\+\-]?\d*\.\d+ ) (?<sep> \s*? [,\s] \s* ) (?<escaped_single_quote> (?<! \\ ) \\ (\\\\)* ' ) (?<nonescaped_single_quote> (?<! \\ ) (\\\\)* ' ) ) `; export const transformer = (decl, { result }, otherPluginOptions) => { let value = decl.value; const s = new MagicString(value); let realMatch; let falseMatch; // First pass, where stuff is actually modified while ((realMatch = realMatchRegex.exec(value))) { processRealMatch({ magicString: s, realMatch, otherPluginOptions }); } value = s.toString(); decl.value = value; while ((falseMatch = falseMatchRegex.exec(value))) { processFalseMatch({ value, falseMatch, otherPluginOptions, decl, result, }); } }; function processFalseMatch({ value, falseMatch, decl, result, }) { var _a, _b; let index = falseMatch.index; let endIndex = indexOfClosingParenthesis(value, value.indexOf('(', index)) + 1; // I'm not sure how `decl.raws.between` would ever be `undefined`, so I'm // choosing to exclude it from the test coverage report. /* v8 ignore next 1 */ const shift = decl.prop.length + ((_b = (_a = decl.raws.between) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); const problemString = value.slice(index, endIndex); for (const [ppr, message] of potentialProblems) { const match = ppr.exec(problemString); if (match) { index += match.index; endIndex = index + match[0].length; index += shift; endIndex += shift; decl.warn(result, message, { index, endIndex }); return; } } index += shift; endIndex += shift; decl.warn(result, "Couldn't parse egal color", { index, endIndex }); } const potentialProblems = [ [/none\s*%/u, 'none% is not a valid value'], [ /(\d+\.?\d*|\d*\.\d+)\s+%/u, 'Whitespace between number and % is forbidden', ], [ regex('i') `['"]( ${gamutRegexes.srgb}|${gamutRegexes.p3}|${gamutRegexes.rec2020} )['"]`, "Don't use quotes around gamut", ], [regex('i') `"\{.*\}"`, 'Use single quotes around JSON object'], ]; export function indexOfClosingParenthesis(str, indexOfOpeningParenthesis) { let depth = 0; for (let i = indexOfOpeningParenthesis + 1; i < str.length; i++) { if (str[i] === '(') { depth++; } else if (str[i] === ')') { if (depth === 0) { return i; } depth--; } } return -1; // No closing parenthesis found } function processRealMatch({ magicString, realMatch, otherPluginOptions, }) { const { lightness, lightness_percent, chroma, chroma_percent, hue, hue_unit, alpha, alpha_percent, gamut, json, } = realMatch.groups; let l = lightness === 'none' ? 0 : parseFloat(lightness); if (lightness_percent) l /= 100; let c = chroma === 'none' ? 0 : parseFloat(chroma); if (chroma_percent) c /= 100; let h = hue === 'none' ? 0 : parseFloat(hue); if (hue_unit) h *= cssAngleUnitsToDegrees[hue_unit]; let overrideOptions = {}; if (json) { try { overrideOptions = JSON.parse(json); } catch (_a) { return; } } if (alpha) { let a = alpha === 'none' ? 0 : parseFloat(alpha); if (alpha_percent) a /= 100; overrideOptions.opacity = a; } if (gamut) { const g = Object.keys(gamutRegexes).find((key) => gamutRegexes[key].test(gamut)); if (g) overrideOptions.gamut = g; } magicString.overwrite(realMatch.index, realMatch.index + realMatch[0].length, egal(l, c, h, { ...otherPluginOptions, ...overrideOptions, })); }