fix(compiler): handle `:host-context` and `:host` in the same selector (#40494)
In `ViewEncapsulation.Emulated` mode the compiler converts `:host` and `:host-context` pseudo classes into new CSS selectors. Previously, when there was both `:host-context` and `:host` classes in a selector, the compiler was generating incorrect selectors. There are two scenarios: * Both classes are on the same element (i.e. not separated). E.g. `:host-context(.foo):host(.bar)`. This setup should only match the host element if it has both `foo` and `bar` classes. So the generated CSS selector should be: `.foo.bar<hostmarker>`. * The `:host` class is on a descendant of the `:host-context`. E.g. `:host-context(.foo) :host(.bar)`. This setup should only match the `.foo` selector if it is a proper ancestor of the host (and not on the host itself). So the generated CSS selector should be: `.foo .bar<hostmarker>`. This commit fixes the generation to handle these scenarios. Fixes #14349 PR Close #40494
This commit is contained in:
parent
ba3f99d7cc
commit
679c3bf7ea
|
@ -314,7 +314,7 @@ export class ShadowCss {
|
|||
|
||||
// The context selectors now must be combined with each other to capture all the possible
|
||||
// selectors that `:host-context` can match.
|
||||
return combineHostContextSelectors(_polyfillHostNoCombinator, contextSelectors, selectorText);
|
||||
return combineHostContextSelectors(contextSelectors, selectorText);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -682,8 +682,10 @@ function escapeBlocks(
|
|||
* @param contextSelectors an array of context selectors that will be combined.
|
||||
* @param otherSelectors the rest of the selectors that are not context selectors.
|
||||
*/
|
||||
function combineHostContextSelectors(
|
||||
hostMarker: string, contextSelectors: string[], otherSelectors: string): string {
|
||||
function combineHostContextSelectors(contextSelectors: string[], otherSelectors: string): string {
|
||||
const hostMarker = _polyfillHostNoCombinator;
|
||||
const otherSelectorsHasHost = _polyfillHostRe.test(otherSelectors);
|
||||
|
||||
// If there are no context selectors then just output a host marker
|
||||
if (contextSelectors.length === 0) {
|
||||
return hostMarker + otherSelectors;
|
||||
|
@ -706,6 +708,9 @@ function combineHostContextSelectors(
|
|||
// Finally connect the selector to the `hostMarker`s: either acting directly on the host
|
||||
// (A<hostMarker>) or as an ancestor (A <hostMarker>).
|
||||
return combined
|
||||
.map(s => `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`)
|
||||
.map(
|
||||
s => otherSelectorsHasHost ?
|
||||
`${s}${otherSelectors}` :
|
||||
`${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`)
|
||||
.join(',');
|
||||
}
|
||||
|
|
|
@ -260,6 +260,21 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
|
|||
});
|
||||
});
|
||||
|
||||
describe((':host-context and :host combination selector'), () => {
|
||||
it('should handle selectors on the same element', () => {
|
||||
expect(s(':host-context(div):host(.x) > .y {}', 'contenta', 'a-host'))
|
||||
.toEqual('div.x[a-host] > .y[contenta] {}');
|
||||
});
|
||||
|
||||
it('should handle selectors on different elements', () => {
|
||||
expect(s(':host-context(div) :host(.x) > .y {}', 'contenta', 'a-host'))
|
||||
.toEqual('div .x[a-host] > .y[contenta] {}');
|
||||
|
||||
expect(s(':host-context(div) > :host(.x) > .y {}', 'contenta', 'a-host'))
|
||||
.toEqual('div > .x[a-host] > .y[contenta] {}');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support polyfill-next-selector', () => {
|
||||
let css = s('polyfill-next-selector {content: \'x > y\'} z {}', 'contenta');
|
||||
expect(css).toEqual('x[contenta] > y[contenta]{}');
|
||||
|
|
Loading…
Reference in New Issue