@@ -58,7 +58,7 @@ export default class Selector {
58
58
apply ( node ) {
59
59
/** @type {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }> } */
60
60
const to_encapsulate = [ ] ;
61
- apply_selector ( this . local_blocks . slice ( ) , node , to_encapsulate ) ;
61
+ apply_selector ( this . local_blocks , node , to_encapsulate ) ;
62
62
if ( to_encapsulate . length > 0 ) {
63
63
to_encapsulate . forEach ( ( { node, block } ) => {
64
64
this . stylesheet . nodes_with_css_class . add ( node ) ;
@@ -203,20 +203,36 @@ export default class Selector {
203
203
* @param {Block[] } blocks
204
204
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | null } node
205
205
* @param {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }> } to_encapsulate
206
+ * @param {boolean } [has_render_tag]
206
207
* @returns {boolean }
207
208
*/
208
- function apply_selector ( blocks , node , to_encapsulate ) {
209
+ function apply_selector (
210
+ blocks ,
211
+ node ,
212
+ to_encapsulate ,
213
+ has_render_tag = node ?. fragment . nodes . some ( ( n ) => n . type === 'RenderTag' )
214
+ ) {
215
+ blocks = blocks . slice ( ) ;
209
216
const block = blocks . pop ( ) ;
210
217
if ( ! block ) return false ;
211
218
if ( ! node ) {
212
219
return (
213
220
( block . global && blocks . every ( ( block ) => block . global ) ) || ( block . host && blocks . length === 0 )
214
221
) ;
215
222
}
216
- const applies = block_might_apply_to_node ( block , node ) ;
223
+
224
+ let applies = block_might_apply_to_node ( block , node ) ;
217
225
218
226
if ( applies === NO_MATCH ) {
219
- return false ;
227
+ if ( has_render_tag ) {
228
+ // If the element contains a render tag then we assume the selector might match something inside the rendered snippet
229
+ // and traverse the blocks upwards to see if the present blocks match our node further upwards.
230
+ // We could do more static analysis and check the render tag reference to see if this snippet block continues
231
+ // with elements that actually match the selector, but that would be a lot of work for little gain
232
+ return apply_selector ( blocks , node , to_encapsulate , true ) ;
233
+ } else {
234
+ return false ;
235
+ }
220
236
}
221
237
222
238
if ( applies === UNKNOWN_SELECTOR ) {
@@ -225,7 +241,7 @@ function apply_selector(blocks, node, to_encapsulate) {
225
241
}
226
242
227
243
if ( block . combinator ) {
228
- if ( block . combinator . type === 'Combinator' && block . combinator . name === ' ' ) {
244
+ if ( block . combinator . name === ' ' ) {
229
245
for ( const ancestor_block of blocks ) {
230
246
if ( ancestor_block . global ) {
231
247
continue ;
@@ -234,7 +250,7 @@ function apply_selector(blocks, node, to_encapsulate) {
234
250
to_encapsulate . push ( { node, block } ) ;
235
251
return true ;
236
252
}
237
- /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | null } */
253
+ /** @type {ReturnType<typeof get_element_parent> } */
238
254
let parent = node ;
239
255
while ( ( parent = get_element_parent ( parent ) ) ) {
240
256
if ( block_might_apply_to_node ( ancestor_block , parent ) !== NO_MATCH ) {
@@ -250,10 +266,27 @@ function apply_selector(blocks, node, to_encapsulate) {
250
266
to_encapsulate . push ( { node, block } ) ;
251
267
return true ;
252
268
}
269
+ // The inverse of the render tag logic above: mark the node as encapsulated if it's inside a snippet block.
270
+ // May result in false positives just like the render tag logic for the same reasons.
271
+ // TODO try to get rid of .parent in favor of path in the long run
272
+ if ( node . parent ?. type === 'SnippetBlock' ) {
273
+ to_encapsulate . push ( { node, block } ) ;
274
+ return true ;
275
+ }
253
276
return false ;
254
277
} else if ( block . combinator . name === '>' ) {
255
278
const has_global_parent = blocks . every ( ( block ) => block . global ) ;
256
- if ( has_global_parent || apply_selector ( blocks , get_element_parent ( node ) , to_encapsulate ) ) {
279
+ if (
280
+ has_global_parent ||
281
+ apply_selector ( blocks , get_element_parent ( node ) , to_encapsulate , has_render_tag )
282
+ ) {
283
+ to_encapsulate . push ( { node, block } ) ;
284
+ return true ;
285
+ }
286
+ // The inverse of the render tag logic above: mark the node as encapsulated if it's inside a snippet block.
287
+ // May result in false positives just like the render tag logic for the same reasons.
288
+ // TODO try to get rid of .parent in favor of path in the long run
289
+ if ( node . parent ?. type === 'SnippetBlock' ) {
257
290
to_encapsulate . push ( { node, block } ) ;
258
291
return true ;
259
292
}
@@ -273,7 +306,7 @@ function apply_selector(blocks, node, to_encapsulate) {
273
306
return true ;
274
307
}
275
308
for ( const possible_sibling of siblings . keys ( ) ) {
276
- if ( apply_selector ( blocks . slice ( ) , possible_sibling , to_encapsulate ) ) {
309
+ if ( apply_selector ( blocks , possible_sibling , to_encapsulate , has_render_tag ) ) {
277
310
to_encapsulate . push ( { node, block } ) ;
278
311
has_match = true ;
279
312
}
@@ -514,9 +547,10 @@ function get_element_parent(node) {
514
547
// @ts -expect-error TODO figure out a more elegant solution
515
548
( parent = parent . parent ) &&
516
549
parent . type !== 'RegularElement' &&
517
- parent . type !== 'SvelteElement'
550
+ parent . type !== 'SvelteElement' &&
551
+ parent . type !== 'SnippetBlock'
518
552
) ;
519
- return parent ?? null ;
553
+ return parent ?. type !== 'SnippetBlock' ? parent ?? null : null ;
520
554
}
521
555
522
556
/**
0 commit comments