Skip to content

Commit

Permalink
support <elem slot=...> in slots forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Feb 23, 2023
1 parent b4df71f commit af38d89
Show file tree
Hide file tree
Showing 17 changed files with 253 additions and 116 deletions.
75 changes: 3 additions & 72 deletions src/compiler/compile/nodes/InlineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns';
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 {
type: 'InlineComponent';
Expand Down Expand Up @@ -107,66 +107,9 @@ export default class InlineComponent extends Node {
});
});

const children = [];
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);
}
}
const children = extract_children_to_slot_templates(component, info, true);

children.push(slot_template);
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.children = map_children(component, this, this.scope, children);

this.validate_duplicate_slot_name();
}
Expand All @@ -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) {
const parent_element = parent.find_nearest(/^Element/);

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

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;
}
3 changes: 2 additions & 1 deletion src/compiler/compile/nodes/Slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';

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;
children: INode[];
slot_name: string;
Expand Down
21 changes: 2 additions & 19 deletions src/compiler/compile/nodes/SlotTemplateIfBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import compiler_errors from '../compiler_errors';
import get_const_tags from './shared/get_const_tags';
import { TemplateNode } from '../../interfaces';
import ConstTag from './ConstTag';
import { regex_only_whitespaces } from '../../utils/patterns';
import SlotTemplate from './SlotTemplate';
import { INode } from './interfaces';

import { extract_children_to_slot_templates } from './extract_children_to_slot_templates';

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

const children = [];
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);
}
}
const children = extract_children_to_slot_templates(component, info, false);

this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
Expand Down
107 changes: 107 additions & 0 deletions src/compiler/compile/nodes/extract_children_to_slot_templates.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<slot name="a">default a</slot>
<hr/>
<slot name="b">default b</slot>
Original file line number Diff line number Diff line change
@@ -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>
`);
}
};
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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
`);
}
};
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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
`);
}
};
Loading

0 comments on commit af38d89

Please sign in to comment.