From 4a5316ced537426875fb21cfa0cae7bdee8fece6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 22 Nov 2022 12:52:06 +0100 Subject: [PATCH] [FIX] t-model takes precedence over t-on-input With this commit, we make sure that the code for t-model is run before t-on-input event handler. This is useful to make sure that the state reflected by t-model is up-to-date. closes #1295 --- src/compiler/code_generator.ts | 66 +++++++++---------- .../__snapshots__/t_model.test.ts.snap | 43 ++++++++---- tests/components/t_model.test.ts | 19 ++++++ 3 files changed, 83 insertions(+), 45 deletions(-) diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 049fe4c9..479a6c0f 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -606,39 +606,6 @@ export class CodeGenerator { } } - // event handlers - for (let ev in ast.on) { - const name = this.generateHandlerCode(ev, ast.on[ev]); - const idx = block!.insertData(name, "hdlr"); - attrs[`block-handler-${idx}`] = ev; - } - - // t-ref - if (ast.ref) { - this.target.hasRef = true; - const isDynamic = INTERP_REGEXP.test(ast.ref); - if (isDynamic) { - const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true)); - const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref"); - attrs["block-ref"] = String(idx); - } else { - let name = ast.ref; - if (name in this.target.refInfo) { - // ref has already been defined - this.helpers.add("multiRefSetter"); - const info = this.target.refInfo[name]; - const index = block!.data.push(info[0]) - 1; - attrs["block-ref"] = String(index); - info[1] = `multiRefSetter(refs, \`${name}\`)`; - } else { - let id = generateId("ref"); - this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`]; - const index = block!.data.push(id) - 1; - attrs["block-ref"] = String(index); - } - } - } - // t-model let tModelSelectedExpr; if (ast.model) { @@ -685,6 +652,39 @@ export class CodeGenerator { attrs[`block-handler-${idx}`] = eventType; } + // event handlers + for (let ev in ast.on) { + const name = this.generateHandlerCode(ev, ast.on[ev]); + const idx = block!.insertData(name, "hdlr"); + attrs[`block-handler-${idx}`] = ev; + } + + // t-ref + if (ast.ref) { + this.target.hasRef = true; + const isDynamic = INTERP_REGEXP.test(ast.ref); + if (isDynamic) { + const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true)); + const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref"); + attrs["block-ref"] = String(idx); + } else { + let name = ast.ref; + if (name in this.target.refInfo) { + // ref has already been defined + this.helpers.add("multiRefSetter"); + const info = this.target.refInfo[name]; + const index = block!.data.push(info[0]) - 1; + attrs["block-ref"] = String(index); + info[1] = `multiRefSetter(refs, \`${name}\`)`; + } else { + let id = generateId("ref"); + this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`]; + const index = block!.data.push(id) - 1; + attrs["block-ref"] = String(index); + } + } + } + const dom = xmlDoc.createElement(ast.tag); for (const [attr, val] of Object.entries(attrs)) { if (!(attr === "class" && val === "")) { diff --git a/tests/components/__snapshots__/t_model.test.ts.snap b/tests/components/__snapshots__/t_model.test.ts.snap index 053ebfbb..411c7f65 100644 --- a/tests/components/__snapshots__/t_model.test.ts.snap +++ b/tests/components/__snapshots__/t_model.test.ts.snap @@ -120,15 +120,15 @@ exports[`t-model directive can also define t-on directive on same event, part 1 let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { toNumber } = helpers; - let block1 = createBlock(\`
\`); + let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - let hdlr1 = [ctx['onInput'], ctx]; const bExpr1 = ctx['state']; const expr1 = 'text'; let attr1 = bExpr1[expr1]; - let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; - return block1([hdlr1, attr1, hdlr2]); + let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; + let hdlr2 = [ctx['onInput'], ctx]; + return block1([attr1, hdlr1, hdlr2]); } }" `; @@ -139,25 +139,25 @@ exports[`t-model directive can also define t-on directive on same event, part 2 let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { toNumber } = helpers; - let block1 = createBlock(\`
\`); + let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - let hdlr1 = [ctx['onClick'], ctx]; const bExpr1 = ctx['state']; const expr1 = 'choice'; let attr1 = bExpr1[expr1] === 'One'; - let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; - let hdlr3 = [ctx['onClick'], ctx]; + let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; + let hdlr2 = [ctx['onClick'], ctx]; const bExpr2 = ctx['state']; const expr2 = 'choice'; let attr2 = bExpr2[expr2] === 'Two'; - let hdlr4 = [(ev) => { bExpr2[expr2] = ev.target.value; }]; - let hdlr5 = [ctx['onClick'], ctx]; + let hdlr3 = [(ev) => { bExpr2[expr2] = ev.target.value; }]; + let hdlr4 = [ctx['onClick'], ctx]; const bExpr3 = ctx['state']; const expr3 = 'choice'; let attr3 = bExpr3[expr3] === 'Three'; - let hdlr6 = [(ev) => { bExpr3[expr3] = ev.target.value; }]; - return block1([hdlr1, attr1, hdlr2, hdlr3, attr2, hdlr4, hdlr5, attr3, hdlr6]); + let hdlr5 = [(ev) => { bExpr3[expr3] = ev.target.value; }]; + let hdlr6 = [ctx['onClick'], ctx]; + return block1([attr1, hdlr1, hdlr2, attr2, hdlr3, hdlr4, attr3, hdlr5, hdlr6]); } }" `; @@ -410,6 +410,25 @@ exports[`t-model directive on an textarea 1`] = ` }" `; +exports[`t-model directive t-model is applied before t-on-input 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + let { toNumber } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + const bExpr1 = ctx['state']; + const expr1 = 'text'; + let attr1 = bExpr1[expr1]; + let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; + let hdlr2 = [ctx['onInput'], ctx]; + return block1([attr1, hdlr1, hdlr2]); + } +}" +`; + exports[`t-model directive t-model on an input with an undefined value 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/components/t_model.test.ts b/tests/components/t_model.test.ts index 3b22ca2f..940e4418 100644 --- a/tests/components/t_model.test.ts +++ b/tests/components/t_model.test.ts @@ -625,4 +625,23 @@ describe("t-model directive", () => { await mount(Test, fixture); expect(fixture.querySelector("select")!.value).toEqual("b"); }); + + test("t-model is applied before t-on-input", async () => { + expect.assertions(3); + class SomeComponent extends Component { + static template = xml` +
+ +
+ `; + state = useState({ text: "", other: "" }); + onInput(ev: InputEvent) { + expect(this.state.text).toBe("Beam me up, Scotty"); + expect((ev.target as HTMLInputElement).value).toBe("Beam me up, Scotty"); + } + } + await mount(SomeComponent, fixture); + const input = fixture.querySelector("input")!; + await editInput(input, "Beam me up, Scotty"); + }); });