diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 1013d13f..e4bb897f 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -232,6 +232,7 @@ export class CodeGenerator { translatableAttributes: string[] = TRANSLATABLE_ATTRS; ast: AST; staticDefs: { id: string; expr: string }[] = []; + slotNames: Set = new Set(); helpers: Set = new Set(); constructor(ast: AST, options: CodeGenOptions) { @@ -1245,28 +1246,37 @@ export class CodeGenerator { let blockString: string; let slotName; let dynamic = false; + let isMultiple = false; if (ast.name.match(INTERP_REGEXP)) { dynamic = true; + isMultiple = true; slotName = interpolate(ast.name); } else { slotName = "'" + ast.name + "'"; + isMultiple = isMultiple || this.slotNames.has(ast.name); + this.slotNames.add(ast.name); } const dynProps = ast.attrs ? ast.attrs["t-props"] : null; if (ast.attrs) { delete ast.attrs["t-props"]; } + let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key"; + if (isMultiple) { + key = `${key} + \`${this.generateComponentKey()}\``; + } + const props = ast.attrs ? this.formatPropObject(ast.attrs) : []; const scope = this.getPropString(props, dynProps); if (ast.defaultContent) { const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx); - blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`; + blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`; } else { if (dynamic) { let name = generateId("slot"); this.define(name, slotName); - blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`; + blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`; } else { - blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`; + blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`; } } // event handling diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap index a9f0c944..4930b7d1 100644 --- a/tests/components/__snapshots__/slots.test.ts.snap +++ b/tests/components/__snapshots__/slots.test.ts.snap @@ -604,6 +604,64 @@ exports[`slots default slot work with text nodes 2`] = ` }" `; +exports[`slots dynamic slot in multiple locations 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { capture, markRaw } = helpers; + const comp1 = app.createComponent(\`Child\`, true, false, false, true); + const comp2 = app.createComponent(\`Slotter\`, true, true, false, false); + + function slot1(ctx, node, key = \\"\\") { + const b2 = text(\`hello \`); + const b3 = comp1({}, key + \`__1\`, node, this, null); + return multi([b2, b3]); + } + + return function template(ctx, node, key = \\"\\") { + const ctx1 = capture(ctx); + return comp2({location: ctx['state'].location,slots: markRaw({'coffee': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, this, null); + } +}" +`; + +exports[`slots dynamic slot in multiple locations 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { callSlot } = helpers; + + let block2 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b4; + if (ctx['props'].location===1) { + const slot1 = ('coffee'); + const b3 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {})); + b2 = block2([], [b3]); + } + if (ctx['props'].location===2) { + const slot2 = ('coffee'); + b4 = toggler(slot2, callSlot(ctx, node, key + \`__2\`, slot2, true, {})); + } + return multi([b2, b4]); + } +}" +`; + +exports[`slots dynamic slot in multiple locations 3`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
child
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`slots dynamic t-slot call 1`] = ` "function anonymous(app, bdom, helpers ) { @@ -645,7 +703,7 @@ exports[`slots dynamic t-slot call 2`] = ` return function template(ctx, node, key = \\"\\") { let hdlr1 = [ctx['toggle'], ctx]; const slot1 = (ctx['current'].slot); - const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {})); + const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {})); return block1([hdlr1], [b2]); } }" @@ -695,7 +753,7 @@ exports[`slots dynamic t-slot call with default 2`] = ` return function template(ctx, node, key = \\"\\") { let hdlr1 = [ctx['toggle'], ctx]; - const b3 = callSlot(ctx, node, key, (ctx['current'].slot), true, {}, defaultContent1); + const b3 = callSlot(ctx, node, key + \`__1\`, (ctx['current'].slot), true, {}, defaultContent1); return block1([hdlr1], [b3]); } }" @@ -726,7 +784,7 @@ exports[`slots fun: two calls to the same slot 2`] = ` return function template(ctx, node, key = \\"\\") { const b2 = callSlot(ctx, node, key, 'default', false, {}); - const b3 = callSlot(ctx, node, key, 'default', false, {}); + const b3 = callSlot(ctx, node, key + \`__1\`, 'default', false, {}); return multi([b2, b3]); } }" @@ -1527,7 +1585,7 @@ exports[`slots simple dynamic slot with slot scope 2`] = ` return function template(ctx, node, key = \\"\\") { const slot1 = ('slotName'); - const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {bool: ctx['state'].bool})); + const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {bool: ctx['state'].bool})); return block1([], [b2]); } }" @@ -1854,7 +1912,7 @@ exports[`slots slot content has different key from other content -- dynamic slot return function template(ctx, node, key = \\"\\") { const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null); const slot1 = (ctx['slotName']); - const b3 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {})); + const b3 = toggler(slot1, callSlot(ctx, node, key + \`__2\`, slot1, true, {})); return multi([b2, b3]); } }" @@ -1995,6 +2053,118 @@ exports[`slots slot content is bound to caller 2`] = ` }" `; +exports[`slots slot in multiple locations 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { markRaw } = helpers; + const comp1 = app.createComponent(\`Child\`, true, false, false, true); + const comp2 = app.createComponent(\`Slotter\`, true, true, false, false); + + function slot1(ctx, node, key = \\"\\") { + const b2 = text(\` hello \`); + const b3 = comp1({}, key + \`__1\`, node, this, null); + return multi([b2, b3]); + } + + return function template(ctx, node, key = \\"\\") { + return comp2({location: ctx['state'].location,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null); + } +}" +`; + +exports[`slots slot in multiple locations 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { callSlot } = helpers; + + let block2 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + let b2,b4; + if (ctx['props'].location===1) { + const b3 = callSlot(ctx, node, key, 'default', false, {}); + b2 = block2([], [b3]); + } + if (ctx['props'].location===2) { + b4 = callSlot(ctx, node, key + \`__1\`, 'default', false, {}); + } + return multi([b2, b4]); + } +}" +`; + +exports[`slots slot in multiple locations 3`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
child
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`slots slot in t-foreach locations 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { markRaw } = helpers; + const comp1 = app.createComponent(\`Child\`, true, false, false, true); + const comp2 = app.createComponent(\`Slotter\`, true, true, false, false); + + function slot1(ctx, node, key = \\"\\") { + const b2 = text(\` hello \`); + const b3 = comp1({}, key + \`__1\`, node, this, null); + return multi([b2, b3]); + } + + return function template(ctx, node, key = \\"\\") { + return comp2({list: ctx['state'].list,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null); + } +}" +`; + +exports[`slots slot in t-foreach locations 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { prepareList, callSlot, withKey } = helpers; + + let block2 = createBlock(\`

\`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['props'].list);; + for (let i1 = 0; i1 < l_block1; i1++) { + ctx[\`elem\`] = v_block1[i1]; + ctx[\`elem_index\`] = i1; + const key1 = ctx['elem_index']; + let txt1 = ctx['elem']; + const b3 = callSlot(ctx, node, key1, 'default', false, {}); + c_block1[i1] = withKey(block2([txt1], [b3]), key1); + } + return list(c_block1); + } +}" +`; + +exports[`slots slot in t-foreach locations 3`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
child
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`slots slot preserves properly parented relationship 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/components/slots.test.ts b/tests/components/slots.test.ts index 370be644..be1e574d 100644 --- a/tests/components/slots.test.ts +++ b/tests/components/slots.test.ts @@ -1819,4 +1819,102 @@ describe("slots", () => { await nextTick(); expect(fixture.innerHTML).toBe("[B][C][A][sub1] [sub2334]"); }); + + test("slot in multiple locations", async () => { + class Child extends Component { + static template = xml`
child
`; + } + + class Slotter extends Component { + static components = { Child }; + static template = xml` + +

+
+ + + + `; + } + + class Parent extends Component { + static components = { Child, Slotter }; + static template = xml` + + hello + `; + state = useState({ location: 1 }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("

hello

child

"); + parent.state.location = 2; + await nextTick(); + expect(fixture.innerHTML).toBe(" hello
child
"); + }); + + test("dynamic slot in multiple locations", async () => { + class Child extends Component { + static template = xml`
child
`; + } + + class Slotter extends Component { + static components = { Child }; + static template = xml` + +

+
+ + + + `; + } + + class Parent extends Component { + static components = { Child, Slotter }; + static template = xml` + + hello + `; + state = useState({ location: 1 }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("

hello

child

"); + parent.state.location = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("hello
child
"); + }); + + test("slot in t-foreach locations", async () => { + class Child extends Component { + static template = xml`
child
`; + } + + class Slotter extends Component { + static components = { Child }; + static template = xml` + +

+
+ `; + } + + class Parent extends Component { + static components = { Child, Slotter }; + static template = xml` + + hello + `; + state = useState({ list: [1] }); + } + + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe("

1 hello

child

"); + parent.state.list.push(2); + await nextTick(); + expect(fixture.innerHTML).toBe( + "

1 hello

child

2 hello

child

" + ); + }); });