diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts index 06ef7cd5..0478bdb5 100644 --- a/packages/csr/src/reactive-runtime.ts +++ b/packages/csr/src/reactive-runtime.ts @@ -2512,6 +2512,11 @@ export const REACTIVE_RUNTIME = String.raw` var stmt = attr.value; var listener = function (event) { + // An unbound output falls back to a same-named CustomEvent for + // external consumers. Do not feed that synthetic event back into + // the component's own declarative DOM handler (for example, + // @click="output.click()"), which would recurse indefinitely. + if (event && event.__wrnexusComponentOutput) return; var locals = decodeLoopLocals(node); @@ -4167,6 +4172,7 @@ export const REACTIVE_RUNTIME = String.raw` bubbles: true, detail: detail || {}, }); + event.__wrnexusComponentOutput = true; root.dispatchEvent(event); // Compatibility for applications using the former prefixed contract. root.dispatchEvent(new EventConstructor("wrnexus:" + String(name), { diff --git a/packages/csr/test/reactive.test.ts b/packages/csr/test/reactive.test.ts index f311346c..8c233bc9 100644 --- a/packages/csr/test/reactive.test.ts +++ b/packages/csr/test/reactive.test.ts @@ -781,6 +781,26 @@ test("development runtime allows an output with no parent binding", () => { expect(warnings).toHaveLength(0); }); +test("an unbound native-named output does not re-enter its DOM handler", () => { + const win = mount( + `