165 lines
5.1 KiB
JavaScript
165 lines
5.1 KiB
JavaScript
const LEGACY_RGB_NAME = /^--legacy-[a-z0-9-]+-rgb$/i;
|
|
const RGB_NAME = /^--[a-z0-9-]+-rgb$/i;
|
|
const RGB_CHANNELS = /^\s*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s*$/;
|
|
const RGB_CHANNEL_REFERENCE = /^\s*var\(\s*(--[a-z0-9-]+-rgb)(?:\s*,\s*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?))?\s*\)\s*$/i;
|
|
const INHERENT_ALPHA_TOKENS = new Set([
|
|
"border",
|
|
"input",
|
|
"ring",
|
|
"sidebar-border",
|
|
]);
|
|
const ALPHA_MULTIPLIERS = [0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1];
|
|
|
|
function alphaVariableSuffix(multiplier) {
|
|
return String(Math.round(multiplier * 1000));
|
|
}
|
|
|
|
function formatAlpha(value) {
|
|
return String(Math.round(value * 1_000_000) / 1_000_000);
|
|
}
|
|
|
|
function legacyAlphaToken(variableName) {
|
|
const match = variableName.match(/^--legacy-(.+)-rgb$/);
|
|
return match && INHERENT_ALPHA_TOKENS.has(match[1]) ? match[1] : null;
|
|
}
|
|
|
|
function normalizeAlpha(variableName, alpha) {
|
|
const percentage = alpha.match(/^(\d+(?:\.\d+)?)%$/);
|
|
if (percentage) return String(Number(percentage[1]) / 100);
|
|
|
|
const token = legacyAlphaToken(variableName);
|
|
if (!token) return alpha;
|
|
const composite = alpha.match(
|
|
new RegExp(
|
|
`^calc\\(var\\(--${token}-alpha,\\s*1\\)\\s*\\*\\s*(.+)\\)$`,
|
|
),
|
|
);
|
|
if (!composite) return alpha;
|
|
|
|
const multiplier = composite[1].trim();
|
|
const numeric = Number(multiplier);
|
|
if (Number.isFinite(numeric)) {
|
|
return `var(--ff52-${token}-alpha-${alphaVariableSuffix(numeric)})`;
|
|
}
|
|
if (/^var\(--tw-[a-z-]+-opacity,\s*1\)$/.test(multiplier)) {
|
|
return `var(--ff52-${token}-alpha-${alphaVariableSuffix(1)})`;
|
|
}
|
|
return alpha;
|
|
}
|
|
|
|
function findClosingParenthesis(value, openingIndex) {
|
|
let depth = 0;
|
|
for (let index = openingIndex; index < value.length; index += 1) {
|
|
if (value[index] === "(") depth += 1;
|
|
if (value[index] !== ")") continue;
|
|
depth -= 1;
|
|
if (depth === 0) return index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function commaChannelValue(value) {
|
|
const channels = value.match(RGB_CHANNELS);
|
|
if (channels) return `${channels[1]}, ${channels[2]}, ${channels[3]}`;
|
|
|
|
const reference = value.match(RGB_CHANNEL_REFERENCE);
|
|
if (!reference) return null;
|
|
const fallback = reference[2]
|
|
? `, ${reference[2]}, ${reference[3]}, ${reference[4]}`
|
|
: "";
|
|
return `var(${reference[1]}-comma${fallback})`;
|
|
}
|
|
|
|
function rewriteLegacyRgbFunctions(
|
|
value,
|
|
convertibleNames = { has: (name) => LEGACY_RGB_NAME.test(name) },
|
|
) {
|
|
let output = "";
|
|
let cursor = 0;
|
|
|
|
while (cursor < value.length) {
|
|
const start = value.indexOf("rgb(var(--", cursor);
|
|
if (start < 0) return output + value.slice(cursor);
|
|
output += value.slice(cursor, start);
|
|
|
|
const varStart = start + 4;
|
|
const varEnd = findClosingParenthesis(value, varStart + 3);
|
|
const rgbEnd = findClosingParenthesis(value, start + 3);
|
|
if (varEnd < 0 || rgbEnd < 0 || varEnd >= rgbEnd) {
|
|
output += value.slice(start, start + 4);
|
|
cursor = start + 4;
|
|
continue;
|
|
}
|
|
|
|
const variableName = value.slice(varStart + 4, varEnd).trim();
|
|
const separator = value.slice(varEnd + 1, rgbEnd);
|
|
const slash = separator.indexOf("/");
|
|
if (!RGB_NAME.test(variableName) || !convertibleNames.has(variableName) || slash < 0) {
|
|
output += value.slice(start, rgbEnd + 1);
|
|
cursor = rgbEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
const alpha = normalizeAlpha(
|
|
variableName,
|
|
separator.slice(slash + 1).trim(),
|
|
);
|
|
output += `rgba(var(${variableName}-comma), ${alpha})`;
|
|
cursor = rgbEnd + 1;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Firefox 52 cannot parse CSS Color 4's `rgb(var(--channels) / alpha)` form.
|
|
* Keep the space-separated live channels for modern builds, add an equivalent
|
|
* comma-separated variable, and rewrite only the Firefox artifact to rgba().
|
|
*/
|
|
export default function firefox52RgbVars() {
|
|
const convertibleNames = new Set();
|
|
return {
|
|
postcssPlugin: "postcss-firefox52-rgb-vars",
|
|
Once(root) {
|
|
root.walkDecls((declaration) => {
|
|
if (RGB_NAME.test(declaration.prop)) {
|
|
const commaValue = commaChannelValue(declaration.value);
|
|
if (commaValue) {
|
|
convertibleNames.add(declaration.prop);
|
|
declaration.cloneAfter({
|
|
prop: `${declaration.prop}-comma`,
|
|
value: commaValue,
|
|
});
|
|
|
|
const token = legacyAlphaToken(declaration.prop);
|
|
if (token) {
|
|
const alphaDeclaration = declaration.parent?.nodes?.find(
|
|
(node) =>
|
|
node.type === "decl" && node.prop === `--${token}-alpha`,
|
|
);
|
|
const inherentAlpha = Number(alphaDeclaration?.value ?? 1);
|
|
for (const multiplier of ALPHA_MULTIPLIERS) {
|
|
declaration.cloneAfter({
|
|
prop: `--ff52-${token}-alpha-${alphaVariableSuffix(multiplier)}`,
|
|
value: formatAlpha(inherentAlpha * multiplier),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
Declaration(declaration) {
|
|
if (!declaration.value.includes("rgb(var(--")) return;
|
|
declaration.value = rewriteLegacyRgbFunctions(
|
|
declaration.value,
|
|
convertibleNames,
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
firefox52RgbVars.postcss = true;
|
|
|
|
export { rewriteLegacyRgbFunctions };
|