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' ;
98
109/**
1110 * The following set contains all keywords that can be used in the animation css shorthand
@@ -526,40 +525,6 @@ export class ShadowCss {
526525 } ) ;
527526 }
528527
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-
563528 /*
564529 * convert a rule like :host-context(.foo) > .bar { }
565530 *
@@ -576,14 +541,38 @@ export class ShadowCss {
576541 * .foo<scopeName> .bar { ... }
577542 */
578543 private _convertColonHostContext ( cssText : string ) : string {
544+ const length = cssText . length ;
545+ let parens = 0 ;
546+ let prev = 0 ;
547+ let result = '' ;
548+
579549 // Splits up the selectors on their top-level commas, processes the :host-context in them
580550 // individually and stitches them back together. This ensures that individual selectors don't
581551 // affect each other.
582- const results : string [ ] = [ ] ;
583- for ( const part of this . _splitOnTopLevelCommas ( cssText ) ) {
584- results . push ( this . _convertColonHostContextInSelectorPart ( part ) ) ;
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+ }
585573 }
586- return results . join ( ',' ) ;
574+
575+ return result ;
587576 }
588577
589578 private _convertColonHostContextInSelectorPart ( cssText : string ) : string {
@@ -598,28 +587,18 @@ export class ShadowCss {
598587
599588 // There may be more than `:host-context` in this selector so `selectorText` could look like:
600589 // `:host-context(.one):host-context(.two)`.
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- }
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 !== '' ) ;
623602
624603 // We must duplicate the current selector group for each of these new selectors.
625604 // For example if the current groups are:
@@ -648,8 +627,7 @@ export class ShadowCss {
648627 }
649628
650629 // Update the `selectorText` and see repeat to see if there are more `:host-context`s.
651- selectorText = afterPrefix . substring ( endIndex + 1 ) ;
652- startIndex = selectorText . indexOf ( _polyfillHostContext ) ;
630+ selectorText = match [ 2 ] ;
653631 }
654632
655633 // The context selectors now must be combined with each other to capture all the possible
@@ -1082,6 +1060,7 @@ const _cssColonHostContextReGlobal = new RegExp(
10821060 `${ _cssScopedPseudoFunctionPrefix } (${ _hostContextPattern } )` ,
10831061 'gim' ,
10841062) ;
1063+ const _cssColonHostContextRe = new RegExp ( _hostContextPattern , 'im' ) ;
10851064const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator' ;
10861065const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp (
10871066 `${ _polyfillHostNoCombinator } (?![^(]*\\))` ,
0 commit comments