| 
									
										
										
										
											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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-07 15:40:00 -07:00
										 |  |  | import {getHtmlTagDefinition} from './ml_parser/html_tags'; | 
					
						
							| 
									
										
										
										
											2014-10-02 20:39:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  | const _SELECTOR_REGEXP = new RegExp( | 
					
						
							| 
									
										
										
										
											2017-01-25 13:17:18 -08:00
										 |  |  |     '(\\:not\\()|' +           //":not("
 | 
					
						
							|  |  |  |         '([-\\w]+)|' +         // "tag"
 | 
					
						
							|  |  |  |         '(?:\\.([-\\w]+))|' +  // ".class"
 | 
					
						
							| 
									
										
										
										
											2017-01-25 10:27:18 -08:00
										 |  |  |         // "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
 | 
					
						
							| 
									
										
										
										
											2017-01-24 14:47:51 -08:00
										 |  |  |         '(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' +  // "[name]", "[name=value]"
 | 
					
						
							| 
									
										
										
										
											2016-12-27 15:23:49 -08:00
										 |  |  |         '(\\))|' +                                 // ")"
 | 
					
						
							|  |  |  |         '(\\s*,\\s*)',                             // ","
 | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     'g'); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * A css selector contains an element name, | 
					
						
							|  |  |  |  * css classes and attribute/value pairs with the purpose | 
					
						
							|  |  |  |  * of selecting subsets out of them. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class CssSelector { | 
					
						
							| 
									
										
										
										
											2015-06-12 23:11:11 +02:00
										 |  |  |   element: string = null; | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   classNames: string[] = []; | 
					
						
							|  |  |  |   attrs: string[] = []; | 
					
						
							|  |  |  |   notSelectors: CssSelector[] = []; | 
					
						
							| 
									
										
										
										
											2015-06-12 23:11:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   static parse(selector: string): CssSelector[] { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     const results: CssSelector[] = []; | 
					
						
							|  |  |  |     const _addResult = (res: CssSelector[], cssSel: CssSelector) => { | 
					
						
							|  |  |  |       if (cssSel.notSelectors.length > 0 && !cssSel.element && cssSel.classNames.length == 0 && | 
					
						
							|  |  |  |           cssSel.attrs.length == 0) { | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |         cssSel.element = '*'; | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |       res.push(cssSel); | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let cssSelector = new CssSelector(); | 
					
						
							|  |  |  |     let match: string[]; | 
					
						
							|  |  |  |     let current = cssSelector; | 
					
						
							|  |  |  |     let inNot = false; | 
					
						
							| 
									
										
										
										
											2016-08-05 09:50:49 -07:00
										 |  |  |     _SELECTOR_REGEXP.lastIndex = 0; | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     while (match = _SELECTOR_REGEXP.exec(selector)) { | 
					
						
							|  |  |  |       if (match[1]) { | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |         if (inNot) { | 
					
						
							| 
									
										
										
										
											2016-08-25 00:50:16 -07:00
										 |  |  |           throw new Error('Nesting :not is not allowed in a selector'); | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |         inNot = true; | 
					
						
							|  |  |  |         current = new CssSelector(); | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |         cssSelector.notSelectors.push(current); | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       if (match[2]) { | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |         current.setElement(match[2]); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       if (match[3]) { | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |         current.addClassName(match[3]); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       if (match[4]) { | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |         current.addAttribute(match[4], match[5]); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       if (match[6]) { | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |         inNot = false; | 
					
						
							|  |  |  |         current = cssSelector; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       if (match[7]) { | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |         if (inNot) { | 
					
						
							| 
									
										
										
										
											2016-08-25 00:50:16 -07:00
										 |  |  |           throw new Error('Multiple selectors in :not are not supported'); | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |         _addResult(results, cssSelector); | 
					
						
							|  |  |  |         cssSelector = current = new CssSelector(); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |     _addResult(results, cssSelector); | 
					
						
							|  |  |  |     return results; | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |   isElementSelector(): boolean { | 
					
						
							| 
									
										
										
										
											2016-08-23 10:52:40 -07:00
										 |  |  |     return this.hasElementSelector() && this.classNames.length == 0 && this.attrs.length == 0 && | 
					
						
							|  |  |  |         this.notSelectors.length === 0; | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-04-27 15:14:30 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-23 10:52:40 -07:00
										 |  |  |   hasElementSelector(): boolean { return !!this.element; } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-23 16:02:19 -08:00
										 |  |  |   setElement(element: string = null) { this.element = element; } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-28 11:38:40 -07:00
										 |  |  |   /** Gets a template string for an element that matches the selector. */ | 
					
						
							|  |  |  |   getMatchingElementTemplate(): string { | 
					
						
							| 
									
										
										
										
											2016-09-07 15:38:44 -07:00
										 |  |  |     const tagName = this.element || 'div'; | 
					
						
							|  |  |  |     const classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : ''; | 
					
						
							| 
									
										
										
										
											2015-07-28 11:38:40 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     let attrs = ''; | 
					
						
							|  |  |  |     for (let i = 0; i < this.attrs.length; i += 2) { | 
					
						
							| 
									
										
										
										
											2016-09-07 15:38:44 -07:00
										 |  |  |       const attrName = this.attrs[i]; | 
					
						
							|  |  |  |       const attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : ''; | 
					
						
							| 
									
										
										
										
											2015-07-28 11:38:40 -07:00
										 |  |  |       attrs += ` ${attrName}${attrValue}`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-07 15:40:00 -07:00
										 |  |  |     return getHtmlTagDefinition(tagName).isVoid ? `<${tagName}${classAttr}${attrs}/>` : | 
					
						
							|  |  |  |                                                   `<${tagName}${classAttr}${attrs}></${tagName}>`; | 
					
						
							| 
									
										
										
										
											2015-07-28 11:38:40 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |   addAttribute(name: string, value: string = '') { | 
					
						
							|  |  |  |     this.attrs.push(name, value && value.toLowerCase() || ''); | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |   addClassName(name: string) { this.classNames.push(name.toLowerCase()); } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |   toString(): string { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let res: string = this.element || ''; | 
					
						
							|  |  |  |     if (this.classNames) { | 
					
						
							|  |  |  |       this.classNames.forEach(klass => res += `.${klass}`); | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (this.attrs) { | 
					
						
							|  |  |  |       for (let i = 0; i < this.attrs.length; i += 2) { | 
					
						
							|  |  |  |         const name = this.attrs[i]; | 
					
						
							|  |  |  |         const value = this.attrs[i + 1]; | 
					
						
							|  |  |  |         res += `[${name}${value ? '=' + value : ''}]`; | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-10-07 09:09:43 -07:00
										 |  |  |     this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`); | 
					
						
							| 
									
										
										
										
											2015-05-21 16:42:19 +02:00
										 |  |  |     return res; | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Reads a list of CssSelectors and allows to calculate which ones | 
					
						
							|  |  |  |  * are contained in a given CssSelector. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class SelectorMatcher { | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     const notMatcher = new SelectorMatcher(); | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |     notMatcher.addSelectables(notSelectors, null); | 
					
						
							| 
									
										
										
										
											2015-05-19 15:05:02 -07:00
										 |  |  |     return notMatcher; | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-05-19 15:05:02 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |   private _elementMap = new Map<string, SelectorContext[]>(); | 
					
						
							|  |  |  |   private _elementPartialMap = new Map<string, SelectorMatcher>(); | 
					
						
							|  |  |  |   private _classMap = new Map<string, SelectorContext[]>(); | 
					
						
							|  |  |  |   private _classPartialMap = new Map<string, SelectorMatcher>(); | 
					
						
							|  |  |  |   private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>(); | 
					
						
							|  |  |  |   private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>(); | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   private _listContexts: SelectorListContext[] = []; | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let listContext: SelectorListContext = null; | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |     if (cssSelectors.length > 1) { | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |       listContext = new SelectorListContext(cssSelectors); | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |       this._listContexts.push(listContext); | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     for (let i = 0; i < cssSelectors.length; i++) { | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |       this._addSelectable(cssSelectors[i], callbackCtxt, listContext); | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2014-10-02 20:39:27 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |    * Add an object that can be found later on by calling `match`. | 
					
						
							|  |  |  |    * @param cssSelector A css selector | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  |    * @param callbackCtxt An opaque object that will be given to the callback of the `match` function | 
					
						
							| 
									
										
										
										
											2014-10-02 20:39:27 -07:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   private _addSelectable( | 
					
						
							|  |  |  |       cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let matcher: SelectorMatcher = this; | 
					
						
							|  |  |  |     const element = cssSelector.element; | 
					
						
							|  |  |  |     const classNames = cssSelector.classNames; | 
					
						
							|  |  |  |     const attrs = cssSelector.attrs; | 
					
						
							|  |  |  |     const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (element) { | 
					
						
							|  |  |  |       const isTerminal = attrs.length === 0 && classNames.length === 0; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |       if (isTerminal) { | 
					
						
							|  |  |  |         this._addTerminal(matcher._elementMap, element, selectable); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         matcher = this._addPartial(matcher._elementPartialMap, element); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (classNames) { | 
					
						
							|  |  |  |       for (let i = 0; i < classNames.length; i++) { | 
					
						
							|  |  |  |         const isTerminal = attrs.length === 0 && i === classNames.length - 1; | 
					
						
							|  |  |  |         const className = classNames[i]; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |         if (isTerminal) { | 
					
						
							|  |  |  |           this._addTerminal(matcher._classMap, className, selectable); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           matcher = this._addPartial(matcher._classPartialMap, className); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (attrs) { | 
					
						
							|  |  |  |       for (let i = 0; i < attrs.length; i += 2) { | 
					
						
							|  |  |  |         const isTerminal = i === attrs.length - 2; | 
					
						
							|  |  |  |         const name = attrs[i]; | 
					
						
							|  |  |  |         const value = attrs[i + 1]; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |         if (isTerminal) { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |           const terminalMap = matcher._attrValueMap; | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |           let terminalValuesMap = terminalMap.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |           if (!terminalValuesMap) { | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |             terminalValuesMap = new Map<string, SelectorContext[]>(); | 
					
						
							|  |  |  |             terminalMap.set(name, terminalValuesMap); | 
					
						
							| 
									
										
										
										
											2015-05-20 09:48:15 -07:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |           this._addTerminal(terminalValuesMap, value, selectable); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |           const partialMap = matcher._attrValuePartialMap; | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |           let partialValuesMap = partialMap.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |           if (!partialValuesMap) { | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |             partialValuesMap = new Map<string, SelectorMatcher>(); | 
					
						
							|  |  |  |             partialMap.set(name, partialValuesMap); | 
					
						
							| 
									
										
										
										
											2015-05-20 09:48:15 -07:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |           matcher = this._addPartial(partialValuesMap, value); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   private _addTerminal( | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |       map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) { | 
					
						
							|  |  |  |     let terminalList = map.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |     if (!terminalList) { | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |       terminalList = []; | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |       map.set(name, terminalList); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-17 11:17:21 -07:00
										 |  |  |     terminalList.push(selectable); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |   private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher { | 
					
						
							|  |  |  |     let matcher = map.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |     if (!matcher) { | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |       matcher = new SelectorMatcher(); | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |       map.set(name, matcher); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     return matcher; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Find the objects that have been added via `addSelectable` | 
					
						
							|  |  |  |    * whose css selector is contained in the given css selector. | 
					
						
							|  |  |  |    * @param cssSelector A css selector | 
					
						
							|  |  |  |    * @param matchedCallback This callback will be called with the object handed into `addSelectable` | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |    * @return boolean true if a match was found | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |   */ | 
					
						
							| 
									
										
										
										
											2015-10-09 09:28:12 -07:00
										 |  |  |   match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let result = false; | 
					
						
							|  |  |  |     const element = cssSelector.element; | 
					
						
							|  |  |  |     const classNames = cssSelector.classNames; | 
					
						
							|  |  |  |     const attrs = cssSelector.attrs; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     for (let i = 0; i < this._listContexts.length; i++) { | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |       this._listContexts[i].alreadyMatched = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |     result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |     result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |         result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (classNames) { | 
					
						
							|  |  |  |       for (let i = 0; i < classNames.length; i++) { | 
					
						
							|  |  |  |         const className = classNames[i]; | 
					
						
							| 
									
										
										
										
											2015-05-18 11:57:20 -07:00
										 |  |  |         result = | 
					
						
							|  |  |  |             this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result; | 
					
						
							|  |  |  |         result = | 
					
						
							|  |  |  |             this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) || | 
					
						
							|  |  |  |             result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (attrs) { | 
					
						
							|  |  |  |       for (let i = 0; i < attrs.length; i += 2) { | 
					
						
							|  |  |  |         const name = attrs[i]; | 
					
						
							|  |  |  |         const value = attrs[i + 1]; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |         const terminalValuesMap = this._attrValueMap.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |         if (value) { | 
					
						
							|  |  |  |           result = | 
					
						
							|  |  |  |               this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |         result = | 
					
						
							|  |  |  |             this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |         const partialValuesMap = this._attrValuePartialMap.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |         if (value) { | 
					
						
							|  |  |  |           result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2015-06-11 11:35:02 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-05-20 09:48:15 -07:00
										 |  |  |         result = | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |             this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |     return result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-09 17:21:25 -07:00
										 |  |  |   /** @internal */ | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   _matchTerminal( | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |       map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector, | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |       matchedCallback: (c: CssSelector, a: any) => void): boolean { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (!map || typeof name !== 'string') { | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-04-17 13:01:07 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-09 21:45:48 +03:00
										 |  |  |     let selectables: SelectorContext[] = map.get(name) || []; | 
					
						
							|  |  |  |     const starSelectables: SelectorContext[] = map.get('*'); | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (starSelectables) { | 
					
						
							| 
									
										
										
										
											2015-08-28 11:29:19 -07:00
										 |  |  |       selectables = selectables.concat(starSelectables); | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-12-09 21:45:48 +03:00
										 |  |  |     if (selectables.length === 0) { | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let selectable: SelectorContext; | 
					
						
							|  |  |  |     let result = false; | 
					
						
							|  |  |  |     for (let i = 0; i < selectables.length; i++) { | 
					
						
							|  |  |  |       selectable = selectables[i]; | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       result = selectable.finalize(cssSelector, matchedCallback) || result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |     return result; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-11-11 17:33:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-09 17:21:25 -07:00
										 |  |  |   /** @internal */ | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   _matchPartial( | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |       map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector, | 
					
						
							| 
									
										
										
										
											2016-06-11 21:23:37 -07:00
										 |  |  |       matchedCallback: (c: CssSelector, a: any) => void): boolean { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (!map || typeof name !== 'string') { | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-18 01:19:18 +03:00
										 |  |  |     const nestedSelector = map.get(name); | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |     if (!nestedSelector) { | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2014-10-28 14:46:55 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     // TODO(perf): get rid of recursion and measure again
 | 
					
						
							|  |  |  |     // TODO(perf): don't pass the whole selector into the recursion,
 | 
					
						
							|  |  |  |     // but only the not processed parts
 | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |     return nestedSelector.match(cssSelector, matchedCallback); | 
					
						
							| 
									
										
										
										
											2014-10-02 20:39:27 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-09 17:32:42 -07:00
										 |  |  | export class SelectorListContext { | 
					
						
							| 
									
										
										
										
											2015-06-12 23:11:11 +02:00
										 |  |  |   alreadyMatched: boolean = false; | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   constructor(public selectors: CssSelector[]) {} | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  | // Store context to pass back selector and context when a selector is matched
 | 
					
						
							| 
									
										
										
										
											2015-07-09 17:32:42 -07:00
										 |  |  | export class SelectorContext { | 
					
						
							| 
									
										
										
										
											2015-06-24 13:46:39 -07:00
										 |  |  |   notSelectors: CssSelector[]; | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |       public selector: CssSelector, public cbContext: any, | 
					
						
							|  |  |  |       public listContext: SelectorListContext) { | 
					
						
							| 
									
										
										
										
											2015-06-01 14:24:19 -07:00
										 |  |  |     this.notSelectors = selector.notSelectors; | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-09 09:28:12 -07:00
										 |  |  |   finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     let result = true; | 
					
						
							| 
									
										
										
										
											2016-09-30 09:26:53 -07:00
										 |  |  |     if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) { | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |       const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors); | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       result = !notMatcher.match(cssSelector, null); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-09-25 11:28:47 -07:00
										 |  |  |     if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) { | 
					
						
							|  |  |  |       if (this.listContext) { | 
					
						
							| 
									
										
										
										
											2015-03-19 17:01:42 +01:00
										 |  |  |         this.listContext.alreadyMatched = true; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-03-12 09:44:49 +01:00
										 |  |  |       callback(this.selector, this.cbContext); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-02-06 15:41:02 -08:00
										 |  |  | } |