Merge pull request #2317 from sveltejs/gh-2291-alt

Allow arbitrary slot names
pull/2319/head
Rich Harris 6 years ago committed by GitHub
commit 4d6321ca0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -636,23 +636,6 @@ export default class Element extends Node {
}); });
} }
get_static_attribute_value(name: string) {
const attribute = this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
if (!attribute) return null;
if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
is_media_node() { is_media_node() {
return this.name === 'audio' || this.name === 'video'; return this.name === 'audio' || this.name === 'video';
} }

@ -5,6 +5,7 @@ import Attribute from './Attribute';
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; type: 'Element';
name: string; name: string;
slot_name: string;
attributes: Attribute[]; attributes: Attribute[];
children: Node[]; children: Node[];
@ -27,8 +28,8 @@ export default class Slot extends Element {
}); });
} }
const slot_name = attr.value[0].data; this.slot_name = attr.value[0].data;
if (slot_name === 'default') { if (this.slot_name === 'default') {
component.error(attr, { component.error(attr, {
code: `invalid-slot-name`, code: `invalid-slot-name`,
message: `default is a reserved word — it cannot be used as a slot name` message: `default is a reserved word — it cannot be used as a slot name`
@ -46,6 +47,8 @@ export default class Slot extends Element {
// validator.slots.add(slot_name); // validator.slots.add(slot_name);
}); });
if (!this.slot_name) this.slot_name = 'default';
// if (node.attributes.length === 0) && validator.slots.has('default')) { // if (node.attributes.length === 0) && validator.slots.has('default')) {
// validator.error(node, { // validator.error(node, {
// code: `duplicate-slot`, // code: `duplicate-slot`,
@ -53,21 +56,4 @@ export default class Slot extends Element {
// }); // });
// } // }
} }
get_static_attribute_value(name: string) {
const attribute = this.attributes.find(
attr => attr.name.toLowerCase() === name
);
if (!attribute) return null;
if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
} }

@ -37,17 +37,34 @@ export default class Node {
} }
} }
find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector);
}
get_static_attribute_value(name: string) {
const attribute = this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
if (!attribute) return null;
if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
has_ancestor(type: string) { has_ancestor(type: string) {
return this.parent ? return this.parent ?
this.parent.type === type || this.parent.has_ancestor(type) : this.parent.type === type || this.parent.has_ancestor(type) :
false; false;
} }
find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector);
}
warn_if_empty_block() { warn_if_empty_block() {
if (!/Block$/.test(this.type) || !this.children) return; if (!/Block$/.test(this.type) || !this.children) return;
if (this.children.length > 1) return; if (this.children.length > 1) return;

@ -10,7 +10,6 @@ import add_to_set from '../utils/add_to_set';
import get_object from '../utils/get_object'; import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match'; import { nodes_match } from '../../utils/nodes_match';
import { sanitize } from '../../utils/names';
export default function dom( export default function dom(
component: Component, component: Component,
@ -305,8 +304,7 @@ export default function dom(
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
if (renderer.slots.size > 0) { if (renderer.slots.size > 0) {
const arr = Array.from(renderer.slots); filtered_declarations.push('$$slots', '$$scope');
filtered_declarations.push(...arr.map(name => `$$slot_${sanitize(name)}`), '$$scope');
} }
if (renderer.binding_groups.length > 0) { if (renderer.binding_groups.length > 0) {
@ -399,7 +397,7 @@ export default function dom(
${component.javascript} ${component.javascript}
${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`} ${renderer.slots.size && `let { $$slots = {}, $$scope } = $$props;`}
${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`} ${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}

@ -117,7 +117,7 @@ export default class AttributeWrapper {
updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`; updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) { } else if (is_select_value_attribute) {
// annoying special case // annoying special case
const is_multiple_select = element.get_static_attribute_value('multiple'); const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i'); const i = block.get_unique_name('i');
const option = block.get_unique_name('option'); const option = block.get_unique_name('option');

@ -153,7 +153,7 @@ export default class BindingWrapper {
break; break;
case 'value': case 'value':
if (parent.get_static_attribute_value('type') === 'file') { if (parent.node.get_static_attribute_value('type') === 'file') {
update_dom = null; update_dom = null;
} }
} }

@ -20,6 +20,7 @@ import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions'; import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
import Slot from '../../../nodes/Slot';
const events = [ const events = [
{ {
@ -213,8 +214,7 @@ export default class ElementWrapper extends Wrapper {
const { renderer } = this; const { renderer } = this;
if (this.node.name === 'slot') { if (this.node.name === 'slot') {
const slotName = this.get_static_attribute_value('name') || 'default'; renderer.slots.add((this.node as Slot).slot_name);
renderer.slots.add(slotName);
} }
if (this.node.name === 'noscript') return; if (this.node.name === 'noscript') return;
@ -804,23 +804,6 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
get_static_attribute_value(name: string) {
const attribute = this.node.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
if (!attribute) return null;
if (attribute.is_true) return true;
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
}
return null;
}
add_css_class(class_name = this.component.stylesheet.id) { add_css_class(class_name = this.component.stylesheet.id) {
const class_attribute = this.attributes.find(a => a.name === 'class'); const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) { if (class_attribute && !class_attribute.is_true) {

@ -119,16 +119,19 @@ export default class InlineComponentWrapper extends Wrapper {
const uses_spread = !!this.node.attributes.find(a => a.is_spread); const uses_spread = !!this.node.attributes.find(a => a.is_spread);
const slot_props = Array.from(this.slots).map(([name, slot]) => `$$slot_${sanitize(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`); const slot_props = Array.from(this.slots).map(([name, slot]) => `${quote_name_if_necessary(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`);
if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`);
const initial_props = slot_props.length > 0
? [`$$slots: ${stringify_props(slot_props)}`, `$$scope: { ctx }`]
: [];
const attribute_object = uses_spread const attribute_object = uses_spread
? stringify_props(slot_props) ? stringify_props(initial_props)
: stringify_props( : stringify_props(
this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(slot_props) this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(initial_props)
); );
if (this.node.attributes.length || this.node.bindings.length || slot_props.length) { if (this.node.attributes.length || this.node.bindings.length || initial_props.length) {
if (!uses_spread && this.node.bindings.length === 0) { if (!uses_spread && this.node.bindings.length === 0) {
component_opts.push(`props: ${attribute_object}`); component_opts.push(`props: ${attribute_object}`);
} else { } else {

@ -4,7 +4,7 @@ import Block from '../Block';
import Slot from '../../nodes/Slot'; import Slot from '../../nodes/Slot';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import { sanitize } from '../../../utils/names'; import { sanitize, quote_prop_if_necessary } from '../../../utils/names';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props'; import { stringify_props } from '../../utils/stringify_props';
@ -55,7 +55,7 @@ export default class SlotWrapper extends Wrapper {
) { ) {
const { renderer } = this; const { renderer } = this;
const slot_name = this.node.get_static_attribute_value('name') || 'default'; const { slot_name } = this.node;
renderer.slots.add(slot_name); renderer.slots.add(slot_name);
let get_slot_changes; let get_slot_changes;
@ -99,7 +99,7 @@ export default class SlotWrapper extends Wrapper {
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
const ${slot_definition} = ctx.$$slot_${sanitize(slot_name)}; const ${slot_definition} = ctx.$$slots${quote_prop_if_necessary(slot_name)};
const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context}); const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context});
`); `);

@ -2,10 +2,7 @@ import { quote_prop_if_necessary } from '../../../utils/names';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
export default function(node, renderer, options) { export default function(node, renderer, options) {
const name = node.attributes.find(attribute => attribute.name === 'name'); const prop = quote_prop_if_necessary(node.slot_name);
const slot_name = name && name.chunks[0].data || 'default';
const prop = quote_prop_if_necessary(slot_name);
const slot_data = get_slot_data(node.attributes, true); const slot_data = get_slot_data(node.attributes, true);

@ -111,5 +111,9 @@ export function quote_prop_if_necessary(name: string) {
} }
export function sanitize(name: string) { export function sanitize(name: string) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, ''); return name
} .replace(/[^a-zA-Z0-9_]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^[0-9]/, '_$&');
}

@ -0,0 +1,8 @@
<div>
<slot name="header1" />
<slot name="-header2_" />
<slot name="3header" />
<slot name="_header4" />
<slot name="header-5" />
<slot name="header&5" />
</div>

@ -0,0 +1,12 @@
export default {
html: `
<div>
<h1 slot="header1">Header 1</h1>
<h2 slot="-header2_">Header 2</h2>
<h3 slot="3header">Header 3</h3>
<h4 slot="_header4">Header 4</h4>
<h5 slot="header-5">Header 5</h5>
<h5 slot="header&5">Header 5b</h5>
</div>
`
};

@ -0,0 +1,12 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
<h1 slot="header1">Header 1</h1>
<h2 slot="-header2_">Header 2</h2>
<h3 slot="3header">Header 3</h3>
<h4 slot="_header4">Header 4</h4>
<h5 slot="header-5">Header 5</h5>
<h5 slot="header&5">Header 5b</h5>
</Nested>
Loading…
Cancel
Save