@@ -5,6 +5,8 @@ import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../
5
5
import { get_attribute_chunks , is_text_attribute } from '../../../utils/ast.js' ;
6
6
7
7
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS } NodeExistsValue */
8
+ /** @typedef {Compiler.AST.CSS.BaseNode & { type: 'ElementSelector', element: Compiler.AST.RegularElement | Compiler.AST.SvelteElement } } ElementSelector */
9
+ /** @typedef {Omit<Compiler.AST.CSS.RelativeSelector, 'selectors'> & { selectors: Array<Compiler.AST.CSS.SimpleSelector | ElementSelector> } } ExtendedRelativeSelector */
8
10
9
11
const NODE_PROBABLY_EXISTS = 0 ;
10
12
const NODE_DEFINITELY_EXISTS = 1 ;
@@ -234,7 +236,7 @@ function apply_combinator(relative_selector, parent_selectors, rule, node) {
234
236
235
237
case '+' :
236
238
case '~' : {
237
- const siblings = get_possible_element_siblings ( node , name === '+' ) ;
239
+ const siblings = get_possible_element_preceding_siblings ( node , name === '+' ) ;
238
240
239
241
let sibling_matched = false ;
240
242
@@ -310,7 +312,7 @@ const regex_backslash_and_following_character = /\\(.)/g;
310
312
/**
311
313
* Ensure that `element` satisfies each simple selector in `relative_selector`
312
314
*
313
- * @param {Compiler.AST.CSS.RelativeSelector } relative_selector
315
+ * @param {ExtendedRelativeSelector } relative_selector
314
316
* @param {Compiler.AST.CSS.Rule } rule
315
317
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
316
318
* @returns {boolean }
@@ -331,13 +333,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
331
333
// If we're called recursively from a :has(...) selector, we're on the way of checking if the other selectors match.
332
334
// In that case ignore this check (because we just came from this) to avoid an infinite loop.
333
335
if ( has_selectors . length > 0 ) {
334
- /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
335
- const child_elements = [ ] ;
336
- /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
337
- const descendant_elements = [ ] ;
338
- /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
339
- let sibling_elements ; // do them lazy because it's rarely used and expensive to calculate
340
-
341
336
// If this is a :has inside a global selector, we gotta include the element itself, too,
342
337
// because the global selector might be for an element that's outside the component,
343
338
// e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
@@ -353,46 +348,33 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
353
348
)
354
349
)
355
350
) ;
356
- if ( include_self ) {
357
- child_elements . push ( element ) ;
358
- descendant_elements . push ( element ) ;
359
- }
360
351
361
- const seen = new Set ( ) ;
352
+ // set them lazy because it's expensive to calculate
353
+ /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
354
+ let descendant_elements ;
355
+ /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
356
+ let sibling_elements ;
357
+ /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
358
+ let sibling_descendant_elements ;
362
359
363
360
/**
364
- * @param {Compiler.AST.SvelteNode } node
365
- * @param {{ is_child: boolean } } state
361
+ * @param {ExtendedRelativeSelector[] } selectors
366
362
*/
367
- function walk_children ( node , state ) {
368
- walk ( node , state , {
369
- _ ( node , context ) {
370
- if ( node . type === 'RegularElement' || node . type === 'SvelteElement' ) {
371
- descendant_elements . push ( node ) ;
372
-
373
- if ( context . state . is_child ) {
374
- child_elements . push ( node ) ;
375
- context . state . is_child = false ;
376
- context . next ( ) ;
377
- context . state . is_child = true ;
378
- } else {
379
- context . next ( ) ;
380
- }
381
- } else if ( node . type === 'RenderTag' ) {
382
- for ( const snippet of node . metadata . snippets ) {
383
- if ( seen . has ( snippet ) ) continue ;
363
+ const get_elements = ( selectors ) => {
364
+ const left_most_combinator = selectors [ 0 ] ?. combinator ?? descendant_combinator ;
384
365
385
- seen . add ( snippet ) ;
386
- walk_children ( snippet . body , context . state ) ;
387
- }
388
- } else {
389
- context . next ( ) ;
390
- }
391
- }
392
- } ) ;
393
- }
366
+ if ( left_most_combinator . name === ' ' || left_most_combinator . name === '>' ) {
367
+ descendant_elements ??= get_descendant_elements ( element , include_self ) ;
368
+ return descendant_elements ;
369
+ }
394
370
395
- walk_children ( element . fragment , { is_child : true } ) ;
371
+ sibling_elements ??= get_following_sibling_elements ( element , include_self ) ;
372
+ if ( selectors . some ( s => s . combinator ?. name === ' ' || s . combinator ?. name === '>' ) ) {
373
+ sibling_descendant_elements ??= sibling_elements . flatMap ( el => get_descendant_elements ( el , false ) ) ;
374
+ return sibling_descendant_elements ;
375
+ }
376
+ return sibling_elements ;
377
+ }
396
378
397
379
// :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
398
380
// upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
@@ -403,32 +385,23 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
403
385
let matched = false ;
404
386
405
387
for ( const complex_selector of complex_selectors ) {
388
+ /** @type {ExtendedRelativeSelector[] } */
406
389
const selectors = truncate ( complex_selector ) ;
407
- const left_most_combinator = selectors [ 0 ] ?. combinator ?? descendant_combinator ;
408
- // In .x:has(> y), we want to search for y, ignoring the left-most combinator
409
- // (else it would try to walk further up and fail because there are no selectors left)
390
+ const elements = get_elements ( selectors ) ;
391
+ // In .x:has(> y), we complete the selector by prepending a special one that
392
+ // matches only this `element`, otherwise it can mismatch with an ancestor element
410
393
if ( selectors . length > 0 ) {
411
- selectors [ 0 ] = {
412
- ...selectors [ 0 ] ,
413
- combinator : null
414
- } ;
394
+ selectors . unshift ( make_element_selector ( element ) ) ;
415
395
}
416
396
417
- const descendants =
418
- left_most_combinator . name === '+' || left_most_combinator . name === '~'
419
- ? ( sibling_elements ??= get_following_sibling_elements ( element , include_self ) )
420
- : left_most_combinator . name === '>'
421
- ? child_elements
422
- : descendant_elements ;
423
-
424
397
let selector_matched = false ;
425
398
426
399
// Iterate over all descendant elements and check if the selector inside :has matches
427
- for ( const element of descendants ) {
400
+ for ( const element of elements ) {
428
401
if (
429
402
selectors . length === 0 /* is :global(...) */ ||
430
403
( element . metadata . scoped && selector_matched ) ||
431
- apply_selector ( selectors , rule , element )
404
+ apply_selector ( /** @type { Compiler.AST.CSS.RelativeSelector[] } */ ( selectors ) , rule , element )
432
405
) {
433
406
complex_selector . metadata . used = true ;
434
407
selector_matched = matched = true ;
@@ -445,6 +418,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
445
418
for ( const selector of other_selectors ) {
446
419
if ( selector . type === 'Percentage' || selector . type === 'Nth' ) continue ;
447
420
421
+ if ( selector . type === 'ElementSelector' ) {
422
+ return element === selector . element ;
423
+ }
424
+
448
425
const name = selector . name . replace ( regex_backslash_and_following_character , '$1' ) ;
449
426
450
427
switch ( selector . type ) {
@@ -686,6 +663,51 @@ function get_following_sibling_elements(element, include_self) {
686
663
return siblings ;
687
664
}
688
665
666
+ /**
667
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
668
+ * @param {boolean } include_self
669
+ */
670
+ function get_descendant_elements ( element , include_self ) {
671
+ /** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement> } */
672
+ const descendants = include_self ? [ element ] : [ ] ;
673
+ const seen = new Set ( ) ;
674
+
675
+ /**
676
+ * @param {Compiler.AST.SvelteNode } node
677
+ * @param {{ is_child: boolean } } state
678
+ */
679
+ function walk_children ( node , state ) {
680
+ walk ( node , state , {
681
+ _ ( node , context ) {
682
+ if ( node . type === 'RegularElement' || node . type === 'SvelteElement' ) {
683
+ descendants . push ( node ) ;
684
+
685
+ if ( context . state . is_child ) {
686
+ context . state . is_child = false ;
687
+ context . next ( ) ;
688
+ context . state . is_child = true ;
689
+ } else {
690
+ context . next ( ) ;
691
+ }
692
+ } else if ( node . type === 'RenderTag' ) {
693
+ for ( const snippet of node . metadata . snippets ) {
694
+ if ( seen . has ( snippet ) ) continue ;
695
+
696
+ seen . add ( snippet ) ;
697
+ walk_children ( snippet . body , context . state ) ;
698
+ }
699
+ } else {
700
+ context . next ( ) ;
701
+ }
702
+ }
703
+ } ) ;
704
+ }
705
+
706
+ walk_children ( element . fragment , { is_child : true } ) ;
707
+
708
+ return descendants ;
709
+ }
710
+
689
711
/**
690
712
* @param {any } operator
691
713
* @param {any } expected_value
@@ -847,7 +869,7 @@ function get_element_parent(node) {
847
869
* @param {Set<Compiler.AST.SnippetBlock> } seen
848
870
* @returns {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue> }
849
871
*/
850
- function get_possible_element_siblings ( node , adjacent_only , seen = new Set ( ) ) {
872
+ function get_possible_element_preceding_siblings ( node , adjacent_only , seen = new Set ( ) ) {
851
873
/** @type {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue> } */
852
874
const result = new Map ( ) ;
853
875
const path = node . metadata . path ;
@@ -910,7 +932,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
910
932
seen . add ( current ) ;
911
933
912
934
for ( const site of current . metadata . sites ) {
913
- const siblings = get_possible_element_siblings ( site , adjacent_only , seen ) ;
935
+ const siblings = get_possible_element_preceding_siblings ( site , adjacent_only , seen ) ;
914
936
add_to_map ( siblings , result ) ;
915
937
916
938
if ( adjacent_only && current . metadata . sites . size === 1 && has_definite_elements ( siblings ) ) {
@@ -1067,3 +1089,27 @@ function is_block(node) {
1067
1089
node . type === 'SlotElement'
1068
1090
) ;
1069
1091
}
1092
+
1093
+ /**
1094
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement } element
1095
+ * @return {ExtendedRelativeSelector }
1096
+ */
1097
+ function make_element_selector ( element ) {
1098
+ return {
1099
+ type : 'RelativeSelector' ,
1100
+ selectors : [ {
1101
+ type : 'ElementSelector' ,
1102
+ element,
1103
+ start : - 1 ,
1104
+ end : - 1 ,
1105
+ } ] ,
1106
+ combinator : null ,
1107
+ metadata : {
1108
+ is_global : false ,
1109
+ is_global_like : false ,
1110
+ scoped : false ,
1111
+ } ,
1112
+ start : - 1 ,
1113
+ end : - 1 ,
1114
+ } ;
1115
+ }
0 commit comments