support <elem slot=...> in slots forwarding

pull/8304/head
tanhauhau 4 years ago
parent f9108d01d7
commit 07d4350a6f

@ -10,8 +10,8 @@ import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns';
import { validate_get_slot_names } from './SlotTemplateIfBlock'; import { validate_get_slot_names } from './SlotTemplateIfBlock';
import { extract_children_to_slot_templates } from './extract_children_to_slot_templates';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';
@ -107,66 +107,9 @@ export default class InlineComponent extends Node {
}); });
}); });
const children = []; const children = extract_children_to_slot_templates(component, info, true);
for (let i = info.children.length - 1; i >= 0; i--) {
const child = info.children[i];
if (child.type === 'SlotTemplate') {
children.push(child);
info.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
const slot_template = {
start: child.start,
end: child.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: [child]
};
// transfer attributes
for (let i = child.attributes.length - 1; i >= 0; i--) {
const attribute = child.attributes[i];
if (attribute.type === 'Let') {
slot_template.attributes.push(attribute);
child.attributes.splice(i, 1);
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
slot_template.attributes.push(attribute);
}
}
// transfer const
for (let i = child.children.length - 1; i >= 0; i--) {
const child_child = child.children[i];
if (child_child.type === 'ConstTag') {
slot_template.children.push(child_child);
child.children.splice(i, 1);
}
}
children.push(slot_template); this.children = map_children(component, this, this.scope, children);
info.children.splice(i, 1);
} else if (child.type === 'Comment' && children.length > 0) {
children[children.length - 1].children.unshift(child);
info.children.splice(i, 1);
} else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) {
children.push({
...child,
type: 'SlotTemplateIfBlock'
});
info.children.splice(i, 1);
}
}
if (info.children.some(node => not_whitespace_text(node))) {
children.push({
start: info.start,
end: info.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: info.children
});
}
this.children = map_children(component, this, this.scope, children.reverse());
this.validate_duplicate_slot_name(); this.validate_duplicate_slot_name();
} }
@ -180,10 +123,6 @@ export default class InlineComponent extends Node {
} }
} }
function not_whitespace_text(node) {
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
}
function get_namespace(parent: Node, explicit_namespace: string) { function get_namespace(parent: Node, explicit_namespace: string) {
const parent_element = parent.find_nearest(/^Element/); const parent_element = parent.find_nearest(/^Element/);
@ -193,11 +132,3 @@ function get_namespace(parent: Node, explicit_namespace: string) {
return parent_element.namespace; return parent_element.namespace;
} }
function if_block_contains_slot_template(node: TemplateNode) {
for (const child of node.children) {
if (child.type === 'SlotTemplate') return true;
if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true;
}
return false;
}

@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; // @ts-ignore unable to override the type from Element, but this give us a right type
type: 'Slot';
name: string; name: string;
children: INode[]; children: INode[];
slot_name: string; slot_name: string;

@ -8,10 +8,9 @@ import compiler_errors from '../compiler_errors';
import get_const_tags from './shared/get_const_tags'; import get_const_tags from './shared/get_const_tags';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import ConstTag from './ConstTag'; import ConstTag from './ConstTag';
import { regex_only_whitespaces } from '../../utils/patterns';
import SlotTemplate from './SlotTemplate'; import SlotTemplate from './SlotTemplate';
import { INode } from './interfaces'; import { INode } from './interfaces';
import { extract_children_to_slot_templates } from './extract_children_to_slot_templates';
export default class SlotTemplateIfBlock extends AbstractBlock { export default class SlotTemplateIfBlock extends AbstractBlock {
type: 'SlotTemplateIfBlock'; type: 'SlotTemplateIfBlock';
@ -30,23 +29,7 @@ export default class SlotTemplateIfBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
const children = []; const children = extract_children_to_slot_templates(component, info, false);
for (const child of info.children) {
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
children.push(child);
} else if (child.type === 'Comment') {
// ignore
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
// ignore
} else if (child.type === 'IfBlock') {
children.push({
...child,
type: 'SlotTemplateIfBlock'
});
} else {
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
}
}
this.expression = new Expression(component, this, this.scope, info.expression); this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(children, component, this, this)); ([this.const_tags, this.children] = get_const_tags(children, component, this, this));

@ -0,0 +1,107 @@
import { x } from 'code-red';
import { TemplateNode } from '../../interfaces';
import { regex_only_whitespaces } from '../../utils/patterns';
import compiler_errors from '../compiler_errors';
import Component from '../Component';
export function extract_children_to_slot_templates(component: Component, node: TemplateNode, extract_default_slot: boolean) {
const result = [];
for (let i = node.children.length - 1; i >= 0; i--) {
const child = node.children[i];
if (child.type === 'SlotTemplate') {
result.push(child);
node.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
let slot_template = {
start: child.start,
end: child.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: [child]
};
// transfer attributes
for (let i = child.attributes.length - 1; i >= 0; i--) {
const attribute = child.attributes[i];
if (attribute.type === 'Let') {
slot_template.attributes.push(attribute);
child.attributes.splice(i, 1);
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
slot_template.attributes.push(attribute);
}
}
// transfer const
for (let i = child.children.length - 1; i >= 0; i--) {
const child_child = child.children[i];
if (child_child.type === 'ConstTag') {
slot_template.children.push(child_child);
child.children.splice(i, 1);
}
}
// if the <slot slot="x"> does not have any fallback
// then we make <slot slot="x" name="b" />
// into {#if $$slots.x}<slot slot="x" name="b" />{/if}
// this makes the slots forwarding to passthrough
if (child.type === 'Slot' && child.children.length === 0) {
const slot_template_name = child.attributes.find(attribute => attribute.name === 'name')?.value[0].data ?? 'default';
slot_template = {
start: slot_template.start,
end: slot_template.end,
type: 'SlotTemplateIfBlock',
expression: x`$$slots.${slot_template_name}`,
children: [slot_template]
} as any;
}
result.push(slot_template);
node.children.splice(i, 1);
} else if (child.type === 'Comment' && result.length > 0) {
result[result.length - 1].children.unshift(child);
node.children.splice(i, 1);
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
// ignore
} else if (child.type === 'ConstTag') {
if (!extract_default_slot) {
result.push(child);
}
} else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) {
result.push({
...child,
type: 'SlotTemplateIfBlock'
});
node.children.splice(i, 1);
} else if (!extract_default_slot) {
component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
}
}
if (extract_default_slot) {
if (node.children.some(node => not_whitespace_text(node))) {
result.push({
start: node.start,
end: node.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: node.children
});
}
}
return result.reverse();
}
function not_whitespace_text(node) {
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
}
function if_block_contains_slot_template(node: TemplateNode) {
for (const child of node.children) {
if (child.type === 'SlotTemplate') return true;
if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true;
if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) return true;
}
return false;
}

