feat(ShadowCss): Make the shim also accept a selector for the host

This commit is contained in:
Victor Berchet 2015-02-18 19:14:19 +01:00 committed by Misko Hevery
parent d67f0299cd
commit 5111f9ae37
2 changed files with 33 additions and 27 deletions

View File

@ -153,18 +153,22 @@ export class ShadowCss {
* Shim a style element with the given selector. Returns cssText that can * Shim a style element with the given selector. Returns cssText that can
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css). * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
*/ */
shimStyle(style: StyleElement, selector: string): string { shimStyle(style: StyleElement, selector: string, hostSelector: string = ''): string {
var cssText = DOM.getText(style); var cssText = DOM.getText(style);
return this.shimCssText(cssText, selector); return this.shimCssText(cssText, selector, hostSelector);
} }
/* /*
* Shim some cssText with the given selector. Returns cssText that can * Shim some cssText with the given selector. Returns cssText that can
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css). * 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): string { shimCssText(cssText: string, selector: string, hostSelector: string = ''): string {
cssText = this._insertDirectives(cssText); cssText = this._insertDirectives(cssText);
return this._scopeCssText(cssText, selector); return this._scopeCssText(cssText, selector, hostSelector);
} }
_insertDirectives(cssText: string): string { _insertDirectives(cssText: string): string {
@ -226,7 +230,7 @@ export class ShadowCss {
* *
* scopeName .foo { ... } * scopeName .foo { ... }
*/ */
_scopeCssText(cssText: string, scopeSelector: string): string { _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
var unscoped = this._extractUnscopedRulesFromCssText(cssText); var unscoped = this._extractUnscopedRulesFromCssText(cssText);
cssText = this._insertPolyfillHostInCssText(cssText); cssText = this._insertPolyfillHostInCssText(cssText);
@ -235,7 +239,7 @@ export class ShadowCss {
cssText = this._convertShadowDOMSelectors(cssText); cssText = this._convertShadowDOMSelectors(cssText);
if (isPresent(scopeSelector)) { if (isPresent(scopeSelector)) {
_withCssRules(cssText, (rules) => { _withCssRules(cssText, (rules) => {
cssText = this._scopeRules(rules, scopeSelector); cssText = this._scopeRules(rules, scopeSelector, hostSelector);
}); });
} }
cssText = cssText + '\n' + unscoped; cssText = cssText + '\n' + unscoped;
@ -344,18 +348,18 @@ export class ShadowCss {
} }
// change a selector like 'div' to 'name div' // change a selector like 'div' to 'name div'
_scopeRules(cssRules, scopeSelector: string): string { _scopeRules(cssRules, scopeSelector: string, hostSelector: string): string {
var cssText = ''; var cssText = '';
if (isPresent(cssRules)) { if (isPresent(cssRules)) {
for (var i = 0; i < cssRules.length; i++) { for (var i = 0; i < cssRules.length; i++) {
var rule = cssRules[i]; var rule = cssRules[i];
if (CSSRuleWrapper.isStyleRule(rule) || CSSRuleWrapper.isPageRule(rule)) { if (CSSRuleWrapper.isStyleRule(rule) || CSSRuleWrapper.isPageRule(rule)) {
cssText += this._scopeSelector(rule.selectorText, scopeSelector, cssText += this._scopeSelector(rule.selectorText, scopeSelector, hostSelector,
this.strictStyling) + ' {\n'; this.strictStyling) + ' {\n';
cssText += this._propertiesFromRule(rule) + '\n}\n\n'; cssText += this._propertiesFromRule(rule) + '\n}\n\n';
} else if (CSSRuleWrapper.isMediaRule(rule)) { } else if (CSSRuleWrapper.isMediaRule(rule)) {
cssText += '@media ' + rule.media.mediaText + ' {\n'; cssText += '@media ' + rule.media.mediaText + ' {\n';
cssText += this._scopeRules(rule.cssRules, scopeSelector); cssText += this._scopeRules(rule.cssRules, scopeSelector, hostSelector);
cssText += '\n}\n\n'; cssText += '\n}\n\n';
} else { } else {
// KEYFRAMES_RULE in IE throws when we query cssText // KEYFRAMES_RULE in IE throws when we query cssText
@ -388,7 +392,8 @@ export class ShadowCss {
return cssText; return cssText;
} }
_scopeSelector(selector: string, scopeSelector: string, strict: boolean): string { _scopeSelector(selector: string, scopeSelector: string, hostSelector: string,
strict: boolean): string {
var r = [], parts = selector.split(','); var r = [], parts = selector.split(',');
for (var i = 0; i < parts.length; i++) { for (var i = 0; i < parts.length; i++) {
var p = parts[i]; var p = parts[i];
@ -396,7 +401,7 @@ export class ShadowCss {
if (this._selectorNeedsScoping(p, scopeSelector)) { if (this._selectorNeedsScoping(p, scopeSelector)) {
p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ? p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ?
this._applyStrictSelectorScope(p, scopeSelector) : this._applyStrictSelectorScope(p, scopeSelector) :
this._applySelectorScope(p, scopeSelector); this._applySelectorScope(p, scopeSelector, hostSelector);
} }
ListWrapper.push(r, p); ListWrapper.push(r, p);
} }
@ -416,16 +421,17 @@ export class ShadowCss {
return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm'); return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
} }
_applySelectorScope(selector: string, scopeSelector: string): string { _applySelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
// Difference from webcomponentsjs: scopeSelector could not be an array // Difference from webcomponentsjs: scopeSelector could not be an array
return this._applySimpleSelectorScope(selector, scopeSelector); return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector);
} }
// scope via name and [is=name] // scope via name and [is=name]
_applySimpleSelectorScope(selector: string, scopeSelector: string): string { _applySimpleSelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) { if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) {
selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, scopeSelector); var replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
return StringWrapper.replaceAll(selector, _polyfillHostRe, scopeSelector + ' '); selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy);
return StringWrapper.replaceAll(selector, _polyfillHostRe, replaceBy + ' ');
} else { } else {
return scopeSelector + ' ' + selector; return scopeSelector + ' ' + selector;
} }

View File

@ -6,9 +6,9 @@ import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
export function main() { export function main() {
describe('ShadowCss', function() { describe('ShadowCss', function() {
function s(css: string, tag:string) { function s(css: string, contentAttr:string, hostAttr:string = '') {
var shadowCss = new ShadowCss(); var shadowCss = new ShadowCss();
var shim = shadowCss.shimCssText(css, tag); var shim = shadowCss.shimCssText(css, contentAttr, hostAttr);
var nlRegexp = RegExpWrapper.create('\\n'); var nlRegexp = RegExpWrapper.create('\\n');
return StringWrapper.replaceAll(shim, nlRegexp, ''); return StringWrapper.replaceAll(shim, nlRegexp, '');
} }
@ -65,14 +65,14 @@ export function main() {
}); });
it('should handle :host', () => { it('should handle :host', () => {
expect(s(':host {}', 'a')).toEqual('a {}'); expect(s(':host {}', 'a', 'a-host')).toEqual('[a-host] {}');
expect(s(':host(.x,.y) {}', 'a')).toEqual('a.x, a.y {}'); expect(s(':host(.x,.y) {}', 'a', 'a-host')).toEqual('[a-host].x, [a-host].y {}');
expect(s(':host(.x,.y) > .z {}', 'a')).toEqual('a.x > .z, a.y > .z {}'); expect(s(':host(.x,.y) > .z {}', 'a', 'a-host')).toEqual('[a-host].x > .z, [a-host].y > .z {}');
}); });
it('should handle :host-context', () => { it('should handle :host-context', () => {
expect(s(':host-context(.x) {}', 'a')).toEqual('a.x, .x a {}'); expect(s(':host-context(.x) {}', 'a', 'a-host')).toEqual('[a-host].x, .x [a-host] {}');
expect(s(':host-context(.x) > .y {}', 'a')).toEqual('a.x > .y, .x a > .y {}'); expect(s(':host-context(.x) > .y {}', 'a', 'a-host')).toEqual('[a-host].x > .y, .x [a-host] > .y {}');
}); });
it('should support polyfill-next-selector', () => { it('should support polyfill-next-selector', () => {
@ -92,11 +92,11 @@ export function main() {
}); });
it('should support polyfill-rule', () => { it('should support polyfill-rule', () => {
var css = s("polyfill-rule {content: ':host.foo .bar';background: blue;}", 'a'); var css = s("polyfill-rule {content: ':host.foo .bar';background: blue;}", 'a', 'a-host');
expect(css).toEqual('a.foo .bar {background: blue;}'); expect(css).toEqual('[a-host].foo .bar {background: blue;}');
css = s('polyfill-rule {content: ":host.foo .bar";background: blue;}', 'a'); css = s('polyfill-rule {content: ":host.foo .bar";background: blue;}', 'a', 'a-host');
expect(css).toEqual('a.foo .bar {background: blue;}'); expect(css).toEqual('[a-host].foo .bar {background: blue;}');
}); });
it('should handle ::shadow', () => { it('should handle ::shadow', () => {