123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /*
- Stencil Client Platform v2.18.1 | MIT Licensed | https://stenciljs.com
- */
- /**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- *
- * This file is a port of shadowCSS from webcomponents.js to TypeScript.
- * https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
- * https://github.com/angular/angular/blob/master/packages/compiler/src/shadow_css.ts
- */
- const safeSelector = (selector) => {
- const placeholders = [];
- let index = 0;
- // Replaces attribute selectors with placeholders.
- // The WS in [attr="va lue"] would otherwise be interpreted as a selector separator.
- selector = selector.replace(/(\[[^\]]*\])/g, (_, keep) => {
- const replaceBy = `__ph-${index}__`;
- placeholders.push(keep);
- index++;
- return replaceBy;
- });
- // Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
- // WS and "+" would otherwise be interpreted as selector separators.
- const content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => {
- const replaceBy = `__ph-${index}__`;
- placeholders.push(exp);
- index++;
- return pseudo + replaceBy;
- });
- const ss = {
- content,
- placeholders,
- };
- return ss;
- };
- const restoreSafeSelector = (placeholders, content) => {
- return content.replace(/__ph-(\d+)__/g, (_, index) => placeholders[+index]);
- };
- const _polyfillHost = '-shadowcsshost';
- const _polyfillSlotted = '-shadowcssslotted';
- // note: :host-context pre-processed to -shadowcsshostcontext.
- const _polyfillHostContext = '-shadowcsscontext';
- const _parenSuffix = ')(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))?([^,{]*)';
- const _cssColonHostRe = new RegExp('(' + _polyfillHost + _parenSuffix, 'gim');
- const _cssColonHostContextRe = new RegExp('(' + _polyfillHostContext + _parenSuffix, 'gim');
- const _cssColonSlottedRe = new RegExp('(' + _polyfillSlotted + _parenSuffix, 'gim');
- const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
- const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
- const _shadowDOMSelectorsRe = [/::shadow/g, /::content/g];
- const _selectorReSuffix = '([>\\s~+[.,{:][\\s\\S]*)?$';
- const _polyfillHostRe = /-shadowcsshost/gim;
- const _colonHostRe = /:host/gim;
- const _colonSlottedRe = /::slotted/gim;
- const _colonHostContextRe = /:host-context/gim;
- const _commentRe = /\/\*\s*[\s\S]*?\*\//g;
- const stripComments = (input) => {
- return input.replace(_commentRe, '');
- };
- const _commentWithHashRe = /\/\*\s*#\s*source(Mapping)?URL=[\s\S]+?\*\//g;
- const extractCommentsWithHash = (input) => {
- return input.match(_commentWithHashRe) || [];
- };
- const _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g;
- const _curlyRe = /([{}])/g;
- const _selectorPartsRe = /(^.*?[^\\])??((:+)(.*)|$)/;
- const OPEN_CURLY = '{';
- const CLOSE_CURLY = '}';
- const BLOCK_PLACEHOLDER = '%BLOCK%';
- const processRules = (input, ruleCallback) => {
- const inputWithEscapedBlocks = escapeBlocks(input);
- let nextBlockIndex = 0;
- return inputWithEscapedBlocks.escapedString.replace(_ruleRe, (...m) => {
- const selector = m[2];
- let content = '';
- let suffix = m[4];
- let contentPrefix = '';
- if (suffix && suffix.startsWith('{' + BLOCK_PLACEHOLDER)) {
- content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
- suffix = suffix.substring(BLOCK_PLACEHOLDER.length + 1);
- contentPrefix = '{';
- }
- const cssRule = {
- selector,
- content,
- };
- const rule = ruleCallback(cssRule);
- return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
- });
- };
- const escapeBlocks = (input) => {
- const inputParts = input.split(_curlyRe);
- const resultParts = [];
- const escapedBlocks = [];
- let bracketCount = 0;
- let currentBlockParts = [];
- for (let partIndex = 0; partIndex < inputParts.length; partIndex++) {
- const part = inputParts[partIndex];
- if (part === CLOSE_CURLY) {
- bracketCount--;
- }
- if (bracketCount > 0) {
- currentBlockParts.push(part);
- }
- else {
- if (currentBlockParts.length > 0) {
- escapedBlocks.push(currentBlockParts.join(''));
- resultParts.push(BLOCK_PLACEHOLDER);
- currentBlockParts = [];
- }
- resultParts.push(part);
- }
- if (part === OPEN_CURLY) {
- bracketCount++;
- }
- }
- if (currentBlockParts.length > 0) {
- escapedBlocks.push(currentBlockParts.join(''));
- resultParts.push(BLOCK_PLACEHOLDER);
- }
- const strEscapedBlocks = {
- escapedString: resultParts.join(''),
- blocks: escapedBlocks,
- };
- return strEscapedBlocks;
- };
- const insertPolyfillHostInCssText = (selector) => {
- selector = selector
- .replace(_colonHostContextRe, _polyfillHostContext)
- .replace(_colonHostRe, _polyfillHost)
- .replace(_colonSlottedRe, _polyfillSlotted);
- return selector;
- };
- const convertColonRule = (cssText, regExp, partReplacer) => {
- // m[1] = :host(-context), m[2] = contents of (), m[3] rest of rule
- return cssText.replace(regExp, (...m) => {
- if (m[2]) {
- const parts = m[2].split(',');
- const r = [];
- for (let i = 0; i < parts.length; i++) {
- const p = parts[i].trim();
- if (!p)
- break;
- r.push(partReplacer(_polyfillHostNoCombinator, p, m[3]));
- }
- return r.join(',');
- }
- else {
- return _polyfillHostNoCombinator + m[3];
- }
- });
- };
- const colonHostPartReplacer = (host, part, suffix) => {
- return host + part.replace(_polyfillHost, '') + suffix;
- };
- const convertColonHost = (cssText) => {
- return convertColonRule(cssText, _cssColonHostRe, colonHostPartReplacer);
- };
- const colonHostContextPartReplacer = (host, part, suffix) => {
- if (part.indexOf(_polyfillHost) > -1) {
- return colonHostPartReplacer(host, part, suffix);
- }
- else {
- return host + part + suffix + ', ' + part + ' ' + host + suffix;
- }
- };
- const convertColonSlotted = (cssText, slotScopeId) => {
- const slotClass = '.' + slotScopeId + ' > ';
- const selectors = [];
- cssText = cssText.replace(_cssColonSlottedRe, (...m) => {
- if (m[2]) {
- const compound = m[2].trim();
- const suffix = m[3];
- const slottedSelector = slotClass + compound + suffix;
- let prefixSelector = '';
- for (let i = m[4] - 1; i >= 0; i--) {
- const char = m[5][i];
- if (char === '}' || char === ',') {
- break;
- }
- prefixSelector = char + prefixSelector;
- }
- const orgSelector = prefixSelector + slottedSelector;
- const addedSelector = `${prefixSelector.trimRight()}${slottedSelector.trim()}`;
- if (orgSelector.trim() !== addedSelector.trim()) {
- const updatedSelector = `${addedSelector}, ${orgSelector}`;
- selectors.push({
- orgSelector,
- updatedSelector,
- });
- }
- return slottedSelector;
- }
- else {
- return _polyfillHostNoCombinator + m[3];
- }
- });
- return {
- selectors,
- cssText,
- };
- };
- const convertColonHostContext = (cssText) => {
- return convertColonRule(cssText, _cssColonHostContextRe, colonHostContextPartReplacer);
- };
- const convertShadowDOMSelectors = (cssText) => {
- return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText);
- };
- const makeScopeMatcher = (scopeSelector) => {
- const lre = /\[/g;
- const rre = /\]/g;
- scopeSelector = scopeSelector.replace(lre, '\\[').replace(rre, '\\]');
- return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
- };
- const selectorNeedsScoping = (selector, scopeSelector) => {
- const re = makeScopeMatcher(scopeSelector);
- return !re.test(selector);
- };
- const injectScopingSelector = (selector, scopingSelector) => {
- return selector.replace(_selectorPartsRe, (_, before = '', _colonGroup, colon = '', after = '') => {
- return before + scopingSelector + colon + after;
- });
- };
- const applySimpleSelectorScope = (selector, scopeSelector, hostSelector) => {
- // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
- _polyfillHostRe.lastIndex = 0;
- if (_polyfillHostRe.test(selector)) {
- const replaceBy = `.${hostSelector}`;
- return selector
- .replace(_polyfillHostNoCombinatorRe, (_, selector) => injectScopingSelector(selector, replaceBy))
- .replace(_polyfillHostRe, replaceBy + ' ');
- }
- return scopeSelector + ' ' + selector;
- };
- const applyStrictSelectorScope = (selector, scopeSelector, hostSelector) => {
- const isRe = /\[is=([^\]]*)\]/g;
- scopeSelector = scopeSelector.replace(isRe, (_, ...parts) => parts[0]);
- const className = '.' + scopeSelector;
- const _scopeSelectorPart = (p) => {
- let scopedP = p.trim();
- if (!scopedP) {
- return '';
- }
- if (p.indexOf(_polyfillHostNoCombinator) > -1) {
- scopedP = applySimpleSelectorScope(p, scopeSelector, hostSelector);
- }
- else {
- // remove :host since it should be unnecessary
- const t = p.replace(_polyfillHostRe, '');
- if (t.length > 0) {
- scopedP = injectScopingSelector(t, className);
- }
- }
- return scopedP;
- };
- const safeContent = safeSelector(selector);
- selector = safeContent.content;
- let scopedSelector = '';
- let startIndex = 0;
- let res;
- const sep = /( |>|\+|~(?!=))\s*/g;
- // If a selector appears before :host it should not be shimmed as it
- // matches on ancestor elements and not on elements in the host's shadow
- // `:host-context(div)` is transformed to
- // `-shadowcsshost-no-combinatordiv, div -shadowcsshost-no-combinator`
- // the `div` is not part of the component in the 2nd selectors and should not be scoped.
- // Historically `component-tag:host` was matching the component so we also want to preserve
- // this behavior to avoid breaking legacy apps (it should not match).
- // The behavior should be:
- // - `tag:host` -> `tag[h]` (this is to avoid breaking legacy apps, should not match anything)
- // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a
- // `:host-context(tag)`)
- const hasHost = selector.indexOf(_polyfillHostNoCombinator) > -1;
- // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present
- let shouldScope = !hasHost;
- while ((res = sep.exec(selector)) !== null) {
- const separator = res[1];
- const part = selector.slice(startIndex, res.index).trim();
- shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1;
- const scopedPart = shouldScope ? _scopeSelectorPart(part) : part;
- scopedSelector += `${scopedPart} ${separator} `;
- startIndex = sep.lastIndex;
- }
- const part = selector.substring(startIndex);
- shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1;
- scopedSelector += shouldScope ? _scopeSelectorPart(part) : part;
- // replace the placeholders with their original values
- return restoreSafeSelector(safeContent.placeholders, scopedSelector);
- };
- const scopeSelector = (selector, scopeSelectorText, hostSelector, slotSelector) => {
- return selector
- .split(',')
- .map((shallowPart) => {
- if (slotSelector && shallowPart.indexOf('.' + slotSelector) > -1) {
- return shallowPart.trim();
- }
- if (selectorNeedsScoping(shallowPart, scopeSelectorText)) {
- return applyStrictSelectorScope(shallowPart, scopeSelectorText, hostSelector).trim();
- }
- else {
- return shallowPart.trim();
- }
- })
- .join(', ');
- };
- const scopeSelectors = (cssText, scopeSelectorText, hostSelector, slotSelector, commentOriginalSelector) => {
- return processRules(cssText, (rule) => {
- let selector = rule.selector;
- let content = rule.content;
- if (rule.selector[0] !== '@') {
- selector = scopeSelector(rule.selector, scopeSelectorText, hostSelector, slotSelector);
- }
- else if (rule.selector.startsWith('@media') ||
- rule.selector.startsWith('@supports') ||
- rule.selector.startsWith('@page') ||
- rule.selector.startsWith('@document')) {
- content = scopeSelectors(rule.content, scopeSelectorText, hostSelector, slotSelector);
- }
- const cssRule = {
- selector: selector.replace(/\s{2,}/g, ' ').trim(),
- content,
- };
- return cssRule;
- });
- };
- const scopeCssText = (cssText, scopeId, hostScopeId, slotScopeId, commentOriginalSelector) => {
- cssText = insertPolyfillHostInCssText(cssText);
- cssText = convertColonHost(cssText);
- cssText = convertColonHostContext(cssText);
- const slotted = convertColonSlotted(cssText, slotScopeId);
- cssText = slotted.cssText;
- cssText = convertShadowDOMSelectors(cssText);
- if (scopeId) {
- cssText = scopeSelectors(cssText, scopeId, hostScopeId, slotScopeId);
- }
- cssText = cssText.replace(/-shadowcsshost-no-combinator/g, `.${hostScopeId}`);
- cssText = cssText.replace(/>\s*\*\s+([^{, ]+)/gm, ' $1 ');
- return {
- cssText: cssText.trim(),
- slottedSelectors: slotted.selectors,
- };
- };
- const scopeCss = (cssText, scopeId, commentOriginalSelector) => {
- const hostScopeId = scopeId + '-h';
- const slotScopeId = scopeId + '-s';
- const commentsWithHash = extractCommentsWithHash(cssText);
- cssText = stripComments(cssText);
- const orgSelectors = [];
- if (commentOriginalSelector) {
- const processCommentedSelector = (rule) => {
- const placeholder = `/*!@___${orgSelectors.length}___*/`;
- const comment = `/*!@${rule.selector}*/`;
- orgSelectors.push({ placeholder, comment });
- rule.selector = placeholder + rule.selector;
- return rule;
- };
- cssText = processRules(cssText, (rule) => {
- if (rule.selector[0] !== '@') {
- return processCommentedSelector(rule);
- }
- else if (rule.selector.startsWith('@media') ||
- rule.selector.startsWith('@supports') ||
- rule.selector.startsWith('@page') ||
- rule.selector.startsWith('@document')) {
- rule.content = processRules(rule.content, processCommentedSelector);
- return rule;
- }
- return rule;
- });
- }
- const scoped = scopeCssText(cssText, scopeId, hostScopeId, slotScopeId);
- cssText = [scoped.cssText, ...commentsWithHash].join('\n');
- if (commentOriginalSelector) {
- orgSelectors.forEach(({ placeholder, comment }) => {
- cssText = cssText.replace(placeholder, comment);
- });
- }
- scoped.slottedSelectors.forEach((slottedSelector) => {
- cssText = cssText.replace(slottedSelector.orgSelector, slottedSelector.updatedSelector);
- });
- return cssText;
- };
- export { scopeCss };
|