@ -0,0 +1,3 @@
<slot name="a">default a</slot>
<hr/>
<slot name="b">default b</slot>

@ -0,0 +1,15 @@
export default {
html: `
default a
<hr>
<div slot="b">hello b</div>
`,
test({ assert, component, target }) {
component.condition = true;
assert.htmlEqual(target.innerHTML, `
<div slot="a">hello a</div>
<hr>
<div slot="b">hello b</div>
`);
}
};

@ -0,0 +1,11 @@
<script>
import Foo from "./Foo.svelte";
export let condition = false;
</script>
<Foo>
{#if condition}
<div slot="a">hello a</div>
{/if}
<div slot="b">hello b</div>
</Foo>

@ -0,0 +1,3 @@
x: <slot name="x">fallback x</slot>
y: <slot name="y">fallback y</slot>
z: <slot name="z">fallback z</slot>

@ -0,0 +1,15 @@
<script>
import Bar from './Bar.svelte';
export let condition1 = false;
export let condition2 = true;
</script>
<Bar>
<slot name="a" slot="x">Fallback a</slot>
{#if condition1}
<slot name="b" slot="y">Fallback b</slot>
{/if}
{#if condition2}
<slot name="c" slot="z">Fallback c</slot>
{/if}
</Bar>

@ -0,0 +1,37 @@
export default {
html: `
x: Fallback a
y: fallback y
z: <div slot="c">hello c</div>
`,
test({ assert, component, target }) {
component.condition1 = true;
assert.htmlEqual(target.innerHTML, `
x: Fallback a
y: <div slot="b">hello b</div>
z: <div slot="c">hello c</div>
`);
component.condition3 = true;
assert.htmlEqual(target.innerHTML, `
x: <div slot="a">hello a</div>
y: <div slot="b">hello b</div>
z: <div slot="c">hello c</div>
`);
component.condition4 = false;
component.condition1 = false;
assert.htmlEqual(target.innerHTML, `
x: <div slot="a">hello a</div>
y: fallback y
z: <div slot="c">hello c</div>
`);
component.condition2 = false;
assert.htmlEqual(target.innerHTML, `
x: <div slot="a">hello a</div>
y: fallback y
z: fallback z
`);
}
};

@ -0,0 +1,17 @@
<script>
import Foo from "./Foo.svelte";
export let condition1;
export let condition2;
export let condition3 = false;
export let condition4 = true;
</script>
<Foo {condition1} {condition2}>
{#if condition3}
<div slot="a">hello a</div>
{/if}
{#if condition4}
<div slot="b">hello b</div>
{/if}
<div slot="c">hello c</div>
</Foo>

@ -0,0 +1,3 @@
x: <slot name="x">fallback x</slot>
y: <slot name="y">fallback y</slot>
z: <slot name="z">fallback z</slot>

@ -0,0 +1,9 @@
<script>
import Bar from './Bar.svelte';
</script>
<Bar>
<slot name="a" slot="x" />
<slot name="b" slot="y" />
<slot name="c" slot="z" />
</Bar>

@ -0,0 +1,15 @@
export default {
html: `
x: fallback x
y: <div slot="b">hello b</div>
z: fallback z
`,
test({ assert, component, target }) {
component.condition = true;
assert.htmlEqual(target.innerHTML, `
x: <div slot="a">hello a</div>
y: <div slot="b">hello b</div>
z: fallback z
`);
}
};

@ -0,0 +1,11 @@
<script>
import Foo from "./Foo.svelte";
export let condition = false;
</script>
<Foo>
{#if condition}
<div slot="a">hello a</div>
{/if}
<div slot="b">hello b</div>
</Foo>

@ -1,12 +0,0 @@
[{
"code": "invalid-slotted-content",
"message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element",
"start": {
"line": 7,
"column": 7
},
"end": {
"line": 7,
"column": 17
}
}]

@ -1,9 +0,0 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested>
{#if thing}
<div slot='foo'>{thing}</div>
{/if}
</Nested>
Loading…
Cancel
Save