@@ -260,15 +260,29 @@ export class ShadowCss {
260260 * .foo<scopeName> > .bar
261261 */
262262 private _convertColonHost ( cssText : string ) : string {
263- return this . _convertColonRule ( cssText , _cssColonHostRe , this . _colonHostPartReplacer ) ;
263+ return cssText . replace ( _cssColonHostRe , ( _ , hostSelectors : string , otherSelectors : string ) => {
264+ if ( hostSelectors ) {
265+ const convertedSelectors : string [ ] = [ ] ;
266+ const hostSelectorArray = hostSelectors . split ( ',' ) . map ( p => p . trim ( ) ) ;
267+ for ( const hostSelector of hostSelectorArray ) {
268+ if ( ! hostSelector ) break ;
269+ const convertedSelector =
270+ _polyfillHostNoCombinator + hostSelector . replace ( _polyfillHost , '' ) + otherSelectors ;
271+ convertedSelectors . push ( convertedSelector ) ;
272+ }
273+ return convertedSelectors . join ( ',' ) ;
274+ } else {
275+ return _polyfillHostNoCombinator + otherSelectors ;
276+ }
277+ } ) ;
264278 }
265279
266280 /*
267281 * convert a rule like :host-context(.foo) > .bar { }
268282 *
269283 * to
270284 *
271- * .foo<scopeName> > .bar, .foo scopeName > .bar { }
285+ * .foo<scopeName> > .bar, .foo < scopeName> > .bar { }
272286 *
273287 * and
274288 *
@@ -279,38 +293,26 @@ export class ShadowCss {
279293 * .foo<scopeName> .bar { ... }
280294 */
281295 private _convertColonHostContext ( cssText : string ) : string {
282- return this . _convertColonRule (
283- cssText , _cssColonHostContextRe , this . _colonHostContextPartReplacer ) ;
284- }
285-
286- private _convertColonRule ( cssText : string , regExp : RegExp , partReplacer : Function ) : string {
287- // m[1] = :host(-context), m[2] = contents of (), m[3] rest of rule
288- return cssText . replace ( regExp , function ( ...m : string [ ] ) {
289- if ( m [ 2 ] ) {
290- const parts = m [ 2 ] . split ( ',' ) ;
291- const r : string [ ] = [ ] ;
292- for ( let i = 0 ; i < parts . length ; i ++ ) {
293- const p = parts [ i ] . trim ( ) ;
294- if ( ! p ) break ;
295- r . push ( partReplacer ( _polyfillHostNoCombinator , p , m [ 3 ] ) ) ;
296- }
297- return r . join ( ',' ) ;
298- } else {
299- return _polyfillHostNoCombinator + m [ 3 ] ;
296+ return cssText . replace ( _cssColonHostContextReGlobal , selectorText => {
297+ // We have captured a selector that contains a `:host-context` rule.
298+ // There may be more than one so `selectorText` could look like:
299+ // `:host-context(.one):host-context(.two)`.
300+
301+ const contextSelectors : string [ ] = [ ] ;
302+ let match : RegExpMatchArray | null ;
303+
304+ // Execute `_cssColonHostContextRe` over and over until we have extracted all the
305+ // `:host-context` selectors from this selector.
306+ while ( match = _cssColonHostContextRe . exec ( selectorText ) ) {
307+ // `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>]
308+ contextSelectors . push ( match [ 1 ] . trim ( ) ) ;
309+ selectorText = match [ 2 ] ;
300310 }
301- } ) ;
302- }
303-
304- private _colonHostContextPartReplacer ( host : string , part : string , suffix : string ) : string {
305- if ( part . indexOf ( _polyfillHost ) > - 1 ) {
306- return this . _colonHostPartReplacer ( host , part , suffix ) ;
307- } else {
308- return host + part + suffix + ', ' + part + ' ' + host + suffix ;
309- }
310- }
311311
312- private _colonHostPartReplacer ( host : string , part : string , suffix : string ) : string {
313- return host + part . replace ( _polyfillHost , '' ) + suffix ;
312+ // The context selectors now must be combined with each other to capture all the possible
313+ // selectors that `:host-context` can match.
314+ return combineHostContextSelectors ( _polyfillHostNoCombinator , contextSelectors , selectorText ) ;
315+ } ) ;
314316 }
315317
316318 /*
@@ -534,11 +536,12 @@ const _cssContentUnscopedRuleRe =
534536const _polyfillHost = '-shadowcsshost' ;
535537// note: :host-context pre-processed to -shadowcsshostcontext.
536538const _polyfillHostContext = '-shadowcsscontext' ;
537- const _parenSuffix = ') (?:\\((' +
539+ const _parenSuffix = '(?:\\((' +
538540 '(?:\\([^)(]*\\)|[^)(]*)+?' +
539541 ')\\))?([^,{]*)' ;
540- const _cssColonHostRe = new RegExp ( '(' + _polyfillHost + _parenSuffix , 'gim' ) ;
541- const _cssColonHostContextRe = new RegExp ( '(' + _polyfillHostContext + _parenSuffix , 'gim' ) ;
542+ const _cssColonHostRe = new RegExp ( _polyfillHost + _parenSuffix , 'gim' ) ;
543+ const _cssColonHostContextReGlobal = new RegExp ( _polyfillHostContext + _parenSuffix , 'gim' ) ;
544+ const _cssColonHostContextRe = new RegExp ( _polyfillHostContext + _parenSuffix , 'im' ) ;
542545const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator' ;
543546const _polyfillHostNoCombinatorRe = / - s h a d o w c s s h o s t - n o - c o m b i n a t o r ( [ ^ \s ] * ) / ;
544547const _shadowDOMSelectorsRe = [
@@ -650,3 +653,51 @@ function escapeBlocks(
650653 }
651654 return new StringWithEscapedBlocks ( resultParts . join ( '' ) , escapedBlocks ) ;
652655}
656+
657+ /**
658+ * Combine the `contextSelectors` with the `hostMarker` and the `otherSelectors`
659+ * to create a selector that matches the same as `:host-context()`.
660+ *
661+ * Given a single context selector `A` we need to output selectors that match on the host and as an
662+ * ancestor of the host:
663+ *
664+ * ```
665+ * A <hostMarker>, A<hostMarker> {}
666+ * ```
667+ *
668+ * When there is more than one context selector we also have to create combinations of those
669+ * selectors with each other. For example if there are `A` and `B` selectors the output is:
670+ *
671+ * ```
672+ * AB<hostMarker>, AB <hostMarker>, A B<hostMarker>,
673+ * B A<hostMarker>, A B <hostMarker>, B A <hostMarker> {}
674+ * ```
675+ *
676+ * And so on...
677+ *
678+ * @param hostMarker the string that selects the host element.
679+ * @param contextSelectors an array of context selectors that will be combined.
680+ * @param otherSelectors the rest of the selectors that are not context selectors.
681+ */
682+ function combineHostContextSelectors (
683+ hostMarker : string , contextSelectors : string [ ] , otherSelectors : string ) : string {
684+ const combined : string [ ] = [ contextSelectors . pop ( ) || '' ] ;
685+ while ( contextSelectors . length > 0 ) {
686+ const length = combined . length ;
687+ const contextSelector = contextSelectors . pop ( ) ;
688+ for ( let i = 0 ; i < length ; i ++ ) {
689+ const previousSelectors = combined [ i ] ;
690+ // Add the new selector as a descendant of the previous selectors
691+ combined [ length * 2 + i ] = previousSelectors + ' ' + contextSelector ;
692+ // Add the new selector as an ancestor of the previous selectors
693+ combined [ length + i ] = contextSelector + ' ' + previousSelectors ;
694+ // Add the new selector to act on the same element as the previous selectors
695+ combined [ i ] = contextSelector + previousSelectors ;
696+ }
697+ }
698+ // Finally connect the selector to the `hostMarker`s: either acting directly on the host
699+ // (A<hostMarker>) or as an ancestor (A <hostMarker>).
700+ return combined
701+ . map ( s => `${ s } ${ hostMarker } ${ otherSelectors } , ${ s } ${ hostMarker } ${ otherSelectors } ` )
702+ . join ( ',' ) ;
703+ }
0 commit comments