UNPKG

@stylable/core

Version:

CSS for Components

315 lines 15 kB
"use strict"; 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