@license
Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
function dirShadowTransform(selector) {
if (!selector.match(/:dir\(/)) {
return selector;
}
return splitSelectorList(selector).map(function(s) {
s = ensureScopedDir(s);
s = transformDir(s);
let m = HOST_CONTEXT_PAREN.exec(s);
if (m) {
s += additionalDirSelectors(m[2], m[3], '');
}
return s;
}, this).join(COMPLEX_SELECTOR_SEP);
}
function splitSelectorList(selector) {
let parts = [];
let part = '';
for (let i = 0; i >= 0 && i < selector.length; i++) {
if (selector[i] === '(') {
let end = findMatchingParen(selector, i);
part += selector.slice(i, end + 1);
i = end;
} else if (selector[i] === COMPLEX_SELECTOR_SEP) {
parts.push(part);
part = '';
} else {
part += selector[i];
}
}
if (part) {
parts.push(part);
}
if (parts.length === 0) {
parts.push(selector);
}
return parts;
}
function ensureScopedDir(s) {
let m = s.match(DIR_PAREN);
if (m && m[1] === '' && m[0].length === s.length) {
s = '*' + s;
}
return s;
}
function additionalDirSelectors(dir, after, prefix) {
if (!dir || !after) {
return '';
}
prefix = prefix || '';
return `${COMPLEX_SELECTOR_SEP}${prefix} ${dir} ${after}`;
}
function transformDir(s) {
s = s.replace(HOST_DIR, HOST_DIR_REPLACE);
s = s.replace(DIR_PAREN, DIR_REPLACE);
return s;
}
* Walk from text[start] matching parens and
* returns position of the outer end paren
* @param {string} text
* @param {number} start
* @return {number}
*/
function findMatchingParen(text, start) {
let level = 0;
for (let i=start, l=text.length; i < l; i++) {
if (text[i] === '(') {
level++;
} else if (text[i] === ')') {
if (--level === 0) {
return i;
}
}
}
return -1;
}
function slottedToContent(cssText) {
return cssText.replace(SLOTTED_PAREN, `${CONTENT} > $1`);
}
function shadyReplaceContent(selector, scope) {
if (selector.indexOf(CONTENT) === -1) {
return selector;
}
const contentForm = `.${scope}::content`;
const replaceContent = new RegExp(`[+~>]? ${contentForm}`);
const end = new RegExp('.' + scope + '$');
const beforePseudo = new RegExp('.' + scope + ':');
return splitSelectorList(selector).map((sel) => {
if (sel.indexOf(contentForm) > -1) {
return sel.replace(replaceContent, '')
.replace(beforePseudo, ':')
.replace(end, '');
} else {
return sel;
}
}).join(',');
}
function fixBadVars(rule) {
const cssText = rule.cssText;
rule.cssText = cssText.replace(BAD_VAR, fixVars);
}
function fixVars(matchText, varA, varB) {
return `var(${varA},var(${varB}))`;
}
const COMPLEX_SELECTOR_SEP = ',';
const CONTENT = '::content';
const DIR_PAREN = /(.*):dir\((ltr|rtl)\)/;
const DIR_REPLACE = ':host-context([dir="$2"]) $1';
const HOST_CONTEXT_PAREN = /(.*)(?::host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
const HOST_DIR = /:host\(:dir\((rtl|ltr)\)\)/g;
const HOST_DIR_REPLACE = ':host-context([dir="$1"])';
const SLOTTED_PAREN = /(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
const BAD_VAR = /var\(\s*(--[^,]*),\s*(--[^)]*)\)/g;
module.exports = {
dirShadowTransform,
slottedToContent,
shadyReplaceContent,
fixBadVars
};