UNPKG

20.5 kBJavaScriptView Raw
1'use strict';
2
3/*
4Extremely simple css parser. Intended to be not more than what we need
5and definitely not necessarily correct =).
6*/
7/** @unrestricted */
8var StyleNode = /** @class */ (function () {
9 function StyleNode() {
10 this.start = 0;
11 this.end = 0;
12 this.previous = null;
13 this.parent = null;
14 this.rules = null;
15 this.parsedCssText = '';
16 this.cssText = '';
17 this.atRule = false;
18 this.type = 0;
19 this.keyframesName = '';
20 this.selector = '';
21 this.parsedSelector = '';
22 }
23 return StyleNode;
24}());
25// given a string of css, return a simple rule tree
26/**
27 * @param {string} text
28 * @return {StyleNode}
29 */
30function parse(text) {
31 text = clean(text);
32 return parseCss(lex(text), text);
33}
34// remove stuff we don't care about that may hinder parsing
35/**
36 * @param {string} cssText
37 * @return {string}
38 */
39function clean(cssText) {
40 return cssText.replace(RX.comments, '').replace(RX.port, '');
41}
42// super simple {...} lexer that returns a node tree
43/**
44 * @param {string} text
45 * @return {StyleNode}
46 */
47function lex(text) {
48 var root = new StyleNode();
49 root['start'] = 0;
50 root['end'] = text.length;
51 var n = root;
52 for (var i = 0, l = text.length; i < l; i++) {
53 if (text[i] === OPEN_BRACE) {
54 if (!n['rules']) {
55 n['rules'] = [];
56 }
57 var p = n;
58 var previous = p['rules'][p['rules'].length - 1] || null;
59 n = new StyleNode();
60 n['start'] = i + 1;
61 n['parent'] = p;
62 n['previous'] = previous;
63 p['rules'].push(n);
64 }
65 else if (text[i] === CLOSE_BRACE) {
66 n['end'] = i + 1;
67 n = n['parent'] || root;
68 }
69 }
70 return root;
71}
72// add selectors/cssText to node tree
73/**
74 * @param {StyleNode} node
75 * @param {string} text
76 * @return {StyleNode}
77 */
78function parseCss(node, text) {
79 var t = text.substring(node['start'], node['end'] - 1);
80 node['parsedCssText'] = node['cssText'] = t.trim();
81 if (node.parent) {
82 var ss = node.previous ? node.previous['end'] : node.parent['start'];
83 t = text.substring(ss, node['start'] - 1);
84 t = _expandUnicodeEscapes(t);
85 t = t.replace(RX.multipleSpaces, ' ');
86 // TODO(sorvell): ad hoc; make selector include only after last ;
87 // helps with mixin syntax
88 t = t.substring(t.lastIndexOf(';') + 1);
89 var s = node['parsedSelector'] = node['selector'] = t.trim();
90 node['atRule'] = (s.indexOf(AT_START) === 0);
91 // note, support a subset of rule types...
92 if (node['atRule']) {
93 if (s.indexOf(MEDIA_START) === 0) {
94 node['type'] = types.MEDIA_RULE;
95 }
96 else if (s.match(RX.keyframesRule)) {
97 node['type'] = types.KEYFRAMES_RULE;
98 node['keyframesName'] = node['selector'].split(RX.multipleSpaces).pop();
99 }
100 }
101 else {
102 if (s.indexOf(VAR_START) === 0) {
103 node['type'] = types.MIXIN_RULE;
104 }
105 else {
106 node['type'] = types.STYLE_RULE;
107 }
108 }
109 }
110 var r$ = node['rules'];
111 if (r$) {
112 for (var i = 0, l = r$.length, r = void 0; (i < l) && (r = r$[i]); i++) {
113 parseCss(r, text);
114 }
115 }
116 return node;
117}
118/**
119 * conversion of sort unicode escapes with spaces like `\33 ` (and longer) into
120 * expanded form that doesn't require trailing space `\000033`
121 * @param {string} s
122 * @return {string}
123 */
124function _expandUnicodeEscapes(s) {
125 return s.replace(/\\([0-9a-f]{1,6})\s/gi, function () {
126 var code = arguments[1], repeat = 6 - code.length;
127 while (repeat--) {
128 code = '0' + code;
129 }
130 return '\\' + code;
131 });
132}
133/** @enum {number} */
134var types = {
135 STYLE_RULE: 1,
136 KEYFRAMES_RULE: 7,
137 MEDIA_RULE: 4,
138 MIXIN_RULE: 1000
139};
140var OPEN_BRACE = '{';
141var CLOSE_BRACE = '}';
142// helper regexp's
143var RX = {
144 comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
145 port: /@import[^;]*;/gim,
146 customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
147 mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
148 mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
149 varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
150 keyframesRule: /^@[^\s]*keyframes/,
151 multipleSpaces: /\s+/g
152};
153var VAR_START = '--';
154var MEDIA_START = '@media';
155var AT_START = '@';
156function findRegex(regex, cssText, offset) {
157 regex['lastIndex'] = 0;
158 var r = cssText.substring(offset).match(regex);
159 if (r) {
160 var start = offset + r['index'];
161 return {
162 start: start,
163 end: start + r[0].length
164 };
165 }
166 return null;
167}
168var VAR_USAGE_START = /\bvar\(/;
169var VAR_ASSIGN_START = /\B--[\w-]+\s*:/;
170var COMMENTS = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim;
171var TRAILING_LINES = /^[\t ]+\n/gm;
172function resolveVar(props, prop, fallback) {
173 if (props[prop]) {
174 return props[prop];
175 }
176 if (fallback) {
177 return executeTemplate(fallback, props);
178 }
179 return '';
180}
181function findVarEndIndex(cssText, offset) {
182 var count = 0;
183 var i = offset;
184 for (; i < cssText.length; i++) {
185 var c = cssText[i];
186 if (c === '(') {
187 count++;
188 }
189 else if (c === ')') {
190 count--;
191 if (count <= 0) {
192 return i + 1;
193 }
194 }
195 }
196 return i;
197}
198function parseVar(cssText, offset) {
199 var varPos = findRegex(VAR_USAGE_START, cssText, offset);
200 if (!varPos) {
201 return null;
202 }
203 var endVar = findVarEndIndex(cssText, varPos.start);
204 var varContent = cssText.substring(varPos.end, endVar - 1);
205 var _a = varContent.split(','), propName = _a[0], fallback = _a.slice(1);
206 return {
207 start: varPos.start,
208 end: endVar,
209 propName: propName.trim(),
210 fallback: fallback.length > 0 ? fallback.join(',').trim() : undefined
211 };
212}
213function compileVar(cssText, template, offset) {
214 var varMeta = parseVar(cssText, offset);
215 if (!varMeta) {
216 template.push(cssText.substring(offset, cssText.length));
217 return cssText.length;
218 }
219 var propName = varMeta.propName;
220 var fallback = varMeta.fallback != null ? compileTemplate(varMeta.fallback) : undefined;
221 template.push(cssText.substring(offset, varMeta.start), function (params) { return resolveVar(params, propName, fallback); });
222 return varMeta.end;
223}
224function executeTemplate(template, props) {
225 var final = '';
226 for (var i = 0; i < template.length; i++) {
227 var s = template[i];
228 final += (typeof s === 'string')
229 ? s
230 : s(props);
231 }
232 return final;
233}
234function findEndValue(cssText, offset) {
235 var onStr = false;
236 var double = false;
237 var i = offset;
238 for (; i < cssText.length; i++) {
239 var c = cssText[i];
240 if (onStr) {
241 if (double && c === '"') {
242 onStr = false;
243 }
244 if (!double && c === '\'') {
245 onStr = false;
246 }
247 }
248 else {
249 if (c === '"') {
250 onStr = true;
251 double = true;
252 }
253 else if (c === '\'') {
254 onStr = true;
255 double = false;
256 }
257 else if (c === ';') {
258 return i + 1;
259 }
260 else if (c === '}') {
261 return i;
262 }
263 }
264 }
265 return i;
266}
267function removeCustomAssigns(cssText) {
268 var final = '';
269 var offset = 0;
270 while (true) {
271 var assignPos = findRegex(VAR_ASSIGN_START, cssText, offset);
272 var start = assignPos ? assignPos.start : cssText.length;
273 final += cssText.substring(offset, start);
274 if (assignPos) {
275 offset = findEndValue(cssText, start);
276 }
277 else {
278 break;
279 }
280 }
281 return final;
282}
283function compileTemplate(cssText) {
284 var index = 0;
285 cssText = cssText.replace(COMMENTS, '');
286 cssText = removeCustomAssigns(cssText)
287 .replace(TRAILING_LINES, '');
288 var segments = [];
289 while (index < cssText.length) {
290 index = compileVar(cssText, segments, index);
291 }
292 return segments;
293}
294function resolveValues(selectors) {
295 var props = {};
296 selectors.forEach(function (selector) {
297 selector.declarations.forEach(function (dec) {
298 props[dec.prop] = dec.value;
299 });
300 });
301 var propsValues = {};
302 var entries = Object.entries(props);
303 var _loop_1 = function (i) {
304 var dirty = false;
305 entries.forEach(function (_a) {
306 var key = _a[0], value = _a[1];
307 var propValue = executeTemplate(value, propsValues);
308 if (propValue !== propsValues[key]) {
309 propsValues[key] = propValue;
310 dirty = true;
311 }
312 });
313 if (!dirty) {
314 return "break";
315 }
316 };
317 for (var i = 0; i < 10; i++) {
318 var state_1 = _loop_1();
319 if (state_1 === "break")
320 break;
321 }
322 return propsValues;
323}
324function getSelectors(root, index) {
325 if (index === void 0) { index = 0; }
326 if (!root.rules) {
327 return [];
328 }
329 var selectors = [];
330 root.rules
331 .filter(function (rule) { return rule.type === types.STYLE_RULE; })
332 .forEach(function (rule) {
333 var declarations = getDeclarations(rule.cssText);
334 if (declarations.length > 0) {
335 rule.parsedSelector.split(',').forEach(function (selector) {
336 selector = selector.trim();
337 selectors.push({
338 selector: selector,
339 declarations: declarations,
340 specificity: computeSpecificity(),
341 nu: index
342 });
343 });
344 }
345 index++;
346 });
347 return selectors;
348}
349function computeSpecificity(_selector) {
350 return 1;
351}
352var IMPORTANT = '!important';
353var FIND_DECLARATIONS = /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gm;
354function getDeclarations(cssText) {
355 var declarations = [];
356 var xArray;
357 while (xArray = FIND_DECLARATIONS.exec(cssText.trim())) {
358 var _a = normalizeValue(xArray[2]), value = _a.value, important = _a.important;
359 declarations.push({
360 prop: xArray[1].trim(),
361 value: compileTemplate(value),
362 important: important,
363 });
364 }
365 return declarations;
366}
367function normalizeValue(value) {
368 var regex = /\s+/gim;
369 value = value.replace(regex, ' ').trim();
370 var important = value.endsWith(IMPORTANT);
371 if (important) {
372 value = value.substr(0, value.length - IMPORTANT.length).trim();
373 }
374 return {
375 value: value,
376 important: important
377 };
378}
379function getActiveSelectors(hostEl, hostScopeMap, globalScopes) {
380 // computes the css scopes that might affect this particular element
381 var scopes = globalScopes.concat(getScopesForElement(hostScopeMap, hostEl));
382 // each scope might have an array of associated selectors
383 // let's flatten the complete array of selectors from all the scopes
384 var selectorSet = getSelectorsForScopes(scopes);
385 // we filter to only the selectors that matches the hostEl
386 var activeSelectors = selectorSet.filter(function (selector) { return matches(hostEl, selector.selector); });
387 // sort selectors by specifity
388 return sortSelectors(activeSelectors);
389}
390function getScopesForElement(hostTemplateMap, node) {
391 var scopes = [];
392 while (node) {
393 var scope = hostTemplateMap.get(node);
394 if (scope) {
395 scopes.push(scope);
396 }
397 node = node.parentElement;
398 }
399 return scopes;
400}
401function getSelectorsForScopes(scopes) {
402 var selectors = [];
403 scopes.forEach(function (scope) {
404 selectors.push.apply(selectors, scope.selectors);
405 });
406 return selectors;
407}
408function sortSelectors(selectors) {
409 selectors.sort(function (a, b) {
410 if (a.specificity === b.specificity) {
411 return a.nu - b.nu;
412 }
413 return a.specificity - b.specificity;
414 });
415 return selectors;
416}
417function matches(el, selector) {
418 return el.matches(selector);
419}
420function parseCSS(original) {
421 var ast = parse(original);
422 var template = compileTemplate(original);
423 var selectors = getSelectors(ast);
424 return {
425 original: original,
426 template: template,
427 selectors: selectors,
428 isDynamic: template.length > 1
429 };
430}
431function addGlobalStyle(globalScopes, styleEl) {
432 var css = parseCSS(styleEl.innerHTML);
433 css.styleEl = styleEl;
434 globalScopes.push(css);
435}
436function updateGlobalScopes(scopes) {
437 var selectors = getSelectorsForScopes(scopes);
438 var props = resolveValues(selectors);
439 scopes.forEach(function (scope) {
440 if (scope.isDynamic) {
441 scope.styleEl.innerHTML = executeTemplate(scope.template, props);
442 }
443 });
444}
445function reScope(scope, cssScopeId) {
446 var template = scope.template.map(function (segment) {
447 return (typeof segment === 'string')
448 ? replaceScope(segment, scope.cssScopeId, cssScopeId)
449 : segment;
450 });
451 var selectors = scope.selectors.map(function (sel) {
452 return Object.assign({}, sel, { selector: replaceScope(sel.selector, scope.cssScopeId, cssScopeId) });
453 });
454 return Object.assign({}, scope, { template: template,
455 selectors: selectors,
456 cssScopeId: cssScopeId });
457}
458function replaceScope(original, oldScopeId, newScopeId) {
459 original = replaceAll(original, "\\." + oldScopeId, "." + newScopeId);
460 return original;
461}
462function replaceAll(input, find, replace) {
463 return input.replace(new RegExp(find, 'g'), replace);
464}
465function loadDocument(doc, globalScopes) {
466 return loadDocumentLinks(doc, globalScopes).then(function () {
467 loadDocumentStyles(doc, globalScopes);
468 });
469}
470function loadDocumentLinks(doc, globalScopes) {
471 var promises = [];
472 var linkElms = doc.querySelectorAll('link[rel="stylesheet"][href]');
473 for (var i = 0; i < linkElms.length; i++) {
474 promises.push(addGlobalLink(doc, globalScopes, linkElms[i]));
475 }
476 return Promise.all(promises);
477}
478function loadDocumentStyles(doc, globalScopes) {
479 var styleElms = doc.querySelectorAll('style');
480 for (var i = 0; i < styleElms.length; i++) {
481 addGlobalStyle(globalScopes, styleElms[i]);
482 }
483}
484function addGlobalLink(doc, globalScopes, linkElm) {
485 var url = linkElm.href;
486 return fetch(url).then(function (rsp) { return rsp.text(); }).then(function (text) {
487 if (hasCssVariables(text) && linkElm.parentNode) {
488 if (hasRelativeUrls(text)) {
489 text = fixRelativeUrls(text, url);
490 }
491 var styleEl = doc.createElement('style');
492 styleEl.innerHTML = text;
493 addGlobalStyle(globalScopes, styleEl);
494 linkElm.parentNode.insertBefore(styleEl, linkElm);
495 linkElm.remove();
496 }
497 }).catch(function (err) {
498 console.error(err);
499 });
500}
501// This regexp tries to determine when a variable is declared, for example:
502//
503// .my-el { --highlight-color: green; }
504//
505// but we don't want to trigger when a classname uses "--" or a pseudo-class is
506// used. We assume that the only characters that can preceed a variable
507// declaration are "{", from an opening block, ";" from a preceeding rule, or a
508// space. This prevents the regexp from matching a word in a selector, since
509// they would need to start with a "." or "#". (We assume element names don't
510// start with "--").
511var CSS_VARIABLE_REGEXP = /[\s;{]--[-a-zA-Z0-9]+\s*:/m;
512function hasCssVariables(css) {
513 return css.indexOf('var(') > -1 || CSS_VARIABLE_REGEXP.test(css);
514}
515// This regexp find all url() usages with relative urls
516var CSS_URL_REGEXP = /url[\s]*\([\s]*['"]?(?![http|/])([^\'\"\)]*)[\s]*['"]?\)[\s]*/gim;
517function hasRelativeUrls(css) {
518 CSS_URL_REGEXP.lastIndex = 0;
519 return CSS_URL_REGEXP.test(css);
520}
521function fixRelativeUrls(css, originalUrl) {
522 // get the basepath from the original import url
523 var basePath = originalUrl.replace(/[^/]*$/, '');
524 // replace the relative url, with the new relative url
525 return css.replace(CSS_URL_REGEXP, function (fullMatch, url) {
526 // rhe new relative path is the base path + uri
527 // TODO: normalize relative URL
528 var relativeUrl = basePath + url;
529 return fullMatch.replace(url, relativeUrl);
530 });
531}
532var CustomStyle = /** @class */ (function () {
533 function CustomStyle(win, doc) {
534 this.win = win;
535 this.doc = doc;
536 this.count = 0;
537 this.hostStyleMap = new WeakMap();
538 this.hostScopeMap = new WeakMap();
539 this.globalScopes = [];
540 this.scopesMap = new Map();
541 }
542 CustomStyle.prototype.initShim = function () {
543 var _this = this;
544 return new Promise(function (resolve) {
545 _this.win.requestAnimationFrame(function () {
546 loadDocument(_this.doc, _this.globalScopes).then(function () { return resolve(); });
547 });
548 });
549 };
550 CustomStyle.prototype.addLink = function (linkEl) {
551 var _this = this;
552 return addGlobalLink(this.doc, this.globalScopes, linkEl).then(function () {
553 _this.updateGlobal();
554 });
555 };
556 CustomStyle.prototype.addGlobalStyle = function (styleEl) {
557 addGlobalStyle(this.globalScopes, styleEl);
558 this.updateGlobal();
559 };
560 CustomStyle.prototype.createHostStyle = function (hostEl, cssScopeId, cssText) {
561 if (this.hostScopeMap.has(hostEl)) {
562 throw new Error('host style already created');
563 }
564 var baseScope = this.registerHostTemplate(cssText, cssScopeId);
565 var isDynamicScoped = !!(baseScope.isDynamic && baseScope.cssScopeId);
566 var needStyleEl = isDynamicScoped || !baseScope.styleEl;
567 var styleEl = this.doc.createElement('style');
568 if (!needStyleEl) {
569 styleEl.innerHTML = cssText;
570 }
571 else {
572 if (isDynamicScoped) {
573 styleEl['s-sc'] = cssScopeId = baseScope.cssScopeId + "-" + this.count;
574 styleEl.innerHTML = '/*needs update*/';
575 this.hostStyleMap.set(hostEl, styleEl);
576 this.hostScopeMap.set(hostEl, reScope(baseScope, cssScopeId));
577 this.count++;
578 }
579 else {
580 baseScope.styleEl = styleEl;
581 if (!baseScope.isDynamic) {
582 styleEl.innerHTML = executeTemplate(baseScope.template, {});
583 }
584 this.globalScopes.push(baseScope);
585 this.updateGlobal();
586 this.hostScopeMap.set(hostEl, baseScope);
587 }
588 }
589 return styleEl;
590 };
591 CustomStyle.prototype.removeHost = function (hostEl) {
592 var css = this.hostStyleMap.get(hostEl);
593 if (css) {
594 css.remove();
595 }
596 this.hostStyleMap.delete(hostEl);
597 this.hostScopeMap.delete(hostEl);
598 };
599 CustomStyle.prototype.updateHost = function (hostEl) {
600 var scope = this.hostScopeMap.get(hostEl);
601 if (scope && scope.isDynamic && scope.cssScopeId) {
602 var styleEl = this.hostStyleMap.get(hostEl);
603 if (styleEl) {
604 var selectors = getActiveSelectors(hostEl, this.hostScopeMap, this.globalScopes);
605 var props = resolveValues(selectors);
606 styleEl.innerHTML = executeTemplate(scope.template, props);
607 }
608 }
609 };
610 CustomStyle.prototype.updateGlobal = function () {
611 updateGlobalScopes(this.globalScopes);
612 };
613 CustomStyle.prototype.registerHostTemplate = function (cssText, scopeId) {
614 var scope = this.scopesMap.get(scopeId);
615 if (!scope) {
616 scope = parseCSS(cssText);
617 scope.cssScopeId = scopeId;
618 this.scopesMap.set(scopeId, scope);
619 }
620 return scope;
621 };
622 return CustomStyle;
623}());
624var win = window;
625function needsShim() {
626 return !(win.CSS && win.CSS.supports && win.CSS.supports('color', 'var(--c)'));
627}
628if (!win.__stencil_cssshim && needsShim()) {
629 win.__stencil_cssshim = new CustomStyle(win, document);
630}