Skip to content

Commit af38d89

Browse files
committed
support <elem slot=...> in slots forwarding
1 parent b4df71f commit af38d89

File tree

17 files changed

+253
-116
lines changed

17 files changed

+253
-116
lines changed

src/compiler/compile/nodes/InlineComponent.ts

+3-72
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import TemplateScope from './shared/TemplateScope';
1010
import { INode } from './interfaces';
1111
import { TemplateNode } from '../../interfaces';
1212
import compiler_errors from '../compiler_errors';
13-
import { regex_only_whitespaces } from '../../utils/patterns';
1413
import { validate_get_slot_names } from './SlotTemplateIfBlock';
14+
import { extract_children_to_slot_templates } from './extract_children_to_slot_templates';
1515

1616
export default class InlineComponent extends Node {
1717
type: 'InlineComponent';
@@ -107,66 +107,9 @@ export default class InlineComponent extends Node {
107107
});
108108
});
109109

110-
const children = [];
111-
for (let i = info.children.length - 1; i >= 0; i--) {
112-
const child = info.children[i];
113-
if (child.type === 'SlotTemplate') {
114-
children.push(child);
115-
info.children.splice(i, 1);
116-
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
117-
const slot_template = {
118-
start: child.start,
119-
end: child.end,
120-
type: 'SlotTemplate',
121-
name: 'svelte:fragment',
122-
attributes: [],
123-
children: [child]
124-
};
125-
126-
// transfer attributes
127-
for (let i = child.attributes.length - 1; i >= 0; i--) {
128-
const attribute = child.attributes[i];
129-
if (attribute.type === 'Let') {
130-
slot_template.attributes.push(attribute);
131-
child.attributes.splice(i, 1);
132-
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
133-
slot_template.attributes.push(attribute);
134-
}
135-
}
136-
// transfer const
137-
for (let i = child.children.length - 1; i >= 0; i--) {
138-
const child_child = child.children[i];
139-
if (child_child.type === 'ConstTag') {
140-
slot_template.children.push(child_child);
141-
child.children.splice(i, 1);
142-
}
143-
}
110+
const children = extract_children_to_slot_templates(component, info, true);
144111

145-
children.push(slot_template);
146-
info.children.splice(i, 1);
147-
} else if (child.type === 'Comment' && children.length > 0) {
148-
children[children.length - 1].children.unshift(child);
149-
info.children.splice(i, 1);
150-
} else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) {
151-
children.push({
152-
...child,
153-
type: 'SlotTemplateIfBlock'
154-
});
155-
info.children.splice(i, 1);
156-
}
157-
}
158-
if (info.children.some(node => not_whitespace_text(node))) {
159-
children.push({
160-
start: info.start,
161-
end: info.end,
162-
type: 'SlotTemplate',
163-
name: 'svelte:fragment',
164-
attributes: [],
165-
children: info.children
166-
});
167-
}
168-
169-
this.children = map_children(component, this, this.scope, children.reverse());
112+
this.children = map_children(component, this, this.scope, children);
170113

171114
this.validate_duplicate_slot_name();
172115
}
@@ -180,10 +123,6 @@ export default class InlineComponent extends Node {
180123
}
181124
}
182125

