diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts
index 60ea6292..403bcf0b 100644
--- a/src/compiler/code_generator.ts
+++ b/src/compiler/code_generator.ts
@@ -157,9 +157,11 @@ class CodeTarget {
// maps ref name to [id, expr]
refInfo: { [name: string]: [string, string] } = {};
shouldProtectScope: boolean = false;
+ on?: { [key: string]: string };
- constructor(name: string) {
+ constructor(name: string, on?: { [key: string]: string }) {
this.name = name;
+ this.on = on;
}
addLine(line: string, idx?: number) {
@@ -303,14 +305,18 @@ export class CodeGenerator {
return code;
}
- compileInNewTarget(prefix: string, ast: AST, ctx: Context): string {
+ compileInNewTarget(
+ prefix: string,
+ ast: AST,
+ ctx: Context,
+ on?: { [key: string]: string }
+ ): string {
const name = this.generateId(prefix);
const initialTarget = this.target;
- const target = new CodeTarget(name);
+ const target = new CodeTarget(name, on);
this.targets.push(target);
this.target = target;
- const subCtx: Context = createContext(ctx);
- this.compileAST(ast, subCtx);
+ this.compileAST(ast, createContext(ctx));
this.target = initialTarget;
return name;
}
@@ -372,6 +378,9 @@ export class CodeGenerator {
}
if (block.isRoot && !ctx.preventRoot) {
+ if (this.target.on) {
+ blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
+ }
this.addLine(`return ${blockExpr};`);
} else {
this.define(block.varName, blockExpr);
@@ -1110,8 +1119,8 @@ export class CodeGenerator {
}
let slotStr: string[] = [];
for (let slotName in ast.slots) {
- const slotAst = ast.slots[slotName].content;
- const name = this.compileInNewTarget("slot", slotAst, ctx);
+ const slotAst = ast.slots[slotName];
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on || undefined);
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
const scope = ast.slots[slotName].scope;
if (scope) {
@@ -1183,24 +1192,29 @@ export class CodeGenerator {
// event handling
if (ast.on) {
- this.helpers.add("createCatcher");
- let name = this.generateId("catcher");
- let spec: any = {};
- let handlers: any[] = [];
- for (let ev in ast.on) {
- let handlerId = this.generateId("hdlr");
- let idx = handlers.push(handlerId) - 1;
- spec[ev] = idx;
- const handler = this.generateHandlerCode(ev, ast.on[ev]);
- this.define(handlerId, handler);
- }
- blockExpr = `${name}(${blockExpr}, [${handlers.join(",")}])`;
- this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
+ blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
}
+
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockExpr, block, ctx);
}
+ wrapWithEventCatcher(expr: string, on: { [key: string]: string }): string {
+ this.helpers.add("createCatcher");
+ let name = this.generateId("catcher");
+ let spec: any = {};
+ let handlers: any[] = [];
+ for (let ev in on) {
+ let handlerId = this.generateId("hdlr");
+ let idx = handlers.push(handlerId) - 1;
+ spec[ev] = idx;
+ const handler = this.generateHandlerCode(ev, on[ev]);
+ this.define(handlerId, handler);
+ }
+ this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
+ return `${name}(${expr}, [${handlers.join(",")}])`;
+ }
+
compileTSlot(ast: ASTSlot, ctx: Context) {
this.helpers.add("callSlot");
let { block } = ctx;
@@ -1227,6 +1241,11 @@ export class CodeGenerator {
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
}
}
+ // event handling
+ if (ast.on) {
+ blockString = this.wrapWithEventCatcher(blockString, ast.on);
+ }
+
if (block) {
this.insertAnchor(block);
}
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index 86159959..55c1b2d3 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -114,6 +114,13 @@ export interface ASTTCall {
body: AST[] | null;
}
+interface SlotDefinition {
+ content: AST;
+ attrs?: { [key: string]: string };
+ scope?: string;
+ on?: null | { [key: string]: string };
+}
+
export interface ASTComponent {
type: ASTType.TComponent;
name: string;
@@ -121,13 +128,14 @@ export interface ASTComponent {
dynamicProps: string | null;
on: null | { [key: string]: string };
props: { [name: string]: string };
- slots: { [name: string]: { content: AST; attrs?: { [key: string]: string }; scope?: string } };
+ slots: { [name: string]: SlotDefinition };
}
export interface ASTSlot {
type: ASTType.TSlot;
name: string;
attrs: { [key: string]: string };
+ on: null | { [key: string]: string };
defaultContent: AST | null;
}
@@ -736,18 +744,26 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const slotAst = parseNode(slotNode, ctx);
if (slotAst) {
const slotInfo: any = { content: slotAst };
+ let on: SlotDefinition["on"] = null;
const attrs: { [key: string]: string } = {};
for (let attributeName of slotNode.getAttributeNames()) {
const value = slotNode.getAttribute(attributeName)!;
if (attributeName === "t-slot-scope") {
slotInfo.scope = value;
continue;
+ } else if (attributeName.startsWith("t-on-")) {
+ on = on || {};
+ on[attributeName.slice(5)] = value;
+ } else {
+ attrs[attributeName] = value;
}
- attrs[attributeName] = value;
}
if (Object.keys(attrs).length) {
slotInfo.attrs = attrs;
}
+ if (on) {
+ slotInfo.on = on;
+ }
slots[name] = slotInfo;
}
}
@@ -775,14 +791,21 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
const name = node.getAttribute("t-slot")!;
node.removeAttribute("t-slot");
const attrs: { [key: string]: string } = {};
+ let on: ASTComponent["on"] = null;
for (let attributeName of node.getAttributeNames()) {
const value = node.getAttribute(attributeName)!;
- attrs[attributeName] = value;
+ if (attributeName.startsWith("t-on-")) {
+ on = on || {};
+ on[attributeName.slice(5)] = value;
+ } else {
+ attrs[attributeName] = value;
+ }
}
return {
type: ASTType.TSlot,
name,
attrs,
+ on,
defaultContent: parseChildNodes(node, ctx),
};
}
diff --git a/tests/compiler/parser.test.ts b/tests/compiler/parser.test.ts
index 13f8bbe0..ee0bad50 100644
--- a/tests/compiler/parser.test.ts
+++ b/tests/compiler/parser.test.ts
@@ -1378,6 +1378,25 @@ describe("qweb parser", () => {
});
});
+ test("a component with a named slot with t-on", async () => {
+ expect(
+ parse(`
something
\`); + let block7 = createBlock(\`paragraph
\`); + + function slot1(ctx, node, key = \\"\\") { + const b6 = block6(); + const b7 = block7(); + const hdlr1 = [()=>this.state.count++, ctx]; + return catcher1(multi([b6, b7]), [hdlr1]); + } + + return function template(ctx, node, key = \\"\\") { + const b2 = text(\` [\`); + const b3 = text(ctx['state'].count); + const b4 = text(\`] \`); + const ctx1 = capture(ctx); + const b8 = component(\`Child\`, {slots: markRaw({'myslot': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx); + return multi([b2, b3, b4, b8]); + } +}" +`; + +exports[`t-on t-on on t-set-slots 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + let { callSlot } = helpers; + + return function template(ctx, node, key = \\"\\") { + return callSlot(ctx, node, key, 'myslot', false, {}); + } +}" +`; + +exports[`t-on t-on on t-slots 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + let { markRaw } = helpers; + + let block1 = createBlock(\`something
\`); + + function slot1(ctx, node, key = \\"\\") { + return block1(); + } + + return function template(ctx, node, key = \\"\\") { + return component(\`Child\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__1\`, node, ctx); + } +}" +`; + +exports[`t-on t-on on t-slots 2`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + let { callSlot, createCatcher } = helpers; + const catcher1 = createCatcher({\\"click\\":0}); + + return function template(ctx, node, key = \\"\\") { + const b2 = text(\` [\`); + const b3 = text(ctx['state'].count); + const b4 = text(\`] \`); + const hdlr1 = [()=>this.state.count++, ctx]; + const b5 = catcher1(callSlot(ctx, node, key, 'default', false, {}), [hdlr1]); + return multi([b2, b3, b4, b5]); + } +}" +`; diff --git a/tests/components/t_on.test.ts b/tests/components/t_on.test.ts index 613f3dd8..2dc4e61f 100644 --- a/tests/components/t_on.test.ts +++ b/tests/components/t_on.test.ts @@ -196,4 +196,53 @@ describe("t-on", () => { await nextTick(); expect(fixture.innerHTML).toBe("something
+something
"); + fixture.querySelector("p")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe(" [1]something
"); + }); + + test("t-on on t-set-slots", async () => { + class Child extends Component { + static template = xml`something
+paragraph
+something
paragraph
"); + fixture.querySelector("p")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe(" [1]something
paragraph
"); + }); });