diff --git a/modules/@angular/compiler/src/shadow_css.ts b/modules/@angular/compiler/src/shadow_css.ts index 71d0c33bc7..ddc30555e3 100644 --- a/modules/@angular/compiler/src/shadow_css.ts +++ b/modules/@angular/compiler/src/shadow_css.ts @@ -209,7 +209,8 @@ export class ShadowCss { * scopeName .foo { ... } */ private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { - const unscoped = this._extractUnscopedRulesFromCssText(cssText); + const unscopedRules = this._extractUnscopedRulesFromCssText(cssText); + // replace :host and :host-context -shadowcsshost and -shadowcsshost respectively cssText = this._insertPolyfillHostInCssText(cssText); cssText = this._convertColonHost(cssText); cssText = this._convertColonHostContext(cssText); @@ -217,7 +218,7 @@ export class ShadowCss { if (scopeSelector) { cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector); } - cssText = cssText + '\n' + unscoped; + cssText = cssText + '\n' + unscopedRules; return cssText.trim(); } @@ -280,15 +281,15 @@ export class ShadowCss { } private _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string { - // m[1] = :host, m[2] = contents of (), m[3] rest of rule + // m[1] = :host(-context), m[2] = contents of (), m[3] rest of rule return cssText.replace(regExp, function(...m: string[]) { if (m[2]) { const parts = m[2].split(','); const r: string[] = []; for (let i = 0; i < parts.length; i++) { - let p = parts[i]; + let p = parts[i].trim(); if (!p) break; - r.push(partReplacer(_polyfillHostNoCombinator, p.trim(), m[3])); + r.push(partReplacer(_polyfillHostNoCombinator, p, m[3])); } return r.join(','); } else { @@ -314,8 +315,7 @@ export class ShadowCss { * by replacing with space. */ private _convertShadowDOMSelectors(cssText: string): string { - return _shadowDOMSelectorsRe.reduce( - (result, pattern) => { return result.replace(pattern, ' '); }, cssText); + return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText); } // change a selector like 'div' to 'name div' @@ -420,38 +420,38 @@ export class ShadowCss { return scopedP; }; - const sep = /( |>|\+|~(?!=)|\[|\])\s*/g; - const scopeAfter = selector.indexOf(_polyfillHostNoCombinator); + let attrSelectorIndex = 0; + const attrSelectors: string[] = []; - let scoped = ''; + // replace attribute selectors with placeholders to avoid issue with white space being treated + // as separator + selector = selector.replace(/\[[^\]]*\]/g, (attrSelector) => { + const replaceBy = `__attr_sel_${attrSelectorIndex}__`; + attrSelectors.push(attrSelector); + attrSelectorIndex++; + return replaceBy; + }); + + let scopedSelector = ''; let startIndex = 0; let res: RegExpExecArray; - let inAttributeSelector: boolean = false; + const sep = /( |>|\+|~(?!=))\s*/g; + const scopeAfter = selector.indexOf(_polyfillHostNoCombinator); while ((res = sep.exec(selector)) !== null) { const separator = res[1]; - if (separator === '[') { - inAttributeSelector = true; - scoped += selector.slice(startIndex, res.index).trim() + '['; - startIndex = sep.lastIndex; - } - if (!inAttributeSelector) { - const part = selector.slice(startIndex, res.index).trim(); - // if a selector appears before :host-context it should not be shimmed as it - // matches on ancestor elements and not on elements in the host's shadow - const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; - scoped += `${scopedPart} ${separator} `; - startIndex = sep.lastIndex; - } else if (separator === ']') { - const part = selector.slice(startIndex, res.index).trim() + ']'; - const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; - scoped += `${scopedPart} `; - startIndex = sep.lastIndex; - inAttributeSelector = false; - } + const part = selector.slice(startIndex, res.index).trim(); + // if a selector appears before :host-context it should not be shimmed as it + // matches on ancestor elements and not on elements in the host's shadow + const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; + scopedSelector += `${scopedPart} ${separator} `; + startIndex = sep.lastIndex; } - return scoped + _scopeSelectorPart(selector.substring(startIndex)); + scopedSelector += _scopeSelectorPart(selector.substring(startIndex)); + + // replace the placeholders with their original values + return scopedSelector.replace(/__attr_sel_(\d+)__/g, (ph, index) => attrSelectors[+index]); } private _insertPolyfillHostInCssText(selector: string): string { diff --git a/modules/@angular/compiler/test/shadow_css_spec.ts b/modules/@angular/compiler/test/shadow_css_spec.ts index 30a4455d14..6afb87de01 100644 --- a/modules/@angular/compiler/test/shadow_css_spec.ts +++ b/modules/@angular/compiler/test/shadow_css_spec.ts @@ -108,30 +108,61 @@ export function main() { expect(s('[is="one"] {}', 'a')).toEqual('[is="one"][a] {}'); }); - it('should handle :host', () => { - expect(s(':host {}', 'a', 'a-host')).toEqual('[a-host] {}'); + describe((':host'), () => { + it('should handle no context', + () => { expect(s(':host {}', 'a', 'a-host')).toEqual('[a-host] {}'); }); - expect(s(':host(.x) {}', 'a', 'a-host')).toEqual('.x[a-host] {}'); - expect(s(':host(ul) {}', 'a', 'a-host')).toEqual('ul[a-host] {}'); + it('should handle tag selector', () => { + expect(s(':host(ul) {}', 'a', 'a-host')).toEqual('ul[a-host] {}'); - expect(s(':host(.x,.y) {}', 'a', 'a-host')).toEqual('.x[a-host], .y[a-host] {}'); - expect(s(':host(ul,li) {}', 'a', 'a-host')).toEqual('ul[a-host], li[a-host] {}'); + }); - expect(s(':host(.x,.y) > .z {}', 'a', 'a-host')) - .toEqual('.x[a-host] > .z[a], .y[a-host] > .z[a] {}'); - expect(s(':host(ul,li) > .z {}', 'a', 'a-host')) - .toEqual('ul[a-host] > .z[a], li[a-host] > .z[a] {}'); + it('should handle class selector', + () => { expect(s(':host(.x) {}', 'a', 'a-host')).toEqual('.x[a-host] {}'); }); + + it('should handle attribute selector', () => { + expect(s(':host([a="b"]) {}', 'a', 'a-host')).toEqual('[a="b"][a-host] {}'); + expect(s(':host([a=b]) {}', 'a', 'a-host')).toEqual('[a="b"][a-host] {}'); + }); + + it('should handle multiple tag selectors', () => { + expect(s(':host(ul,li) {}', 'a', 'a-host')).toEqual('ul[a-host], li[a-host] {}'); + expect(s(':host(ul,li) > .z {}', 'a', 'a-host')) + .toEqual('ul[a-host] > .z[a], li[a-host] > .z[a] {}'); + }); + + it('should handle multiple class selectors', () => { + expect(s(':host(.x,.y) {}', 'a', 'a-host')).toEqual('.x[a-host], .y[a-host] {}'); + expect(s(':host(.x,.y) > .z {}', 'a', 'a-host')) + .toEqual('.x[a-host] > .z[a], .y[a-host] > .z[a] {}'); + }); + + it('should handle multiple attribute selectors', () => { + expect(s(':host([a="b"],[c=d]) {}', 'a', 'a-host')) + .toEqual('[a="b"][a-host], [c="d"][a-host] {}'); + }); }); - it('should handle :host-context', () => { - expect(s(':host-context(.x) {}', 'a', 'a-host')).toEqual('.x[a-host], .x [a-host] {}'); - expect(s(':host-context(div) {}', 'a', 'a-host')).toEqual('div[a-host], div [a-host] {}'); + describe((':host-context'), () => { + it('should handle tag selector', () => { + expect(s(':host-context(div) {}', 'a', 'a-host')).toEqual('div[a-host], div [a-host] {}'); + expect(s(':host-context(ul) > .y {}', 'a', 'a-host')) + .toEqual('ul[a-host] > .y[a], ul [a-host] > .y[a] {}'); + }); - expect(s(':host-context(.x) > .y {}', 'a', 'a-host')) - .toEqual('.x[a-host] > .y[a], .x [a-host] > .y[a] {}'); - expect(s(':host-context(ul) > .y {}', 'a', 'a-host')) - .toEqual('ul[a-host] > .y[a], ul [a-host] > .y[a] {}'); + it('should handle class selector', () => { + expect(s(':host-context(.x) {}', 'a', 'a-host')).toEqual('.x[a-host], .x [a-host] {}'); + expect(s(':host-context(.x) > .y {}', 'a', 'a-host')) + .toEqual('.x[a-host] > .y[a], .x [a-host] > .y[a] {}'); + }); + + it('should handle attribute selector', () => { + expect(s(':host-context([a="b"]) {}', 'a', 'a-host')) + .toEqual('[a="b"][a-host], [a="b"] [a-host] {}'); + expect(s(':host-context([a=b]) {}', 'a', 'a-host')) + .toEqual('[a=b][a-host], [a="b"] [a-host] {}'); + }); }); it('should support polyfill-next-selector', () => { @@ -142,7 +173,7 @@ export function main() { expect(css).toEqual('x[a] > y[a]{}'); css = s(`polyfill-next-selector {content: 'button[priority="1"]'} z {}`, 'a'); - expect(css).toEqual('button[priority="1"][a] {}'); + expect(css).toEqual('button[priority="1"][a]{}'); }); it('should support polyfill-unscoped-rule', () => {