Skip to content

Commit 870745f

Browse files
authored
fix: handle sibling combinators within :has (#14213)
We didn't collect sibling elements of a given element to then check the `:has` selectors. This adds the logic for that. Fixes #14072
1 parent 53af138 commit 870745f

File tree

5 files changed

+91
-1
lines changed

5 files changed

+91
-1
lines changed

.changeset/four-jobs-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: handle sibling combinators within `:has`

packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,8 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
412412
const child_elements = [];
413413
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
414414
const descendant_elements = [];
415+
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
416+
let sibling_elements; // do them lazy because it's rarely used and expensive to calculate
415417

416418
walk(
417419
/** @type {Compiler.SvelteNode} */ (element.fragment),
@@ -457,7 +459,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
457459
}
458460

459461
const descendants =
460-
left_most_combinator.name === '>' ? child_elements : descendant_elements;
462+
left_most_combinator.name === '+' || left_most_combinator.name === '~'
463+
? (sibling_elements ??= get_following_sibling_elements(element))
464+
: left_most_combinator.name === '>'
465+
? child_elements
466+
: descendant_elements;
461467

462468
let selector_matched = false;
463469

@@ -675,6 +681,51 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
675681
return true;
676682
}
677683

684+
/** @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element */
685+
function get_following_sibling_elements(element) {
686+
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null} */
687+
let parent = get_element_parent(element);
688+
689+
if (!parent) {
690+
parent = element;
691+
while (parent?.type !== 'Root') {
692+
parent = /** @type {any} */ (parent).parent;
693+
}
694+
}
695+
696+
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
697+
const sibling_elements = [];
698+
let found_parent = false;
699+
700+
for (const el of parent.fragment.nodes) {
701+
if (found_parent) {
702+
walk(
703+
el,
704+
{},
705+
{
706+
RegularElement(node) {
707+
sibling_elements.push(node);
708+
},
709+
SvelteElement(node) {
710+
sibling_elements.push(node);
711+
}
712+
}
713+
);
714+
} else {
715+
/** @type {any} */
716+
let child = element;
717+
while (child !== el && child !== parent) {
718+
child = child.parent;
719+
}
720+
if (child === el) {
721+
found_parent = true;
722+
}
723+
}
724+
}
725+
726+
return sibling_elements;
727+
}
728+
678729
/**
679730
* @param {any} operator
680731
* @param {any} expected_value

packages/svelte/tests/css/samples/has/_config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ export default test({
127127
column: 11,
128128
character: 1134
129129
}
130+
},
131+
{
132+
code: 'css_unused_selector',
133+
message: 'Unused CSS selector "x:has(~ y)"',
134+
start: {
135+
line: 121,
136+
column: 1,
137+
character: 1326
138+
},
139+
end: {
140+
line: 121,
141+
column: 11,
142+
character: 1336
143+
}
130144
}
131145
]
132146
});

packages/svelte/tests/css/samples/has/expected.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,13 @@
101101
x.svelte-xyz:has(y:where(.svelte-xyz)) + c:where(.svelte-xyz) {
102102
color: green;
103103
}
104+
105+
x.svelte-xyz:has(+ c:where(.svelte-xyz)) {
106+
color: green;
107+
}
108+
x.svelte-xyz:has(~ c:where(.svelte-xyz)) {
109+
color: green;
110+
}
111+
/* (unused) x:has(~ y) {
112+
color: red;
113+
}*/

packages/svelte/tests/css/samples/has/input.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,14 @@
111111
x:has(y) + c {
112112
color: green;
113113
}
114+
115+
x:has(+ c) {
116+
color: green;
117+
}
118+
x:has(~ c) {
119+
color: green;
120+
}
121+
x:has(~ y) {
122+
color: red;
123+
}
114124
</style>

0 commit comments

Comments
 (0)