183-
function not_whitespace_text(node) {
184-
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
185-
}
186-
187126
function get_namespace(parent: Node, explicit_namespace: string) {
188127
const parent_element = parent.find_nearest(/^Element/);
189128

@@ -193,11 +132,3 @@ function get_namespace(parent: Node, explicit_namespace: string) {
193132

194133
return parent_element.namespace;
195134
}
196-
197-
function if_block_contains_slot_template(node: TemplateNode) {
198-
for (const child of node.children) {
199-
if (child.type === 'SlotTemplate') return true;
200-
if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true;
201-
}
202-
return false;
203-
}

src/compiler/compile/nodes/Slot.ts

+2-1
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 unable to override the type from Element, but this give us a right type
11+
type: 'Slot';
1112
name: string;
1213
children: INode[];
1314
slot_name: string;

src/compiler/compile/nodes/SlotTemplateIfBlock.ts

+2-19
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import compiler_errors from '../compiler_errors';
88
import get_const_tags from './shared/get_const_tags';
99
import { TemplateNode } from '../../interfaces';
1010
import ConstTag from './ConstTag';
11-
import { regex_only_whitespaces } from '../../utils/patterns';
1211
import SlotTemplate from './SlotTemplate';
1312
import { INode } from './interfaces';
14-
13+
import { extract_children_to_slot_templates } from './extract_children_to_slot_templates';
1514

1615
export default class SlotTemplateIfBlock extends AbstractBlock {
1716
type: 'SlotTemplateIfBlock';
@@ -30,23 +29,7 @@ export default class SlotTemplateIfBlock extends AbstractBlock {
3029
super(component, parent, scope, info);
3130
this.scope = scope.child();
3231

33-
const children = [];
34-
for (const child of info.children) {
35-
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
36-
children.push(child);
37-
} else if (child.type === 'Comment') {
38-
// ignore
39-
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
40-
// ignore
41-
} else if (child.type === 'IfBlock') {
42-
children.push({
43-
...child,
44-
type: 'SlotTemplateIfBlock'
45-
});
46-
} else {
47-
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
48-
}
49-
}
32+
const children = extract_children_to_slot_templates(component, info, false);
5033

5134
this.expression = new Expression(component, this, this.scope, info.expression);
5235
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { x } from 'code-red';
2+
import { TemplateNode } from '../../interfaces';
3+
import { regex_only_whitespaces } from '../../utils/patterns';
4+
import compiler_errors from '../compiler_errors';
5+
import Component from '../Component';
6+
7+
export function extract_children_to_slot_templates(component: Component, node: TemplateNode, extract_default_slot: boolean) {
8+
const result = [];
9+
for (let i = node.children.length - 1; i >= 0; i--) {
10+
const child = node.children[i];
11+
if (child.type === 'SlotTemplate') {
12+
result.push(child);
13+
node.children.splice(i, 1);
14+
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
15+
let slot_template = {
16+
start: child.start,
17+
end: child.end,
18+
type: 'SlotTemplate',
19+
name: 'svelte:fragment',
20+
attributes: [],
21+
children: [child]
22+
};
23+
24+
// transfer attributes
25+
for (let i = child.attributes.length - 1; i >= 0; i--) {
26+
const attribute = child.attributes[i];
27+
if (attribute.type === 'Let') {
28+
slot_template.attributes.push(attribute);
29+
child.attributes.splice(i, 1);
30+
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
31+
slot_template.attributes.push(attribute);
32+
}
33+
}
34+
// transfer const
35+
for (let i = child.children.length - 1; i >= 0; i--) {
36+
const child_child = child.children[i];
37+
if (child_child.type === 'ConstTag') {
38+
slot_template.children.push(child_child);
39+
child.children.splice(i, 1);
40+
}
41+
}
42+
43+
// if the <slot slot="x"> does not have any fallback
44+
// then we make <slot slot="x" name="b" />
45+
// into {#if $$slots.x}<slot slot="x" name="b" />{/if}
46+
// this makes the slots forwarding to passthrough
47+
if (child.type === 'Slot' && child.children.length === 0) {
48+
const slot_template_name = child.attributes.find(attribute => attribute.name === 'name')?.value[0].data ?? 'default';
49+
slot_template = {
50+
start: slot_template.start,
51+
end: slot_template.end,
52+
type: 'SlotTemplateIfBlock',
53+
expression: x`$$slots.${slot_template_name}`,
54+
children: [slot_template]
55+
} as any;
56+
}
57+
58+
result.push(slot_template);
59+
node.children.splice(i, 1);
60+
} else if (child.type === 'Comment' && result.length > 0) {
61+
result[result.length - 1].children.unshift(child);
62+
node.children.splice(i, 1);
63+
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
64+
// ignore
65+
} else if (child.type === 'ConstTag') {
66+
if (!extract_default_slot) {
67+
result.push(child);
68+
}
69+
} else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) {
70+
result.push({
71+
...child,
72+
type: 'SlotTemplateIfBlock'
73+
});
74+
node.children.splice(i, 1);
75+
} else if (!extract_default_slot) {
76+
component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
77+
}
78+
}
79+
80+
if (extract_default_slot) {
81+
if (node.children.some(node => not_whitespace_text(node))) {
82+
result.push({
83+
start: node.start,
84+
end: node.end,
85+
type: 'SlotTemplate',
86+
name: 'svelte:fragment',
87+
attributes: [],
88+
children: node.children
89+
});
90+
}
91+
}
92+
return result.reverse();
93+
}
94+
95+
96+
function not_whitespace_text(node) {
97+
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
98+
}
99+
100+
function if_block_contains_slot_template(node: TemplateNode) {
101+
for (const child of node.children) {
102+
if (child.type === 'SlotTemplate') return true;
103+
if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true;
104+
if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) return true;
105+
}
106+
return false;
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<slot name="a">default a</slot>
2+
<hr/>
3+
<slot name="b">default b</slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default {
2+
html: `
3+
default a
4+
<hr>
5+
<div slot="b">hello b</div>
6+
`,
7+
test({ assert, component, target }) {
8+
component.condition = true;
9+
assert.htmlEqual(target.innerHTML, `
10+
<div slot="a">hello a</div>
11+
<hr>
12+
<div slot="b">hello b</div>
13+
`);
14+
}
15+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import Foo from "./Foo.svelte";
3+
export let condition = false;
4+
</script>
5+
6+
<Foo>
7+
{#if condition}
8+
<div slot="a">hello a</div>
9+
{/if}
10+
<div slot="b">hello b</div>
11+
</Foo>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
x: <slot name="x">fallback x</slot>
2+
y: <slot name="y">fallback y</slot>
3+
z: <slot name="z">fallback z</slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import Bar from './Bar.svelte';
3+
export let condition1 = false;
4+
export let condition2 = true;
5+
</script>
6+
7+
<Bar>
8+
<slot name="a" slot="x">Fallback a</slot>
9+
{#if condition1}
10+
<slot name="b" slot="y">Fallback b</slot>
11+
{/if}
12+
{#if condition2}
13+
<slot name="c" slot="z">Fallback c</slot>
14+
{/if}
15+
</Bar>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export default {
2+
html: `
3+
x: Fallback a
4+
y: fallback y
5+
z: <div slot="c">hello c</div>
6+
`,
7+
test({ assert, component, target }) {
8+
component.condition1 = true;
9+
assert.htmlEqual(target.innerHTML, `
10+
x: Fallback a
11+
y: <div slot="b">hello b</div>
12+
z: <div slot="c">hello c</div>
13+
`);
14+
15+
component.condition3 = true;
16+
assert.htmlEqual(target.innerHTML, `
17+
x: <div slot="a">hello a</div>
18+
y: <div slot="b">hello b</div>
19+
z: <div slot="c">hello c</div>
20+
`);
21+
22+
component.condition4 = false;
23+
component.condition1 = false;
24+
assert.htmlEqual(target.innerHTML, `
25+
x: <div slot="a">hello a</div>
26+
y: fallback y
27+
z: <div slot="c">hello c</div>
28+
`);
29+
30+
component.condition2 = false;
31+
assert.htmlEqual(target.innerHTML, `
32+
x: <div slot="a">hello a</div>
33+
y: fallback y
34+
z: fallback z
35+
`);
36+
}
37+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
import Foo from "./Foo.svelte";
3+
export let condition1;
4+
export let condition2;
5+
export let condition3 = false;
6+
export let condition4 = true;
7+
</script>
8+
9+
<Foo {condition1} {condition2}>
10+
{#if condition3}
11+
<div slot="a">hello a</div>
12+
{/if}
13+
{#if condition4}
14+
<div slot="b">hello b</div>
15+
{/if}
16+
<div slot="c">hello c</div>
17+
</Foo>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
x: <slot name="x">fallback x</slot>
2+
y: <slot name="y">fallback y</slot>
3+
z: <slot name="z">fallback z</slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import Bar from './Bar.svelte';
3+
</script>
4+
5+
<Bar>
6+
<slot name="a" slot="x" />
7+
<slot name="b" slot="y" />
8+
<slot name="c" slot="z" />
9+
</Bar>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default {
2+
html: `
3+
x: fallback x
4+
y: <div slot="b">hello b</div>
5+
z: fallback z
6+
`,
7+
test({ assert, component, target }) {
8+
component.condition = true;
9+
assert.htmlEqual(target.innerHTML, `
10+
x: <div slot="a">hello a</div>
11+
y: <div slot="b">hello b</div>
12+
z: fallback z
13+
`);
14+
}
15+
};

0 commit comments

Comments
 (0)