@@ -616,7 +616,7 @@ export class ShadowCss {
616616 let selector = rule . selector ;
617617 let content = rule . content ;
618618 if ( rule . selector [ 0 ] !== '@' ) {
619- selector = this . _scopeSelector ( rule . selector , scopeSelector , hostSelector ) ;
619+ selector = this . _scopeSelector ( { selector, scopeSelector, hostSelector} ) ;
620620 } else if ( scopedAtRuleIdentifiers . some ( ( atRule ) => rule . selector . startsWith ( atRule ) ) ) {
621621 content = this . _scopeSelectors ( rule . content , scopeSelector , hostSelector ) ;
622622 } else if ( rule . selector . startsWith ( '@font-face' ) || rule . selector . startsWith ( '@page' ) ) {
@@ -656,15 +656,34 @@ export class ShadowCss {
656656 } ) ;
657657 }
658658
659- private _scopeSelector ( selector : string , scopeSelector : string , hostSelector : string ) : string {
659+ private _scopeSelector ( {
660+ selector,
661+ scopeSelector,
662+ hostSelector,
663+ shouldScope,
664+ } : {
665+ selector : string ;
666+ scopeSelector : string ;
667+ hostSelector : string ;
668+ shouldScope ?: boolean ;
669+ } ) : string {
670+ // Split the selector into independent parts by `,` (comma) unless
671+ // comma is within parenthesis, for example `:is(.one, two)`.
672+ const selectorSplitRe = / ? , (? ! [ ^ \( ] * \) ) ? / ;
673+
660674 return selector
661- . split ( / ? , ? / )
675+ . split ( selectorSplitRe )
662676 . map ( ( part ) => part . split ( _shadowDeepSelectors ) )
663677 . map ( ( deepParts ) => {
664678 const [ shallowPart , ...otherParts ] = deepParts ;
665679 const applyScope = ( shallowPart : string ) => {
666680 if ( this . _selectorNeedsScoping ( shallowPart , scopeSelector ) ) {
667- return this . _applySelectorScope ( shallowPart , scopeSelector , hostSelector ) ;
681+ return this . _applySelectorScope ( {
682+ selector : shallowPart ,
683+ scopeSelector,
684+ hostSelector,
685+ shouldScope,
686+ } ) ;
668687 } else {
669688 return shallowPart ;
670689 }
@@ -713,11 +732,17 @@ export class ShadowCss {
713732
714733 // return a selector with [name] suffix on each simple selector
715734 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
716- private _applySelectorScope (
717- selector : string ,
718- scopeSelector : string ,
719- hostSelector : string ,
720- ) : string {
735+ private _applySelectorScope ( {
736+ selector,
737+ scopeSelector,
738+ hostSelector,
739+ shouldScope,
740+ } : {
741+ selector : string ;
742+ scopeSelector : string ;
743+ hostSelector : string ;
744+ shouldScope ?: boolean ;
745+ } ) : string {
721746 const isRe = / \[ i s = ( [ ^ \] ] * ) \] / g;
722747 scopeSelector = scopeSelector . replace ( isRe , ( _ : string , ...parts : string [ ] ) => parts [ 0 ] ) ;
723748
@@ -746,13 +771,46 @@ export class ShadowCss {
746771 return scopedP ;
747772 } ;
748773
774+ // Wraps `_scopeSelectorPart()` to not use it directly on selectors with
775+ // pseudo selector functions like `:where()`. Selectors within pseudo selector
776+ // functions are recursively sent to `_scopeSelector()` with the `shouldScope`
777+ // argument, so the selectors get scoped correctly.
778+ const _pseudoFunctionAwareScopeSelectorPart = ( selectorPart : string ) => {
779+ let scopedPart = '' ;
780+
781+ const cssPseudoSelectorFunctionMatch = selectorPart . match ( _cssPseudoSelectorFunctionPrefix ) ;
782+ if ( cssPseudoSelectorFunctionMatch ) {
783+ const [ cssPseudoSelectorFunction ] = cssPseudoSelectorFunctionMatch ;
784+ // Unwrap the pseudo selector, to scope its contents.
785+ // For example, `:where(selectorToScope)` -> `selectorToScope`.
786+ const selectorToScope = selectorPart . slice ( cssPseudoSelectorFunction . length , - 1 ) ;
787+
788+ const scopedInnerPart = this . _scopeSelector ( {
789+ selector : selectorToScope ,
790+ scopeSelector,
791+ hostSelector,
792+ shouldScope : shouldScopeIndicator ,
793+ } ) ;
794+ // Put the result back into the pseudo selector function.
795+ scopedPart = `${ cssPseudoSelectorFunction } ${ scopedInnerPart } )` ;
796+ } else {
797+ shouldScopeIndicator =
798+ shouldScopeIndicator || selectorPart . includes ( _polyfillHostNoCombinator ) ;
799+ scopedPart = shouldScopeIndicator ? _scopeSelectorPart ( selectorPart ) : selectorPart ;
800+ }
801+
802+ return scopedPart ;
803+ } ;
804+
749805 const safeContent = new SafeSelector ( selector ) ;
750806 selector = safeContent . content ( ) ;
751807
752808 let scopedSelector = '' ;
753809 let startIndex = 0 ;
754810 let res : RegExpExecArray | null ;
755- const sep = / ( | > | \+ | ~ (? ! = ) ) \s * / g;
811+ // Spaces aren't used as a delimeter if they are within parenthesis, for example
812+ // `:where(.one .two)` stays intact.
813+ const sep = / ( (? ! [ ^ \( ] * \) ) | > | \+ | ~ (? ! = ) ) \s * / g;
756814
757815 // If a selector appears before :host it should not be shimmed as it
758816 // matches on ancestor elements and not on elements in the host's shadow
@@ -767,7 +825,7 @@ export class ShadowCss {
767825 // `:host-context(tag)`)
768826 const hasHost = selector . includes ( _polyfillHostNoCombinator ) ;
769827 // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present
770- let shouldScope = ! hasHost ;
828+ let shouldScopeIndicator = shouldScope ?? ! hasHost ;
771829
772830 while ( ( res = sep . exec ( selector ) ) !== null ) {
773831 const separator = res [ 1 ] ;
@@ -786,15 +844,13 @@ export class ShadowCss {
786844 continue ;
787845 }
788846
789- shouldScope = shouldScope || part . includes ( _polyfillHostNoCombinator ) ;
790- const scopedPart = shouldScope ? _scopeSelectorPart ( part ) : part ;
847+ const scopedPart = _pseudoFunctionAwareScopeSelectorPart ( part ) ;
791848 scopedSelector += `${ scopedPart } ${ separator } ` ;
792849 startIndex = sep . lastIndex ;
793850 }
794851
795852 const part = selector . substring ( startIndex ) ;
796- shouldScope = shouldScope || part . includes ( _polyfillHostNoCombinator ) ;
797- scopedSelector += shouldScope ? _scopeSelectorPart ( part ) : part ;
853+ scopedSelector += _pseudoFunctionAwareScopeSelectorPart ( part ) ;
798854
799855 // replace the placeholders with their original values
800856 return safeContent . restore ( scopedSelector ) ;
@@ -862,6 +918,7 @@ class SafeSelector {
862918 }
863919}
864920
921+ const _cssPseudoSelectorFunctionPrefix = / ^ : ( w h e r e | i s ) \( / gi;
865922const _cssContentNextSelectorRe =
866923 / p o l y f i l l - n e x t - s e l e c t o r [ ^ } ] * c o n t e n t : [ \s ] * ?( [ ' " ] ) ( .* ?) \1[ ; \s ] * } ( [ ^ { ] * ?) { / gim;
867924const _cssContentRuleRe = / ( p o l y f i l l - r u l e ) [ ^ } ] * ( c o n t e n t : [ \s ] * ( [ ' " ] ) ( .* ?) \3) [ ; \s ] * [ ^ } ] * } / gim;
0 commit comments