2016-06-23 12:47:54 -04: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-06-08 19:38:52 -04:00
|
|
|
import {ListWrapper, Map} from '../src/facade/collection';
|
2016-04-28 20:50:03 -04:00
|
|
|
import {BaseException} from '../src/facade/exceptions';
|
2016-06-08 19:38:52 -04:00
|
|
|
import {RegExpMatcherWrapper, RegExpWrapper, StringWrapper, isBlank, isPresent} from '../src/facade/lang';
|
2014-10-02 23:39:27 -04:00
|
|
|
|
2016-04-26 01:25:21 -04:00
|
|
|
const _EMPTY_ATTR_VALUE = /*@ts2dart_const*/ '';
|
2014-10-28 17:46:55 -04:00
|
|
|
|
2014-11-11 20:33:47 -05:00
|
|
|
// TODO: Can't use `const` here as
|
|
|
|
// in Dart this is not transpiled into `final` yet...
|
2015-05-18 14:57:20 -04:00
|
|
|
var _SELECTOR_REGEXP = RegExpWrapper.create(
|
|
|
|
'(\\:not\\()|' + //":not("
|
|
|
|
'([-\\w]+)|' + // "tag"
|
|
|
|
'(?:\\.([-\\w]+))|' + // ".class"
|
|
|
|
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
|
2015-06-01 17:24:19 -04:00
|
|
|
'(\\))|' + // ")"
|
2015-05-18 14:57:20 -04:00
|
|
|
'(\\s*,\\s*)'); // ","
|
2014-10-28 17:46:55 -04:00
|
|
|
|
2014-11-11 20:33:47 -05: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 17:11:11 -04:00
|
|
|
element: string = null;
|
2015-06-24 16:46:39 -04:00
|
|
|
classNames: string[] = [];
|
|
|
|
attrs: string[] = [];
|
|
|
|
notSelectors: CssSelector[] = [];
|
2015-06-12 17:11:11 -04:00
|
|
|
|
2015-06-24 16:46:39 -04:00
|
|
|
static parse(selector: string): CssSelector[] {
|
2015-06-17 14:17:21 -04:00
|
|
|
var results: CssSelector[] = [];
|
2016-06-12 00:23:37 -04:00
|
|
|
var _addResult = (res: CssSelector[], cssSel: CssSelector) => {
|
2015-06-01 17:24:19 -04:00
|
|
|
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
2015-05-18 14:57:20 -04:00
|
|
|
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
2016-06-08 19:38:52 -04:00
|
|
|
cssSel.element = '*';
|
2015-03-19 12:01:42 -04:00
|
|
|
}
|
2015-06-17 14:17:21 -04:00
|
|
|
res.push(cssSel);
|
2015-05-21 10:42:19 -04:00
|
|
|
};
|
|
|
|
var cssSelector = new CssSelector();
|
|
|
|
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
2016-06-12 00:23:37 -04:00
|
|
|
var match: string[];
|
2015-05-21 10:42:19 -04:00
|
|
|
var current = cssSelector;
|
2015-06-01 17:24:19 -04:00
|
|
|
var inNot = false;
|
2015-05-21 10:42:19 -04:00
|
|
|
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
|
|
|
if (isPresent(match[1])) {
|
2015-06-01 17:24:19 -04:00
|
|
|
if (inNot) {
|
2015-05-21 10:42:19 -04:00
|
|
|
throw new BaseException('Nesting :not is not allowed in a selector');
|
|
|
|
}
|
2015-06-01 17:24:19 -04:00
|
|
|
inNot = true;
|
|
|
|
current = new CssSelector();
|
2015-06-17 14:17:21 -04:00
|
|
|
cssSelector.notSelectors.push(current);
|
2015-05-21 10:42:19 -04:00
|
|
|
}
|
|
|
|
if (isPresent(match[2])) {
|
|
|
|
current.setElement(match[2]);
|
|
|
|
}
|
|
|
|
if (isPresent(match[3])) {
|
|
|
|
current.addClassName(match[3]);
|
|
|
|
}
|
|
|
|
if (isPresent(match[4])) {
|
|
|
|
current.addAttribute(match[4], match[5]);
|
|
|
|
}
|
|
|
|
if (isPresent(match[6])) {
|
2015-06-01 17:24:19 -04:00
|
|
|
inNot = false;
|
|
|
|
current = cssSelector;
|
|
|
|
}
|
|
|
|
if (isPresent(match[7])) {
|
|
|
|
if (inNot) {
|
|
|
|
throw new BaseException('Multiple selectors in :not are not supported');
|
|
|
|
}
|
2015-05-21 10:42:19 -04:00
|
|
|
_addResult(results, cssSelector);
|
|
|
|
cssSelector = current = new CssSelector();
|
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2015-05-21 10:42:19 -04:00
|
|
|
_addResult(results, cssSelector);
|
|
|
|
return results;
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2014-10-28 17:46:55 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
isElementSelector(): boolean {
|
|
|
|
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
|
2016-06-08 19:38:52 -04:00
|
|
|
ListWrapper.isEmpty(this.attrs) && this.notSelectors.length === 0;
|
2015-05-21 10:42:19 -04:00
|
|
|
}
|
2015-04-27 18:14:30 -04:00
|
|
|
|
2015-11-23 19:02:19 -05:00
|
|
|
setElement(element: string = null) { this.element = element; }
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2015-07-28 14:38:40 -04:00
|
|
|
/** Gets a template string for an element that matches the selector. */
|
|
|
|
getMatchingElementTemplate(): string {
|
|
|
|
let tagName = isPresent(this.element) ? this.element : 'div';
|
|
|
|
let classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : '';
|
|
|
|
|
|
|
|
let attrs = '';
|
|
|
|
for (let i = 0; i < this.attrs.length; i += 2) {
|
|
|
|
let attrName = this.attrs[i];
|
|
|
|
let attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : '';
|
|
|
|
attrs += ` ${attrName}${attrValue}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `<${tagName}${classAttr}${attrs}></${tagName}>`;
|
|
|
|
}
|
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
|
2015-11-23 19:02:19 -05:00
|
|
|
this.attrs.push(name);
|
2015-05-21 10:42:19 -04:00
|
|
|
if (isPresent(value)) {
|
|
|
|
value = value.toLowerCase();
|
|
|
|
} else {
|
|
|
|
value = _EMPTY_ATTR_VALUE;
|
|
|
|
}
|
2015-06-17 14:17:21 -04:00
|
|
|
this.attrs.push(value);
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
|
|
|
|
2015-06-17 14:17:21 -04:00
|
|
|
addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
toString(): string {
|
|
|
|
var res = '';
|
|
|
|
if (isPresent(this.element)) {
|
|
|
|
res += this.element;
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2015-05-21 10:42:19 -04:00
|
|
|
if (isPresent(this.classNames)) {
|
|
|
|
for (var i = 0; i < this.classNames.length; i++) {
|
|
|
|
res += '.' + this.classNames[i];
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
|
|
|
}
|
2015-05-21 10:42:19 -04:00
|
|
|
if (isPresent(this.attrs)) {
|
|
|
|
for (var i = 0; i < this.attrs.length;) {
|
|
|
|
var attrName = this.attrs[i++];
|
|
|
|
var attrValue = this.attrs[i++];
|
|
|
|
res += '[' + attrName;
|
|
|
|
if (attrValue.length > 0) {
|
|
|
|
res += '=' + attrValue;
|
|
|
|
}
|
|
|
|
res += ']';
|
|
|
|
}
|
|
|
|
}
|
2015-10-07 12:09:43 -04:00
|
|
|
this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
|
2015-05-21 10:42:19 -04:00
|
|
|
return res;
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads a list of CssSelectors and allows to calculate which ones
|
|
|
|
* are contained in a given CssSelector.
|
|
|
|
*/
|
|
|
|
export class SelectorMatcher {
|
2015-06-24 16:46:39 -04:00
|
|
|
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
2015-05-18 14:57:20 -04:00
|
|
|
var notMatcher = new SelectorMatcher();
|
2015-06-01 17:24:19 -04:00
|
|
|
notMatcher.addSelectables(notSelectors, null);
|
2015-05-19 18:05:02 -04:00
|
|
|
return notMatcher;
|
2015-05-18 14:57:20 -04:00
|
|
|
}
|
2015-05-19 18:05:02 -04:00
|
|
|
|
2015-09-29 14:11:06 -04: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 16:46:39 -04:00
|
|
|
private _listContexts: SelectorListContext[] = [];
|
2015-03-19 12:01:42 -04:00
|
|
|
|
2015-06-24 16:46:39 -04:00
|
|
|
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
2016-06-12 00:23:37 -04:00
|
|
|
var listContext: SelectorListContext = null;
|
2015-03-19 12:01:42 -04:00
|
|
|
if (cssSelectors.length > 1) {
|
2015-05-18 14:57:20 -04:00
|
|
|
listContext = new SelectorListContext(cssSelectors);
|
2015-06-17 14:17:21 -04:00
|
|
|
this._listContexts.push(listContext);
|
2015-03-19 12:01:42 -04:00
|
|
|
}
|
|
|
|
for (var i = 0; i < cssSelectors.length; i++) {
|
2015-05-18 14:57:20 -04:00
|
|
|
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
2015-03-19 12:01:42 -04:00
|
|
|
}
|
2014-10-02 23:39:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-10-28 17:46:55 -04:00
|
|
|
* Add an object that can be found later on by calling `match`.
|
|
|
|
* @param cssSelector A css selector
|
2015-02-06 18:41:02 -05:00
|
|
|
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
|
2014-10-02 23:39:27 -04:00
|
|
|
*/
|
2016-06-08 19:38:52 -04:00
|
|
|
private _addSelectable(
|
|
|
|
cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) {
|
2015-12-09 16:42:36 -05:00
|
|
|
var matcher: SelectorMatcher = this;
|
2014-10-28 17:46:55 -04:00
|
|
|
var element = cssSelector.element;
|
|
|
|
var classNames = cssSelector.classNames;
|
|
|
|
var attrs = cssSelector.attrs;
|
2015-03-19 12:01:42 -04:00
|
|
|
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
2015-02-06 18:41:02 -05:00
|
|
|
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isPresent(element)) {
|
|
|
|
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
|
|
|
if (isTerminal) {
|
|
|
|
this._addTerminal(matcher._elementMap, element, selectable);
|
|
|
|
} else {
|
|
|
|
matcher = this._addPartial(matcher._elementPartialMap, element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPresent(classNames)) {
|
2015-05-18 14:57:20 -04:00
|
|
|
for (var index = 0; index < classNames.length; index++) {
|
2014-10-28 17:46:55 -04:00
|
|
|
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
|
|
|
|
var className = classNames[index];
|
|
|
|
if (isTerminal) {
|
|
|
|
this._addTerminal(matcher._classMap, className, selectable);
|
|
|
|
} else {
|
|
|
|
matcher = this._addPartial(matcher._classPartialMap, className);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPresent(attrs)) {
|
2015-05-18 14:57:20 -04:00
|
|
|
for (var index = 0; index < attrs.length;) {
|
2014-11-11 20:33:47 -05:00
|
|
|
var isTerminal = index === attrs.length - 2;
|
|
|
|
var attrName = attrs[index++];
|
|
|
|
var attrValue = attrs[index++];
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isTerminal) {
|
2015-05-20 12:48:15 -04:00
|
|
|
var terminalMap = matcher._attrValueMap;
|
2015-06-17 19:21:40 -04:00
|
|
|
var terminalValuesMap = terminalMap.get(attrName);
|
2015-05-20 12:48:15 -04:00
|
|
|
if (isBlank(terminalValuesMap)) {
|
2015-09-29 14:11:06 -04:00
|
|
|
terminalValuesMap = new Map<string, SelectorContext[]>();
|
2015-06-17 19:21:40 -04:00
|
|
|
terminalMap.set(attrName, terminalValuesMap);
|
2015-05-20 12:48:15 -04:00
|
|
|
}
|
|
|
|
this._addTerminal(terminalValuesMap, attrValue, selectable);
|
2014-10-28 17:46:55 -04:00
|
|
|
} else {
|
2015-05-20 12:48:15 -04:00
|
|
|
var parttialMap = matcher._attrValuePartialMap;
|
2015-06-17 19:21:40 -04:00
|
|
|
var partialValuesMap = parttialMap.get(attrName);
|
2015-05-20 12:48:15 -04:00
|
|
|
if (isBlank(partialValuesMap)) {
|
2015-09-29 14:11:06 -04:00
|
|
|
partialValuesMap = new Map<string, SelectorMatcher>();
|
2015-06-17 19:21:40 -04:00
|
|
|
parttialMap.set(attrName, partialValuesMap);
|
2015-05-20 12:48:15 -04:00
|
|
|
}
|
|
|
|
matcher = this._addPartial(partialValuesMap, attrValue);
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _addTerminal(
|
|
|
|
map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) {
|
2015-06-17 19:21:40 -04:00
|
|
|
var terminalList = map.get(name);
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isBlank(terminalList)) {
|
2015-06-17 14:17:21 -04:00
|
|
|
terminalList = [];
|
2015-06-17 19:21:40 -04:00
|
|
|
map.set(name, terminalList);
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2015-06-17 14:17:21 -04:00
|
|
|
terminalList.push(selectable);
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2015-05-20 12:48:15 -04:00
|
|
|
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
|
2015-06-17 19:21:40 -04:00
|
|
|
var matcher = map.get(name);
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isBlank(matcher)) {
|
|
|
|
matcher = new SelectorMatcher();
|
2015-06-17 19:21:40 -04:00
|
|
|
map.set(name, matcher);
|
2014-10-28 17:46:55 -04: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 04:44:49 -04:00
|
|
|
* @return boolean true if a match was found
|
2014-10-28 17:46:55 -04:00
|
|
|
*/
|
2015-10-09 12:28:12 -04:00
|
|
|
match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
2015-03-12 04:44:49 -04:00
|
|
|
var result = false;
|
2014-10-28 17:46:55 -04:00
|
|
|
var element = cssSelector.element;
|
|
|
|
var classNames = cssSelector.classNames;
|
|
|
|
var attrs = cssSelector.attrs;
|
|
|
|
|
2015-03-19 12:01:42 -04:00
|
|
|
for (var i = 0; i < this._listContexts.length; i++) {
|
|
|
|
this._listContexts[i].alreadyMatched = false;
|
|
|
|
}
|
|
|
|
|
2015-03-12 04:44:49 -04:00
|
|
|
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
|
2015-05-18 14:57:20 -04:00
|
|
|
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
|
2016-06-08 19:38:52 -04:00
|
|
|
result;
|
2014-10-28 17:46:55 -04:00
|
|
|
|
|
|
|
if (isPresent(classNames)) {
|
2015-05-18 14:57:20 -04:00
|
|
|
for (var index = 0; index < classNames.length; index++) {
|
2014-10-28 17:46:55 -04:00
|
|
|
var className = classNames[index];
|
2015-05-18 14:57:20 -04:00
|
|
|
result =
|
|
|
|
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
|
|
|
result =
|
|
|
|
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
|
|
|
|
result;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPresent(attrs)) {
|
2015-05-18 14:57:20 -04:00
|
|
|
for (var index = 0; index < attrs.length;) {
|
2014-11-11 20:33:47 -05:00
|
|
|
var attrName = attrs[index++];
|
|
|
|
var attrValue = attrs[index++];
|
2014-10-28 17:46:55 -04:00
|
|
|
|
2015-06-17 19:21:40 -04:00
|
|
|
var terminalValuesMap = this._attrValueMap.get(attrName);
|
2014-11-11 20:33:47 -05:00
|
|
|
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
2016-06-08 19:38:52 -04:00
|
|
|
result = this._matchTerminal(
|
|
|
|
terminalValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
|
|
|
|
result;
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2015-05-20 12:48:15 -04:00
|
|
|
result = this._matchTerminal(terminalValuesMap, attrValue, cssSelector, matchedCallback) ||
|
2016-06-08 19:38:52 -04:00
|
|
|
result;
|
2014-10-28 17:46:55 -04:00
|
|
|
|
2015-06-17 19:21:40 -04:00
|
|
|
var partialValuesMap = this._attrValuePartialMap.get(attrName);
|
2015-06-11 14:35:02 -04:00
|
|
|
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
2016-06-08 19:38:52 -04:00
|
|
|
result = this._matchPartial(
|
|
|
|
partialValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
|
|
|
|
result;
|
2015-06-11 14:35:02 -04:00
|
|
|
}
|
2015-05-20 12:48:15 -04:00
|
|
|
result =
|
|
|
|
this._matchPartial(partialValuesMap, attrValue, cssSelector, matchedCallback) || result;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
|
|
|
}
|
2015-03-12 04:44:49 -04:00
|
|
|
return result;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2015-10-09 20:21:25 -04:00
|
|
|
/** @internal */
|
2016-06-08 19:38:52 -04:00
|
|
|
_matchTerminal(
|
2016-06-12 00:23:37 -04:00
|
|
|
map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector,
|
2016-06-08 19:38:52 -04:00
|
|
|
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isBlank(map) || isBlank(name)) {
|
2015-03-12 04:44:49 -04:00
|
|
|
return false;
|
|
|
|
}
|
2015-04-17 16:01:07 -04:00
|
|
|
|
2015-06-17 19:21:40 -04:00
|
|
|
var selectables = map.get(name);
|
2016-06-08 19:38:52 -04:00
|
|
|
var starSelectables = map.get('*');
|
2015-03-12 04:44:49 -04:00
|
|
|
if (isPresent(starSelectables)) {
|
2015-08-28 14:29:19 -04:00
|
|
|
selectables = selectables.concat(starSelectables);
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
|
|
|
if (isBlank(selectables)) {
|
2015-03-12 04:44:49 -04:00
|
|
|
return false;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2016-06-12 00:23:37 -04:00
|
|
|
var selectable: SelectorContext;
|
2015-03-12 04:44:49 -04:00
|
|
|
var result = false;
|
2015-05-18 14:57:20 -04:00
|
|
|
for (var index = 0; index < selectables.length; index++) {
|
2015-02-06 18:41:02 -05:00
|
|
|
selectable = selectables[index];
|
2015-03-12 04:44:49 -04:00
|
|
|
result = selectable.finalize(cssSelector, matchedCallback) || result;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2015-03-12 04:44:49 -04:00
|
|
|
return result;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2015-10-09 20:21:25 -04:00
|
|
|
/** @internal */
|
2016-06-08 19:38:52 -04:00
|
|
|
_matchPartial(
|
2016-06-12 00:23:37 -04:00
|
|
|
map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector,
|
|
|
|
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isBlank(map) || isBlank(name)) {
|
2015-03-12 04:44:49 -04:00
|
|
|
return false;
|
2014-10-28 17:46:55 -04:00
|
|
|
}
|
2015-06-17 19:21:40 -04:00
|
|
|
var nestedSelector = map.get(name);
|
2014-10-28 17:46:55 -04:00
|
|
|
if (isBlank(nestedSelector)) {
|
2015-03-12 04:44:49 -04:00
|
|
|
return false;
|
2014-10-28 17:46:55 -04: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 04:44:49 -04:00
|
|
|
return nestedSelector.match(cssSelector, matchedCallback);
|
2014-10-02 23:39:27 -04:00
|
|
|
}
|
|
|
|
}
|
2015-02-06 18:41:02 -05:00
|
|
|
|
|
|
|
|
2015-07-09 20:32:42 -04:00
|
|
|
export class SelectorListContext {
|
2015-06-12 17:11:11 -04:00
|
|
|
alreadyMatched: boolean = false;
|
2015-03-19 12:01:42 -04:00
|
|
|
|
2015-06-24 16:46:39 -04:00
|
|
|
constructor(public selectors: CssSelector[]) {}
|
2015-03-19 12:01:42 -04:00
|
|
|
}
|
|
|
|
|
2015-02-06 18:41:02 -05:00
|
|
|
// Store context to pass back selector and context when a selector is matched
|
2015-07-09 20:32:42 -04:00
|
|
|
export class SelectorContext {
|
2015-06-24 16:46:39 -04:00
|
|
|
notSelectors: CssSelector[];
|
2015-02-06 18:41:02 -05:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
constructor(
|
|
|
|
public selector: CssSelector, public cbContext: any,
|
|
|
|
public listContext: SelectorListContext) {
|
2015-06-01 17:24:19 -04:00
|
|
|
this.notSelectors = selector.notSelectors;
|
2015-02-06 18:41:02 -05:00
|
|
|
}
|
2015-03-12 04:44:49 -04:00
|
|
|
|
2015-10-09 12:28:12 -04:00
|
|
|
finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean {
|
2015-03-12 04:44:49 -04:00
|
|
|
var result = true;
|
2015-06-01 17:24:19 -04:00
|
|
|
if (this.notSelectors.length > 0 &&
|
2015-05-18 14:57:20 -04:00
|
|
|
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
2015-06-01 17:24:19 -04:00
|
|
|
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
2015-03-12 04:44:49 -04:00
|
|
|
result = !notMatcher.match(cssSelector, null);
|
|
|
|
}
|
2015-05-18 14:57:20 -04:00
|
|
|
if (result && isPresent(callback) &&
|
|
|
|
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
2015-03-19 12:01:42 -04:00
|
|
|
if (isPresent(this.listContext)) {
|
|
|
|
this.listContext.alreadyMatched = true;
|
|
|
|
}
|
2015-03-12 04:44:49 -04:00
|
|
|
callback(this.selector, this.cbContext);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2015-02-06 18:41:02 -05:00
|
|
|
}
|