Skip to content

Commit 4d6321c

Browse files
authored
Merge pull request sveltejs#2317 from sveltejs/sveltejsgh-2291-alt
Allow arbitrary slot names
2 parents a432f73 + 52c42d3 commit 4d6321c

File tree

14 files changed

+83
-80
lines changed

14 files changed

+83
-80
lines changed

src/compile/nodes/Element.ts

-17
Original file line numberDiff line numberDiff line change
@@ -636,23 +636,6 @@ export default class Element extends Node {
636636
});
637637
}
638638

639-
get_static_attribute_value(name: string) {
640-
const attribute = this.attributes.find(
641-
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
642-
);
643-
644-
if (!attribute) return null;
645-
646-
if (attribute.is_true) return true;
647-
if (attribute.chunks.length === 0) return '';
648-
649-
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
650-
return attribute.chunks[0].data;
651-
}
652-
653-
return null;
654-
}
655-
656639
is_media_node() {
657640
return this.name === 'audio' || this.name === 'video';
658641
}

src/compile/nodes/Slot.ts

+5-19
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Attribute from './Attribute';
55
export default class Slot extends Element {
66
type: 'Element';
77
name: string;
8+
slot_name: string;
89
attributes: Attribute[];
910
children: Node[];
1011

@@ -27,8 +28,8 @@ export default class Slot extends Element {
2728
});
2829
}
2930

30-
const slot_name = attr.value[0].data;
31-
if (slot_name === 'default') {
31+
this.slot_name = attr.value[0].data;
32+
if (this.slot_name === 'default') {
3233
component.error(attr, {
3334
code: `invalid-slot-name`,
3435
message: `default is a reserved word — it cannot be used as a slot name`
@@ -46,28 +47,13 @@ export default class Slot extends Element {
4647
// validator.slots.add(slot_name);
4748
});
4849

50+
if (!this.slot_name) this.slot_name = 'default';
51+
4952
// if (node.attributes.length === 0) && validator.slots.has('default')) {
5053
// validator.error(node, {
5154
// code: `duplicate-slot`,
5255
// message: `duplicate default <slot> element`
5356
// });
5457
// }
5558
}
56-
57-
get_static_attribute_value(name: string) {
58-
const attribute = this.attributes.find(
59-
attr => attr.name.toLowerCase() === name
60-
);
61-
62-
if (!attribute) return null;
63-
64-
if (attribute.is_true) return true;
65-
if (attribute.chunks.length === 0) return '';
66-
67-
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
68-
return attribute.chunks[0].data;
69-
}
70-
71-
return null;
72-
}
7359
}

src/compile/nodes/shared/Node.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,34 @@ export default class Node {
3737
}
3838
}
3939

40+
find_nearest(selector: RegExp) {
41+
if (selector.test(this.type)) return this;
42+
if (this.parent) return this.parent.find_nearest(selector);
43+
}
44+
45+
get_static_attribute_value(name: string) {
46+
const attribute = this.attributes.find(
47+
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
48+
);
49+
50+
if (!attribute) return null;
51+
52+
if (attribute.is_true) return true;
53+
if (attribute.chunks.length === 0) return '';
54+
55+
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
56+
return attribute.chunks[0].data;
57+
}
58+
59+
return null;
60+
}
61+
4062
has_ancestor(type: string) {
4163
return this.parent ?
4264
this.parent.type === type || this.parent.has_ancestor(type) :
4365
false;
4466
}
4567

