| 
									
										
										
										
											2016-06-23 09:47:54 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2015-06-15 15:57:42 +02:00
										 |  |  |  * This file is a port of shadowCSS from webcomponents.js to TypeScript. | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Please make sure to keep to edits in sync with the source file. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |  * Source: | 
					
						
							|  |  |  |  * https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
 | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |  * | 
					
						
							|  |  |  |  * The original file level comment is reproduced below | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  |   This is a limited shim for ShadowDOM css styling. | 
					
						
							|  |  |  |   https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   The intention here is to support only the styling features which can be | 
					
						
							|  |  |  |   relatively simply implemented. The goal is to allow users to avoid the | 
					
						
							|  |  |  |   most obvious pitfalls and do so without compromising performance significantly. | 
					
						
							|  |  |  |   For ShadowDOM styling that's not covered here, a set of best practices | 
					
						
							|  |  |  |   can be provided that should allow users to accomplish more complex styling. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   The following is a list of specific ShadowDOM styling features and a brief | 
					
						
							|  |  |  |   discussion of the approach used to shim. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Shimmed features: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host | 
					
						
							|  |  |  |   element using the :host rule. To shim this feature, the :host styles are | 
					
						
							|  |  |  |   reformatted and prefixed with a given scope name and promoted to a | 
					
						
							|  |  |  |   document level stylesheet. | 
					
						
							|  |  |  |   For example, given a scope name of .foo, a rule like this: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :host { | 
					
						
							|  |  |  |         background: red; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   becomes: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     .foo { | 
					
						
							|  |  |  |       background: red; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   * encapsulation: Styles defined within ShadowDOM, apply only to | 
					
						
							| 
									
										
										
										
											2015-12-16 15:47:48 +08:00
										 |  |  |   dom inside the ShadowDOM. Polymer uses one of two techniques to implement | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   this feature. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   By default, rules are prefixed with the host element tag name | 
					
						
							|  |  |  |   as a descendant selector. This ensures styling does not leak out of the 'top' | 
					
						
							|  |  |  |   of the element's ShadowDOM. For example, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   div { | 
					
						
							|  |  |  |       font-weight: bold; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   becomes: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   x-foo div { | 
					
						
							|  |  |  |       font-weight: bold; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   becomes: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then | 
					
						
							|  |  |  |   selectors are scoped by adding an attribute selector suffix to each | 
					
						
							|  |  |  |   simple selector that contains the host element tag name. Each element | 
					
						
							|  |  |  |   in the element's ShadowDOM template is also given the scope attribute. | 
					
						
							|  |  |  |   Thus, these rules match only elements that have the scope attribute. | 
					
						
							|  |  |  |   For example, given a scope name of x-foo, a rule like this: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     div { | 
					
						
							|  |  |  |       font-weight: bold; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   becomes: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     div[x-foo] { | 
					
						
							|  |  |  |       font-weight: bold; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Note that elements that are dynamically added to a scope must have the scope | 
					
						
							|  |  |  |   selector added to them manually. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   * upper/lower bound encapsulation: Styles which are defined outside a | 
					
						
							|  |  |  |   shadowRoot should not cross the ShadowDOM boundary and should not apply | 
					
						
							|  |  |  |   inside a shadowRoot. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   This styling behavior is not emulated. Some possible ways to do this that | 
					
						
							|  |  |  |   were rejected due to complexity and/or performance concerns include: (1) reset | 
					
						
							|  |  |  |   every possible property for every possible selector for a given scope name; | 
					
						
							|  |  |  |   (2) re-implement css in javascript. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   As an alternative, users should make sure to use selectors | 
					
						
							|  |  |  |   specific to the scope in which they are working. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   * ::distributed: This behavior is not emulated. It's often not necessary | 
					
						
							|  |  |  |   to style the contents of a specific insertion point and instead, descendants | 
					
						
							|  |  |  |   of the host element can be styled selectively. Users can also create an | 
					
						
							|  |  |  |   extra node around an insertion point and style that node's contents | 
					
						
							|  |  |  |   via descendent selectors. For example, with a shadowRoot like this: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     <style> | 
					
						
							|  |  |  |       ::content(div) { | 
					
						
							|  |  |  |         background: red; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     </style> | 
					
						
							|  |  |  |     <content></content> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   could become: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     <style> | 
					
						
							|  |  |  |       / *@polyfill .content-container div * / | 
					
						
							|  |  |  |       ::content(div) { | 
					
						
							|  |  |  |         background: red; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     </style> | 
					
						
							|  |  |  |     <div class="content-container"> | 
					
						
							|  |  |  |       <content></content> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Note the use of @polyfill in the comment above a ShadowDOM specific style | 
					
						
							|  |  |  |   declaration. This is a directive to the styling shim to use the selector | 
					
						
							|  |  |  |   in comments in lieu of the next selector when running under polyfill. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class ShadowCss { | 
					
						
							| 
									
										
										
										
											2015-06-12 23:11:11 +02:00
										 |  |  |   strictStyling: boolean = true; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-12 23:11:11 +02:00
										 |  |  |   constructor() {} | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   * Shim some cssText with the given selector. Returns cssText that can | 
					
						
							|  |  |  |   * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css). | 
					
						
							| 
									
										
										
										
											2015-02-18 19:14:19 +01:00
										 |  |  |   * | 
					
						
							|  |  |  |   * When strictStyling is true: | 
					
						
							|  |  |  |   * - selector is the attribute added to all elements inside the host, | 
					
						
							|  |  |  |   * - hostSelector is the attribute added to the host itself. | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-02-18 19:14:19 +01:00
										 |  |  |   shimCssText(cssText: string, selector: string, hostSelector: string = ''): string { | 
					
						
							| 
									
										
										
										
											2016-08-12 16:39:43 +08:00
										 |  |  |     const sourceMappingUrl: string = extractSourceMappingUrl(cssText); | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |     cssText = stripComments(cssText); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     cssText = this._insertDirectives(cssText); | 
					
						
							| 
									
										
										
										
											2016-08-12 16:39:43 +08:00
										 |  |  |     return this._scopeCssText(cssText, selector, hostSelector) + sourceMappingUrl; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _insertDirectives(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     cssText = this._insertPolyfillDirectivesInCssText(cssText); | 
					
						
							|  |  |  |     return this._insertPolyfillRulesInCssText(cssText); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * Process styles to convert native ShadowDOM rules that will trip | 
					
						
							|  |  |  |    * up the css parser; we rely on decorating the stylesheet with inert rules. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * For example, we convert this rule: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * polyfill-next-selector { content: ':host menu-item'; } | 
					
						
							|  |  |  |    * ::content menu-item { | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to this: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * scopeName menu-item { | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |   **/ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _insertPolyfillDirectivesInCssText(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     // Difference with webcomponents.js: does not handle comments
 | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     return cssText.replace( | 
					
						
							| 
									
										
										
										
											2016-09-30 13:20:48 -07:00
										 |  |  |         _cssContentNextSelectorRe, function(...m: string[]) { return m[2] + '{'; }); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * Process styles to add rules which will only apply under the polyfill | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * For example, we convert this rule: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * polyfill-rule { | 
					
						
							|  |  |  |    *   content: ':host menu-item'; | 
					
						
							|  |  |  |    * ... | 
					
						
							|  |  |  |    * } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to this: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * scopeName menu-item {...} | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |   **/ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _insertPolyfillRulesInCssText(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     // Difference with webcomponents.js: does not handle comments
 | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     return cssText.replace(_cssContentRuleRe, (...m: string[]) => { | 
					
						
							|  |  |  |       const rule = m[0].replace(m[1], '').replace(m[2], ''); | 
					
						
							| 
									
										
										
										
											2016-09-30 13:20:48 -07:00
										 |  |  |       return m[4] + rule; | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Ensure styles are scoped. Pseudo-scoping takes a rule like: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    *  .foo {... } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    *  and converts this to | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    *  scopeName .foo { ... } | 
					
						
							|  |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     const unscopedRules = this._extractUnscopedRulesFromCssText(cssText); | 
					
						
							|  |  |  |     // replace :host and :host-context -shadowcsshost and -shadowcsshost respectively
 | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     cssText = this._insertPolyfillHostInCssText(cssText); | 
					
						
							|  |  |  |     cssText = this._convertColonHost(cssText); | 
					
						
							|  |  |  |     cssText = this._convertColonHostContext(cssText); | 
					
						
							|  |  |  |     cssText = this._convertShadowDOMSelectors(cssText); | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     if (scopeSelector) { | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  |       cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     cssText = cssText + '\n' + unscopedRules; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     return cssText.trim(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * Process styles to add rules which will only apply under the polyfill | 
					
						
							|  |  |  |    * and do not process via CSSOM. (CSSOM is destructive to rules on rare | 
					
						
							|  |  |  |    * occasions, e.g. -webkit-calc on Safari.) | 
					
						
							|  |  |  |    * For example, we convert this rule: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @polyfill-unscoped-rule { | 
					
						
							|  |  |  |    *   content: 'menu-item'; | 
					
						
							|  |  |  |    * ... } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to this: | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * menu-item {...} | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |   **/ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _extractUnscopedRulesFromCssText(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     // Difference with webcomponents.js: does not handle comments
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     let r = ''; | 
					
						
							|  |  |  |     let m: RegExpExecArray; | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     _cssContentUnscopedRuleRe.lastIndex = 0; | 
					
						
							|  |  |  |     while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) { | 
					
						
							| 
									
										
										
										
											2016-09-30 13:20:48 -07:00
										 |  |  |       const rule = m[0].replace(m[2], '').replace(m[1], m[4]); | 
					
						
							| 
									
										
										
										
											2015-07-10 10:38:24 -07:00
										 |  |  |       r += rule + '\n\n'; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     return r; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * convert a rule like :host(.foo) > .bar { } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to | 
					
						
							|  |  |  |    * | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  |    * .foo<scopeName> > .bar | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _convertColonHost(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |     return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * convert a rule like :host-context(.foo) > .bar { } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to | 
					
						
							|  |  |  |    * | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  |    * .foo<scopeName> > .bar, .foo scopeName > .bar { } | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |    * | 
					
						
							|  |  |  |    * and | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * :host-context(.foo:host) .bar { ... } | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * to | 
					
						
							|  |  |  |    * | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  |    * .foo<scopeName> .bar { ... } | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _convertColonHostContext(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |     return this._convertColonRule( | 
					
						
							|  |  |  |         cssText, _cssColonHostContextRe, this._colonHostContextPartReplacer); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string { | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     // m[1] = :host(-context), m[2] = contents of (), m[3] rest of rule
 | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     return cssText.replace(regExp, function(...m: string[]) { | 
					
						
							|  |  |  |       if (m[2]) { | 
					
						
							|  |  |  |         const parts = m[2].split(','); | 
					
						
							|  |  |  |         const r: string[] = []; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |         for (let i = 0; i < parts.length; i++) { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |           const p = parts[i].trim(); | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |           if (!p) break; | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |           r.push(partReplacer(_polyfillHostNoCombinator, p, m[3])); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return r.join(','); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         return _polyfillHostNoCombinator + m[3]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _colonHostContextPartReplacer(host: string, part: string, suffix: string): string { | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     if (part.indexOf(_polyfillHost) > -1) { | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |       return this._colonHostPartReplacer(host, part, suffix); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       return host + part + suffix + ', ' + part + ' ' + host + suffix; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _colonHostPartReplacer(host: string, part: string, suffix: string): string { | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     return host + part.replace(_polyfillHost, '') + suffix; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |    * Convert combinators like ::shadow and pseudo-elements like ::content | 
					
						
							|  |  |  |    * by replacing with space. | 
					
						
							|  |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _convertShadowDOMSelectors(cssText: string): string { | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // change a selector like 'div' to 'name div'
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _scopeSelectors(cssText: string, scopeSelector: string, hostSelector: string): string { | 
					
						
							|  |  |  |     return processRules(cssText, (rule: CssRule) => { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |       let selector = rule.selector; | 
					
						
							|  |  |  |       let content = rule.content; | 
					
						
							| 
									
										
										
										
											2016-09-30 16:26:24 -07:00
										 |  |  |       if (rule.selector[0] != '@') { | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |         selector = | 
					
						
							|  |  |  |             this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling); | 
					
						
							| 
									
										
										
										
											2016-09-30 16:26:24 -07:00
										 |  |  |       } else if ( | 
					
						
							|  |  |  |           rule.selector.startsWith('@media') || rule.selector.startsWith('@supports') || | 
					
						
							|  |  |  |           rule.selector.startsWith('@page') || rule.selector.startsWith('@document')) { | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |         content = this._scopeSelectors(rule.content, scopeSelector, hostSelector); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |       return new CssRule(selector, content); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   private _scopeSelector( | 
					
						
							|  |  |  |       selector: string, scopeSelector: string, hostSelector: string, strict: boolean): string { | 
					
						
							| 
									
										
										
										
											2016-08-13 07:08:37 +08:00
										 |  |  |     return selector.split(',') | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |         .map(part => part.trim().split(_shadowDeepSelectors)) | 
					
						
							| 
									
										
										
										
											2016-08-13 07:08:37 +08:00
										 |  |  |         .map((deepParts) => { | 
					
						
							|  |  |  |           const [shallowPart, ...otherParts] = deepParts; | 
					
						
							|  |  |  |           const applyScope = (shallowPart: string) => { | 
					
						
							|  |  |  |             if (this._selectorNeedsScoping(shallowPart, scopeSelector)) { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |               return strict ? | 
					
						
							|  |  |  |                   this._applyStrictSelectorScope(shallowPart, scopeSelector, hostSelector) : | 
					
						
							| 
									
										
										
										
											2016-08-13 07:08:37 +08:00
										 |  |  |                   this._applySelectorScope(shallowPart, scopeSelector, hostSelector); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               return shallowPart; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           return [applyScope(shallowPart), ...otherParts].join(' '); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .join(', '); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _selectorNeedsScoping(selector: string, scopeSelector: string): boolean { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     const re = this._makeScopeMatcher(scopeSelector); | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     return !re.test(selector); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _makeScopeMatcher(scopeSelector: string): RegExp { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     const lre = /\[/g; | 
					
						
							|  |  |  |     const rre = /\]/g; | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |     scopeSelector = scopeSelector.replace(lre, '\\[').replace(rre, '\\]'); | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm'); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   private _applySelectorScope(selector: string, scopeSelector: string, hostSelector: string): | 
					
						
							|  |  |  |       string { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |     // Difference from webcomponents.js: scopeSelector could not be an array
 | 
					
						
							| 
									
										
										
										
											2015-02-18 19:14:19 +01:00
										 |  |  |     return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // scope via name and [is=name]
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   private _applySimpleSelectorScope(selector: string, scopeSelector: string, hostSelector: string): | 
					
						
							|  |  |  |       string { | 
					
						
							| 
									
										
										
										
											2016-08-29 17:18:55 +02:00
										 |  |  |     // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
 | 
					
						
							|  |  |  |     _polyfillHostRe.lastIndex = 0; | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     if (_polyfillHostRe.test(selector)) { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |       const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector; | 
					
						
							| 
									
										
										
										
											2016-10-10 09:15:15 -07:00
										 |  |  |       return selector | 
					
						
							|  |  |  |           .replace( | 
					
						
							|  |  |  |               _polyfillHostNoCombinatorRe, | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  |               (hnc, selector) => { | 
					
						
							|  |  |  |                 return selector.replace( | 
					
						
							|  |  |  |                     /([^:]*)(:*)(.*)/, | 
					
						
							|  |  |  |                     (_: string, before: string, colon: string, after: string) => { | 
					
						
							|  |  |  |                       return before + replaceBy + colon + after; | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |               }) | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |           .replace(_polyfillHostRe, replaceBy + ' '); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return scopeSelector + ' ' + selector; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // return a selector with [name] suffix on each simple selector
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]  /** @internal */
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   private _applyStrictSelectorScope(selector: string, scopeSelector: string, hostSelector: string): | 
					
						
							|  |  |  |       string { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     const isRe = /\[is=([^\]]*)\]/g; | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |     scopeSelector = scopeSelector.replace(isRe, (_: string, ...parts: string[]) => parts[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     const attrName = '[' + scopeSelector + ']'; | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const _scopeSelectorPart = (p: string) => { | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |       let scopedP = p.trim(); | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |       if (!scopedP) { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |         return ''; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (p.indexOf(_polyfillHostNoCombinator) > -1) { | 
					
						
							|  |  |  |         scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         // remove :host since it should be unnecessary
 | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  |         const t = p.replace(_polyfillHostRe, ''); | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |         if (t.length > 0) { | 
					
						
							|  |  |  |           const matches = t.match(/([^:]*)(:*)(.*)/); | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  |           if (matches) { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |             scopedP = matches[1] + attrName + matches[2] + matches[3]; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return scopedP; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  |     const safeContent = new SafeSelector(selector); | 
					
						
							|  |  |  |     selector = safeContent.content(); | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     let scopedSelector = ''; | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |     let startIndex = 0; | 
					
						
							|  |  |  |     let res: RegExpExecArray; | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     const sep = /( |>|\+|~(?!=))\s*/g; | 
					
						
							|  |  |  |     const scopeAfter = selector.indexOf(_polyfillHostNoCombinator); | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     while ((res = sep.exec(selector)) !== null) { | 
					
						
							|  |  |  |       const separator = res[1]; | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |       const part = selector.slice(startIndex, res.index).trim(); | 
					
						
							|  |  |  |       // if a selector appears before :host-context it should not be shimmed as it
 | 
					
						
							|  |  |  |       // matches on ancestor elements and not on elements in the host's shadow
 | 
					
						
							|  |  |  |       const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; | 
					
						
							|  |  |  |       scopedSelector += `${scopedPart} ${separator} `; | 
					
						
							|  |  |  |       startIndex = sep.lastIndex; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-04 15:40:31 -07:00
										 |  |  |     scopedSelector += _scopeSelectorPart(selector.substring(startIndex)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // replace the placeholders with their original values
 | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  |     return safeContent.restore(scopedSelector); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   private _insertPolyfillHostInCssText(selector: string): string { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |     return selector.replace(_colonHostContextRe, _polyfillHostContext) | 
					
						
							|  |  |  |         .replace(_colonHostRe, _polyfillHost); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-11-07 13:56:04 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class SafeSelector { | 
					
						
							|  |  |  |   private placeholders: string[] = []; | 
					
						
							|  |  |  |   private index = 0; | 
					
						
							|  |  |  |   private _content: string; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(selector: string) { | 
					
						
							|  |  |  |     // 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-${this.index}__`; | 
					
						
							|  |  |  |       this.placeholders.push(keep); | 
					
						
							|  |  |  |       this.index++; | 
					
						
							|  |  |  |       return replaceBy; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
 | 
					
						
							|  |  |  |     // WS and "+" would otherwise be interpreted as selector separators.
 | 
					
						
							|  |  |  |     this._content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => { | 
					
						
							|  |  |  |       const replaceBy = `__ph-${this.index}__`; | 
					
						
							|  |  |  |       this.placeholders.push(exp); | 
					
						
							|  |  |  |       this.index++; | 
					
						
							|  |  |  |       return pseudo + replaceBy; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   restore(content: string): string { | 
					
						
							|  |  |  |     return content.replace(/__ph-(\d+)__/g, (ph, index) => this.placeholders[+index]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   content(): string { return this._content; } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _cssContentNextSelectorRe = | 
					
						
							| 
									
										
										
										
											2016-09-30 13:20:48 -07:00
										 |  |  |     /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim; | 
					
						
							|  |  |  | const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _cssContentUnscopedRuleRe = | 
					
						
							| 
									
										
										
										
											2016-09-30 13:20:48 -07:00
										 |  |  |     /(polyfill-unscoped-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _polyfillHost = '-shadowcsshost'; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | // note: :host-context pre-processed to -shadowcsshostcontext.
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _polyfillHostContext = '-shadowcsscontext'; | 
					
						
							|  |  |  | const _parenSuffix = ')(?:\\((' + | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |     '(?:\\([^)(]*\\)|[^)(]*)+?' + | 
					
						
							|  |  |  |     ')\\))?([^,{]*)'; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _cssColonHostRe = new RegExp('(' + _polyfillHost + _parenSuffix, 'gim'); | 
					
						
							|  |  |  | const _cssColonHostContextRe = new RegExp('(' + _polyfillHostContext + _parenSuffix, 'gim'); | 
					
						
							|  |  |  | const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator'; | 
					
						
							| 
									
										
										
										
											2016-09-30 11:42:46 -07:00
										 |  |  | const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _shadowDOMSelectorsRe = [ | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   /::shadow/g, | 
					
						
							|  |  |  |   /::content/g, | 
					
						
							| 
									
										
										
										
											2015-03-20 08:52:12 +01:00
										 |  |  |   // Deprecated selectors
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   /\/shadow-deep\//g, | 
					
						
							|  |  |  |   /\/shadow\//g, | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | ]; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)/g; | 
					
						
							|  |  |  | const _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$'; | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | const _polyfillHostRe = /-shadowcsshost/gim; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _colonHostRe = /:host/gim; | 
					
						
							|  |  |  | const _colonHostContextRe = /:host-context/gim; | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _commentRe = /\/\*\s*[\s\S]*?\*\//g; | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | function stripComments(input: string): string { | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |   return input.replace(_commentRe, ''); | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-13 21:59:11 -07:00
										 |  |  | // all comments except inline source mapping
 | 
					
						
							| 
									
										
										
										
											2016-09-07 16:48:10 -07:00
										 |  |  | const _sourceMappingUrlRe = /\/\*\s*#\s*sourceMappingURL=[\s\S]+?\*\//; | 
					
						
							| 
									
										
										
										
											2016-08-12 16:39:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | function extractSourceMappingUrl(input: string): string { | 
					
						
							| 
									
										
										
										
											2016-08-12 16:39:43 +08:00
										 |  |  |   const matcher = input.match(_sourceMappingUrlRe); | 
					
						
							| 
									
										
										
										
											2016-09-07 16:48:10 -07:00
										 |  |  |   return matcher ? matcher[0] : ''; | 
					
						
							| 
									
										
										
										
											2016-08-12 16:39:43 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  | const _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g; | 
					
						
							|  |  |  | const _curlyRe = /([{}])/g; | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | const OPEN_CURLY = '{'; | 
					
						
							|  |  |  | const CLOSE_CURLY = '}'; | 
					
						
							|  |  |  | const BLOCK_PLACEHOLDER = '%BLOCK%'; | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | export class CssRule { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   constructor(public selector: string, public content: string) {} | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-30 13:06:14 -07:00
										 |  |  | export function processRules(input: string, ruleCallback: (rule: CssRule) => CssRule): string { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |   const inputWithEscapedBlocks = escapeBlocks(input); | 
					
						
							|  |  |  |   let nextBlockIndex = 0; | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |   return inputWithEscapedBlocks.escapedString.replace(_ruleRe, function(...m: string[]) { | 
					
						
							|  |  |  |     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 rule = ruleCallback(new CssRule(selector, content)); | 
					
						
							|  |  |  |     return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`; | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StringWithEscapedBlocks { | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   constructor(public escapedString: string, public blocks: string[]) {} | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  | function escapeBlocks(input: string): StringWithEscapedBlocks { | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |   const inputParts = input.split(_curlyRe); | 
					
						
							|  |  |  |   const resultParts: string[] = []; | 
					
						
							|  |  |  |   const escapedBlocks: string[] = []; | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |   let bracketCount = 0; | 
					
						
							| 
									
										
										
										
											2016-09-26 09:31:45 -07:00
										 |  |  |   let currentBlockParts: string[] = []; | 
					
						
							| 
									
										
										
										
											2016-08-26 16:11:57 -07:00
										 |  |  |   for (let partIndex = 0; partIndex < inputParts.length; partIndex++) { | 
					
						
							| 
									
										
										
										
											2016-08-13 06:20:58 +08:00
										 |  |  |     const part = inputParts[partIndex]; | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |     if (part == CLOSE_CURLY) { | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  |       bracketCount--; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |     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++; | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   if (currentBlockParts.length > 0) { | 
					
						
							|  |  |  |     escapedBlocks.push(currentBlockParts.join('')); | 
					
						
							|  |  |  |     resultParts.push(BLOCK_PLACEHOLDER); | 
					
						
							| 
									
										
										
										
											2015-10-29 10:57:54 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-10-30 15:09:04 -07:00
										 |  |  |   return new StringWithEscapedBlocks(resultParts.join(''), escapedBlocks); | 
					
						
							| 
									
										
										
										
											2015-02-18 10:06:31 +01:00
										 |  |  | } |