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.
pull/8566/head
Nguyen Tran 2 years ago committed by GitHub
parent 19e163f59d
commit c0363966d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -470,10 +470,52 @@ function get_element_parent(node: Element): Element | null {
return parent as Element | null;
}
/**
* Finds the given node's previous sibling in the DOM
*
* Unless the component is a custom element (web component), which in this
* case, the <slot> element is actually real, the Svelte <slot> is just a
* placeholder and is not actually real. Any children nodes in <slot>
* are 'flattened' and considered as the same level as the <slot>'s siblings
*
* e.g.
* <h1>Heading 1</h1>
* <slot>
* <h2>Heading 2</h2>
* </slot>
*
* is considered to look like:
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
*/
function find_previous_sibling(node: INode): INode {
if (node.component.compile_options.customElement) {
return node.prev;
}
let current_node: INode = node;
do {
if (current_node.type === 'Slot') {
const slot_children = current_node.children;
if (slot_children.length > 0) {
current_node = slot_children.slice(-1)[0]; // go to its last child first
continue;
}
}
while (!current_node.prev && current_node.parent && current_node.parent.type === 'Slot') {
current_node = current_node.parent;
}
current_node = current_node.prev;
} while (current_node && current_node.type === 'Slot');
return current_node;
}
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> {
const result: Map<Element, NodeExist> = new Map();
let prev: INode = node;
while (prev = prev.prev) {
while (prev = find_previous_sibling(prev)) {
if (prev.type === 'Element') {
if (!prev.attributes.find(attr => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot')) {
result.set(prev, NodeExist.Definitely);

@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
export default class Slot extends Element {
type: 'Element';
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
type: 'Slot';
name: string;
children: INode[];
slot_name: string;

@ -0,0 +1,12 @@
<slot>
<h1>Heading 1</h1>
</slot>
<span>Span 1</span>
<span>Span 2</span>
<p>Paragraph 2</p>
<style>
h1 ~ p {
color: red;
}
</style>

@ -0,0 +1,7 @@
<slot><slot><h1>Heading 1</h1></slot></slot><span>Span 1</span><span>Span 2</span><slot><slot><p>Paragraph 2</p></slot></slot>
<style>
h1 ~ p {
color: red;
}
</style>

@ -0,0 +1,18 @@
<slot>
<slot>
<h1>Heading 1</h1>
</slot>
</slot>
<span>Span 1</span>
<span>Span 2</span>
<slot>
<slot>
<p>Paragraph 2</p>
</slot>
</slot>
<style>
h1 ~ p {
color: red;
}
</style>

@ -0,0 +1,12 @@
<h1>Heading 1</h1>
<span>Span 1</span>
<span>Span 2</span>
<slot>
<p>Paragraph 2</p>
</slot>
<style>
h1 ~ p {
color: red;
}
</style>

@ -0,0 +1 @@
h1.svelte-xyz~span.svelte-xyz{color:green}h1.svelte-xyz~p.svelte-xyz{color:red}span.svelte-xyz~p.svelte-xyz{color:blue}

@ -0,0 +1,22 @@
<h1>Heading 1</h1>
<slot>
<span>Span 1</span>
</slot>
<slot>
<span>Span 2</span>
</slot>
<p>Paragraph 2</p>
<style>
h1 ~ span {
color: green;
}
h1 ~ p {
color: red;
}
span ~ p {
color: blue;
}
</style>

@ -0,0 +1,10 @@
<slot>
<h1>test</h1>
</slot>
<span>Hello</span>
<style>
h1 + span {
color: red;
}
</style>

@ -0,0 +1,7 @@
<slot><slot><slot><h1>test</h1></slot></slot></slot><slot><slot><span>Hello</span></slot></slot>
<style>
h1 + span {
color: red;
}
</style>

@ -0,0 +1 @@
h1.svelte-xyz+span.svelte-xyz{color:red}

@ -0,0 +1,18 @@
<slot>
<slot>
<slot>
<h1>test</h1>
</slot>
</slot>
</slot>
<slot>
<slot>
<span>Hello</span>
</slot>
</slot>
<style>
h1 + span {
color: red;
}
</style>

@ -0,0 +1,10 @@
<h1>test</h1>
<slot>
<span>Hello</span>
</slot>
<style>
h1 + span {
color: red;
}
</style>

@ -0,0 +1 @@
h1.svelte-xyz+span.svelte-xyz{color:red}

@ -0,0 +1,11 @@
<h1>test</h1>
<slot name="a"></slot>
<slot name="b"></slot>
<slot name="c"></slot>
<span>Hello</span>
<style>
h1 + span {
color: red;
}
</style>

@ -0,0 +1,20 @@
<svelte:options tag="my-element" />
<h1>Heading 1</h1>
<span>Span 1</span>
<span>Span 2</span>
<slot>
<p>Paragraph 2</p>
</slot>
<style>
/* This will not get picked up */
h1 ~ p {
color: red;
}
/* This will be picked up */
h1 ~ slot > p {
color: red;
}
</style>

@ -0,0 +1,14 @@
[
{
"code": "css-unused-selector",
"message": "Unused CSS selector \"h1 ~ p\"",
"start": {
"column": 1,
"line": 12
},
"end": {
"column": 7,
"line": 12
}
}
]

@ -0,0 +1,18 @@
<svelte:options tag="custom-element" />
<h1>test</h1>
<slot>
<span>Hello</span>
</slot>
<style>
/* This will not be picked up */
h1 + span {
color: red;
}
/* This will be picked up */
h1 + slot > span {
color: red;
}
</style>

@ -0,0 +1,14 @@
[
{
"code": "css-unused-selector",
"end": {
"column": 11,
"line": 10
},
"message": "Unused CSS selector \"h1 + span\"",
"start": {
"column": 2,
"line": 10
}
}
]
Loading…
Cancel
Save