Skip to content

Commit b6798e5

Browse files
jesseskinnerConduitry
authored andcommitted
allow multiple ancestors to be scoped with class (#3544)
1 parent af0557a commit b6798e5

File tree

10 files changed

+131
-38
lines changed

10 files changed

+131
-38
lines changed

src/compiler/compile/css/Selector.ts

+66-38
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
44
import { CssNode } from './interfaces';
55
import Component from '../Component';
66

7+
enum BlockAppliesToNode {
8+
NotPossible,
9+
Possible,
10+
UnknownSelectorType
11+
}
12+
713
export default class Selector {
814
node: CssNode;
915
stylesheet: Stylesheet;
@@ -31,10 +37,10 @@ export default class Selector {
3137
apply(node: CssNode, stack: CssNode[]) {
3238
const to_encapsulate: CssNode[] = [];
3339

34-
apply_selector(this.stylesheet, this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
40+
apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
3541

3642
if (to_encapsulate.length > 0) {
37-
to_encapsulate.filter((_, i) => i === 0 || i === to_encapsulate.length - 1).forEach(({ node, block }) => {
43+
to_encapsulate.forEach(({ node, block }) => {
3844
this.stylesheet.nodes_with_css_class.add(node);
3945
block.should_encapsulate = true;
4046
});
@@ -126,57 +132,38 @@ export default class Selector {
126132
}
127133
}
128134

129-
function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean {
135+
function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean {
130136
const block = blocks.pop();
131137
if (!block) return false;
132138

133139
if (!node) {
134140
return blocks.every(block => block.global);
135141
}
136142

137-
let i = block.selectors.length;
138-
139-
while (i--) {
140-
const selector = block.selectors[i];
141-
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
142-
143-
if (selector.type === 'PseudoClassSelector' && name === 'global') {
144-
// TODO shouldn't see this here... maybe we should enforce that :global(...)
145-
// cannot be sandwiched between non-global selectors?
143+
switch (block_might_apply_to_node(block, node)) {
144+
case BlockAppliesToNode.NotPossible:
146145
return false;
147-
}
148-
149-
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
150-
continue;
151-
}
152-
153-
if (selector.type === 'ClassSelector') {
154-
if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return false;
155-
}
156-
157-
else if (selector.type === 'IdSelector') {
158-
if (!attribute_matches(node, 'id', name, '=', false)) return false;
159-
}
160146

161-
else if (selector.type === 'AttributeSelector') {
162-
if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return false;
163-
}
164-
165-
else if (selector.type === 'TypeSelector') {
166-
if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return false;
167-
}
168-
169-
else {
147+
case BlockAppliesToNode.UnknownSelectorType:
170148
// bail. TODO figure out what these could be
171149
to_encapsulate.push({ node, block });
172150
return true;
173-
}
174151
}
175152

176153
if (block.combinator) {
177154
if (block.combinator.type === 'WhiteSpace') {
178-
while (stack.length) {
179-
if (apply_selector(stylesheet, blocks.slice(), stack.pop(), stack, to_encapsulate)) {
155+
for (const ancestor_block of blocks) {
156+
if (ancestor_block.global) {
157+
continue;
158+
}
159+
160+
for (const stack_node of stack) {
161+
if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) {
162+
to_encapsulate.push({ node: stack_node, block: ancestor_block });
163+
}
164+
}
165+
166+
if (to_encapsulate.length) {
180167
to_encapsulate.push({ node, block });
181168
return true;
182169
}
@@ -189,7 +176,7 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode,
189176

190177
return false;
191178
} else if (block.combinator.name === '>') {
192-
if (apply_selector(stylesheet, blocks, stack.pop(), stack, to_encapsulate)) {
179+
if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) {
193180
to_encapsulate.push({ node, block });
194181
return true;
195182
}
@@ -206,6 +193,47 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode,
206193
return true;
207194
}
208195

196+
function block_might_apply_to_node(block, node): BlockAppliesToNode {
197+
let i = block.selectors.length;
198+
199+
while (i--) {
200+
const selector = block.selectors[i];
201+
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
202+
203+
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
204+
continue;
205+
}
206+
207+
if (selector.type === 'PseudoClassSelector' && name === 'global') {
208+
// TODO shouldn't see this here... maybe we should enforce that :global(...)
209+
// cannot be sandwiched between non-global selectors?
210+
return BlockAppliesToNode.NotPossible;
211+
}
212+
213+
if (selector.type === 'ClassSelector') {
214+
if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible;
215+
}
216+
217+
else if (selector.type === 'IdSelector') {
218+
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
219+
}
220+
221+
else if (selector.type === 'AttributeSelector') {
222+
if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return BlockAppliesToNode.NotPossible;
223+
}
224+
225+
else if (selector.type === 'TypeSelector') {
226+
if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return BlockAppliesToNode.NotPossible;
227+
}
228+
229+
else {
230+
return BlockAppliesToNode.UnknownSelectorType;
231+
}
232+
}
233+
234+
return BlockAppliesToNode.Possible;
235+
}
236+
209237
function test_attribute(operator, expected_value, case_insensitive, value) {
210238
if (case_insensitive) {
211239
expected_value = expected_value.toLowerCase();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.root.svelte-xyz p{color:red}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="root svelte-xyz">
2+
<section class="whatever svelte-xyz">
3+
</section>
4+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
export let unknown1 = 'root';
3+
export let unknown2 = 'whatever';
4+
</script>
5+
6+
<style>
7+
.root :global(p) {
8+
color: red;
9+
}
10+
</style>
11+
12+
<div class={unknown1}>
13+
<section class={unknown2}>
14+
<!-- injected somehow -->
15+
</section>
16+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
html body .root.svelte-xyz p.svelte-xyz{color:red}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div class="root svelte-xyz">
2+
<section class="whatever svelte-xyz">
3+
<p class="svelte-xyz">hello</p>
4+
</section>
5+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
export let unknown1 = 'root';
3+
export let unknown2 = 'whatever';
4+
</script>
5+
6+
<style>
7+
:global(html) :global(body) .root p {
8+
color: red;
9+
}
10+
</style>
11+
12+
<div class={unknown1}>
13+
<section class={unknown2}>
14+
<p>hello</p>
15+
</section>
16+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.root.svelte-xyz p.svelte-xyz{color:red}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div class="root svelte-xyz">
2+
<section class="whatever svelte-xyz">
3+
<p class="svelte-xyz">hello</p>
4+
</section>
5+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
export let unknown1 = 'root';
3+
export let unknown2 = 'whatever';
4+
</script>
5+
6+
<style>
7+
.root p {
8+
color: red;
9+
}
10+
</style>
11+
12+
<div class={unknown1}>
13+
<section class={unknown2}>
14+
<p>hello</p>
15+
</section>
16+
</div>

0 commit comments

Comments
 (0)