46-
find_nearest(selector: RegExp) {
47-
if (selector.test(this.type)) return this;
48-
if (this.parent) return this.parent.find_nearest(selector);
49-
}
50-
5168
warn_if_empty_block() {
5269
if (!/Block$/.test(this.type) || !this.children) return;
5370
if (this.children.length > 1) return;

src/compile/render-dom/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import add_to_set from '../utils/add_to_set';
1010
import get_object from '../utils/get_object';
1111
import { extract_names } from '../utils/scope';
1212
import { nodes_match } from '../../utils/nodes_match';
13-
import { sanitize } from '../../utils/names';
1413

1514
export default function dom(
1615
component: Component,
@@ -305,8 +304,7 @@ export default function dom(
305304
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
306305

307306
if (renderer.slots.size > 0) {
308-
const arr = Array.from(renderer.slots);
309-
filtered_declarations.push(...arr.map(name => `$$slot_${sanitize(name)}`), '$$scope');
307+
filtered_declarations.push('$$slots', '$$scope');
310308
}
311309

312310
if (renderer.binding_groups.length > 0) {
@@ -399,7 +397,7 @@ export default function dom(
399397
400398
${component.javascript}
401399
402-
${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`}
400+
${renderer.slots.size && `let { $$slots = {}, $$scope } = $$props;`}
403401
404402
${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}
405403

src/compile/render-dom/wrappers/Element/Attribute.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export default class AttributeWrapper {
117117
updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`;
118118
} else if (is_select_value_attribute) {
119119
// annoying special case
120-
const is_multiple_select = element.get_static_attribute_value('multiple');
120+
const is_multiple_select = element.node.get_static_attribute_value('multiple');
121121
const i = block.get_unique_name('i');
122122
const option = block.get_unique_name('option');
123123

src/compile/render-dom/wrappers/Element/Binding.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export default class BindingWrapper {
153153
break;
154154

155155
case 'value':
156-
if (parent.get_static_attribute_value('type') === 'file') {
156+
if (parent.node.get_static_attribute_value('type') === 'file') {
157157
update_dom = null;
158158
}
159159
}

src/compile/render-dom/wrappers/Element/index.ts

+2-19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import add_event_handlers from '../shared/add_event_handlers';
2020
import add_actions from '../shared/add_actions';
2121
import create_debugging_comment from '../shared/create_debugging_comment';
2222
import { get_context_merger } from '../shared/get_context_merger';
23+
import Slot from '../../../nodes/Slot';
2324

2425
const events = [
2526
{
@@ -213,8 +214,7 @@ export default class ElementWrapper extends Wrapper {
213214
const { renderer } = this;
214215

215216
if (this.node.name === 'slot') {
216-
const slotName = this.get_static_attribute_value('name') || 'default';
217-
renderer.slots.add(slotName);
217+
renderer.slots.add((this.node as Slot).slot_name);
218218
}
219219

220220
if (this.node.name === 'noscript') return;
@@ -804,23 +804,6 @@ export default class ElementWrapper extends Wrapper {
804804
});
805805
}
806806

807-
get_static_attribute_value(name: string) {
808-
const attribute = this.node.attributes.find(
809-
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
810-
);
811-
812-
if (!attribute) return null;
813-
814-
if (attribute.is_true) return true;
815-
if (attribute.chunks.length === 0) return '';
816-
817-
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
818-
return attribute.chunks[0].data;
819-
}
820-
821-
return null;
822-
}
823-
824807
add_css_class(class_name = this.component.stylesheet.id) {
825808
const class_attribute = this.attributes.find(a => a.name === 'class');
826809
if (class_attribute && !class_attribute.is_true) {

src/compile/render-dom/wrappers/InlineComponent/index.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,19 @@ export default class InlineComponentWrapper extends Wrapper {
119119

120120
const uses_spread = !!this.node.attributes.find(a => a.is_spread);
121121

122-
const slot_props = Array.from(this.slots).map(([name, slot]) => `$$slot_${sanitize(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`);
123-
if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`);
122+
const slot_props = Array.from(this.slots).map(([name, slot]) => `${quote_name_if_necessary(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`);
123+
124+
const initial_props = slot_props.length > 0
125+
? [`$$slots: ${stringify_props(slot_props)}`, `$$scope: { ctx }`]
126+
: [];
124127

125128
const attribute_object = uses_spread
126-
? stringify_props(slot_props)
129+
? stringify_props(initial_props)
127130
: stringify_props(
128-
this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(slot_props)
131+
this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(initial_props)
129132
);
130133

131-
if (this.node.attributes.length || this.node.bindings.length || slot_props.length) {
134+
if (this.node.attributes.length || this.node.bindings.length || initial_props.length) {
132135
if (!uses_spread && this.node.bindings.length === 0) {
133136
component_opts.push(`props: ${attribute_object}`);
134137
} else {

src/compile/render-dom/wrappers/Slot.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Block from '../Block';
44
import Slot from '../../nodes/Slot';
55
import FragmentWrapper from './Fragment';
66
import deindent from '../../utils/deindent';
7-
import { sanitize } from '../../../utils/names';
7+
import { sanitize, quote_prop_if_necessary } from '../../../utils/names';
88
import add_to_set from '../../utils/add_to_set';
99
import get_slot_data from '../../utils/get_slot_data';
1010
import { stringify_props } from '../../utils/stringify_props';
@@ -55,7 +55,7 @@ export default class SlotWrapper extends Wrapper {
5555
) {
5656
const { renderer } = this;
5757

58-
const slot_name = this.node.get_static_attribute_value('name') || 'default';
58+
const { slot_name } = this.node;
5959
renderer.slots.add(slot_name);
6060

6161
let get_slot_changes;
@@ -99,7 +99,7 @@ export default class SlotWrapper extends Wrapper {
9999
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
100100

101101
block.builders.init.add_block(deindent`
102-
const ${slot_definition} = ctx.$$slot_${sanitize(slot_name)};
102+
const ${slot_definition} = ctx.$$slots${quote_prop_if_necessary(slot_name)};
103103
const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context});
104104
`);
105105

src/compile/render-ssr/handlers/Slot.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { quote_prop_if_necessary } from '../../../utils/names';
22
import get_slot_data from '../../utils/get_slot_data';
33

44
export default function(node, renderer, options) {
5-
const name = node.attributes.find(attribute => attribute.name === 'name');
6-
7-
const slot_name = name && name.chunks[0].data || 'default';
8-
const prop = quote_prop_if_necessary(slot_name);
5+
const prop = quote_prop_if_necessary(node.slot_name);
96

107
const slot_data = get_slot_data(node.attributes, true);
118

src/utils/names.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,9 @@ export function quote_prop_if_necessary(name: string) {
111111
}
112112

113113
export function sanitize(name: string) {
114-
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
115-
}
114+
return name
115+
.replace(/[^a-zA-Z0-9_]+/g, '_')
116+
.replace(/^_/, '')
117+
.replace(/_$/, '')
118+
.replace(/^[0-9]/, '_$&');
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div>
2+
<slot name="header1" />
3+
<slot name="-header2_" />
4+
<slot name="3header" />
5+
<slot name="_header4" />
6+
<slot name="header-5" />
7+
<slot name="header&5" />
8+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
html: `
3+
<div>
4+
<h1 slot="header1">Header 1</h1>
5+
<h2 slot="-header2_">Header 2</h2>
6+
<h3 slot="3header">Header 3</h3>
7+
<h4 slot="_header4">Header 4</h4>
8+
<h5 slot="header-5">Header 5</h5>
9+
<h5 slot="header&5">Header 5b</h5>
10+
</div>
11+
`
12+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
</script>
4+
5+
<Nested>
6+
<h1 slot="header1">Header 1</h1>
7+
<h2 slot="-header2_">Header 2</h2>
8+
<h3 slot="3header">Header 3</h3>
9+
<h4 slot="_header4">Header 4</h4>
10+
<h5 slot="header-5">Header 5</h5>
11+
<h5 slot="header&5">Header 5b</h5>
12+
</Nested>

0 commit comments

Comments
 (0)