@stylable/core
Version:
CSS for Components
110 lines (101 loc) • 3.63 kB
text/typescript
import { createFeature } from './feature';
import { parseSelectorWithCache, scopeNestedSelector } from '../helpers/selector';
import type { Stylable } from '../stylable';
import type { ImmutablePseudoClass } from '@tokey/css-selector-parser';
import * as postcss from 'postcss';
import type { SRule } from '../deprecated/postcss-ast-extension';
export const diagnostics = {
// INVALID_SCOPING: createDiagnosticReporter(
// '11009',
// 'error',
// () => '"@st-scope" requires a valid selector or empty value'
// ),
};
// HOOKS
export const hooks = createFeature<{ IMMUTABLE_SELECTOR: ImmutablePseudoClass }>({
analyzeAtRule({ context, atRule, analyzeRule }) {
if (!isStScopeStatement(atRule)) {
return;
}
// notice: any value from params would be taken as a scoping
// selector to be prepended to nested selectors
analyzeRule(
postcss.rule({
selector: atRule.params,
source: atRule.source,
}),
{
isScoped: true,
originalNode: atRule,
}
);
context.meta.scopes.push(atRule);
},
prepareAST({ node, toRemove }) {
// called with experimentalSelectorInference=false
// flatten @st-scope before transformation
if (isStScopeStatement(node)) {
flattenScope(node);
toRemove.push(() => node.replaceWith(node.nodes || []));
}
},
transformAtRuleNode({ context: { meta, inferredSelectorMixin }, atRule, transformer }) {
if (isStScopeStatement(atRule)) {
const { selector, inferredSelector } = transformer.scopeSelector(
meta,
atRule.params,
atRule,
undefined,
inferredSelectorMixin
);
// transform selector in params
atRule.params = selector;
// track selector context for nested selector nodes
transformer.containerInferredSelectorMap.set(atRule, inferredSelector);
}
},
transformLastPass({ ast }) {
// called with experimentalSelectorInference=true
// flatten @st-scope after transformation
const toRemove = [];
for (const node of ast.nodes) {
if (isStScopeStatement(node)) {
flattenScope(node);
toRemove.push(() => node.replaceWith(node.nodes || []));
}
}
toRemove.forEach((remove) => remove());
},
});
// API
export class StylablePublicApi {
constructor(private stylable: Stylable) {}
public getStScope(rule: postcss.Rule) {
return getStScope(rule);
}
}
export function isStScopeStatement(node: any): node is postcss.AtRule {
return node.type === 'atrule' && node.name === 'st-scope';
}
function flattenScope(atRule: postcss.AtRule) {
const scopeSelector = atRule.params;
if (scopeSelector) {
atRule.walkRules((rule) => {
rule.selector = scopeNestedSelector(
parseSelectorWithCache(scopeSelector),
parseSelectorWithCache(rule.selector)
).selector;
(rule as SRule).stScopeSelector = atRule.params;
});
}
}
function getStScope(rule: postcss.Rule): postcss.AtRule | undefined {
let current: postcss.Container | postcss.Document = rule;
while (current?.parent) {
current = current.parent;
if (isStScopeStatement(current) && current.parent?.type === 'root') {
return current;
}
}
return;
}