Skip to content

Commit 8056829

Browse files
authored
allow <slot> to be part of a slot (#4295)
1 parent 0645631 commit 8056829

File tree

9 files changed

+137
-46
lines changed

9 files changed

+137
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* Support `<slot slot="...">` ([#2079](https://github.com/sveltejs/svelte/issues/2079))
56
* Add `EventSource` to known globals ([#5463](https://github.com/sveltejs/svelte/issues/5463))
67
* Fix compiler exception with `~`/`+` combinators and `{...spread}` attributes ([#5465](https://github.com/sveltejs/svelte/issues/5465))
78

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import ElementWrapper from './index';
2+
import SlotWrapper from '../Slot';
3+
import Block from '../../Block';
4+
import { sanitize } from '../../../../utils/names';
5+
import InlineComponentWrapper from '../InlineComponent';
6+
import create_debugging_comment from '../shared/create_debugging_comment';
7+
import { get_slot_definition } from '../shared/get_slot_definition';
8+
9+
export default function create_slot_block(attribute, element: ElementWrapper | SlotWrapper, block: Block) {
10+
const owner = find_slot_owner(element.parent);
11+
12+
if (owner && owner.node.type === 'InlineComponent') {
13+
const name = attribute.get_static_value() as string;
14+
15+
if (!((owner as unknown) as InlineComponentWrapper).slots.has(name)) {
16+
const child_block = block.child({
17+
comment: create_debugging_comment(element.node, element.renderer.component),
18+
name: element.renderer.component.get_unique_name(
19+
`create_${sanitize(name)}_slot`
20+
),
21+
type: 'slot'
22+
});
23+
24+
const { scope, lets } = element.node;
25+
const seen = new Set(lets.map(l => l.name.name));
26+
27+
((owner as unknown) as InlineComponentWrapper).node.lets.forEach(l => {
28+
if (!seen.has(l.name.name)) lets.push(l);
29+
});
30+
31+
((owner as unknown) as InlineComponentWrapper).slots.set(
32+
name,
33+
get_slot_definition(child_block, scope, lets)
34+
);
35+
element.renderer.blocks.push(child_block);
36+
}
37+
38+
element.slot_block = ((owner as unknown) as InlineComponentWrapper).slots.get(
39+
name
40+
).block;
41+
42+
return element.slot_block;
43+
}
44+
45+
return block;
46+
}
47+
48+
function find_slot_owner(owner) {
49+
while (owner) {
50+
if (owner.node.type === 'InlineComponent') {
51+
break;
52+
}
53+
54+
if (owner.node.type === 'Element' && /-/.test(owner.node.name)) {
55+
break;
56+
}
57+
58+
owner = owner.parent;
59+
}
60+
return owner;
61+
}

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Renderer from '../../Renderer';
22
import Element from '../../../nodes/Element';
33
import Wrapper from '../shared/Wrapper';
44
import Block from '../../Block';
5-
import { is_void, sanitize } from '../../../../utils/names';
5+
import { is_void } from '../../../../utils/names';
66
import FragmentWrapper from '../Fragment';
77
import { escape_html, string_literal } from '../../../utils/stringify';
88
import TextWrapper from '../Text';
@@ -14,12 +14,9 @@ import StyleAttributeWrapper from './StyleAttribute';
1414
import SpreadAttributeWrapper from './SpreadAttribute';
1515
import { dimensions } from '../../../../utils/patterns';
1616
import Binding from './Binding';
17-
import InlineComponentWrapper from '../InlineComponent';
1817
import add_to_set from '../../../utils/add_to_set';
1918
import { add_event_handler } from '../shared/add_event_handlers';
2019
import { add_action } from '../shared/add_actions';
21-
import create_debugging_comment from '../shared/create_debugging_comment';
22-
import { get_slot_definition } from '../shared/get_slot_definition';
2320
import bind_this from '../shared/bind_this';
2421
import { is_head } from '../shared/is_head';
2522
import { Identifier } from 'estree';
@@ -28,6 +25,7 @@ import { extract_names } from 'periscopic';
2825
import Action from '../../../nodes/Action';
2926
import MustacheTagWrapper from '../MustacheTag';
3027
import RawMustacheTagWrapper from '../RawMustacheTag';
28+
import create_slot_block from './create_slot_block';
3129

3230
interface BindingGroup {
3331
events: string[];
@@ -177,47 +175,7 @@ export default class ElementWrapper extends Wrapper {
177175

178176
this.attributes = this.node.attributes.map(attribute => {
179177
if (attribute.name === 'slot') {
180-
// TODO make separate subclass for this?
181-
let owner = this.parent;
182-
while (owner) {
183-
if (owner.node.type === 'InlineComponent') {
184-
break;
185-
}
186-
187-
if (owner.node.type === 'Element' && /-/.test(owner.node.name)) {
188-
break;
189-
}
190-
191-
owner = owner.parent;
192-
}
193-
194-
if (owner && owner.node.type === 'InlineComponent') {
195-
const name = attribute.get_static_value() as string;
196-
197-
if (!(owner as unknown as InlineComponentWrapper).slots.has(name)) {
198-
const child_block = block.child({
199-
comment: create_debugging_comment(node, this.renderer.component),
200-
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`),
201-
type: 'slot'
202-
});
203-
204-
const { scope, lets } = this.node;
205-
const seen = new Set(lets.map(l => l.name.name));
206-
207-
(owner as unknown as InlineComponentWrapper).node.lets.forEach(l => {
208-
if (!seen.has(l.name.name)) lets.push(l);
209-
});
210-
211-
(owner as unknown as InlineComponentWrapper).slots.set(
212-
name,
213-
get_slot_definition(child_block, scope, lets)
214-
);
215-
this.renderer.blocks.push(child_block);
216-
}
217-
218-
this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;
219-
block = this.slot_block;
220-
}
178+
block = create_slot_block(attribute, this, block);
221179
}
222180
if (attribute.name === 'style') {
223181
return new StyleAttributeWrapper(this, block, attribute);

src/compiler/compile/render_dom/wrappers/Slot.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import Expression from '../../nodes/shared/Expression';
1212
import is_dynamic from './shared/is_dynamic';
1313
import { Identifier, ObjectExpression } from 'estree';
1414
import create_debugging_comment from './shared/create_debugging_comment';
15+
import create_slot_block from './Element/create_slot_block';
1516

1617
export default class SlotWrapper extends Wrapper {
1718
node: Slot;
1819
fragment: FragmentWrapper;
1920
fallback: Block | null = null;
21+
slot_block: Block;
2022

2123
var: Identifier = { type: 'Identifier', name: 'slot' };
2224
dependencies: Set<string> = new Set(['$$scope']);
@@ -42,6 +44,10 @@ export default class SlotWrapper extends Wrapper {
4244
renderer.blocks.push(this.fallback);
4345
}
4446

47+
if (this.node.values.has('slot')) {
48+
block = create_slot_block(this.node.values.get('slot'), this, block);
49+
}
50+
4551
this.fragment = new FragmentWrapper(
4652
renderer,
4753
this.fallback,
@@ -71,6 +77,10 @@ export default class SlotWrapper extends Wrapper {
7177

7278
const { slot_name } = this.node;
7379

80+
if (this.slot_block) {
81+
block = this.slot_block;
82+
}
83+
7484
let get_slot_changes_fn;
7585
let get_slot_context_fn;
7686

src/compiler/compile/render_ssr/handlers/Slot.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@ import Renderer, { RenderOptions } from '../Renderer';
22
import Slot from '../../nodes/Slot';
33
import { x } from 'code-red';
44
import get_slot_data from '../../utils/get_slot_data';
5+
import { get_slot_scope } from './shared/get_slot_scope';
56

6-
export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
7+
export default function(node: Slot, renderer: Renderer, options: RenderOptions & {
8+
slot_scopes: Map<any, any>;
9+
}) {
710
const slot_data = get_slot_data(node.values);
11+
const slot = node.get_static_attribute_value('slot');
12+
const nearest_inline_component = node.find_nearest(/InlineComponent/);
13+
14+
if (slot && nearest_inline_component) {
15+
renderer.push();
16+
}
817

918
renderer.push();
1019
renderer.render(node.children, options);
@@ -15,4 +24,17 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions)
1524
? #slots.${node.slot_name}(${slot_data})
1625
: ${result}
1726
`);
27+
28+
if (slot && nearest_inline_component) {
29+
const lets = node.lets;
30+
const seen = new Set(lets.map(l => l.name.name));
31+
32+
nearest_inline_component.lets.forEach(l => {
33+
if (!seen.has(l.name.name)) lets.push(l);
34+
});
35+
options.slot_scopes.set(slot, {
36+
input: get_slot_scope(node.lets),
37+
output: renderer.pop()
38+
});
39+
}
1840
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import Two from './Two.svelte';
3+
export let a, b;
4+
</script>
5+
6+
<Two {b} let:two>
7+
<slot name="one" slot="two" one={a} two={two}></slot>
8+
</Two>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
export let b;
3+
</script>
4+
<slot name="two" two={b}></slot>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default {
2+
html: `
3+
<p slot='one'>one: 1 two: 2</p>
4+
`,
5+
test({ assert, component, target }) {
6+
component.a = 3;
7+
component.b = 4;
8+
assert.htmlEqual(target.innerHTML, `
9+
<p slot='one'>one: 3 two: 4</p>
10+
`);
11+
12+
component.a = 5;
13+
component.b = 6;
14+
assert.htmlEqual(target.innerHTML, `
15+
<p slot='one'>one: 5 two: 6</p>
16+
`);
17+
}
18+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import One from './One.svelte';
3+
export let a = 1;
4+
export let b = 2;
5+
</script>
6+
7+
<One {a} {b}>
8+
<p slot='one' let:one let:two>one: {one} two: {two}</p>
9+
</One>

0 commit comments

Comments
 (0)