feat allow multiple elements assigned with same slot

pull/4140/head
Tan Li Hau 6 years ago
parent 109639c57c
commit 5b030d0753

@ -205,6 +205,14 @@ export default class ElementWrapper extends Wrapper {
get_slot_definition(child_block, scope, lets)
);
this.renderer.blocks.push(child_block);
} else {
const { lets } = this.node;
const seen = new Set(lets.map(l => l.name.name));
(owner as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
(owner as InlineComponentWrapper).slots.get(name).add_let_binding(lets);
}
this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;

@ -9,9 +9,8 @@ import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute';
import get_object from '../../../utils/get_object';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_slot_definition } from '../shared/get_slot_definition';
import { get_slot_definition, SlotDefinition } from '../shared/get_slot_definition';
import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this';
import { Node, Identifier, ObjectExpression } from 'estree';
@ -20,7 +19,7 @@ import { extract_names } from 'periscopic';
export default class InlineComponentWrapper extends Wrapper {
var: Identifier;
slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
slots: Map<string, SlotDefinition> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;
@ -130,7 +129,7 @@ export default class InlineComponentWrapper extends Wrapper {
? [
p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`;
return p`${name}: ${slot.render()}`;
})}
}`,
p`$$scope: {

@ -4,78 +4,112 @@ import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope };
const input = {
type: 'ObjectPattern',
properties: lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name
}))
};
const names: Set<string> = new Set();
lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});
const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
};
const { context_lookup } = block.renderer;
// i am well aware that this code is gross
// TODO make it less gross
const changes = {
type: 'ParenthesizedExpression',
get expression() {
if (block.renderer.context_overflow) {
const grouped = [];
Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31);
if (!grouped[g]) grouped[g] = [];
grouped[g].push({ name, n: i % 31 });
});
const elements = [];
for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
: x`0`;
}
export function get_slot_definition(
block: Block,
scope: TemplateScope,
lets: Let[]
) {
return new SlotDefinition(block, scope, lets);
}
export class SlotDefinition {
block: Block;
scope: TemplateScope;
lets: Let[];
lets_set: Set<string>;
constructor(block: Block, scope: TemplateScope, lets: Let[]) {
this.block = block;
this.scope = scope;
this.lets = lets;
this.lets_set = new Set(this.lets.map(l => l.name.name));
}
return {
type: 'ArrayExpression',
elements
};
add_let_binding(lets: Let[]) {
for (const l of lets) {
if (!this.lets_set.has(l.name.name)) {
this.lets_set.add(l.name.name);
this.lets.push(l);
}
}
}
return Array.from(names)
.map(name => {
const i = context_lookup.get(name).index.value as number;
return x`${name} ? ${1 << i} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
render() {
if (this.lets.length === 0) {
return x`[${this.block.name}, null, null]`;
}
};
return {
block,
scope,
get_context: x`${input} => ${context}`,
get_changes: x`${input} => ${changes}`
};
const input = {
type: 'ObjectPattern',
properties: this.lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name,
})),
};
const names: Set<string> = new Set();
this.lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});
const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(
name =>
p`${this.block.renderer.context_lookup.get(name).index}: ${name}`
),
};
const { context_lookup } = this.block.renderer;
const { renderer } = this.block;
// i am well aware that this code is gross
// TODO make it less gross
const changes = {
type: 'ParenthesizedExpression',
get expression() {
if (renderer.context_overflow) {
const grouped = [];
Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31);
if (!grouped[g]) grouped[g] = [];
grouped[g].push({ name, n: i % 31 });
});
const elements = [];
for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
: x`0`;
}
return {
type: 'ArrayExpression',
elements,
};
}
return Array.from(names)
.map(name => {
const i = context_lookup.get(name).index.value as number;
return x`${name} ? ${1 << i} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
},
};
const get_context = x`${input} => ${context}`;
const get_changes = x`${input} => ${changes}`;
return x`[${this.block.name}, ${get_context}, ${get_changes}]`;
}
}

@ -1,14 +1,14 @@
import { is_void } from '../../../utils/names';
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { get_slot_scope } from './shared/get_slot_scope';
import { boolean_attributes } from './shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import { SlotDefinition } from './shared/get_slot_definition';
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
slot_scopes: Map<string, SlotDefinition>;
}) {
// awkward special case
let node_contents;
@ -154,10 +154,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (!seen.has(l.name.name)) lets.push(l);
});
options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
if (options.slot_scopes.has(slot as string)) {
options.slot_scopes.get(slot as string).add(node.lets, renderer.pop());
} else {
options.slot_scopes.set(slot as string, new SlotDefinition(node.lets, renderer.pop()));
}
} else {
renderer.render(node.children, options);

@ -1,8 +1,8 @@
import { string_literal } from '../../utils/stringify';
import Renderer, { RenderOptions } from '../Renderer';
import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import { p, x } from 'code-red';
import { SlotDefinition } from './shared/get_slot_definition';
function get_prop_value(attribute) {
if (attribute.is_true) return x`true`;
@ -68,7 +68,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
const slot_fns = [];
if (node.children.length) {
const slot_scopes = new Map();
const slot_scopes: Map<string, SlotDefinition> = new Map();
renderer.push();
@ -76,14 +76,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
slot_scopes
}));
slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
slot_scopes.set('default', new SlotDefinition(node.lets, renderer.pop()));
slot_scopes.forEach(({ input, output }, name) => {
slot_scopes.forEach((slot, name) => {
slot_fns.push(
p`${name}: (${input}) => ${output}`
p`${name}: ${slot.render()}`
);
});
}

@ -0,0 +1,59 @@
import Let from '../../../nodes/Let';
import { TemplateLiteral } from 'estree';
import { x } from 'code-red';
import { get_slot_scope } from './get_slot_scope';
export function get_slot_definition(
lets: Let[],
output_template: TemplateLiteral
) {
return new SlotDefinition(lets, output_template);
}
export class SlotDefinition {
lets: Let[];
lets_set: Set<string>;
output_template: TemplateLiteral;
constructor(lets: Let[], output_template: TemplateLiteral) {
this.lets = lets;
this.output_template = output_template;
this.lets_set = new Set(this.lets.map(l => l.name.name));
}
add(lets: Let[], output_template: TemplateLiteral) {
for (const l of lets) {
if (!this.lets_set.has(l.name.name)) {
this.lets_set.add(l.name.name);
this.lets.push(l);
}
}
this.output_template = merge_template_literal(
this.output_template,
output_template
);
}
render() {
return x`(${get_slot_scope(this.lets)}) => ${this.output_template}`;
}
}
function merge_template_literal(a: TemplateLiteral, b: TemplateLiteral): TemplateLiteral {
const quasis = [...a.quasis];
quasis[quasis.length - 1] = {
type: 'TemplateElement',
value: {
raw: quasis[quasis.length - 1].value.raw + b.quasis[0].value.raw,
cooked: quasis[quasis.length - 1].value.cooked + b.quasis[0].value.cooked,
},
tail: false,
};
quasis.push(...b.quasis.slice(1));
return {
type: 'TemplateLiteral',
quasis,
expressions: [...a.expressions, ...b.expressions],
};
}

@ -0,0 +1,6 @@
<script>
let a = "A", b = "B", x = "X";
</script>
<slot name="a" {x} {a}></slot>
<slot name="b" {x} {b}></slot>

@ -0,0 +1,8 @@
export default {
html: `
<p slot="a">A X</p>
<p slot="a">X</p>
<p slot="b">X</p>
<p slot="b">B X</p>
`,
};

@ -0,0 +1,11 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested let:x>
<p slot="a" let:a>{a} {x}</p>
<p slot="a">{x}</p>
<p slot="b">{x}</p>
<p slot="b" let:b>{b} {x}</p>
</Nested>
Loading…
Cancel
Save