55 * Use of this source code is governed by an MIT-style license that can be 
66 * found in the LICENSE file at https://angular.dev/license 
77 */ 
8+ import  *  as  chars  from  './chars' ; 
89
910/** 
1011 * The following set contains all keywords that can be used in the animation css shorthand 
@@ -525,6 +526,40 @@ export class ShadowCss {
525526    } ) ; 
526527  } 
527528
529+   /** 
530+    * Generator function that splits a string on top-level commas (commas that are not inside parentheses). 
531+    * Yields each part of the string between top-level commas. Terminates if an extra closing paren is found. 
532+    * 
533+    * @param  text The string to split 
534+    */ 
535+   private  * _splitOnTopLevelCommas ( text : string ) : Generator < string >  { 
536+     const  length  =  text . length ; 
537+     let  parens  =  0 ; 
538+     let  prev  =  0 ; 
539+ 
540+     for  ( let  i  =  0 ;  i  <  length ;  i ++ )  { 
541+       const  charCode  =  text . charCodeAt ( i ) ; 
542+ 
543+       if  ( charCode  ===  chars . $LPAREN )  { 
544+         parens ++ ; 
545+       }  else  if  ( charCode  ===  chars . $RPAREN )  { 
546+         parens -- ; 
547+         if  ( parens  <  0 )  { 
548+           // Found an extra closing paren. Assume we want the list terminated here 
549+           yield  text . slice ( prev ,  i ) ; 
550+           return ; 
551+         } 
552+       }  else  if  ( charCode  ===  chars . $COMMA  &&  parens  ===  0 )  { 
553+         // Found a top-level comma, yield the current chunk 
554+         yield  text . slice ( prev ,  i ) ; 
555+         prev  =  i  +  1 ; 
556+       } 
557+     } 
558+ 
559+     // Yield the final chunk 
560+     yield  text . slice ( prev ) ; 
561+   } 
562+ 
528563  /* 
529564   * convert a rule like :host-context(.foo) > .bar { } 
530565   * 
@@ -541,38 +576,14 @@ export class ShadowCss {
541576   * .foo<scopeName> .bar { ... } 
542577   */ 
543578  private  _convertColonHostContext ( cssText : string ) : string  { 
544-     const  length  =  cssText . length ; 
545-     let  parens  =  0 ; 
546-     let  prev  =  0 ; 
547-     let  result  =  '' ; 
548- 
549579    // Splits up the selectors on their top-level commas, processes the :host-context in them 
550580    // individually and stitches them back together. This ensures that individual selectors don't 
551581    // affect each other. 
552-     for  ( let  i  =  0 ;  i  <  length ;  i ++ )  { 
553-       const  char  =  cssText [ i ] ; 
554- 
555-       // If we hit a comma and there are no open parentheses, take the current chunk and process it. 
556-       if  ( char  ===  ','  &&  parens  ===  0 )  { 
557-         result  +=  this . _convertColonHostContextInSelectorPart ( cssText . slice ( prev ,  i ) )  +  ',' ; 
558-         prev  =  i  +  1 ; 
559-         continue ; 
560-       } 
561- 
562-       // We've hit the end. Take everything since the last comma. 
563-       if  ( i  ===  length  -  1 )  { 
564-         result  +=  this . _convertColonHostContextInSelectorPart ( cssText . slice ( prev ) ) ; 
565-         break ; 
566-       } 
567- 
568-       if  ( char  ===  '(' )  { 
569-         parens ++ ; 
570-       }  else  if  ( char  ===  ')' )  { 
571-         parens -- ; 
572-       } 
582+     const  results : string [ ]  =  [ ] ; 
583+     for  ( const  part  of  this . _splitOnTopLevelCommas ( cssText ) )  { 
584+       results . push ( this . _convertColonHostContextInSelectorPart ( part ) ) ; 
573585    } 
574- 
575-     return  result ; 
586+     return  results . join ( ',' ) ; 
576587  } 
577588
578589  private  _convertColonHostContextInSelectorPart ( cssText : string ) : string  { 
@@ -587,18 +598,28 @@ export class ShadowCss {
587598
588599      // There may be more than `:host-context` in this selector so `selectorText` could look like: 
589600      // `:host-context(.one):host-context(.two)`. 
590-       // Execute `_cssColonHostContextRe` over and over until we have extracted all the 
591-       // `:host-context` selectors from this selector. 
592-       let  match : RegExpExecArray  |  null ; 
593-       while  ( ( match  =  _cssColonHostContextRe . exec ( selectorText ) ) )  { 
594-         // `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>] 
595- 
596-         // The `<selectors>` could actually be a comma separated list: `:host-context(.one, .two)`. 
597-         const  newContextSelectors  =  ( match [ 1 ]  ??  '' ) 
598-           . trim ( ) 
599-           . split ( ',' ) 
600-           . map ( ( m )  =>  m . trim ( ) ) 
601-           . filter ( ( m )  =>  m  !==  '' ) ; 
601+       // Loop until every :host-context in the compound selector has been processed. 
602+       let  startIndex  =  selectorText . indexOf ( _polyfillHostContext ) ; 
603+       while  ( startIndex  !==  - 1 )  { 
604+         const  afterPrefix  =  selectorText . substring ( startIndex  +  _polyfillHostContext . length ) ; 
605+ 
606+         if  ( ! afterPrefix  ||  afterPrefix [ 0 ]  !==  '(' )  { 
607+           // Edge case of :host-context with no parens (e.g. `:host-context .inner`) 
608+           selectorText  =  afterPrefix ; 
609+           startIndex  =  selectorText . indexOf ( _polyfillHostContext ) ; 
610+           continue ; 
611+         } 
612+ 
613+         // Extract comma-separated selectors between the parentheses 
614+         const  newContextSelectors : string [ ]  =  [ ] ; 
615+         let  endIndex  =  0 ;  // Index of the closing paren of the :host-context() 
616+         for  ( const  selector  of  this . _splitOnTopLevelCommas ( afterPrefix . substring ( 1 ) ) )  { 
617+           endIndex  =  endIndex  +  selector . length  +  1 ; 
618+           const  trimmed  =  selector . trim ( ) ; 
619+           if  ( trimmed )  { 
620+             newContextSelectors . push ( trimmed ) ; 
621+           } 
622+         } 
602623
603624        // We must duplicate the current selector group for each of these new selectors. 
604625        // For example if the current groups are: 
@@ -627,7 +648,8 @@ export class ShadowCss {
627648        } 
628649
629650        // Update the `selectorText` and see repeat to see if there are more `:host-context`s. 
630-         selectorText  =  match [ 2 ] ; 
651+         selectorText  =  afterPrefix . substring ( endIndex  +  1 ) ; 
652+         startIndex  =  selectorText . indexOf ( _polyfillHostContext ) ; 
631653      } 
632654
633655      // The context selectors now must be combined with each other to capture all the possible 
@@ -1054,7 +1076,6 @@ const _cssColonHostContextReGlobal = new RegExp(
10541076  `${ _cssScopedPseudoFunctionPrefix } ${ _hostContextPattern }  , 
10551077  'gim' , 
10561078) ; 
1057- const  _cssColonHostContextRe  =  new  RegExp ( _hostContextPattern ,  'im' ) ; 
10581079const  _polyfillHostNoCombinator  =  _polyfillHost  +  '-no-combinator' ; 
10591080const  _polyfillHostNoCombinatorOutsidePseudoFunction  =  new  RegExp ( 
10601081  `${ _polyfillHostNoCombinator }  , 
0 commit comments