diff --git a/README.md b/README.md index bf07d7f..2f123f1 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,12 @@ postcss([bike()]).process(css).then((res) => console.log(output.css)); } } + @mod theme[foo|bar] { + @elem header { + position: absolute; + } + } + @elem header { flex: 0 0 50px; background-color: #fff; @@ -90,6 +96,10 @@ Transformed to: background-color: #1b1b1b; color: #fff; } +.example_theme_foo .example__header, +.example_theme_bar .example__header { + position: absolute; +} .example__header { flex: 0 0 50px; background-color: #fff; @@ -134,10 +144,11 @@ Allows to set custom name for modifier `@rule`. ### `modifierRegExp` type: `RegExp` -default: `{modifierRegExp: /(\w+)\[(\w+)\]/}` +default: `{modifierRegExp: /(\w+)\[(\w+)| \]/}` Allows to set custom regular expressions for modifier params. Where `$1` is Modifier Name and `$2` is Modifier Value. For changing Modifier Value Separator, change default separator `\[$2\]`, which goes before and after `$2` (only this `[ ]` symbols). +Multiple values (used as 'OR') can be separated with a vertical pipe (`|`). ### License [MIT](LICENSE) diff --git a/src/bem.js b/src/bem.js index b51e28b..fccd4b1 100644 --- a/src/bem.js +++ b/src/bem.js @@ -5,22 +5,29 @@ export const BEM = (block) => (elem, mods) => { return base; } + // Handle multiple bases e.g. comma-separated elements. + let bases = [base]; + if (typeof elem === 'object') { mods = elem; elem = ''; } if (elem !== '') { - base = `.${block}__${elem}`; + bases = elem.split(',').map(elem => { + return `.${block}__${elem.trim()}`; + }); } - return (mods ? Object.entries(mods).reduce((target, [key, value]) => { - if (!value) { - return target; - } + return bases.map(base => { + return mods ? Object.entries(mods).reduce((target, [key, value]) => { + if (!value) { + return target; + } - target += `${value === true ? (`${base}_${key}`) : (`${base}_${key}_${value}`)}`; + target += `${value === true ? (`${base}_${key}`) : (`${base}_${key}_${value}`)}`; - return target; - }, '') : base); + return target; + }, '') : base; + }).join(', '); }; diff --git a/src/index.js b/src/index.js index aab867b..b933ad4 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const DEFAULT_OPTIONS = { component: 'component', element: 'elem', modifier: 'mod', - modifierRegExp: /([\w\-]+)(?:\[([\w\-]+)\])?/ + modifierRegExp: /([\w\-]+)(?:\[([\w\-| ]+)\])?/ }; export default postcss.plugin('postcss-bike', (options = DEFAULT_OPTIONS) => { @@ -21,37 +21,46 @@ export default postcss.plugin('postcss-bike', (options = DEFAULT_OPTIONS) => { node.metadata = { bem: BEM(node.params), type: options.component }; } - let selector = ''; - - switch (node.metadata.type) { - case options.component: - selector = node.metadata.bem(); - break; - case options.modifier: - let [, modName, modVal = true] = node.metadata.name.match(options.modifierRegExp); - - node.metadata.mods = { [modName]: modVal }; - - if (node.parent.metadata.type === options.element) { - selector = node.metadata.bem(node.parent.metadata.name, { [modName]: modVal }); - } else if (node.parent.metadata.type === options.modifier) { - selector = node.metadata.bem({ ...node.parent.metadata.mods, [modName]: modVal }); - } else { - selector = node.metadata.bem({ [modName]: modVal }); - } - break; - case options.element: - if (node.parent.metadata.type === options.modifier) { - selector = [node.parent.selector, node.metadata.bem(node.metadata.name)].join(' '); - } else { - selector = node.metadata.bem(node.metadata.name); - } - break; - } + let selectors = []; + + node.metadata.names = node.metadata.name ? node.metadata.name.split(',').map(val => val.trim()) : ['']; + node.metadata.names.forEach(metaName => { + switch (node.metadata.type) { + case options.component: + selectors.push(node.metadata.bem()); + break; + case options.modifier: + let [, modName, modVals = true] = metaName.match(options.modifierRegExp); + + // Handle possible multiple comma-delimited modifier values. + modVals = typeof modVals === 'string' ? modVals.split('|').map(val => val.trim()) : [modVals]; + modVals.forEach(modVal => { + node.metadata.mods = { [modName]: modVal }; + + if (node.parent.metadata.type === options.element) { + selectors.push(node.metadata.bem(node.parent.metadata.name, { [modName]: modVal })); + } else if (node.parent.metadata.type === options.modifier) { + selectors.push(node.metadata.bem({ ...node.parent.metadata.mods, [modName]: modVal })); + } else { + selectors.push(node.metadata.bem({ [modName]: modVal })); + } + }); + break; + case options.element: + if (node.parent.metadata.type === options.modifier) { + node.parent.selectors.forEach(parentSelector => { + selectors.push([parentSelector, node.metadata.bem(metaName)].join(' ')); + }); + } else { + selectors.push(node.metadata.bem(metaName)); + } + break; + } + }); const rule = postcss.rule({ raws: { semicolon: true }, - selector: selector, + selectors: selectors, source: node.source, metadata: node.metadata });