@oddbird/css-anchor-positioning
Version:
Polyfill for the proposed CSS anchor positioning spec
73 lines (67 loc) • 1.96 kB
text/typescript
import type { Block, CssNode } from 'css-tree';
import walk from 'css-tree/walker';
import { ACCEPTED_POSITION_TRY_PROPERTIES } from './syntax.js';
import {
generateCSS,
getAST,
INSTANCE_UUID,
isDeclaration,
type StyleData,
} from './utils.js';
/**
* Map of CSS property to CSS custom property that the property's value is
* shifted into. This is used to subject properties that are not yet natively
* supported to the CSS cascade and inheritance rules. It is also used by the
* fallback algorithm to find initial, non-computed values.
*/
export const SHIFTED_PROPERTIES: Record<string, string> = [
...ACCEPTED_POSITION_TRY_PROPERTIES,
'anchor-scope',
'anchor-name',
].reduce(
(acc, prop) => {
acc[prop] = `--${prop}-${INSTANCE_UUID}`;
return acc;
},
{} as Record<string, string>,
);
/**
* Shift property declarations for properties that are not yet natively
* supported into custom properties.
*/
function shiftUnsupportedProperties(node: CssNode, block?: Block) {
if (isDeclaration(node) && SHIFTED_PROPERTIES[node.property] && block) {
block.children.appendData({
...node,
property: SHIFTED_PROPERTIES[node.property],
});
return { updated: true };
}
return {};
}
/**
* Update the given style data to enable cascading and inheritance of properties
* that are not yet natively supported.
*/
export function cascadeCSS(styleData: StyleData[]) {
for (const styleObj of styleData) {
let changed = false;
const ast = getAST(styleObj.css, true);
walk(ast, {
visit: 'Declaration',
enter(node) {
const block = this.rule?.block;
const { updated } = shiftUnsupportedProperties(node, block);
if (updated) {
changed = true;
}
},
});
if (changed) {
// Update CSS
styleObj.css = generateCSS(ast);
styleObj.changed = true;
}
}
return styleData.some((styleObj) => styleObj.changed === true);
}