@stylable/core
Version:
CSS for Components
524 lines • 22.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseStExtends = exports.extendTypedRule = exports.disableDirectivesForClass = exports.checkForScopedNodeAfter = exports.validateClassScoping = exports.createWarningRule = exports.addDevRules = exports.namespaceClass = exports.addClass = exports.createSymbol = exports.getAll = exports.get = exports.StylablePublicApi = exports.hooks = exports.diagnostics = void 0;
const feature_1 = require("./feature");
const diagnostics_1 = require("./diagnostics");
const STSymbol = __importStar(require("./st-symbol"));
const STCustomState = __importStar(require("./st-custom-state"));
const resolve_1 = require("../helpers/resolve");
const namespace_1 = require("../helpers/namespace");
const escape_1 = require("../helpers/escape");
const value_1 = require("../helpers/value");
const selector_1 = require("../helpers/selector");
const stylable_utils_1 = require("../stylable-utils");
const custom_state_1 = require("../helpers/custom-state");
const css_selector_parser_1 = require("@tokey/css-selector-parser");
const postcss = __importStar(require("postcss"));
const path_1 = require("path");
const diagnostics_2 = require("../diagnostics");
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
const plugable_record_1 = require("../helpers/plugable-record");
const stPartDirectives = {
'-st-root': true,
'-st-states': true,
'-st-extends': true,
'-st-global': true,
};
exports.diagnostics = {
INVALID_FUNCTIONAL_SELECTOR: diagnostics_1.generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR,
UNSCOPED_CLASS: (0, diagnostics_2.createDiagnosticReporter)('00002', 'warning', (name) => `unscoped class "${name}" will affect all elements of the same type in the document`),
STATE_DEFINITION_IN_ELEMENT: (0, diagnostics_2.createDiagnosticReporter)('11002', 'error', () => 'cannot define pseudo states inside a type selector'),
STATE_DEFINITION_IN_COMPLEX: (0, diagnostics_2.createDiagnosticReporter)('11003', 'error', () => 'cannot define pseudo states inside complex selectors'),
OVERRIDE_TYPED_RULE: (0, diagnostics_2.createDiagnosticReporter)('11006', 'warning', (key, name) => `override "${key}" on typed rule "${name}"`),
CANNOT_RESOLVE_EXTEND: (0, diagnostics_2.createDiagnosticReporter)('11004', 'error', (name) => `cannot resolve '-st-extends' type for '${name}'`),
CANNOT_EXTEND_IN_COMPLEX: (0, diagnostics_2.createDiagnosticReporter)('11005', 'error', () => `cannot define "-st-extends" inside a complex selector`),
EMPTY_ST_GLOBAL: (0, diagnostics_2.createDiagnosticReporter)('00003', 'error', () => `-st-global must contain a valid selector`),
UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL: (0, diagnostics_2.createDiagnosticReporter)('00004', 'error', () => `unsupported multi selector in -st-global`),
UNSUPPORTED_COMPLEX_SELECTOR: (0, diagnostics_2.createDiagnosticReporter)('00010', 'error', () => `unsupported complex selector`),
IMPORT_ISNT_EXTENDABLE: (0, diagnostics_2.createDiagnosticReporter)('00005', 'error', () => 'import is not extendable'),
CANNOT_EXTEND_UNKNOWN_SYMBOL: (0, diagnostics_2.createDiagnosticReporter)('00006', 'error', (name) => `cannot extend unknown symbol "${name}"`),
CANNOT_EXTEND_JS: (0, diagnostics_2.createDiagnosticReporter)('00007', 'error', () => 'JS import is not extendable'),
UNKNOWN_IMPORT_ALIAS: (0, diagnostics_2.createDiagnosticReporter)('00008', 'error', (name) => `cannot use alias for unknown import "${name}"`),
DISABLED_DIRECTIVE: (0, diagnostics_2.createDiagnosticReporter)('00009', 'error', (className, directive) => {
const alternative = directive === '-st-extends'
? ` use "@st .${className} :is(.base)" instead`
: directive === '-st-global'
? `use "@st .${className} => :global(<selector>)" instead`
: directive === '-st-states'
? `use "@st .${className} { @st .state; }" instead`
: '';
return `cannot use ${directive} on .${className} since class is defined with "@st" - ${alternative}`;
}),
};
const dataKey = plugable_record_1.plugableRecord.key('st-structure');
// HOOKS
exports.hooks = (0, feature_1.createFeature)({
metaInit({ meta }) {
plugable_record_1.plugableRecord.set(meta.data, dataKey, {
classesDefinedWithAtSt: new Set(),
});
},
analyzeSelectorNode({ context, node, rule }) {
if (node.nodes) {
// error on functional class
context.diagnostics.report(exports.diagnostics.INVALID_FUNCTIONAL_SELECTOR(`.` + node.value, `class`), {
node: rule,
word: (0, selector_1.stringifySelector)(node),
});
}
addClass(context, node.value, rule);
},
analyzeDeclaration({ context, decl }) {
if (context.meta.type === 'stylable' && isDirectiveDeclaration(decl)) {
handleDirectives(context, decl);
}
},
transformResolve({ context }) {
const resolvedSymbols = context.getResolvedSymbols(context.meta);
const locals = {};
for (const [localName, resolved] of Object.entries(resolvedSymbols.class)) {
const exportedClasses = [];
let first = true;
// collect list of css classes for exports
for (const { meta, symbol } of resolved) {
if (!first && symbol[`-st-root`]) {
// extended stylesheet root: stop collection as root is expected to
// be placed by inner component, for example in <Button class={classes.primaryBtn} />
// `primaryBtn` shouldn't contain `button__root` as it is placed by the Button component
break;
}
first = false;
if (symbol[`-st-global`]) {
// collect global override just in case of
// compound set of CSS classes
let isOnlyClasses = true;
const globalClasses = symbol[`-st-global`].reduce((globalClasses, node) => {
if (node.type === `class`) {
globalClasses.push(node.value);
context.meta.globals[node.value] = true;
}
else {
isOnlyClasses = false;
}
return globalClasses;
}, []);
if (isOnlyClasses) {
exportedClasses.push(...globalClasses);
}
continue;
}
if (symbol.alias && !symbol[`-st-extends`]) {
continue;
}
exportedClasses.push((0, namespace_1.namespace)(symbol.name, meta.namespace));
}
const classNames = (0, escape_1.unescapeCSS)(exportedClasses.join(' '));
if (classNames) {
const directResolve = resolved[0];
const isLocal = directResolve.meta === context.meta && !directResolve.symbol.alias;
locals[localName] = { classes: classNames, isLocal };
}
}
return locals;
},
transformSelectorNode({ context, selectorContext, node }) {
const { originMeta, resolver } = selectorContext;
const resolvedSymbols = context.getResolvedSymbols(context.meta);
const resolved = resolvedSymbols.class[node.value] || [
// used to namespace classes from js mixins since js mixins
// are scoped in the context of the mixed-in stylesheet
// which might not have a definition for the mixed-in class
{ _kind: 'css', meta: originMeta, symbol: createSymbol({ name: node.value }) },
];
selectorContext.setNextSelectorScope(resolved, node, node.value);
const { symbol, meta } = (0, resolve_1.getOriginDefinition)(resolved);
if (selectorContext.originMeta === meta && symbol[`-st-states`]) {
// ToDo: refactor out to transformer validation phase
(0, custom_state_1.validateRuleStateDefinition)(selectorContext.selectorStr, selectorContext.ruleOrAtRule, context.meta, resolver, context.diagnostics);
}
if (selectorContext.transform) {
namespaceClass(meta, symbol, node);
}
},
transformJSExports({ exports, resolved }) {
for (const [localName, { classes, isLocal }] of Object.entries(resolved)) {
if (isLocal) {
exports.classes[localName] = classes;
}
}
},
});
// API
class StylablePublicApi {
constructor(stylable) {
this.stylable = stylable;
}
transformIntoSelector(meta, name) {
const localSymbol = STSymbol.get(meta, name);
const resolved = localSymbol?._kind === 'import'
? this.stylable.resolver.deepResolve(localSymbol)
: { _kind: 'css', meta, symbol: localSymbol };
if (resolved?._kind !== 'css' || resolved.symbol?._kind !== 'class') {
return undefined;
}
const node = {
type: 'class',
value: '',
start: 0,
end: 0,
dotComments: [],
};
namespaceClass(resolved.meta, resolved.symbol, node, false);
return (0, css_selector_parser_1.stringifySelectorAst)(node);
}
}
exports.StylablePublicApi = StylablePublicApi;
function get(meta, name) {
return STSymbol.get(meta, name, `class`);
}
exports.get = get;
function getAll(meta) {
return STSymbol.getAllByType(meta, `class`);
}
exports.getAll = getAll;
function createSymbol(input) {
const parts = input['-st-parts'] || {};
return { ...input, _kind: 'class', '-st-parts': parts };
}
exports.createSymbol = createSymbol;
function addClass(context, name, rule) {
let symbol = STSymbol.get(context.meta, name, `class`);
if (!symbol) {
let alias = STSymbol.get(context.meta, name);
if (alias && alias._kind !== 'import') {
alias = undefined;
}
symbol = STSymbol.addSymbol({
context,
symbol: createSymbol({ name, alias }),
node: rule,
safeRedeclare: !!alias,
});
}
// mark native css as global
if (context.meta.type === 'css' && !symbol['-st-global']) {
symbol['-st-global'] = [
{
type: 'class',
value: name,
dotComments: [],
start: 0,
end: 0,
},
];
}
return symbol;
}
exports.addClass = addClass;
function namespaceClass(meta, symbol, node, // ToDo: check this is the correct type, should this be inline selector?
wrapInGlobal = true) {
if (`-st-global` in symbol && symbol[`-st-global`]) {
// change node to `-st-global` value
if (wrapInGlobal) {
const globalMappedNodes = symbol[`-st-global`];
(0, selector_1.convertToPseudoClass)(node, 'global', [
{
type: 'selector',
nodes: globalMappedNodes,
after: '',
before: '',
end: 0,
start: 0,
},
]);
}
else {
const flatNode = (0, selector_1.convertToSelector)(node);
const globalMappedNodes = symbol[`-st-global`];
flatNode.nodes = globalMappedNodes;
}
}
else {
node = (0, selector_1.convertToClass)(node);
node.value = (0, escape_1.namespaceEscape)(symbol.name, meta.namespace);
}
}
exports.namespaceClass = namespaceClass;
function getNamespacedClass(meta, symbol) {
if (`-st-global` in symbol && symbol[`-st-global`]) {
const selector = symbol[`-st-global`];
return (0, css_selector_parser_1.stringifySelectorAst)(selector);
}
else {
return '.' + (0, escape_1.namespaceEscape)(symbol.name, meta.namespace);
}
}
function addDevRules({ getResolvedSymbols, meta }) {
const resolvedSymbols = getResolvedSymbols(meta);
for (const resolved of Object.values(resolvedSymbols.class)) {
const a = resolved[0];
if (resolved.length > 1 && a.symbol['-st-extends']) {
const b = resolved[resolved.length - 1];
meta.targetAst.append(createWarningRule('.' + b.symbol.name, getNamespacedClass(b.meta, b.symbol), (0, path_1.basename)(b.meta.source), '.' + a.symbol.name, getNamespacedClass(a.meta, a.symbol), (0, path_1.basename)(a.meta.source)));
}
}
}
exports.addDevRules = addDevRules;
function createWarningRule(extendedNode, scopedExtendedNode, extendedFile, extendingNode, scopedExtendingNode, extendingFile) {
const message = `"class extending component '${extendingNode} => ${scopedExtendingNode}' in stylesheet '${extendingFile}' was set on a node that does not extend '${extendedNode} => ${scopedExtendedNode}' from stylesheet '${extendedFile}'" !important`;
return postcss.rule({
raws: { between: ' ' },
selector: `${scopedExtendingNode}:not(${scopedExtendedNode})::before`,
nodes: [
postcss.decl({
prop: 'content',
value: message,
}),
postcss.decl({
prop: 'display',
value: `block !important`,
}),
postcss.decl({
prop: 'font-family',
value: `monospace !important`,
}),
postcss.decl({
prop: 'background-color',
value: `red !important`,
}),
postcss.decl({
prop: 'color',
value: `white !important`,
}),
],
});
}
exports.createWarningRule = createWarningRule;
function validateClassScoping({ context, classSymbol, locallyScoped, reportUnscoped, node, nodes, index, rule, }) {
if (context.meta.type !== 'stylable') {
// ignore in native CSS
return true;
}
if (!classSymbol.alias) {
return true;
}
else if (locallyScoped === false) {
if (checkForScopedNodeAfter(context, rule, nodes, index) === false) {
if (reportUnscoped) {
context.diagnostics.report(exports.diagnostics.UNSCOPED_CLASS(node.value), {
node: rule,
word: node.value,
});
}
return false;
}
else {
return true;
}
}
return locallyScoped;
}
exports.validateClassScoping = validateClassScoping;
// ToDo: support more complex cases (e.g. `:is`)
function checkForScopedNodeAfter(context, rule, nodes, index) {
for (let i = index + 1; i < nodes.length; i++) {
const node = nodes[i];
if (!node) {
// ToDo: can this get here???
break;
}
if (node.type === 'combinator') {
break;
}
if (node.type === 'class') {
const name = node.value;
const classSymbol = addClass(context, name, rule);
if (classSymbol && !classSymbol.alias) {
return true;
}
}
}
return false;
}
exports.checkForScopedNodeAfter = checkForScopedNodeAfter;
function isDirectiveDeclaration(decl) {
return decl.prop in stPartDirectives;
}
function disableDirectivesForClass(context, className) {
// ToDo: move directive analyze to @st-structure
// called when class is defined with @st
const { classesDefinedWithAtSt } = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
classesDefinedWithAtSt.add(className);
}
exports.disableDirectivesForClass = disableDirectivesForClass;
function handleDirectives(context, decl) {
const rule = decl.parent;
if (rule?.type !== 'rule') {
return;
}
const isSimplePerSelector = (0, selector_1.isSimpleSelector)(rule.selector);
const type = isSimplePerSelector.reduce((accType, { type }) => {
return !accType ? type : accType !== type ? `complex` : type;
}, ``);
const isSimple = type !== `complex`;
const { classesDefinedWithAtSt } = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
if (type === 'class' && classesDefinedWithAtSt.has(rule.selector.replace('.', ''))) {
context.diagnostics.report(exports.diagnostics.DISABLED_DIRECTIVE(rule.selector.replace('.', ''), decl.prop), {
node: decl,
});
return;
}
else if (decl.prop === `-st-states`) {
if (isSimple && type !== 'type') {
extendTypedRule(context, decl, rule.selector, `-st-states`, STCustomState.parsePseudoStates(decl.value, decl, context.diagnostics));
}
else {
if (type === 'type') {
context.diagnostics.report(exports.diagnostics.STATE_DEFINITION_IN_ELEMENT(), {
node: decl,
});
}
else {
context.diagnostics.report(exports.diagnostics.STATE_DEFINITION_IN_COMPLEX(), {
node: decl,
});
}
}
}
else if (decl.prop === `-st-extends`) {
if (isSimple) {
const parsed = parseStExtends(decl.value);
const symbolName = parsed.types[0] && parsed.types[0].symbolName;
const extendsRefSymbol = STSymbol.get(context.meta, symbolName);
if ((extendsRefSymbol &&
(extendsRefSymbol._kind === 'import' ||
extendsRefSymbol._kind === 'class' ||
extendsRefSymbol._kind === 'element')) ||
decl.value === context.meta.root) {
extendTypedRule(context, decl, rule.selector, `-st-extends`, (0, stylable_utils_1.getAlias)(extendsRefSymbol) || extendsRefSymbol);
}
else {
context.diagnostics.report(exports.diagnostics.CANNOT_RESOLVE_EXTEND(decl.value), {
node: decl,
word: decl.value,
});
}
}
else {
context.diagnostics.report(exports.diagnostics.CANNOT_EXTEND_IN_COMPLEX(), {
node: decl,
});
}
}
else if (decl.prop === `-st-global`) {
if (isSimple && type !== 'type') {
// set class global mapping
const name = rule.selector.replace('.', '');
const classSymbol = get(context.meta, name);
if (classSymbol) {
const globalSelectorAst = parseStGlobal(context, decl);
if (globalSelectorAst) {
classSymbol[`-st-global`] = globalSelectorAst;
}
}
}
else {
// TODO: diagnostics - scoped on none class
}
}
}
function extendTypedRule(context, node, selector, key, value) {
const name = selector.replace('.', '');
const typedRule = STSymbol.get(context.meta, name);
if (typedRule && typedRule[key]) {
context.diagnostics.report(exports.diagnostics.OVERRIDE_TYPED_RULE(key, name), {
node,
word: name,
});
}
if (typedRule) {
typedRule[key] = value;
}
}
exports.extendTypedRule = extendTypedRule;
function parseStExtends(value) {
const ast = (0, postcss_value_parser_1.default)(value);
const types = [];
ast.walk((node) => {
if (node.type === 'function') {
const args = (0, value_1.getNamedArgs)(node);
types.push({
symbolName: node.value,
args,
});
return false;
}
else if (node.type === 'word') {
types.push({
symbolName: node.value,
args: null,
});
}
return undefined;
}, false);
return {
ast,
types,
};
}
exports.parseStExtends = parseStExtends;
function parseStGlobal(context, decl) {
const selector = (0, selector_1.parseSelectorWithCache)(decl.value.replace(/^['"]/, '').replace(/['"]$/, ''), {
clone: true,
});
if (!selector[0]) {
context.diagnostics.report(exports.diagnostics.EMPTY_ST_GLOBAL(), {
node: decl,
});
return;
}
else if (selector.length > 1) {
context.diagnostics.report(exports.diagnostics.UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL(), {
node: decl,
});
return;
}
else {
for (const node of selector[0].nodes) {
if (node.type === 'combinator') {
context.diagnostics.report(exports.diagnostics.UNSUPPORTED_COMPLEX_SELECTOR(), {
node: decl,
});
return;
}
}
}
return selector[0].nodes;
}
//# sourceMappingURL=css-class.js.map