Skip to content

Commit c036396

Browse files
authored
fix: treat slots as if they don't exist when using CSS adjacent and general sibling combinators (#8422)
Fixes #8284. The problem is that the <slot> element is treated as an actual element, and for this purpose, we have to treat them as if they don't exist. More specifically, we treat all slot fallback children nodes on the same level as the slot's non-slot siblings.
1 parent 19e163f commit c036396

File tree

28 files changed

+254
-2
lines changed

28 files changed

+254
-2
lines changed

src/compiler/compile/css/Selector.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,52 @@ function get_element_parent(node: Element): Element | null {
470470
return parent as Element | null;
471471
}
472472

473+
/**
474+
* Finds the given node's previous sibling in the DOM
475+
*
476+
* Unless the component is a custom element (web component), which in this
477+
* case, the <slot> element is actually real, the Svelte <slot> is just a
478+
* placeholder and is not actually real. Any children nodes in <slot>
479+
* are 'flattened' and considered as the same level as the <slot>'s siblings
480+
*
481+
* e.g.
482+
* <h1>Heading 1</h1>
483+
* <slot>
484+
* <h2>Heading 2</h2>
485+
* </slot>
486+
*
487+
* is considered to look like:
488+
* <h1>Heading 1</h1>
489+
* <h2>Heading 2</h2>
490+
*/
491+
function find_previous_sibling(node: INode): INode {
492+
if (node.component.compile_options.customElement) {
493+
return node.prev;
494+
}
495+
496+
let current_node: INode = node;
497+
do {
498+
if (current_node.type === 'Slot') {
499+
const slot_children = current_node.children;
500+
if (slot_children.length > 0) {
501+
current_node = slot_children.slice(-1)[0]; // go to its last child first
502+
continue;
503+
}
504+
}
505+
506+
while (!current_node.prev && current_node.parent && current_node.parent.type === 'Slot') {
507+
current_node = current_node.parent;
508+
}
509+
current_node = current_node.prev;
510+
} while (current_node && current_node.type === 'Slot');
511+
512+
return current_node;
513+
}
514+
473515
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> {
474516
const result: Map<Element, NodeExist> = new Map();
475517
let prev: INode = node;
476-
while (prev = prev.prev) {
518+
while (prev = find_previous_sibling(prev)) {
477519
if (prev.type === 'Element') {
478520
if (!prev.attributes.find(attr => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot')) {
479521
result.set(prev, NodeExist.Definitely);

src/compiler/compile/nodes/Slot.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces';
77
import compiler_errors from '../compiler_errors';
88

99
export default class Slot extends Element {
10-
type: 'Element';
10+
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
11+
type: 'Slot';
1112
name: string;
1213
children: INode[];
1314
slot_name: string;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz~p.svelte-xyz{color:red}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<slot>
2+
<h1>Heading 1</h1>
3+
</slot>
4+
<span>Span 1</span>
5+
<span>Span 2</span>
6+
<p>Paragraph 2</p>
7+
8+
<style>
9+
h1 ~ p {
10+
color: red;
11+
}
12+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz~p.svelte-xyz{color:red}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<slot><slot><h1>Heading 1</h1></slot></slot><span>Span 1</span><span>Span 2</span><slot><slot><p>Paragraph 2</p></slot></slot>
2+
3+
<style>
4+
h1 ~ p {
5+
color: red;
6+
}
7+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz~p.svelte-xyz{color:red}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<slot>
2+
<slot>
3+
<h1>Heading 1</h1>
4+
</slot>
5+
</slot>
6+
<span>Span 1</span>
7+
<span>Span 2</span>
8+
<slot>
9+
<slot>
10+
<p>Paragraph 2</p>
11+
</slot>
12+
</slot>
13+
14+
<style>
15+
h1 ~ p {
16+
color: red;
17+
}
18+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz~p.svelte-xyz{color:red}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<h1>Heading 1</h1>
2+
<span>Span 1</span>
3+
<span>Span 2</span>
4+
<slot>
5+
<p>Paragraph 2</p>
6+
</slot>
7+
8+
<style>
9+
h1 ~ p {
10+
color: red;
11+
}
12+
</style>

test/css/samples/general-siblings-combinator-slots-between/expected.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<h1>Heading 1</h1>
2+
<slot>
3+
<span>Span 1</span>
4+
</slot>
5+
<slot>
6+
<span>Span 2</span>
7+
</slot>
8+
<p>Paragraph 2</p>
9+
10+
<style>
11+
h1 ~ span {
12+
color: green;
13+
}
14+
15+
h1 ~ p {
16+
color: red;
17+
}
18+
19+
span ~ p {
20+
color: blue;
21+
}
22+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz+span.svelte-xyz{color:red}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<slot>
2+
<h1>test</h1>
3+
</slot>
4+
<span>Hello</span>
5+
6+
<style>
7+
h1 + span {
8+
color: red;
9+
}
10+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz+span.svelte-xyz{color:red}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<slot><slot><slot><h1>test</h1></slot></slot></slot><slot><slot><span>Hello</span></slot></slot>
2+
3+
<style>
4+
h1 + span {
5+
color: red;
6+
}
7+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz+span.svelte-xyz{color:red}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<slot>
2+
<slot>
3+
<slot>
4+
<h1>test</h1>
5+
</slot>
6+
</slot>
7+
</slot>
8+
<slot>
9+
<slot>
10+
<span>Hello</span>
11+
</slot>
12+
</slot>
13+
14+
<style>
15+
h1 + span {
16+
color: red;
17+
}
18+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz+span.svelte-xyz{color:red}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<h1>test</h1>
2+
<slot>
3+
<span>Hello</span>
4+
</slot>
5+
6+
<style>
7+
h1 + span {
8+
color: red;
9+
}
10+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
h1.svelte-xyz+span.svelte-xyz{color:red}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<h1>test</h1>
2+
<slot name="a"></slot>
3+
<slot name="b"></slot>
4+
<slot name="c"></slot>
5+
<span>Hello</span>
6+
7+
<style>
8+
h1 + span {
9+
color: red;
10+
}
11+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
customElement: true
3+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<svelte:options tag="my-element" />
2+
3+
<h1>Heading 1</h1>
4+
<span>Span 1</span>
5+
<span>Span 2</span>
6+
<slot>
7+
<p>Paragraph 2</p>
8+
</slot>
9+
10+
<style>
11+
/* This will not get picked up */
12+
h1 ~ p {
13+
color: red;
14+
}
15+
16+
/* This will be picked up */
17+
h1 ~ slot > p {
18+
color: red;
19+
}
20+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "css-unused-selector",
4+
"message": "Unused CSS selector \"h1 ~ p\"",
5+
"start": {
6+
"column": 1,
7+
"line": 12
8+
},
9+
"end": {
10+
"column": 7,
11+
"line": 12
12+
}
13+
}
14+
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
customElement: true
3+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<svelte:options tag="custom-element" />
2+
3+
<h1>test</h1>
4+
<slot>
5+
<span>Hello</span>
6+
</slot>
7+
8+
<style>
9+
/* This will not be picked up */
10+
h1 + span {
11+
color: red;
12+
}
13+
14+
/* This will be picked up */
15+
h1 + slot > span {
16+
color: red;
17+
}
18+
</style>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "css-unused-selector",
4+
"end": {
5+
"column": 11,
6+
"line": 10
7+
},
8+
"message": "Unused CSS selector \"h1 + span\"",
9+
"start": {
10+
"column": 2,
11+
"line": 10
12+
}
13+
}
14+
]

0 commit comments

Comments
 (0)