@@ -338,7 +338,7 @@ export class ShadowCss {
338
338
* captures how many (if any) leading whitespaces are present or a comma
339
339
* - (?:(?:(['"])((?:\\\\|\\\2|(?!\2).)+)\2)|(-?[A-Za-z][\w\-]*))
340
340
* captures two different possible keyframes, ones which are quoted or ones which are valid css
341
- * idents (custom properties excluded)
341
+ * indents (custom properties excluded)
342
342
* - (?=[,\s;]|$)
343
343
* simply matches the end of the possible keyframe, valid endings are: a comma, a space, a
344
344
* semicolon or the end of the string
@@ -459,7 +459,7 @@ export class ShadowCss {
459
459
*/
460
460
private _scopeCssText ( cssText : string , scopeSelector : string , hostSelector : string ) : string {
461
461
const unscopedRules = this . _extractUnscopedRulesFromCssText ( cssText ) ;
462
- // replace :host and :host-context -shadowcsshost and -shadowcsshost respectively
462
+ // replace :host and :host-context with -shadowcsshost and -shadowcsshostcontext respectively
463
463
cssText = this . _insertPolyfillHostInCssText ( cssText ) ;
464
464
cssText = this . _convertColonHost ( cssText ) ;
465
465
cssText = this . _convertColonHostContext ( cssText ) ;
@@ -616,7 +616,12 @@ export class ShadowCss {
616
616
let selector = rule . selector ;
617
617
let content = rule . content ;
618
618
if ( rule . selector [ 0 ] !== '@' ) {
619
- selector = this . _scopeSelector ( { selector, scopeSelector, hostSelector} ) ;
619
+ selector = this . _scopeSelector ( {
620
+ selector,
621
+ scopeSelector,
622
+ hostSelector,
623
+ isParentSelector : true ,
624
+ } ) ;
620
625
} else if ( scopedAtRuleIdentifiers . some ( ( atRule ) => rule . selector . startsWith ( atRule ) ) ) {
621
626
content = this . _scopeSelectors ( rule . content , scopeSelector , hostSelector ) ;
622
627
} else if ( rule . selector . startsWith ( '@font-face' ) || rule . selector . startsWith ( '@page' ) ) {
@@ -656,20 +661,30 @@ export class ShadowCss {
656
661
} ) ;
657
662
}
658
663
664
+ private _safeSelector : SafeSelector | undefined ;
665
+ private _shouldScopeIndicator : boolean | undefined ;
666
+
667
+ // `isParentSelector` is used to distinguish the selectors which are coming from
668
+ // the initial selector string and any nested selectors, parsed recursively,
669
+ // for example `selector = 'a:where(.one)'` could be the parent, while recursive call
670
+ // would have `selector = '.one'`.
659
671
private _scopeSelector ( {
660
672
selector,
661
673
scopeSelector,
662
674
hostSelector,
663
- shouldScope ,
675
+ isParentSelector = false ,
664
676
} : {
665
677
selector : string ;
666
678
scopeSelector : string ;
667
679
hostSelector : string ;
668
- shouldScope ?: boolean ;
680
+ isParentSelector ?: boolean ;
669
681
} ) : string {
670
682
// Split the selector into independent parts by `,` (comma) unless
671
683
// comma is within parenthesis, for example `:is(.one, two)`.
672
- const selectorSplitRe = / ? , (? ! [ ^ \( ] * \) ) ? / ;
684
+ // Negative lookup after comma allows not splitting inside nested parenthesis,
685
+ // up to three levels (((,))).
686
+ const selectorSplitRe =
687
+ / ? , (? ! (?: [ ^ ) ( ] * (?: \( [ ^ ) ( ] * (?: \( [ ^ ) ( ] * (?: \( [ ^ ) ( ] * \) [ ^ ) ( ] * ) * \) [ ^ ) ( ] * ) * \) [ ^ ) ( ] * ) * \) ) ) ? / ;
673
688
674
689
return selector
675
690
. split ( selectorSplitRe )
@@ -682,7 +697,7 @@ export class ShadowCss {
682
697
selector : shallowPart ,
683
698
scopeSelector,
684
699
hostSelector,
685
- shouldScope ,
700
+ isParentSelector ,
686
701
} ) ;
687
702
} else {
688
703
return shallowPart ;
@@ -716,9 +731,9 @@ export class ShadowCss {
716
731
if ( _polyfillHostRe . test ( selector ) ) {
717
732
const replaceBy = `[${ hostSelector } ]` ;
718
733
return selector
719
- . replace ( _polyfillHostNoCombinatorRe , ( hnc , selector ) => {
734
+ . replace ( _polyfillHostNoCombinatorReGlobal , ( _hnc , selector ) => {
720
735
return selector . replace (
721
- / ( [ ^ : ] * ) ( : * ) ( .* ) / ,
736
+ / ( [ ^ : \) ] * ) ( : * ) ( .* ) / ,
722
737
( _ : string , before : string , colon : string , after : string ) => {
723
738
return before + replaceBy + colon + after ;
724
739
} ,
@@ -736,12 +751,12 @@ export class ShadowCss {
736
751
selector,
737
752
scopeSelector,
738
753
hostSelector,
739
- shouldScope ,
754
+ isParentSelector ,
740
755
} : {
741
756
selector : string ;
742
757
scopeSelector : string ;
743
758
hostSelector : string ;
744
- shouldScope ?: boolean ;
759
+ isParentSelector ?: boolean ;
745
760
} ) : string {
746
761
const isRe = / \[ i s = ( [ ^ \] ] * ) \] / g;
747
762
scopeSelector = scopeSelector . replace ( isRe , ( _ : string , ...parts : string [ ] ) => parts [ 0 ] ) ;
@@ -757,6 +772,10 @@ export class ShadowCss {
757
772
758
773
if ( p . includes ( _polyfillHostNoCombinator ) ) {
759
774
scopedP = this . _applySimpleSelectorScope ( p , scopeSelector , hostSelector ) ;
775
+ if ( _polyfillHostNoCombinatorWithinPseudoFunction . test ( p ) ) {
776
+ const [ _ , before , colon , after ] = scopedP . match ( / ( [ ^ : ] * ) ( : * ) ( .* ) / ) ! ;
777
+ scopedP = before + attrName + colon + after ;
778
+ }
760
779
} else {
761
780
// remove :host since it should be unnecessary
762
781
const t = p . replace ( _polyfillHostRe , '' ) ;
@@ -773,44 +792,66 @@ export class ShadowCss {
773
792
774
793
// Wraps `_scopeSelectorPart()` to not use it directly on selectors with
775
794
// 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.
795
+ // functions are recursively sent to `_scopeSelector()`.
778
796
const _pseudoFunctionAwareScopeSelectorPart = ( selectorPart : string ) => {
779
797
let scopedPart = '' ;
780
798
781
- const cssPseudoSelectorFunctionMatch = selectorPart . match ( _cssPseudoSelectorFunctionPrefix ) ;
782
- if ( cssPseudoSelectorFunctionMatch ) {
783
- const [ cssPseudoSelectorFunction ] = cssPseudoSelectorFunctionMatch ;
799
+ const cssPrefixWithPseudoSelectorFunctionMatch = selectorPart . match (
800
+ _cssPrefixWithPseudoSelectorFunction ,
801
+ ) ;
802
+ if ( cssPrefixWithPseudoSelectorFunctionMatch ) {
803
+ const [ cssPseudoSelectorFunction , mainSelector , pseudoSelector ] =
804
+ cssPrefixWithPseudoSelectorFunctionMatch ;
805
+ const hasOuterHostNoCombinator = mainSelector . includes ( _polyfillHostNoCombinator ) ;
806
+ const scopedMainSelector = mainSelector . replace (
807
+ _polyfillHostNoCombinatorReGlobal ,
808
+ `[${ hostSelector } ]` ,
809
+ ) ;
810
+
784
811
// Unwrap the pseudo selector, to scope its contents.
785
- // For example, `:where(selectorToScope)` -> `selectorToScope`.
812
+ // For example,
813
+ // - `:where(selectorToScope)` -> `selectorToScope`;
814
+ // - `div:is(.foo, .bar)` -> `.foo, .bar`.
786
815
const selectorToScope = selectorPart . slice ( cssPseudoSelectorFunction . length , - 1 ) ;
787
816
817
+ if ( selectorToScope . includes ( _polyfillHostNoCombinator ) ) {
818
+ this . _shouldScopeIndicator = true ;
819
+ }
820
+
788
821
const scopedInnerPart = this . _scopeSelector ( {
789
822
selector : selectorToScope ,
790
823
scopeSelector,
791
824
hostSelector,
792
- shouldScope : shouldScopeIndicator ,
793
825
} ) ;
826
+
794
827
// Put the result back into the pseudo selector function.
795
- scopedPart = `${ cssPseudoSelectorFunction } ${ scopedInnerPart } )` ;
828
+ scopedPart = `${ scopedMainSelector } :${ pseudoSelector } (${ scopedInnerPart } )` ;
829
+
830
+ this . _shouldScopeIndicator = this . _shouldScopeIndicator || hasOuterHostNoCombinator ;
796
831
} else {
797
- shouldScopeIndicator =
798
- shouldScopeIndicator || selectorPart . includes ( _polyfillHostNoCombinator ) ;
799
- scopedPart = shouldScopeIndicator ? _scopeSelectorPart ( selectorPart ) : selectorPart ;
832
+ this . _shouldScopeIndicator =
833
+ this . _shouldScopeIndicator || selectorPart . includes ( _polyfillHostNoCombinator ) ;
834
+ scopedPart = this . _shouldScopeIndicator ? _scopeSelectorPart ( selectorPart ) : selectorPart ;
800
835
}
801
836
802
837
return scopedPart ;
803
838
} ;
804
839
805
- const safeContent = new SafeSelector ( selector ) ;
806
- selector = safeContent . content ( ) ;
840
+ if ( isParentSelector ) {
841
+ this . _safeSelector = new SafeSelector ( selector ) ;
842
+ selector = this . _safeSelector . content ( ) ;
843
+ }
807
844
808
845
let scopedSelector = '' ;
809
846
let startIndex = 0 ;
810
847
let res : RegExpExecArray | null ;
811
848
// Combinators aren't used as a delimiter if they are within parenthesis,
812
849
// for example `:where(.one .two)` stays intact.
813
- const sep = / ( | > | \+ | ~ (? ! = ) ) (? ! [ ^ \( ] * \) ) \s * / g;
850
+ // Similarly to selector separation by comma initially, negative lookahead
851
+ // is used here to not break selectors within nested parenthesis up to three
852
+ // nested layers.
853
+ const sep =
854
+ / ( | > | \+ | ~ (? ! = ) ) (? ! ( [ ^ ) ( ] * (?: \( [ ^ ) ( ] * (?: \( [ ^ ) ( ] * (?: \( [ ^ ) ( ] * \) [ ^ ) ( ] * ) * \) [ ^ ) ( ] * ) * \) [ ^ ) ( ] * ) * \) ) ) \s * / g;
814
855
815
856
// If a selector appears before :host it should not be shimmed as it
816
857
// matches on ancestor elements and not on elements in the host's shadow
@@ -824,8 +865,13 @@ export class ShadowCss {
824
865
// - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a
825
866
// `:host-context(tag)`)
826
867
const hasHost = selector . includes ( _polyfillHostNoCombinator ) ;
827
- // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present
828
- let shouldScopeIndicator = shouldScope ?? ! hasHost ;
868
+ // Only scope parts after or on the same level as the first `-shadowcsshost-no-combinator`
869
+ // when it is present. The selector has the same level when it is a part of a pseudo
870
+ // selector, like `:where()`, for example `:where(:host, .foo)` would result in `.foo`
871
+ // being scoped.
872
+ if ( isParentSelector || this . _shouldScopeIndicator ) {
873
+ this . _shouldScopeIndicator = ! hasHost ;
874
+ }
829
875
830
876
while ( ( res = sep . exec ( selector ) ) !== null ) {
831
877
const separator = res [ 1 ] ;
@@ -853,7 +899,8 @@ export class ShadowCss {
853
899
scopedSelector += _pseudoFunctionAwareScopeSelectorPart ( part ) ;
854
900
855
901
// replace the placeholders with their original values
856
- return safeContent . restore ( scopedSelector ) ;
902
+ // using values stored inside the `safeSelector` instance.
903
+ return this . _safeSelector ! . restore ( scopedSelector ) ;
857
904
}
858
905
859
906
private _insertPolyfillHostInCssText ( selector : string ) : string {
@@ -918,7 +965,7 @@ class SafeSelector {
918
965
}
919
966
}
920
967
921
- const _cssPseudoSelectorFunctionPrefix = / ^ : ( w h e r e | i s ) \( / gi ;
968
+ const _cssPrefixWithPseudoSelectorFunction = / ^ ( [ ^ : ] * ) : ( w h e r e | i s ) \( / i ;
922
969
const _cssContentNextSelectorRe =
923
970
/ 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;
924
971
const _cssContentRuleRe = / ( p o l y f i l l - r u l e ) [ ^ } ] * ( c o n t e n t : [ \s ] * ( [ ' " ] ) ( .* ?) \3) [ ; \s ] * [ ^ } ] * } / gim;
@@ -932,7 +979,11 @@ const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix, 'gim');
932
979
const _cssColonHostContextReGlobal = new RegExp ( _polyfillHostContext + _parenSuffix , 'gim' ) ;
933
980
const _cssColonHostContextRe = new RegExp ( _polyfillHostContext + _parenSuffix , 'im' ) ;
934
981
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator' ;
982
+ const _polyfillHostNoCombinatorWithinPseudoFunction = new RegExp (
983
+ `:.*(.*${ _polyfillHostNoCombinator } .*)` ,
984
+ ) ;
935
985
const _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 ] * ) / ;
986
+ const _polyfillHostNoCombinatorReGlobal = new RegExp ( _polyfillHostNoCombinatorRe , 'g' ) ;
936
987
const _shadowDOMSelectorsRe = [
937
988
/ : : s h a d o w / g,
938
989
/ : : c o n t e n t / g,
0 commit comments