@stylable/core
Version:
CSS for Components
315 lines • 15 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveStateParam = exports.createAttributeState = exports.createStateWithParamClassName = exports.createBooleanStateClassName = exports.setStateToNode = exports.transformPseudoStateSelector = exports.validateStateArgument = exports.validateStateDefinition = exports.processPseudoStates = exports.stateErrors = exports.stateWithParamDelimiter = exports.booleanStateDelimiter = exports.stateMiddleDelimiter = void 0;
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
const is_vendor_prefixed_1 = __importDefault(require("is-vendor-prefixed"));
const functions_1 = require("./functions");
const native_reserved_lists_1 = require("./native-reserved-lists");
const state_validators_1 = require("./state-validators");
const stylable_utils_1 = require("./stylable-utils");
const stylable_value_parsers_1 = require("./stylable-value-parsers");
const stylable_value_parsers_2 = require("./stylable-value-parsers");
const utils_1 = require("./utils");
const { hasOwnProperty } = Object.prototype;
exports.stateMiddleDelimiter = '-';
exports.booleanStateDelimiter = '--';
exports.stateWithParamDelimiter = exports.booleanStateDelimiter + exports.stateMiddleDelimiter;
exports.stateErrors = {
UNKNOWN_STATE_USAGE: (name) => `unknown pseudo-state "${name}"`,
UNKNOWN_STATE_TYPE: (name, type) => `pseudo-state "${name}" defined with unknown type: "${type}"`,
TOO_MANY_STATE_TYPES: (name, types) => `pseudo-state "${name}(${types.join(', ')})" definition must be of a single type`,
NO_STATE_TYPE_GIVEN: (name) => `pseudo-state "${name}" expected a definition of a single type, but received none`,
TOO_MANY_ARGS_IN_VALIDATOR: (name, validator, args) => `pseudo-state "${name}" expected "${validator}" validator to receive a single argument, but it received "${args.join(', ')}"`,
STATE_STARTS_WITH_HYPHEN: (name) => `state "${name}" declaration cannot begin with a "${exports.stateMiddleDelimiter}" chararcter`,
};
// PROCESS
function processPseudoStates(value, decl, diagnostics) {
const mappedStates = {};
const ast = postcss_value_parser_1.default(value);
const statesSplitByComma = stylable_value_parsers_1.groupValues(ast.nodes);
statesSplitByComma.forEach((workingState) => {
const [stateDefinition, ...stateDefault] = workingState;
if (stateDefinition.value.startsWith('-')) {
diagnostics.error(decl, exports.stateErrors.STATE_STARTS_WITH_HYPHEN(stateDefinition.value), {
word: stateDefinition.value,
});
}
if (stateDefinition.type === 'function') {
resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl);
}
else if (stateDefinition.type === 'word') {
resolveBooleanState(mappedStates, stateDefinition);
}
else {
// TODO: Invalid state, edge case needs warning
}
});
return mappedStates;
}
exports.processPseudoStates = processPseudoStates;
function resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl) {
if (stateDefinition.type === 'function' && stateDefinition.nodes.length === 0) {
resolveBooleanState(mappedStates, stateDefinition);
diagnostics.warn(decl, exports.stateErrors.NO_STATE_TYPE_GIVEN(stateDefinition.value), {
word: decl.value,
});
return;
}
if (stateDefinition.nodes.length > 1) {
diagnostics.warn(decl, exports.stateErrors.TOO_MANY_STATE_TYPES(stateDefinition.value, stylable_value_parsers_1.listOptions(stateDefinition)), { word: decl.value });
}
const paramType = stateDefinition.nodes[0];
const stateType = {
type: stateDefinition.nodes[0].value,
arguments: [],
defaultValue: postcss_value_parser_1.default
.stringify(stateDefault)
.trim(),
};
if (isCustomMapping(stateDefinition)) {
mappedStates[stateDefinition.value] = stateType.type.trim().replace(/\\["']/g, '"');
}
else if (typeof stateType === 'object' && stateType.type === 'boolean') {
resolveBooleanState(mappedStates, stateDefinition);
return;
}
else if (paramType.type === 'function' && stateType.type in state_validators_1.systemValidators) {
if (paramType.nodes.length > 0) {
resolveArguments(paramType, stateType, stateDefinition.value, diagnostics, decl);
}
mappedStates[stateDefinition.value] = stateType;
}
else if (stateType.type in state_validators_1.systemValidators) {
mappedStates[stateDefinition.value] = stateType;
}
else {
diagnostics.warn(decl, exports.stateErrors.UNKNOWN_STATE_TYPE(stateDefinition.value, paramType.value), { word: paramType.value });
}
}
function resolveArguments(paramType, stateType, name, diagnostics, decl) {
const separatedByComma = stylable_value_parsers_1.groupValues(paramType.nodes);
separatedByComma.forEach((group) => {
const validator = group[0];
if (validator.type === 'function') {
const args = stylable_value_parsers_1.listOptions(validator);
if (args.length > 1) {
diagnostics.warn(decl, exports.stateErrors.TOO_MANY_ARGS_IN_VALIDATOR(name, validator.value, args), { word: decl.value });
}
else {
stateType.arguments.push({
name: validator.value,
args,
});
}
}
else if (validator.type === 'string' || validator.type === 'word') {
stateType.arguments.push(validator.value);
}
});
}
function isCustomMapping(stateDefinition) {
return stateDefinition.nodes.length === 1 && stateDefinition.nodes[0].type === 'string';
}
function resolveBooleanState(mappedStates, stateDefinition) {
const currentState = mappedStates[stateDefinition.type];
if (!currentState) {
mappedStates[stateDefinition.value] = null; // add boolean state
}
else {
// TODO: warn with such name already exists
}
}
// TRANSFORM
function validateStateDefinition(decl, meta, resolver, diagnostics) {
if (decl.parent && decl.parent.type !== 'root') {
const container = decl.parent;
if (container.type !== 'atrule') {
const sRule = container;
if (sRule.selectorAst.nodes && sRule.selectorAst.nodes.length === 1) {
const singleSelectorAst = sRule.selectorAst.nodes[0];
const selectorChunk = singleSelectorAst.nodes;
if (selectorChunk.length === 1 && selectorChunk[0].type === 'class') {
const className = selectorChunk[0].name;
const classMeta = meta.classes[className];
const states = classMeta[stylable_value_parsers_2.valueMapping.states];
if (classMeta && classMeta._kind === 'class' && states) {
for (const stateName in states) {
// TODO: Sort out types
const state = states[stateName];
if (state && typeof state === 'object') {
const res = validateStateArgument(state, meta, state.defaultValue || '', resolver, diagnostics, sRule, true, !!state.defaultValue);
if (res.errors) {
res.errors.unshift(`pseudo-state "${stateName}" default value "${state.defaultValue}" failed validation:`);
diagnostics.warn(decl, res.errors.join('\n'), {
word: decl.value,
});
}
}
}
}
else {
// TODO: error state on non-class
}
}
}
}
}
}
exports.validateStateDefinition = validateStateDefinition;
function validateStateArgument(stateAst, meta, value, resolver, diagnostics, rule, validateDefinition, validateValue = true) {
const resolvedValidations = {
res: resolveParam(meta, resolver, diagnostics, rule, value || stateAst.defaultValue),
errors: null,
};
const { type: paramType } = stateAst;
const validator = state_validators_1.systemValidators[paramType];
try {
if (resolvedValidations.res || validateDefinition) {
const { errors } = validator.validate(resolvedValidations.res, stateAst.arguments, resolveParam.bind(null, meta, resolver, diagnostics, rule), !!validateDefinition, validateValue);
resolvedValidations.errors = errors;
}
}
catch (error) {
// TODO: warn about validation throwing exception
}
return resolvedValidations;
}
exports.validateStateArgument = validateStateArgument;
function transformPseudoStateSelector(meta, node, name, symbol, origin, originSymbol, resolver, diagnostics, rule) {
let current = meta;
let currentSymbol = symbol;
if (originSymbol && symbol !== originSymbol) {
current = origin;
currentSymbol = originSymbol;
}
let found = false;
while (current && currentSymbol) {
if (currentSymbol._kind === 'class' || currentSymbol._kind === 'element') {
const states = currentSymbol[stylable_value_parsers_2.valueMapping.states];
const extend = currentSymbol[stylable_value_parsers_2.valueMapping.extends];
const alias = currentSymbol.alias;
if (states && hasOwnProperty.call(states, name)) {
found = true;
setStateToNode(states, meta, name, node, current.namespace, resolver, diagnostics, rule);
break;
}
else if (extend) {
if (current.mappedSymbols[extend.name] &&
current.mappedSymbols[extend.name]._kind !== 'import') {
const nextCurrentSymbol = current.mappedSymbols[extend.name];
if (currentSymbol === nextCurrentSymbol) {
break;
}
currentSymbol = nextCurrentSymbol;
}
else {
const next = resolver.resolve(extend);
if (next && next.meta) {
currentSymbol = next.symbol;
current = next.meta;
}
else {
break;
}
}
}
else if (alias) {
const next = resolver.resolve(alias);
if (next && next.meta) {
currentSymbol = next.symbol;
current = next.meta;
}
else {
break;
}
}
else {
break;
}
}
else {
break;
}
}
if (!found && rule) {
if (!native_reserved_lists_1.nativePseudoClasses.includes(name) && !is_vendor_prefixed_1.default(name)) {
diagnostics.warn(rule, exports.stateErrors.UNKNOWN_STATE_USAGE(name), { word: name });
}
}
return meta;
}
exports.transformPseudoStateSelector = transformPseudoStateSelector;
function setStateToNode(states, meta, name, node, namespace, resolver, diagnostics, rule) {
const stateDef = states[name];
if (stateDef === null) {
node.type = 'class';
node.name = createBooleanStateClassName(name, namespace);
}
else if (typeof stateDef === 'string') {
node.type = 'invalid'; // simply concat global mapped selector - ToDo: maybe change to 'selector'
node.value = stateDef;
}
else if (typeof stateDef === 'object') {
resolveStateValue(meta, resolver, diagnostics, rule, node, stateDef, name, namespace);
}
}
exports.setStateToNode = setStateToNode;
function resolveStateValue(meta, resolver, diagnostics, rule, node, stateDef, name, namespace) {
let actualParam = resolveParam(meta, resolver, diagnostics, rule, node.content || stateDef.defaultValue);
const validator = state_validators_1.systemValidators[stateDef.type];
let stateParamOutput;
try {
stateParamOutput = validator.validate(actualParam, stateDef.arguments, resolveParam.bind(null, meta, resolver, diagnostics, rule), false, true);
}
catch (e) {
// TODO: warn about validation throwing exception
}
if (stateParamOutput !== undefined) {
if (stateParamOutput.res !== actualParam) {
actualParam = stateParamOutput.res;
}
if (rule && stateParamOutput.errors) {
stateParamOutput.errors.unshift(`pseudo-state "${name}" with parameter "${actualParam}" failed validation:`);
diagnostics.warn(rule, stateParamOutput.errors.join('\n'), { word: actualParam });
}
}
const strippedParam = utils_1.stripQuotation(actualParam);
if (stylable_utils_1.isValidClassName(strippedParam)) {
node.type = 'class';
node.name = createStateWithParamClassName(name, namespace, strippedParam);
}
else {
node.type = 'attribute';
node.content = createAttributeState(name, namespace, strippedParam);
}
}
function resolveParam(meta, resolver, diagnostics, rule, nodeContent) {
const defaultStringValue = '';
const param = nodeContent || defaultStringValue;
return functions_1.evalDeclarationValue(resolver, param, meta, rule, undefined, undefined, diagnostics);
}
function createBooleanStateClassName(stateName, namespace) {
return `${namespace}${exports.booleanStateDelimiter}${stateName}`;
}
exports.createBooleanStateClassName = createBooleanStateClassName;
function createStateWithParamClassName(stateName, namespace, param) {
return `${namespace}${exports.stateWithParamDelimiter}${stateName}${resolveStateParam(param)}`;
}
exports.createStateWithParamClassName = createStateWithParamClassName;
function createAttributeState(stateName, namespace, param) {
return `class~="${createStateWithParamClassName(stateName, namespace, param)}"`;
}
exports.createAttributeState = createAttributeState;
function resolveStateParam(param) {
if (stylable_utils_1.isValidClassName(param)) {
return `${exports.stateMiddleDelimiter}${param.length}${exports.stateMiddleDelimiter}${param}`;
}
else {
return `${exports.stateMiddleDelimiter}${param.length}${exports.stateMiddleDelimiter}${utils_1.stripQuotation(JSON.stringify(param).replace(/\s/gm, '_'))}`;
}
}
exports.resolveStateParam = resolveStateParam;
//# sourceMappingURL=pseudo-states.js.map
;