1
1
/** @import { Visitors } from 'zimmerframe' */
2
2
/** @import * as Compiler from '#compiler' */
3
3
import { 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' ;
5
5
import { regex_ends_with_whitespace , regex_starts_with_whitespace } from '../../patterns.js' ;
6
6
import { get_attribute_chunks , is_text_attribute } from '../../../utils/ast.js' ;
7
7
@@ -172,7 +172,7 @@ function get_relative_selectors(node) {
172
172
}
173
173
174
174
/**
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
176
176
* @param {Compiler.Css.ComplexSelector } node
177
177
*/
178
178
function truncate ( node ) {
@@ -182,21 +182,22 @@ function truncate(node) {
182
182
// not after a :global selector
183
183
! metadata . is_global_like &&
184
184
! ( 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
196
187
) ;
197
188
} ) ;
198
189
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
+ } ) ;
200
201
}
201
202
202
203
/**
@@ -334,7 +335,9 @@ function apply_combinator(combinator, relative_selector, parent_selectors, rule,
334
335
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
335
336
*/
336
337
function mark ( relative_selector , element ) {
337
- relative_selector . metadata . scoped = true ;
338
+ if ( ! is_outer_global ( relative_selector ) ) {
339
+ relative_selector . metadata . scoped = true ;
340
+ }
338
341
element . metadata . scoped = true ;
339
342
}
340
343
@@ -415,6 +418,21 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
415
418
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
416
419
let sibling_elements ; // do them lazy because it's rarely used and expensive to calculate
417
420
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
+
418
436
walk (
419
437
/** @type {Compiler.SvelteNode } */ ( element . fragment ) ,
420
438
{ is_child : true } ,
@@ -460,7 +478,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
460
478
461
479
const descendants =
462
480
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 ) )
464
482
: left_most_combinator . name === '>'
465
483
? child_elements
466
484
: descendant_elements ;
@@ -481,20 +499,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
481
499
}
482
500
483
501
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
-
498
502
return false ;
499
503
}
500
504
}
@@ -507,9 +511,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
507
511
508
512
switch ( selector . type ) {
509
513
case 'PseudoClassSelector' : {
510
- if ( name === 'host' || name === 'root' ) {
511
- return false ;
512
- }
514
+ if ( name === 'host' || name === 'root' ) return false ;
513
515
514
516
if (
515
517
name === 'global' &&
@@ -578,23 +580,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
578
580
}
579
581
580
582
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
-
598
583
return false ;
599
584
}
600
585
}
@@ -662,7 +647,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
662
647
const parent = /** @type {Compiler.Css.Rule } */ ( rule . metadata . parent_rule ) ;
663
648
664
649
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
+ ) {
666
654
complex_selector . metadata . used = true ;
667
655
matched = true ;
668
656
}
@@ -681,8 +669,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
681
669
return true ;
682
670
}
683
671
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 ) {
686
677
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null } */
687
678
let parent = get_element_parent ( element ) ;
688
679
@@ -723,6 +714,10 @@ function get_following_sibling_elements(element) {
723
714
}
724
715
}
725
716
717
+ if ( include_self ) {
718
+ sibling_elements . push ( element ) ;
719
+ }
720
+
726
721
return sibling_elements ;
727
722
}
728
723
0 commit comments