diff --git a/modules/angular2/src/render/dom/compiler/property_setter_factory.js b/modules/angular2/src/render/dom/compiler/property_setter_factory.js deleted file mode 100644 index 0f93302146..0000000000 --- a/modules/angular2/src/render/dom/compiler/property_setter_factory.js +++ /dev/null @@ -1,138 +0,0 @@ -import {StringWrapper, RegExpWrapper, BaseException, isPresent, isBlank, isString, stringify} from 'angular2/src/facade/lang'; -import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {reflector} from 'angular2/src/reflection/reflection'; - -var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])'); -var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])'); - -export function dashCaseToCamelCase(input:string): string { - return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => { - return m[1].toUpperCase(); - }); -} - -export function camelCaseToDashCase(input:string): string { - return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => { - return '-' + m[1].toLowerCase(); - }); -} - -const STYLE_SEPARATOR = '.'; -var propertySettersCache = StringMapWrapper.create(); -var innerHTMLSetterCache; - -export function setterFactory(property: string): Function { - var setterFn, styleParts, styleSuffix; - if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) { - setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length)); - } else if (StringWrapper.startsWith(property, CLASS_PREFIX)) { - setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); - } else if (StringWrapper.startsWith(property, STYLE_PREFIX)) { - styleParts = property.split(STYLE_SEPARATOR); - styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : ''; - setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix); - } else if (StringWrapper.equals(property, 'innerHtml')) { - if (isBlank(innerHTMLSetterCache)) { - innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value); - } - setterFn = innerHTMLSetterCache; - } else { - property = resolvePropertyName(property); - setterFn = StringMapWrapper.get(propertySettersCache, property); - if (isBlank(setterFn)) { - var propertySetterFn = reflector.setter(property); - setterFn = function(receiver, value) { - if (DOM.hasProperty(receiver, property)) { - return propertySetterFn(receiver, value); - } - } - StringMapWrapper.set(propertySettersCache, property, setterFn); - } - } - return setterFn; -} - -const ATTRIBUTE_PREFIX = 'attr.'; -var attributeSettersCache = StringMapWrapper.create(); - -function _isValidAttributeValue(attrName:string, value: any): boolean { - if (attrName == "role") { - return isString(value); - } else { - return isPresent(value); - } -} - -function attributeSetterFactory(attrName:string): Function { - var setterFn = StringMapWrapper.get(attributeSettersCache, attrName); - var dashCasedAttributeName; - - if (isBlank(setterFn)) { - dashCasedAttributeName = camelCaseToDashCase(attrName); - setterFn = function(element, value) { - if (_isValidAttributeValue(dashCasedAttributeName, value)) { - DOM.setAttribute(element, dashCasedAttributeName, stringify(value)); - } else { - if (isPresent(value)) { - throw new BaseException("Invalid " + dashCasedAttributeName + - " attribute, only string values are allowed, got '" + stringify(value) + "'"); - } - DOM.removeAttribute(element, dashCasedAttributeName); - } - }; - StringMapWrapper.set(attributeSettersCache, attrName, setterFn); - } - - return setterFn; -} - -const CLASS_PREFIX = 'class.'; -var classSettersCache = StringMapWrapper.create(); - -function classSetterFactory(className:string): Function { - var setterFn = StringMapWrapper.get(classSettersCache, className); - - if (isBlank(setterFn)) { - setterFn = function(element, value) { - if (value) { - DOM.addClass(element, className); - } else { - DOM.removeClass(element, className); - } - }; - StringMapWrapper.set(classSettersCache, className, setterFn); - } - - return setterFn; -} - -const STYLE_PREFIX = 'style.'; -var styleSettersCache = StringMapWrapper.create(); - -function styleSetterFactory(styleName:string, styleSuffix:string): Function { - var cacheKey = styleName + styleSuffix; - var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey); - var dashCasedStyleName; - - if (isBlank(setterFn)) { - dashCasedStyleName = camelCaseToDashCase(styleName); - setterFn = function(element, value) { - var valAsStr; - if (isPresent(value)) { - valAsStr = stringify(value); - DOM.setStyle(element, dashCasedStyleName, valAsStr + styleSuffix); - } else { - DOM.removeStyle(element, dashCasedStyleName); - } - }; - StringMapWrapper.set(styleSettersCache, cacheKey, setterFn); - } - - return setterFn; -} - -function resolvePropertyName(attrName:string): string { - var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName); - return isPresent(mappedPropName) ? mappedPropName : attrName; -} diff --git a/modules/angular2/src/render/dom/compiler/selector.js b/modules/angular2/src/render/dom/compiler/selector.js deleted file mode 100644 index 414063805c..0000000000 --- a/modules/angular2/src/render/dom/compiler/selector.js +++ /dev/null @@ -1,352 +0,0 @@ -import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; -import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, BaseException} from 'angular2/src/facade/lang'; - -const _EMPTY_ATTR_VALUE = ''; - -// TODO: Can't use `const` here as -// in Dart this is not transpiled into `final` yet... -var _SELECTOR_REGEXP = - RegExpWrapper.create('(\\:not\\()|' + //":not(" - '([-\\w]+)|' + // "tag" - '(?:\\.([-\\w]+))|' + // ".class" - '(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]" - '(?:\\))|' + // ")" - '(\\s*,\\s*)'); // "," - -/** - * 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 { - element:string; - classNames:List; - attrs:List; - notSelector: CssSelector; - static parse(selector:string): List { - var results = ListWrapper.create(); - var _addResult = (res, cssSel) => { - if (isPresent(cssSel.notSelector) && isBlank(cssSel.element) - && ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) { - cssSel.element = "*"; - } - ListWrapper.push(res, cssSel); - } - var cssSelector = new CssSelector(); - var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector); - var match; - var current = cssSelector; - while (isPresent(match = RegExpMatcherWrapper.next(matcher))) { - if (isPresent(match[1])) { - if (isPresent(cssSelector.notSelector)) { - throw new BaseException('Nesting :not is not allowed in a selector'); - } - current.notSelector = new CssSelector(); - current = current.notSelector; - } - 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])) { - _addResult(results, cssSelector); - cssSelector = current = new CssSelector(); - } - } - _addResult(results, cssSelector); - return results; - } - - constructor() { - this.element = null; - this.classNames = ListWrapper.create(); - this.attrs = ListWrapper.create(); - this.notSelector = null; - } - - setElement(element:string = null) { - if (isPresent(element)) { - element = element.toLowerCase(); - } - this.element = element; - } - - addAttribute(name:string, value:string = _EMPTY_ATTR_VALUE) { - ListWrapper.push(this.attrs, name.toLowerCase()); - if (isPresent(value)) { - value = value.toLowerCase(); - } else { - value = _EMPTY_ATTR_VALUE; - } - ListWrapper.push(this.attrs, value); - } - - addClassName(name:string) { - ListWrapper.push(this.classNames, name.toLowerCase()); - } - - toString():string { - var res = ''; - if (isPresent(this.element)) { - res += this.element; - } - if (isPresent(this.classNames)) { - for (var i=0; i 0) { - res += '=' + attrValue; - } - res += ']'; - } - } - if (isPresent(this.notSelector)) { - res += ":not(" + this.notSelector.toString() + ")"; - } - return res; - } -} - -/** - * Reads a list of CssSelectors and allows to calculate which ones - * are contained in a given CssSelector. - */ -export class SelectorMatcher { - _elementMap:Map; - _elementPartialMap:Map; - _classMap:Map; - _classPartialMap:Map; - _attrValueMap:Map; - _attrValuePartialMap:Map; - _listContexts:List; - constructor() { - this._elementMap = MapWrapper.create(); - this._elementPartialMap = MapWrapper.create(); - - this._classMap = MapWrapper.create(); - this._classPartialMap = MapWrapper.create(); - - this._attrValueMap = MapWrapper.create(); - this._attrValuePartialMap = MapWrapper.create(); - - this._listContexts = ListWrapper.create(); - } - - addSelectables(cssSelectors:List, callbackCtxt) { - var listContext = null; - if (cssSelectors.length > 1) { - listContext= new SelectorListContext(cssSelectors); - ListWrapper.push(this._listContexts, listContext); - } - for (var i = 0; i < cssSelectors.length; i++) { - this.addSelectable(cssSelectors[i], callbackCtxt, listContext); - } - } - - /** - * Add an object that can be found later on by calling `match`. - * @param cssSelector A css selector - * @param callbackCtxt An opaque object that will be given to the callback of the `match` function - */ - addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) { - var matcher = this; - var element = cssSelector.element; - var classNames = cssSelector.classNames; - var attrs = cssSelector.attrs; - var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext); - - - 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)) { - for (var index = 0; index, name:string, selectable) { - var terminalList = MapWrapper.get(map, name) - if (isBlank(terminalList)) { - terminalList = ListWrapper.create(); - MapWrapper.set(map, name, terminalList); - } - ListWrapper.push(terminalList, selectable); - } - - _addPartial(map:Map, name:string) { - var matcher = MapWrapper.get(map, name) - if (isBlank(matcher)) { - matcher = new SelectorMatcher(); - MapWrapper.set(map, name, matcher); - } - 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` - * @return boolean true if a match was found - */ - match(cssSelector:CssSelector, matchedCallback:Function):boolean { - var result = false; - var element = cssSelector.element; - var classNames = cssSelector.classNames; - var attrs = cssSelector.attrs; - - for (var i = 0; i < this._listContexts.length; i++) { - this._listContexts[i].alreadyMatched = false; - } - - result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result; - result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result; - - if (isPresent(classNames)) { - for (var index = 0; index = null, name, cssSelector, matchedCallback):boolean { - if (isBlank(map) || isBlank(name)) { - return false; - } - - var selectables = MapWrapper.get(map, name); - var starSelectables = MapWrapper.get(map, "*"); - if (isPresent(starSelectables)) { - selectables = ListWrapper.concat(selectables, starSelectables); - } - if (isBlank(selectables)) { - return false; - } - var selectable; - var result = false; - for (var index=0; index = null, name, cssSelector, matchedCallback):boolean { - if (isBlank(map) || isBlank(name)) { - return false; - } - var nestedSelector = MapWrapper.get(map, name) - if (isBlank(nestedSelector)) { - return false; - } - // 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 - return nestedSelector.match(cssSelector, matchedCallback); - } -} - - -class SelectorListContext { - selectors: List; - alreadyMatched: boolean; - - constructor(selectors:List) { - this.selectors = selectors; - this.alreadyMatched = false; - } -} - -// Store context to pass back selector and context when a selector is matched -class SelectorContext { - selector:CssSelector; - notSelector:CssSelector; - cbContext; // callback context - listContext: SelectorListContext; - - constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) { - this.selector = selector; - this.notSelector = selector.notSelector; - this.cbContext = cbContext; - this.listContext = listContext; - } - - finalize(cssSelector: CssSelector, callback) { - var result = true; - if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) { - var notMatcher = new SelectorMatcher(); - notMatcher.addSelectable(this.notSelector, null, null); - result = !notMatcher.match(cssSelector, null); - } - if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) { - if (isPresent(this.listContext)) { - this.listContext.alreadyMatched = true; - } - callback(this.selector, this.cbContext); - } - return result; - } -} diff --git a/modules/angular2/src/render/dom/shadow_dom/shadow_css.js b/modules/angular2/src/render/dom/shadow_dom/shadow_css.js deleted file mode 100644 index 2d49302d9a..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/shadow_css.js +++ /dev/null @@ -1,537 +0,0 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {List, ListWrapper} from 'angular2/src/facade/collection'; -import { - StringWrapper, - RegExp, - RegExpWrapper, - RegExpMatcherWrapper, - isPresent, - isBlank, - BaseException, - int -} from 'angular2/src/facade/lang'; - -/** - * This file is a port of shadowCSS from webcomponents.js to AtScript. - * - * Please make sure to keep to edits in sync with the source file. - * - * Source: https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js - * - * 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; - } - - * encapsultion: Styles defined within ShadowDOM, apply only to - dom inside the ShadowDOM. Polymer uses one of two techniques to imlement - 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: - - - - - could become: - - -
- -
- - 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 { - strictStyling: boolean; - - constructor() { - this.strictStyling = true; - } - - /* - * Shim a style element with the given selector. Returns cssText that can - * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css). - */ - shimStyle(style, selector: string, hostSelector: string = ''): string { - var cssText = DOM.getText(style); - return this.shimCssText(cssText, selector, hostSelector); - } - - /* - * Shim some cssText with the given selector. Returns cssText that can - * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css). - * - * When strictStyling is true: - * - selector is the attribute added to all elements inside the host, - * - hostSelector is the attribute added to the host itself. - */ - shimCssText(cssText: string, selector: string, hostSelector: string = ''): string { - cssText = this._insertDirectives(cssText); - return this._scopeCssText(cssText, selector, hostSelector); - } - - _insertDirectives(cssText: string): string { - 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 { - * - **/ - _insertPolyfillDirectivesInCssText(cssText: string): string { - // Difference with webcomponents.js: does not handle comments - return StringWrapper.replaceAllMapped(cssText, _cssContentNextSelectorRe, function(m) { - return m[1] + '{'; - }); - } - - /* - * 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 {...} - * - **/ - _insertPolyfillRulesInCssText(cssText: string): string { - // Difference with webcomponents.js: does not handle comments - return StringWrapper.replaceAllMapped(cssText, _cssContentRuleRe, function(m) { - var rule = m[0]; - rule = StringWrapper.replace(rule, m[1], ''); - rule = StringWrapper.replace(rule, m[2], ''); - return m[3] + rule; - }); - } - - /* Ensure styles are scoped. Pseudo-scoping takes a rule like: - * - * .foo {... } - * - * and converts this to - * - * scopeName .foo { ... } - */ - _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { - - var unscoped = this._extractUnscopedRulesFromCssText(cssText); - cssText = this._insertPolyfillHostInCssText(cssText); - cssText = this._convertColonHost(cssText); - cssText = this._convertColonHostContext(cssText); - cssText = this._convertShadowDOMSelectors(cssText); - if (isPresent(scopeSelector)) { - _withCssRules(cssText, (rules) => { - cssText = this._scopeRules(rules, scopeSelector, hostSelector); - }); - } - cssText = cssText + '\n' + unscoped; - 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 {...} - * - **/ - _extractUnscopedRulesFromCssText(cssText: string): string { - // Difference with webcomponents.js: does not handle comments - var r = '', m; - var matcher = RegExpWrapper.matcher(_cssContentUnscopedRuleRe, cssText); - while (isPresent(m = RegExpMatcherWrapper.next(matcher))) { - var rule = m[0]; - rule = StringWrapper.replace(rule, m[2], ''); - rule = StringWrapper.replace(rule, m[1], m[3]); - r = rule + '\n\n'; - } - return r; - } - - /* - * convert a rule like :host(.foo) > .bar { } - * - * to - * - * scopeName.foo > .bar - */ - _convertColonHost(cssText: string): string { - return this._convertColonRule(cssText, _cssColonHostRe, - this._colonHostPartReplacer); - } - - /* - * convert a rule like :host-context(.foo) > .bar { } - * - * to - * - * scopeName.foo > .bar, .foo scopeName > .bar { } - * - * and - * - * :host-context(.foo:host) .bar { ... } - * - * to - * - * scopeName.foo .bar { ... } - */ - _convertColonHostContext(cssText: string): string { - return this._convertColonRule(cssText, _cssColonHostContextRe, - this._colonHostContextPartReplacer); - } - - _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string { - // p1 = :host, p2 = contents of (), p3 rest of rule - return StringWrapper.replaceAllMapped(cssText, regExp, function(m) { - if (isPresent(m[2])) { - var parts = m[2].split(','), r = []; - for (var i = 0; i < parts.length; i++) { - var p = parts[i]; - if (isBlank(p)) break; - p = p.trim(); - ListWrapper.push(r, partReplacer(_polyfillHostNoCombinator, p, m[3])); - } - return r.join(','); - } else { - return _polyfillHostNoCombinator + m[3]; - } - }); - } - - _colonHostContextPartReplacer(host: string, part: string, suffix: string): string { - if (StringWrapper.contains(part, _polyfillHost)) { - return this._colonHostPartReplacer(host, part, suffix); - } else { - return host + part + suffix + ', ' + part + ' ' + host + suffix; - } - } - - _colonHostPartReplacer(host: string, part: string, suffix: string): string { - return host + StringWrapper.replace(part, _polyfillHost, '') + suffix; - } - - /* - * Convert combinators like ::shadow and pseudo-elements like ::content - * by replacing with space. - */ - _convertShadowDOMSelectors(cssText: string): string { - for (var i = 0; i < _shadowDOMSelectorsRe.length; i++) { - cssText = StringWrapper.replaceAll(cssText, _shadowDOMSelectorsRe[i], ' '); - } - return cssText; - } - - // change a selector like 'div' to 'name div' - _scopeRules(cssRules, scopeSelector: string, hostSelector: string): string { - var cssText = ''; - if (isPresent(cssRules)) { - for (var i = 0; i < cssRules.length; i++) { - var rule = cssRules[i]; - if (DOM.isStyleRule(rule) || DOM.isPageRule(rule)) { - cssText += this._scopeSelector(rule.selectorText, scopeSelector, hostSelector, - this.strictStyling) + ' {\n'; - cssText += this._propertiesFromRule(rule) + '\n}\n\n'; - } else if (DOM.isMediaRule(rule)) { - cssText += '@media ' + rule.media.mediaText + ' {\n'; - cssText += this._scopeRules(rule.cssRules, scopeSelector, hostSelector); - cssText += '\n}\n\n'; - } else { - // KEYFRAMES_RULE in IE throws when we query cssText - // when it contains a -webkit- property. - // if this happens, we fallback to constructing the rule - // from the CSSRuleSet - // https://connect.microsoft.com/IE/feedbackdetail/view/955703/accessing-csstext-of-a-keyframe-rule-that-contains-a-webkit-property-via-cssom-generates-exception - try { - if (isPresent(rule.cssText)) { - cssText += rule.cssText + '\n\n'; - } - } catch(x) { - if (DOM.isKeyframesRule(rule) && isPresent(rule.cssRules)) { - cssText += this._ieSafeCssTextFromKeyFrameRule(rule); - } - } - } - } - } - return cssText; - } - - _ieSafeCssTextFromKeyFrameRule(rule): string { - var cssText = '@keyframes ' + rule.name + ' {'; - for (var i = 0; i < rule.cssRules.length; i++) { - var r = rule.cssRules[i]; - cssText += ' ' + r.keyText + ' {' + r.style.cssText + '}'; - } - cssText += ' }'; - return cssText; - } - - _scopeSelector(selector: string, scopeSelector: string, hostSelector: string, - strict: boolean): string { - var r = [], parts = selector.split(','); - for (var i = 0; i < parts.length; i++) { - var p = parts[i]; - p = p.trim(); - if (this._selectorNeedsScoping(p, scopeSelector)) { - p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ? - this._applyStrictSelectorScope(p, scopeSelector) : - this._applySelectorScope(p, scopeSelector, hostSelector); - } - ListWrapper.push(r, p); - } - return r.join(', '); - } - - _selectorNeedsScoping(selector: string, scopeSelector: string): boolean { - var re = this._makeScopeMatcher(scopeSelector); - return !isPresent(RegExpWrapper.firstMatch(re, selector)); - } - - _makeScopeMatcher(scopeSelector: string): RegExp { - var lre = RegExpWrapper.create('\\['); - var rre = RegExpWrapper.create('\\]'); - scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\['); - scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]'); - return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm'); - } - - _applySelectorScope(selector: string, scopeSelector: string, hostSelector: string): string { - // Difference from webcomponentsjs: scopeSelector could not be an array - return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector); - } - - // scope via name and [is=name] - _applySimpleSelectorScope(selector: string, scopeSelector: string, hostSelector: string): string { - if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) { - var replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector; - selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy); - return StringWrapper.replaceAll(selector, _polyfillHostRe, replaceBy + ' '); - } else { - return scopeSelector + ' ' + selector; - } - } - - // return a selector with [name] suffix on each simple selector - // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] - _applyStrictSelectorScope(selector: string, scopeSelector: string): string { - var isRe = RegExpWrapper.create('\\[is=([^\\]]*)\\]'); - scopeSelector = StringWrapper.replaceAllMapped(scopeSelector, isRe, (m) => m[1]); - var splits = [' ', '>', '+', '~'], - scoped = selector, - attrName = '[' + scopeSelector + ']'; - for (var i = 0; i < splits.length; i++) { - var sep = splits[i]; - var parts = scoped.split(sep); - scoped = ListWrapper.map(parts, function(p) { - // remove :host since it should be unnecessary - var t = StringWrapper.replaceAll(p.trim(), _polyfillHostRe, ''); - if (t.length > 0 && - !ListWrapper.contains(splits, t) && - !StringWrapper.contains(t, attrName)) { - var re = RegExpWrapper.create('([^:]*)(:*)(.*)'); - var m = RegExpWrapper.firstMatch(re, t); - if (isPresent(m)) { - p = m[1] + attrName + m[2] + m[3]; - } - } - return p; - }).join(sep); - } - return scoped; - } - - _insertPolyfillHostInCssText(selector: string): string { - selector = StringWrapper.replaceAll(selector, _colonHostContextRe, _polyfillHostContext); - selector = StringWrapper.replaceAll(selector, _colonHostRe, _polyfillHost); - return selector; - } - - _propertiesFromRule(rule): string { - var cssText = rule.style.cssText; - // TODO(sorvell): Safari cssom incorrectly removes quotes from the content - // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) - // don't replace attr rules - var attrRe = RegExpWrapper.create('[\'"]+|attr'); - if (rule.style.content.length > 0 && - !isPresent(RegExpWrapper.firstMatch(attrRe, rule.style.content))) { - var contentRe = RegExpWrapper.create('content:[^;]*;'); - cssText = StringWrapper.replaceAll(cssText, contentRe, 'content: \'' + - rule.style.content + '\';'); - } - // TODO(sorvell): we can workaround this issue here, but we need a list - // of troublesome properties to fix https://github.com/Polymer/platform/issues/53 - // - // inherit rules can be omitted from cssText - // TODO(sorvell): remove when Blink bug is fixed: - // https://code.google.com/p/chromium/issues/detail?id=358273 - //var style = rule.style; - //for (var i = 0; i < style.length; i++) { - // var name = style.item(i); - // var value = style.getPropertyValue(name); - // if (value == 'initial') { - // cssText += name + ': initial; '; - // } - //} - return cssText; - } -} - -var _cssContentNextSelectorRe = RegExpWrapper.create( - 'polyfill-next-selector[^}]*content:[\\s]*?[\'"](.*?)[\'"][;\\s]*}([^{]*?){', 'im'); -var _cssContentRuleRe = RegExpWrapper.create( - '(polyfill-rule)[^}]*(content:[\\s]*[\'"](.*?)[\'"])[;\\s]*[^}]*}', 'im'); -var _cssContentUnscopedRuleRe = RegExpWrapper.create( - '(polyfill-unscoped-rule)[^}]*(content:[\\s]*[\'"](.*?)[\'"])[;\\s]*[^}]*}', 'im'); -var _polyfillHost = '-shadowcsshost'; -// note: :host-context pre-processed to -shadowcsshostcontext. -var _polyfillHostContext = '-shadowcsscontext'; -var _parenSuffix = ')(?:\\((' + - '(?:\\([^)(]*\\)|[^)(]*)+?' + - ')\\))?([^,{]*)'; -var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, 'im'); -var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im'); -var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator'; -var _shadowDOMSelectorsRe = [ - RegExpWrapper.create('>>>'), - RegExpWrapper.create('::shadow'), - RegExpWrapper.create('::content'), - // Deprecated selectors - RegExpWrapper.create('/deep/'), // former >>> - RegExpWrapper.create('/shadow-deep/'), // former /deep/ - RegExpWrapper.create('/shadow/'), // former ::shadow -]; -var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$'; -var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im'); -var _colonHostRe = RegExpWrapper.create(':host', 'im'); -var _colonHostContextRe = RegExpWrapper.create(':host-context', 'im'); - -function _cssToRules(cssText: string) { - return DOM.cssToRules(cssText); -} - -function _withCssRules(cssText: string, callback: Function) { - // Difference from webcomponentjs: remove the workaround for an old bug in Chrome - if (isBlank(callback)) return; - var rules = _cssToRules(cssText); - callback(rules); -} diff --git a/modules/angular2/src/render/dom/shadow_dom/style_inliner.js b/modules/angular2/src/render/dom/shadow_dom/style_inliner.js deleted file mode 100644 index 7cefc69956..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/style_inliner.js +++ /dev/null @@ -1,147 +0,0 @@ -import {XHR} from 'angular2/src/services/xhr'; -import {StyleUrlResolver} from './style_url_resolver'; -import {UrlResolver} from 'angular2/src/services/url_resolver'; - -import {ListWrapper} from 'angular2/src/facade/collection'; -import { - isBlank, - isPresent, - RegExp, - RegExpWrapper, - StringWrapper, - normalizeBlank, -} from 'angular2/src/facade/lang'; -import { - Promise, - PromiseWrapper, -} from 'angular2/src/facade/async'; - -/** - * Inline @import rules in the given CSS. - * - * When an @import rules is inlined, it's url are rewritten. - */ -export class StyleInliner { - _xhr: XHR; - _urlResolver: UrlResolver; - _styleUrlResolver: StyleUrlResolver; - - constructor(xhr: XHR, styleUrlResolver: StyleUrlResolver, urlResolver: UrlResolver) { - this._xhr = xhr; - this._urlResolver = urlResolver; - this._styleUrlResolver = styleUrlResolver; - } - - /** - * Inline the @imports rules in the given CSS text. - * - * The baseUrl is required to rewrite URLs in the inlined content. - * - * @param {string} cssText - * @param {string} baseUrl - * @returns {*} a Promise when @import rules are present, a string otherwise - */ - // TODO(vicb): Union types: returns either a Promise or a string - // TODO(vicb): commented out @import rules should not be inlined - inlineImports(cssText: string, baseUrl: string) { - return this._inlineImports(cssText, baseUrl, []); - } - - _inlineImports(cssText: string, baseUrl: string, inlinedUrls: List) { - var partIndex = 0; - var parts = StringWrapper.split(cssText, _importRe); - - if (parts.length === 1) { - // no @import rule found, return the original css - return cssText; - } - - var promises = []; - - while (partIndex < parts.length - 1) { - // prefix is the content before the @import rule - var prefix = parts[partIndex]; - // rule is the parameter of the @import rule - var rule = parts[partIndex + 1]; - var url = _extractUrl(rule); - if (isPresent(url)) { - url = this._urlResolver.resolve(baseUrl, url); - } - var mediaQuery = _extractMediaQuery(rule); - var promise; - - if (isBlank(url)) { - promise = PromiseWrapper.resolve(`/* Invalid import rule: "@import ${rule};" */`); - } else if (ListWrapper.contains(inlinedUrls, url)) { - // The current import rule has already been inlined, return the prefix only - // Importing again might cause a circular dependency - promise = PromiseWrapper.resolve(prefix); - } else { - ListWrapper.push(inlinedUrls, url); - promise = PromiseWrapper.then( - this._xhr.get(url), - (css) => { - // resolve nested @import rules - css = this._inlineImports(css, url, inlinedUrls); - if (PromiseWrapper.isPromise(css)) { - // wait until nested @import are inlined - return css.then((css) => { - return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n' - }) ; - } else { - // there are no nested @import, return the css - return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n'; - } - }, - (error) => `/* failed to import ${url} */\n` - ); - } - ListWrapper.push(promises, promise); - partIndex += 2; - } - - return PromiseWrapper.all(promises).then(function (cssParts) { - var cssText = cssParts.join(''); - if (partIndex < parts.length) { - // append then content located after the last @import rule - cssText += parts[partIndex]; - } - return cssText; - }); - } - - _transformImportedCss(css: string, mediaQuery: string, url: string): string { - css = this._styleUrlResolver.resolveUrls(css, url); - return _wrapInMediaRule(css, mediaQuery); - } -} - -// Extracts the url from an import rule, supported formats: -// - 'url' / "url", -// - url(url) / url('url') / url("url") -function _extractUrl(importRule: string): string { - var match = RegExpWrapper.firstMatch(_urlRe, importRule); - if (isBlank(match)) return null; - return isPresent(match[1]) ? match[1] : match[2]; -} - -// Extracts the media query from an import rule. -// Returns null when there is no media query. -function _extractMediaQuery(importRule: string): string { - var match = RegExpWrapper.firstMatch(_mediaQueryRe, importRule); - if (isBlank(match)) return null; - var mediaQuery = match[1].trim(); - return (mediaQuery.length > 0) ? mediaQuery: null; -} - -// Wraps the css in a media rule when the media query is not null -function _wrapInMediaRule(css: string, query: string): string { - return (isBlank(query)) ? css : `@media ${query} {\n${css}\n}`; -} - -var _importRe = RegExpWrapper.create('@import\\s+([^;]+);'); -var _urlRe = RegExpWrapper.create( - 'url\\(\\s*?[\'"]?([^\'")]+)[\'"]?|' + // url(url) or url('url') or url("url") - '[\'"]([^\'")]+)[\'"]' // "url" or 'url' -); -var _mediaQueryRe = RegExpWrapper.create('[\'"][^\'"]+[\'"]\\s*\\)?\\s*(.*)'); diff --git a/modules/angular2/src/render/dom/shadow_dom/style_url_resolver.js b/modules/angular2/src/render/dom/shadow_dom/style_url_resolver.js deleted file mode 100644 index c0c5cbdc78..0000000000 --- a/modules/angular2/src/render/dom/shadow_dom/style_url_resolver.js +++ /dev/null @@ -1,38 +0,0 @@ -// Some of the code comes from WebComponents.JS -// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js - -import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang'; -import {UrlResolver} from 'angular2/src/services/url_resolver'; - -/** - * Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL. - */ -export class StyleUrlResolver { - _resolver: UrlResolver; - - constructor(resolver: UrlResolver) { - this._resolver = resolver; - } - - resolveUrls(cssText: string, baseUrl: string) { - cssText = this._replaceUrls(cssText, _cssUrlRe, baseUrl); - cssText = this._replaceUrls(cssText, _cssImportRe, baseUrl); - return cssText; - } - - _replaceUrls(cssText: string, re: RegExp, baseUrl: string) { - return StringWrapper.replaceAllMapped(cssText, re, (m) => { - var pre = m[1]; - var url = StringWrapper.replaceAll(m[2], _quoteRe, ''); - var post = m[3]; - - var resolvedUrl = this._resolver.resolve(baseUrl, url); - - return pre + "'" + resolvedUrl + "'" + post; - }); - } -} - -var _cssUrlRe = RegExpWrapper.create('(url\\()([^)]*)(\\))'); -var _cssImportRe = RegExpWrapper.create('(@import[\\s]+(?!url\\())[\'"]([^\'"]*)[\'"](.*;)'); -var _quoteRe = RegExpWrapper.create('[\'"]'); diff --git a/modules/angular2/test/render/dom/compiler/property_setter_factory_spec.js b/modules/angular2/test/render/dom/compiler/property_setter_factory_spec.js deleted file mode 100644 index 983f4b462c..0000000000 --- a/modules/angular2/test/render/dom/compiler/property_setter_factory_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib'; -import {setterFactory} from 'angular2/src/render/dom/compiler/property_setter_factory'; -import {DOM} from 'angular2/src/dom/dom_adapter'; - -export function main() { - var div; - beforeEach( () => { - div = el('
'); - }); - describe('property setter factory', () => { - - it('should return a setter for a property', () => { - var setterFn = setterFactory('title'); - setterFn(div, 'Hello'); - expect(div.title).toEqual('Hello'); - - var otherSetterFn = setterFactory('title'); - expect(setterFn).toBe(otherSetterFn); - }); - - it('should return a setter for an attribute', () => { - var setterFn = setterFactory('attr.role'); - setterFn(div, 'button'); - expect(DOM.getAttribute(div, 'role')).toEqual('button'); - setterFn(div, null); - expect(DOM.getAttribute(div, 'role')).toEqual(null); - expect(() => { - setterFn(div, 4); - }).toThrowError("Invalid role attribute, only string values are allowed, got '4'"); - - var otherSetterFn = setterFactory('attr.role'); - expect(setterFn).toBe(otherSetterFn); - }); - - it('should return a setter for a class', () => { - var setterFn = setterFactory('class.active'); - setterFn(div, true); - expect(DOM.hasClass(div, 'active')).toEqual(true); - setterFn(div, false); - expect(DOM.hasClass(div, 'active')).toEqual(false); - - var otherSetterFn = setterFactory('class.active'); - expect(setterFn).toBe(otherSetterFn); - }); - - it('should return a setter for a style', () => { - var setterFn = setterFactory('style.width'); - setterFn(div, '40px'); - expect(DOM.getStyle(div, 'width')).toEqual('40px'); - setterFn(div, null); - expect(DOM.getStyle(div, 'width')).toEqual(''); - - var otherSetterFn = setterFactory('style.width'); - expect(setterFn).toBe(otherSetterFn); - }); - - it('should return a setter for a style with a unit', () => { - var setterFn = setterFactory('style.height.px'); - setterFn(div, 40); - expect(DOM.getStyle(div, 'height')).toEqual('40px'); - setterFn(div, null); - expect(DOM.getStyle(div, 'height')).toEqual(''); - - var otherSetterFn = setterFactory('style.height.px'); - expect(setterFn).toBe(otherSetterFn); - }); - - it('should return a setter for innerHtml', () => { - var setterFn = setterFactory('innerHtml'); - setterFn(div, ''); - expect(DOM.getInnerHTML(div)).toEqual(''); - - var otherSetterFn = setterFactory('innerHtml'); - expect(setterFn).toBe(otherSetterFn); - }); - - }); -} diff --git a/modules/angular2/test/render/dom/compiler/selector_spec.js b/modules/angular2/test/render/dom/compiler/selector_spec.js deleted file mode 100644 index b0a0555cf4..0000000000 --- a/modules/angular2/test/render/dom/compiler/selector_spec.js +++ /dev/null @@ -1,282 +0,0 @@ -import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {SelectorMatcher} from 'angular2/src/render/dom/compiler/selector'; -import {CssSelector} from 'angular2/src/render/dom/compiler/selector'; -import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; - -export function main() { - describe('SelectorMatcher', () => { - var matcher, matched, selectableCollector, s1, s2, s3, s4; - - function reset() { - matched = ListWrapper.create(); - } - - beforeEach(() => { - reset(); - s1 = s2 = s3 = s4 = null; - selectableCollector = (selector, context) => { - ListWrapper.push(matched, selector); - ListWrapper.push(matched, context); - } - matcher = new SelectorMatcher(); - }); - - it('should select by element name case insensitive', () => { - matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1); - - expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - }); - - it('should select by class name case insensitive', () => { - matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1); - matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2); - - expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - - reset(); - expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1,s2[0],2]); - }); - - it('should select by attr name case insensitive independent of the value', () => { - matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1); - matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); - - expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - - reset(); - expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - - reset(); - expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1,s2[0],2]); - }); - - it('should select by attr name only once if the value is from the DOM', () => { - matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1); - - var elementSelector = new CssSelector(); - var element = el('
'); - var empty = DOM.getAttribute(element, 'attr'); - elementSelector.addAttribute('some-decor', empty); - matcher.match(elementSelector, selectableCollector); - expect(matched).toEqual([s1[0],1]); - }); - - it('should select by attr name and value case insensitive', () => { - matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1); - - expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - }); - - it('should select by element name, class name and attribute name with value', () => { - matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1); - - expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - - expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - }); - - it('should select by many attributes and independent of the value', () => { - matcher.addSelectables(s1 = CssSelector.parse('input[type=text][control]'), 1); - - var cssSelector = new CssSelector(); - cssSelector.setElement('input'); - cssSelector.addAttribute('type', 'text'); - cssSelector.addAttribute('control', 'one'); - - expect(matcher.match(cssSelector, selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0], 1]); - }); - - it('should select independent of the order in the css selector', () => { - matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1); - matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2); - matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3); - matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4); - - expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1,s2[0],2]); - - reset(); - expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1,s2[0],2]); - - reset(); - expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s3[0],3,s4[0],4]); - - reset(); - expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s4[0],4,s3[0],3]); - }); - - it('should not select with a matching :not selector', () => { - matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1); - matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2); - matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3); - matcher.addSelectables(CssSelector.parse(':not(p)'), 4); - matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5); - - expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)).toEqual(false); - expect(matched).toEqual([]); - }); - - it('should select with a non matching :not selector', () => { - matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1); - matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2); - matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3); - matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4); - - expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1,s2[0],2,s3[0],3,s4[0],4]); - }); - - it('should select with one match in a list', () => { - matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1); - - expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[1],1]); - - reset(); - expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)).toEqual(true); - expect(matched).toEqual([s1[0],1]); - }); - - it('should not select twice with two matches in a list', () => { - matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1); - - expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)).toEqual(true); - expect(matched.length).toEqual(2); - expect(matched).toEqual([s1[0],1]); - }); - }); - - describe('CssSelector.parse', () => { - it('should detect element names', () => { - var cssSelector = CssSelector.parse('sometag')[0]; - expect(cssSelector.element).toEqual('sometag'); - expect(cssSelector.toString()).toEqual('sometag'); - }); - - it('should detect class names', () => { - var cssSelector = CssSelector.parse('.someClass')[0]; - expect(cssSelector.classNames).toEqual(['someclass']); - - expect(cssSelector.toString()).toEqual('.someclass'); - }); - - it('should detect attr names', () => { - var cssSelector = CssSelector.parse('[attrname]')[0]; - expect(cssSelector.attrs).toEqual(['attrname', '']); - - expect(cssSelector.toString()).toEqual('[attrname]'); - }); - - it('should detect attr values', () => { - var cssSelector = CssSelector.parse('[attrname=attrvalue]')[0]; - expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); - expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); - }); - - it('should detect multiple parts', () => { - var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0]; - expect(cssSelector.element).toEqual('sometag'); - expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); - expect(cssSelector.classNames).toEqual(['someclass']); - - expect(cssSelector.toString()).toEqual('sometag.someclass[attrname=attrvalue]'); - }); - - it('should detect multiple attributes', () => { - var cssSelector = CssSelector.parse('input[type=text][control]')[0]; - expect(cssSelector.element).toEqual('input'); - expect(cssSelector.attrs).toEqual(['type', 'text', 'control', '']); - - expect(cssSelector.toString()).toEqual('input[type=text][control]'); - }); - - it('should detect :not', () => { - var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)')[0]; - expect(cssSelector.element).toEqual('sometag'); - expect(cssSelector.attrs.length).toEqual(0); - expect(cssSelector.classNames.length).toEqual(0); - - var notSelector = cssSelector.notSelector; - expect(notSelector.element).toEqual(null); - expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']); - expect(notSelector.classNames).toEqual(['someclass']); - - expect(cssSelector.toString()).toEqual('sometag:not(.someclass[attrname=attrvalue])'); - }); - - it('should detect :not without truthy', () => { - var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0]; - expect(cssSelector.element).toEqual("*"); - - var notSelector = cssSelector.notSelector; - expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']); - expect(notSelector.classNames).toEqual(['someclass']); - - expect(cssSelector.toString()).toEqual('*:not(.someclass[attrname=attrvalue])'); - }); - - it('should throw when nested :not', () => { - expect(() => { - CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0]; - }).toThrowError('Nesting :not is not allowed in a selector'); - }); - - it('should detect lists of selectors', () => { - var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag'); - expect(cssSelectors.length).toEqual(3); - - expect(cssSelectors[0].classNames).toEqual(['someclass']); - expect(cssSelectors[1].attrs).toEqual(['attrname', 'attrvalue']); - expect(cssSelectors[2].element).toEqual('sometag'); - }); - - it('should detect lists of selectors with :not', () => { - var cssSelectors = CssSelector.parse('input[type=text], :not(textarea), textbox:not(.special)'); - expect(cssSelectors.length).toEqual(3); - - expect(cssSelectors[0].element).toEqual('input'); - expect(cssSelectors[0].attrs).toEqual(['type', 'text']); - - expect(cssSelectors[1].element).toEqual('*'); - expect(cssSelectors[1].notSelector.element).toEqual('textarea'); - - expect(cssSelectors[2].element).toEqual('textbox'); - expect(cssSelectors[2].notSelector.classNames).toEqual(['special']); - }); - }); -} \ No newline at end of file