From 679c3bf7ead2b54b6653151c59eb9462fe59d711 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 24 Jan 2021 21:13:25 +0000 Subject: [PATCH] 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`. * 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`. This commit fixes the generation to handle these scenarios. Fixes #14349 PR Close #40494 --- packages/compiler/src/shadow_css.ts | 13 +++++++++---- packages/compiler/test/shadow_css_spec.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index 0ca0858a25..2b8ec4eb5d 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -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) or as an ancestor (A ). return combined - .map(s => `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`) + .map( + s => otherSelectorsHasHost ? + `${s}${otherSelectors}` : + `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`) .join(','); } diff --git a/packages/compiler/test/shadow_css_spec.ts b/packages/compiler/test/shadow_css_spec.ts index f652706ec3..ac0560f82b 100644 --- a/packages/compiler/test/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css_spec.ts @@ -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]{}');