shadow-css.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // src/utils/regular-expression.ts
  2. var escapeRegExpSpecialCharacters = (text) => {
  3. return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  4. };
  5. // src/utils/shadow-css.ts
  6. /**
  7. * @license
  8. * Copyright Google Inc. All Rights Reserved.
  9. *
  10. * Use of this source code is governed by an MIT-style license that can be
  11. * found in the LICENSE file at https://angular.io/license
  12. *
  13. * This file is a port of shadowCSS from `webcomponents.js` to TypeScript.
  14. * https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
  15. * https://github.com/angular/angular/blob/master/packages/compiler/src/shadow_css.ts
  16. */
  17. var safeSelector = (selector) => {
  18. const placeholders = [];
  19. let index = 0;
  20. selector = selector.replace(/(\[[^\]]*\])/g, (_, keep) => {
  21. const replaceBy = `__ph-${index}__`;
  22. placeholders.push(keep);
  23. index++;
  24. return replaceBy;
  25. });
  26. const content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => {
  27. const replaceBy = `__ph-${index}__`;
  28. placeholders.push(exp);
  29. index++;
  30. return pseudo + replaceBy;
  31. });
  32. const ss = {
  33. content,
  34. placeholders
  35. };
  36. return ss;
  37. };
  38. var restoreSafeSelector = (placeholders, content) => {
  39. return content.replace(/__ph-(\d+)__/g, (_, index) => placeholders[+index]);
  40. };
  41. var _polyfillHost = "-shadowcsshost";
  42. var _polyfillSlotted = "-shadowcssslotted";
  43. var _polyfillHostContext = "-shadowcsscontext";
  44. var _parenSuffix = ")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)";
  45. var _cssColonHostRe = new RegExp("(" + _polyfillHost + _parenSuffix, "gim");
  46. var _cssColonHostContextRe = new RegExp("(" + _polyfillHostContext + _parenSuffix, "gim");
  47. var _cssColonSlottedRe = new RegExp("(" + _polyfillSlotted + _parenSuffix, "gim");
  48. var _polyfillHostNoCombinator = _polyfillHost + "-no-combinator";
  49. var _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
  50. var _shadowDOMSelectorsRe = [/::shadow/g, /::content/g];
  51. var _selectorReSuffix = "([>\\s~+[.,{:][\\s\\S]*)?$";
  52. var _polyfillHostRe = /-shadowcsshost/gim;
  53. var createSupportsRuleRe = (selector) => new RegExp(`((?<!(^@supports(.*)))|(?<={.*))(${selector}\\b)`, "gim");
  54. var _colonSlottedRe = createSupportsRuleRe("::slotted");
  55. var _colonHostRe = createSupportsRuleRe(":host");
  56. var _colonHostContextRe = createSupportsRuleRe(":host-context");
  57. var _commentRe = /\/\*\s*[\s\S]*?\*\//g;
  58. var stripComments = (input) => {
  59. return input.replace(_commentRe, "");
  60. };
  61. var _commentWithHashRe = /\/\*\s*#\s*source(Mapping)?URL=[\s\S]+?\*\//g;
  62. var extractCommentsWithHash = (input) => {
  63. return input.match(_commentWithHashRe) || [];
  64. };
  65. var _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g;
  66. var _curlyRe = /([{}])/g;
  67. var _selectorPartsRe = /(^.*?[^\\])??((:+)(.*)|$)/;
  68. var OPEN_CURLY = "{";
  69. var CLOSE_CURLY = "}";
  70. var BLOCK_PLACEHOLDER = "%BLOCK%";
  71. var processRules = (input, ruleCallback) => {
  72. const inputWithEscapedBlocks = escapeBlocks(input);
  73. let nextBlockIndex = 0;
  74. return inputWithEscapedBlocks.escapedString.replace(_ruleRe, (...m) => {
  75. const selector = m[2];
  76. let content = "";
  77. let suffix = m[4];
  78. let contentPrefix = "";
  79. if (suffix && suffix.startsWith("{" + BLOCK_PLACEHOLDER)) {
  80. content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
  81. suffix = suffix.substring(BLOCK_PLACEHOLDER.length + 1);
  82. contentPrefix = "{";
  83. }
  84. const cssRule = {
  85. selector,
  86. content
  87. };
  88. const rule = ruleCallback(cssRule);
  89. return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
  90. });
  91. };
  92. var escapeBlocks = (input) => {
  93. const inputParts = input.split(_curlyRe);
  94. const resultParts = [];
  95. const escapedBlocks = [];
  96. let bracketCount = 0;
  97. let currentBlockParts = [];
  98. for (let partIndex = 0; partIndex < inputParts.length; partIndex++) {
  99. const part = inputParts[partIndex];
  100. if (part === CLOSE_CURLY) {
  101. bracketCount--;
  102. }
  103. if (bracketCount > 0) {
  104. currentBlockParts.push(part);
  105. } else {
  106. if (currentBlockParts.length > 0) {
  107. escapedBlocks.push(currentBlockParts.join(""));
  108. resultParts.push(BLOCK_PLACEHOLDER);
  109. currentBlockParts = [];
  110. }
  111. resultParts.push(part);
  112. }
  113. if (part === OPEN_CURLY) {
  114. bracketCount++;
  115. }
  116. }
  117. if (currentBlockParts.length > 0) {
  118. escapedBlocks.push(currentBlockParts.join(""));
  119. resultParts.push(BLOCK_PLACEHOLDER);
  120. }
  121. const strEscapedBlocks = {
  122. escapedString: resultParts.join(""),
  123. blocks: escapedBlocks
  124. };
  125. return strEscapedBlocks;
  126. };
  127. var insertPolyfillHostInCssText = (cssText) => {
  128. cssText = cssText.replace(_colonHostContextRe, `$1${_polyfillHostContext}`).replace(_colonHostRe, `$1${_polyfillHost}`).replace(_colonSlottedRe, `$1${_polyfillSlotted}`);
  129. return cssText;
  130. };
  131. var convertColonRule = (cssText, regExp, partReplacer) => {
  132. return cssText.replace(regExp, (...m) => {
  133. if (m[2]) {
  134. const parts = m[2].split(",");
  135. const r = [];
  136. for (let i = 0; i < parts.length; i++) {
  137. const p = parts[i].trim();
  138. if (!p) break;
  139. r.push(partReplacer(_polyfillHostNoCombinator, p, m[3]));
  140. }
  141. return r.join(",");
  142. } else {
  143. return _polyfillHostNoCombinator + m[3];
  144. }
  145. });
  146. };
  147. var colonHostPartReplacer = (host, part, suffix) => {
  148. return host + part.replace(_polyfillHost, "") + suffix;
  149. };
  150. var convertColonHost = (cssText) => {
  151. return convertColonRule(cssText, _cssColonHostRe, colonHostPartReplacer);
  152. };
  153. var colonHostContextPartReplacer = (host, part, suffix) => {
  154. if (part.indexOf(_polyfillHost) > -1) {
  155. return colonHostPartReplacer(host, part, suffix);
  156. } else {
  157. return host + part + suffix + ", " + part + " " + host + suffix;
  158. }
  159. };
  160. var convertColonSlotted = (cssText, slotScopeId) => {
  161. const slotClass = "." + slotScopeId + " > ";
  162. const selectors = [];
  163. cssText = cssText.replace(_cssColonSlottedRe, (...m) => {
  164. if (m[2]) {
  165. const compound = m[2].trim();
  166. const suffix = m[3];
  167. const slottedSelector = slotClass + compound + suffix;
  168. let prefixSelector = "";
  169. for (let i = m[4] - 1; i >= 0; i--) {
  170. const char = m[5][i];
  171. if (char === "}" || char === ",") {
  172. break;
  173. }
  174. prefixSelector = char + prefixSelector;
  175. }
  176. const orgSelector = (prefixSelector + slottedSelector).trim();
  177. const addedSelector = `${prefixSelector.trimEnd()}${slottedSelector.trim()}`.trim();
  178. if (orgSelector !== addedSelector) {
  179. const updatedSelector = `${addedSelector}, ${orgSelector}`;
  180. selectors.push({
  181. orgSelector,
  182. updatedSelector
  183. });
  184. }
  185. return slottedSelector;
  186. } else {
  187. return _polyfillHostNoCombinator + m[3];
  188. }
  189. });
  190. return {
  191. selectors,
  192. cssText
  193. };
  194. };
  195. var convertColonHostContext = (cssText) => {
  196. return convertColonRule(cssText, _cssColonHostContextRe, colonHostContextPartReplacer);
  197. };
  198. var convertShadowDOMSelectors = (cssText) => {
  199. return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, " "), cssText);
  200. };
  201. var makeScopeMatcher = (scopeSelector2) => {
  202. const lre = /\[/g;
  203. const rre = /\]/g;
  204. scopeSelector2 = scopeSelector2.replace(lre, "\\[").replace(rre, "\\]");
  205. return new RegExp("^(" + scopeSelector2 + ")" + _selectorReSuffix, "m");
  206. };
  207. var selectorNeedsScoping = (selector, scopeSelector2) => {
  208. const re = makeScopeMatcher(scopeSelector2);
  209. return !re.test(selector);
  210. };
  211. var injectScopingSelector = (selector, scopingSelector) => {
  212. return selector.replace(_selectorPartsRe, (_, before = "", _colonGroup, colon = "", after = "") => {
  213. return before + scopingSelector + colon + after;
  214. });
  215. };
  216. var applySimpleSelectorScope = (selector, scopeSelector2, hostSelector) => {
  217. _polyfillHostRe.lastIndex = 0;
  218. if (_polyfillHostRe.test(selector)) {
  219. const replaceBy = `.${hostSelector}`;
  220. return selector.replace(_polyfillHostNoCombinatorRe, (_, selector2) => injectScopingSelector(selector2, replaceBy)).replace(_polyfillHostRe, replaceBy + " ");
  221. }
  222. return scopeSelector2 + " " + selector;
  223. };
  224. var applyStrictSelectorScope = (selector, scopeSelector2, hostSelector) => {
  225. const isRe = /\[is=([^\]]*)\]/g;
  226. scopeSelector2 = scopeSelector2.replace(isRe, (_, ...parts) => parts[0]);
  227. const className = "." + scopeSelector2;
  228. const _scopeSelectorPart = (p) => {
  229. let scopedP = p.trim();
  230. if (!scopedP) {
  231. return "";
  232. }
  233. if (p.indexOf(_polyfillHostNoCombinator) > -1) {
  234. scopedP = applySimpleSelectorScope(p, scopeSelector2, hostSelector);
  235. } else {
  236. const t = p.replace(_polyfillHostRe, "");
  237. if (t.length > 0) {
  238. scopedP = injectScopingSelector(t, className);
  239. }
  240. }
  241. return scopedP;
  242. };
  243. const safeContent = safeSelector(selector);
  244. selector = safeContent.content;
  245. let scopedSelector = "";
  246. let startIndex = 0;
  247. let res;
  248. const sep = /( |>|\+|~(?!=))\s*/g;
  249. const hasHost = selector.indexOf(_polyfillHostNoCombinator) > -1;
  250. let shouldScope = !hasHost;
  251. while ((res = sep.exec(selector)) !== null) {
  252. const separator = res[1];
  253. const part2 = selector.slice(startIndex, res.index).trim();
  254. shouldScope = shouldScope || part2.indexOf(_polyfillHostNoCombinator) > -1;
  255. const scopedPart = shouldScope ? _scopeSelectorPart(part2) : part2;
  256. scopedSelector += `${scopedPart} ${separator} `;
  257. startIndex = sep.lastIndex;
  258. }
  259. const part = selector.substring(startIndex);
  260. shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1;
  261. scopedSelector += shouldScope ? _scopeSelectorPart(part) : part;
  262. return restoreSafeSelector(safeContent.placeholders, scopedSelector);
  263. };
  264. var scopeSelector = (selector, scopeSelectorText, hostSelector, slotSelector) => {
  265. return selector.split(",").map((shallowPart) => {
  266. if (slotSelector && shallowPart.indexOf("." + slotSelector) > -1) {
  267. return shallowPart.trim();
  268. }
  269. if (selectorNeedsScoping(shallowPart, scopeSelectorText)) {
  270. return applyStrictSelectorScope(shallowPart, scopeSelectorText, hostSelector).trim();
  271. } else {
  272. return shallowPart.trim();
  273. }
  274. }).join(", ");
  275. };
  276. var scopeSelectors = (cssText, scopeSelectorText, hostSelector, slotSelector) => {
  277. return processRules(cssText, (rule) => {
  278. let selector = rule.selector;
  279. let content = rule.content;
  280. if (rule.selector[0] !== "@") {
  281. selector = scopeSelector(rule.selector, scopeSelectorText, hostSelector, slotSelector);
  282. } else if (rule.selector.startsWith("@media") || rule.selector.startsWith("@supports") || rule.selector.startsWith("@page") || rule.selector.startsWith("@document")) {
  283. content = scopeSelectors(rule.content, scopeSelectorText, hostSelector, slotSelector);
  284. }
  285. const cssRule = {
  286. selector: selector.replace(/\s{2,}/g, " ").trim(),
  287. content
  288. };
  289. return cssRule;
  290. });
  291. };
  292. var scopeCssText = (cssText, scopeId, hostScopeId, slotScopeId) => {
  293. cssText = insertPolyfillHostInCssText(cssText);
  294. cssText = convertColonHost(cssText);
  295. cssText = convertColonHostContext(cssText);
  296. const slotted = convertColonSlotted(cssText, slotScopeId);
  297. cssText = slotted.cssText;
  298. cssText = convertShadowDOMSelectors(cssText);
  299. if (scopeId) {
  300. cssText = scopeSelectors(cssText, scopeId, hostScopeId, slotScopeId);
  301. }
  302. cssText = replaceShadowCssHost(cssText, hostScopeId);
  303. cssText = cssText.replace(/>\s*\*\s+([^{, ]+)/gm, " $1 ");
  304. return {
  305. cssText: cssText.trim(),
  306. // We need to replace the shadow CSS host string in each of these selectors since we created
  307. // them prior to the replacement happening in the components CSS text.
  308. slottedSelectors: slotted.selectors.map((ref) => ({
  309. orgSelector: replaceShadowCssHost(ref.orgSelector, hostScopeId),
  310. updatedSelector: replaceShadowCssHost(ref.updatedSelector, hostScopeId)
  311. }))
  312. };
  313. };
  314. var replaceShadowCssHost = (cssText, hostScopeId) => {
  315. return cssText.replace(/-shadowcsshost-no-combinator/g, `.${hostScopeId}`);
  316. };
  317. var scopeCss = (cssText, scopeId) => {
  318. const hostScopeId = scopeId + "-h";
  319. const slotScopeId = scopeId + "-s";
  320. const commentsWithHash = extractCommentsWithHash(cssText);
  321. cssText = stripComments(cssText);
  322. const scoped = scopeCssText(cssText, scopeId, hostScopeId, slotScopeId);
  323. cssText = [scoped.cssText, ...commentsWithHash].join("\n");
  324. scoped.slottedSelectors.forEach((slottedSelector) => {
  325. const regex = new RegExp(escapeRegExpSpecialCharacters(slottedSelector.orgSelector), "g");
  326. cssText = cssText.replace(regex, slottedSelector.updatedSelector);
  327. });
  328. return cssText;
  329. };
  330. export {
  331. scopeCss
  332. };