Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions modules/@angular/compiler/src/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,15 +209,16 @@ 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);
cssText = this._convertShadowDOMSelectors(cssText);
if (scopeSelector) {
cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector);
}
cssText = cssText + '\n' + unscoped;
cssText = cssText + '\n' + unscopedRules;
return cssText.trim();
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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'
Expand Down Expand Up @@ -420,38 +420,38 @@ export class ShadowCss {
return scopedP;
};

const sep = /( |>|\+|~(?!=)|\[|\])\s*/g;
const scopeAfter = selector.indexOf(_polyfillHostNoCombinator);
let attrSelectorIndex = 0;
const attrSelectors: string[] = [];

// 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 scoped = '';
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 {
Expand Down
67 changes: 49 additions & 18 deletions modules/@angular/compiler/test/shadow_css_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] {}');
});

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] {}');
});

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 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', () => {
Expand All @@ -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', () => {
Expand Down