11/** @import { Visitors } from 'zimmerframe' */
22/** @import * as Compiler from '#compiler' */
33import { walk } from 'zimmerframe' ;
4- import { get_possible_values } from './utils.js' ;
4+ import { get_parent_rules , get_possible_values , is_outer_global } from './utils.js' ;
55import { regex_ends_with_whitespace , regex_starts_with_whitespace } from '../../patterns.js' ;
66import { get_attribute_chunks , is_text_attribute } from '../../../utils/ast.js' ;
77
@@ -172,7 +172,7 @@ function get_relative_selectors(node) {
172172}
173173
174174/**
175- * Discard trailing `:global(...)` selectors without a `:has/is/where/not(...)` modifier , these are unused for scoping purposes
175+ * Discard trailing `:global(...)` selectors, these are unused for scoping purposes
176176 * @param {Compiler.Css.ComplexSelector } node
177177 */
178178function truncate ( node ) {
@@ -182,21 +182,22 @@ function truncate(node) {
182182 // not after a :global selector
183183 ! metadata . is_global_like &&
184184 ! ( first . type === 'PseudoClassSelector' && first . name === 'global' && first . args === null ) &&
185- // not a :global(...) without a :has/is/where/not(...) modifier
186- ( ! metadata . is_global ||
187- selectors . some (
188- ( selector ) =>
189- selector . type === 'PseudoClassSelector' &&
190- selector . args !== null &&
191- ( selector . name === 'has' ||
192- selector . name === 'is' ||
193- selector . name === 'where' ||
194- selector . name === 'not' )
195- ) )
185+ // not a :global(...) without a :has/is/where(...) modifier that is scoped
186+ ! metadata . is_global
196187 ) ;
197188 } ) ;
198189
199- return node . children . slice ( 0 , i + 1 ) ;
190+ return node . children . slice ( 0 , i + 1 ) . map ( ( child ) => {
191+ // In case of `:root.y:has(...)`, `y` is unscoped, but everything in `:has(...)` should be scoped (if not global).
192+ // To properly accomplish that, we gotta filter out all selector types except `:has`.
193+ const root = child . selectors . find ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'root' ) ;
194+ if ( ! root || child . metadata . is_global_like ) return child ;
195+
196+ return {
197+ ...child ,
198+ selectors : child . selectors . filter ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'has' )
199+ } ;
200+ } ) ;
200201}
201202
202203/**
@@ -334,7 +335,9 @@ function apply_combinator(combinator, relative_selector, parent_selectors, rule,
334335 * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
335336 */
336337function mark ( relative_selector , element ) {
337- relative_selector . metadata . scoped = true ;
338+ if ( ! is_outer_global ( relative_selector ) ) {
339+ relative_selector . metadata . scoped = true ;
340+ }
338341 element . metadata . scoped = true ;
339342}
340343
@@ -415,6 +418,21 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
415418 /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
416419 let sibling_elements ; // do them lazy because it's rarely used and expensive to calculate
417420
421+ // If this is a :has inside a global selector, we gotta include the element itself, too,
422+ // because the global selector might be for an element that's outside the component (e.g. :root).
423+ const rules = [ rule , ...get_parent_rules ( rule ) ] ;
424+ const include_self =
425+ rules . some ( ( r ) => r . prelude . children . some ( ( c ) => c . children . some ( ( s ) => is_global ( s , r ) ) ) ) ||
426+ rules [ rules . length - 1 ] . prelude . children . some ( ( c ) =>
427+ c . children . some ( ( r ) =>
428+ r . selectors . some ( ( s ) => s . type === 'PseudoClassSelector' && s . name === 'root' )
429+ )
430+ ) ;
431+ if ( include_self ) {
432+ child_elements . push ( element ) ;
433+ descendant_elements . push ( element ) ;
434+ }
435+
418436 walk (
419437 /** @type {Compiler.SvelteNode } */ ( element . fragment ) ,
420438 { is_child : true } ,
@@ -460,7 +478,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
460478
461479 const descendants =
462480 left_most_combinator . name === '+' || left_most_combinator . name === '~'
463- ? ( sibling_elements ??= get_following_sibling_elements ( element ) )
481+ ? ( sibling_elements ??= get_following_sibling_elements ( element , include_self ) )
464482 : left_most_combinator . name === '>'
465483 ? child_elements
466484 : descendant_elements ;
@@ -481,20 +499,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
481499 }
482500
483501 if ( ! matched ) {
484- if ( relative_selector . metadata . is_global && ! relative_selector . metadata . is_global_like ) {
485- // Edge case: `:global(.x):has(.y)` where `.x` is global but `.y` doesn't match.
486- // Since `used` is set to `true` for `:global(.x)` in css-analyze beforehand, and
487- // we have no way of knowing if it's safe to set it back to `false`, we'll mark
488- // the inner selector as used and scoped to prevent it from being pruned, which could
489- // result in a invalid CSS output (e.g. `.x:has(/* unused .y */)`). The result
490- // can't match a real element, so the only drawback is the missing prune.
491- // TODO clean this up some day
492- complex_selectors [ 0 ] . metadata . used = true ;
493- complex_selectors [ 0 ] . children . forEach ( ( selector ) => {
494- selector . metadata . scoped = true ;
495- } ) ;
496- }
497-
498502 return false ;
499503 }
500504 }
@@ -507,9 +511,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
507511
508512 switch ( selector . type ) {
509513 case 'PseudoClassSelector' : {
510- if ( name === 'host' || name === 'root' ) {
511- return false ;
512- }
514+ if ( name === 'host' || name === 'root' ) return false ;
513515
514516 if (
515517 name === 'global' &&
@@ -578,23 +580,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
578580 }
579581
580582 if ( ! matched ) {
581- if (
582- relative_selector . metadata . is_global &&
583- ! relative_selector . metadata . is_global_like
584- ) {
585- // Edge case: `:global(.x):is(.y)` where `.x` is global but `.y` doesn't match.
586- // Since `used` is set to `true` for `:global(.x)` in css-analyze beforehand, and
587- // we have no way of knowing if it's safe to set it back to `false`, we'll mark
588- // the inner selector as used and scoped to prevent it from being pruned, which could
589- // result in a invalid CSS output (e.g. `.x:is(/* unused .y */)`). The result
590- // can't match a real element, so the only drawback is the missing prune.
591- // TODO clean this up some day
592- selector . args . children [ 0 ] . metadata . used = true ;
593- selector . args . children [ 0 ] . children . forEach ( ( selector ) => {
594- selector . metadata . scoped = true ;
595- } ) ;
596- }
597-
598583 return false ;
599584 }
600585 }
@@ -662,7 +647,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
662647 const parent = /** @type {Compiler.Css.Rule } */ ( rule . metadata . parent_rule ) ;
663648
664649 for ( const complex_selector of parent . prelude . children ) {
665- if ( apply_selector ( get_relative_selectors ( complex_selector ) , parent , element , state ) ) {
650+ if (
651+ apply_selector ( get_relative_selectors ( complex_selector ) , parent , element , state ) ||
652+ complex_selector . children . every ( ( s ) => is_global ( s , parent ) )
653+ ) {
666654 complex_selector . metadata . used = true ;
667655 matched = true ;
668656 }
@@ -681,8 +669,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
681669 return true ;
682670}
683671
684- /** @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element */
685- function get_following_sibling_elements ( element ) {
672+ /**
673+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
674+ * @param {boolean } include_self
675+ */
676+ function get_following_sibling_elements ( element , include_self ) {
686677 /** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null } */
687678 let parent = get_element_parent ( element ) ;
688679
@@ -723,6 +714,10 @@ function get_following_sibling_elements(element) {
723714 }
724715 }
725716
717+ if ( include_self ) {
718+ sibling_elements . push ( element ) ;
719+ }
720+
726721 return sibling_elements ;
727722}
728723
0 commit comments