From 6d2bdeb50dc718d337aeb881e76f1c42ce89ee95 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 17 Sep 2026 09:41:31 -0700 Subject: [PATCH] fix(web): move delegated event handlers off the Solid 1 `$$` key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solid 1 delegates from `document` and fires any `$$click`/`$$input`/… it finds while walking up from the target. A 1.x runtime on the same page — an embedded widget, a devtools panel built on 1.x — therefore ran every delegated handler in a 2.x app a second time, and a 2.x root fired 1.x handlers rendered inside it. Both versions used the same key and the same walk; nothing on the event object is consulted by 1.x, so no mark can stop it. The only lever is a key it does not look for. Compiled output (Babel and native) and the runtime (`addEvent`, `eventHandler`) now stamp `_$$` / `_$$Data`. `_$` is the existing Solid-owned expando family (`_$host`, `_$owner`, `_$classes`, `_$styles`, `_$multiple`); the extra `$` gives event keys their own namespace so a custom delegated event can never collide with one of those. The key, the `_$SOLID_EVENT_OWNER` mark, and the walk rules are documented in client.ts as the delegated-event wire contract: shared by every Solid copy on a page and frozen going forward, so two bundles of the same major — or a future major — coordinate through the existing mark rather than needing a new key. Regression coverage carries a faithful copy of the 1.x walker on `document` beside a 2.x root (light DOM, open shadow root, 1.x content nested in a 2.x root) and loads the runtime twice as separate module instances to pin same-major nesting: single dispatch, stopPropagation across instances, outer handlers above the inner root still firing. Co-authored-by: Claude via Cursor --- .changeset/delegated-event-key-namespace.md | 11 + README.md | 2 +- packages/babel-plugin/README.md | 4 +- packages/babel-plugin/src/dom/element.ts | 6 +- .../eventExpressions/output.js | 10 +- .../jsxAttributeValues/output.js | 2 +- .../eventExpressions/output.js | 6 +- .../jsxAttributeValues/output.js | 2 +- .../eventExpressions/output.js | 10 +- .../keyedAccessorEdges/output.js | 2 +- .../tryCatchPending/output.js | 2 +- .../dom-hydratable/eventExpressions/output.js | 6 +- .../jsxAttributeValues/output.js | 2 +- .../fixtures/dom/eventExpressions/output.js | 10 +- .../fixtures/dom/jsxAttributeValues/output.js | 2 +- .../dynamic/eventExpressions/output.js | 10 +- packages/compiler/__tests__/transform.test.js | 6 +- packages/compiler/src/dom/events.rs | 6 +- packages/web/src/client.ts | 30 ++- .../delegated-event-cross-runtime.spec.tsx | 224 ++++++++++++++++++ packages/web/test/element.spec.tsx | 4 +- 21 files changed, 309 insertions(+), 48 deletions(-) create mode 100644 .changeset/delegated-event-key-namespace.md create mode 100644 packages/web/test/delegated-event-cross-runtime.spec.tsx diff --git a/.changeset/delegated-event-key-namespace.md b/.changeset/delegated-event-key-namespace.md new file mode 100644 index 000000000..42da1edf6 --- /dev/null +++ b/.changeset/delegated-event-key-namespace.md @@ -0,0 +1,11 @@ +--- +"@solidjs/web": patch +"@solidjs/babel-plugin": patch +"@solidjs/compiler": patch +--- + +Move delegated event handlers off the `$$` element key Solid 1 uses. + +Solid 1 delegates from `document` and fires any `$$click`/`$$input`/… it finds while walking up from the target, so a 1.x runtime on the same page — an older embedded widget, a devtools panel built on 1.x — ran every delegated handler in a 2.x app a second time. Compiled output and the runtime now stamp `_$$` / `_$$Data` instead; neither version can see the other's handlers, in either nesting direction. + +The key, the `_$SOLID_EVENT_OWNER` mark, and the walk rules are documented in `client.ts` as the delegated-event wire contract shared by every Solid copy on a page. Anything reading `el.$$click` directly must switch to `el._$$click`. diff --git a/README.md b/README.md index ba5114c83..bab6040d0 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ function Counter() { return (() => { const _el$ = _tmpl$(); - _el$.$$click = () => setCount(c => c + 1); + _el$._$$click = () => setCount(c => c + 1); insert(_el$, doubled); return _el$; })(); diff --git a/packages/babel-plugin/README.md b/packages/babel-plugin/README.md index b0ce00450..9c13b959c 100644 --- a/packages/babel-plugin/README.md +++ b/packages/babel-plugin/README.md @@ -56,9 +56,9 @@ const view = ({ item }) => { _el$5 = _el$3.nextSibling, _el$6 = _el$5.firstChild; _$insert(_el$2, itemId); - _el$4.$$click = e => select(item, e); + _el$4._$$click = e => select(item, e); _$insert(_el$4, () => item.label); - _el$6.$$click = e => del(item, e); + _el$6._$$click = e => del(item, e); _$effect( () => selected(), _v$ => _$className(_el$, itemId === _v$ ? "danger" : "") diff --git a/packages/babel-plugin/src/dom/element.ts b/packages/babel-plugin/src/dom/element.ts index 48ec5d2cd..05238dfba 100644 --- a/packages/babel-plugin/src/dom/element.ts +++ b/packages/babel-plugin/src/dom/element.ts @@ -1059,7 +1059,7 @@ function transformAttributes( t.expressionStatement( t.assignmentExpression( "=", - t.memberExpression(elem, t.identifier(`$$${ev}Data`)), + t.memberExpression(elem, t.identifier(`_$$${ev}Data`)), handler.elements[1] as babelTypes.Expression ) ) @@ -1070,7 +1070,7 @@ function transformAttributes( t.expressionStatement( t.assignmentExpression( "=", - t.memberExpression(elem, t.identifier(`$$${ev}`)), + t.memberExpression(elem, t.identifier(`_$$${ev}`)), handler ) ) @@ -1080,7 +1080,7 @@ function transformAttributes( t.expressionStatement( t.assignmentExpression( "=", - t.memberExpression(elem, t.identifier(`$$${ev}`)), + t.memberExpression(elem, t.identifier(`_$$${ev}`)), handler ) ) diff --git a/packages/babel-plugin/test/__dom_fixtures__/eventExpressions/output.js b/packages/babel-plugin/test/__dom_fixtures__/eventExpressions/output.js index 60904254a..1f0f7856d 100644 --- a/packages/babel-plugin/test/__dom_fixtures__/eventExpressions/output.js +++ b/packages/babel-plugin/test/__dom_fixtures__/eventExpressions/output.js @@ -28,11 +28,11 @@ _el$3.addEventListener("change", e => (id => console.log("bound", id))(id, e)); _$addEvent(_el$4, "change", handler); _el$5.addEventListener("change", handler); _el$6.addEventListener("change", hoisted1); -_el$7.$$click = () => console.log("delegated"); -_el$8.$$click = id => console.log("delegated", id); -_el$8.$$clickData = rowId; +_el$7._$$click = () => console.log("delegated"); +_el$8._$$click = id => console.log("delegated", id); +_el$8._$$clickData = rowId; _$addEvent(_el$9, "click", handler, true); -_el$0.$$click = handler; -_el$1.$$click = hoisted2; +_el$0._$$click = handler; +_el$1._$$click = hoisted2; const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/babel-plugin/test/__dom_fixtures__/jsxAttributeValues/output.js b/packages/babel-plugin/test/__dom_fixtures__/jsxAttributeValues/output.js index b991b0d89..157102477 100644 --- a/packages/babel-plugin/test/__dom_fixtures__/jsxAttributeValues/output.js +++ b/packages/babel-plugin/test/__dom_fixtures__/jsxAttributeValues/output.js @@ -60,7 +60,7 @@ _$effect( ); const multiValues = _el$4; var _el$5 = _tmpl$3(); -_el$5.$$click = () => mount(_tmpl$7()); +_el$5._$$click = () => mount(_tmpl$7()); const handlerValue = _el$5; var _el$6 = _tmpl$2(); _$ref(() => el => el.appendChild(_tmpl$8()), _el$6); diff --git a/packages/babel-plugin/test/__dom_hydratable_fixtures__/eventExpressions/output.js b/packages/babel-plugin/test/__dom_hydratable_fixtures__/eventExpressions/output.js index 42a054037..d186a9df4 100644 --- a/packages/babel-plugin/test/__dom_hydratable_fixtures__/eventExpressions/output.js +++ b/packages/babel-plugin/test/__dom_hydratable_fixtures__/eventExpressions/output.js @@ -16,9 +16,9 @@ var _el$ = _$getNextElement(_tmpl$), _el$5 = _el$4.nextSibling; _el$2.addEventListener("change", () => console.log("bound")); _el$3.addEventListener("change", e => (id => console.log("bound", id))(id, e)); -_el$4.$$click = () => console.log("delegated"); -_el$5.$$click = id => console.log("delegated", id); -_el$5.$$clickData = rowId; +_el$4._$$click = () => console.log("delegated"); +_el$5._$$click = id => console.log("delegated", id); +_el$5._$$clickData = rowId; _$runHydrationEvents(); const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/babel-plugin/test/__dom_hydratable_fixtures__/jsxAttributeValues/output.js b/packages/babel-plugin/test/__dom_hydratable_fixtures__/jsxAttributeValues/output.js index 5caba668d..4b3e3f914 100644 --- a/packages/babel-plugin/test/__dom_hydratable_fixtures__/jsxAttributeValues/output.js +++ b/packages/babel-plugin/test/__dom_hydratable_fixtures__/jsxAttributeValues/output.js @@ -62,7 +62,7 @@ _$effect( ); const multiValues = _el$4; var _el$5 = _$getNextElement(_tmpl$3); -_el$5.$$click = () => mount(_$getNextElement(_tmpl$7)); +_el$5._$$click = () => mount(_$getNextElement(_tmpl$7)); _$runHydrationEvents(); const handlerValue = _el$5; var _el$6 = _$getNextElement(_tmpl$2); diff --git a/packages/babel-plugin/test/__dynamic_fixtures__/eventExpressions/output.js b/packages/babel-plugin/test/__dynamic_fixtures__/eventExpressions/output.js index 7cce4ef3e..099dde27e 100644 --- a/packages/babel-plugin/test/__dynamic_fixtures__/eventExpressions/output.js +++ b/packages/babel-plugin/test/__dynamic_fixtures__/eventExpressions/output.js @@ -24,11 +24,11 @@ _el$3.addEventListener("change", e => (id => console.log("bound", id))(id, e)); _$addEvent(_el$4, "change", handler); _el$5.addEventListener("change", handler); _el$6.addEventListener("change", hoisted1); -_el$7.$$click = () => console.log("delegated"); -_el$8.$$click = id => console.log("delegated", id); -_el$8.$$clickData = rowId; +_el$7._$$click = () => console.log("delegated"); +_el$8._$$click = id => console.log("delegated", id); +_el$8._$$clickData = rowId; _$addEvent(_el$9, "click", handler, true); -_el$0.$$click = handler; -_el$1.$$click = hoisted2; +_el$0._$$click = handler; +_el$1._$$click = hoisted2; const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/babel-plugin/test/__tsrx_dom_fixtures__/keyedAccessorEdges/output.js b/packages/babel-plugin/test/__tsrx_dom_fixtures__/keyedAccessorEdges/output.js index ed0ac4e6e..f064cda9a 100644 --- a/packages/babel-plugin/test/__tsrx_dom_fixtures__/keyedAccessorEdges/output.js +++ b/packages/babel-plugin/test/__tsrx_dom_fixtures__/keyedAccessorEdges/output.js @@ -20,7 +20,7 @@ export function Rows({ rows }) { var _el$2 = _tmpl$2(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling; - _el$2.$$click = () => preserve("local", 0); + _el$2._$$click = () => preserve("local", 0); _$insert(_el$2, () => row().name, _el$3); _$insert(_el$2, index, _el$4); _$effect( diff --git a/packages/babel-plugin/test/__tsrx_dom_fixtures__/tryCatchPending/output.js b/packages/babel-plugin/test/__tsrx_dom_fixtures__/tryCatchPending/output.js index 6803fbd22..aa4fa4e1e 100644 --- a/packages/babel-plugin/test/__tsrx_dom_fixtures__/tryCatchPending/output.js +++ b/packages/babel-plugin/test/__tsrx_dom_fixtures__/tryCatchPending/output.js @@ -17,7 +17,7 @@ export const App = () => _el$3 = _el$2.firstChild, _el$4 = _el$2.nextSibling; _$insert(_el$2, () => e().message, null); - _el$4.$$click = () => reset(); + _el$4._$$click = () => reset(); return _el$; })(), get children() { diff --git a/packages/compiler/__tests__/fixtures/dom-hydratable/eventExpressions/output.js b/packages/compiler/__tests__/fixtures/dom-hydratable/eventExpressions/output.js index c22b66d47..087b14161 100644 --- a/packages/compiler/__tests__/fixtures/dom-hydratable/eventExpressions/output.js +++ b/packages/compiler/__tests__/fixtures/dom-hydratable/eventExpressions/output.js @@ -16,9 +16,9 @@ _el$2.addEventListener("change", () => console.log("bound")); _el$3.addEventListener("change", (e) => { return ((id) => console.log("bound", id))(id, e); }); -_el$4.$$click = () => console.log("delegated"); -_el$5.$$click = (id) => console.log("delegated", id); -_el$5.$$clickData = rowId; +_el$4._$$click = () => console.log("delegated"); +_el$5._$$click = (id) => console.log("delegated", id); +_el$5._$$clickData = rowId; _$runHydrationEvents(); const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/compiler/__tests__/fixtures/dom-hydratable/jsxAttributeValues/output.js b/packages/compiler/__tests__/fixtures/dom-hydratable/jsxAttributeValues/output.js index 6dc9efe64..121c0e283 100644 --- a/packages/compiler/__tests__/fixtures/dom-hydratable/jsxAttributeValues/output.js +++ b/packages/compiler/__tests__/fixtures/dom-hydratable/jsxAttributeValues/output.js @@ -60,7 +60,7 @@ _$effect(() => { }); const multiValues = _el$4; var _el$5 = _$getNextElement(_tmpl$3); -_el$5.$$click = () => mount(_$getNextElement(_tmpl$7)); +_el$5._$$click = () => mount(_$getNextElement(_tmpl$7)); _$runHydrationEvents(); const handlerValue = _el$5; var _el$6 = _$getNextElement(_tmpl$2); diff --git a/packages/compiler/__tests__/fixtures/dom/eventExpressions/output.js b/packages/compiler/__tests__/fixtures/dom/eventExpressions/output.js index aa5d35383..beefce641 100644 --- a/packages/compiler/__tests__/fixtures/dom/eventExpressions/output.js +++ b/packages/compiler/__tests__/fixtures/dom/eventExpressions/output.js @@ -28,11 +28,11 @@ _el$3.addEventListener("change", (e) => { _$addEvent(_el$4, "change", handler); _el$5.addEventListener("change", handler); _el$6.addEventListener("change", hoisted1); -_el$7.$$click = () => console.log("delegated"); -_el$8.$$click = (id) => console.log("delegated", id); -_el$8.$$clickData = rowId; +_el$7._$$click = () => console.log("delegated"); +_el$8._$$click = (id) => console.log("delegated", id); +_el$8._$$clickData = rowId; _$addEvent(_el$9, "click", handler, true); -_el$10.$$click = handler; -_el$11.$$click = hoisted2; +_el$10._$$click = handler; +_el$11._$$click = hoisted2; const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/compiler/__tests__/fixtures/dom/jsxAttributeValues/output.js b/packages/compiler/__tests__/fixtures/dom/jsxAttributeValues/output.js index 82271dfff..41bbac9c9 100644 --- a/packages/compiler/__tests__/fixtures/dom/jsxAttributeValues/output.js +++ b/packages/compiler/__tests__/fixtures/dom/jsxAttributeValues/output.js @@ -58,7 +58,7 @@ _$effect(() => { }); const multiValues = _el$4; var _el$5 = _tmpl$3(); -_el$5.$$click = () => mount(_tmpl$7()); +_el$5._$$click = () => mount(_tmpl$7()); const handlerValue = _el$5; var _el$6 = _tmpl$2(); _$ref(() => { diff --git a/packages/compiler/__tests__/fixtures/dynamic/eventExpressions/output.js b/packages/compiler/__tests__/fixtures/dynamic/eventExpressions/output.js index 1fa22f2ee..ef759e8f3 100644 --- a/packages/compiler/__tests__/fixtures/dynamic/eventExpressions/output.js +++ b/packages/compiler/__tests__/fixtures/dynamic/eventExpressions/output.js @@ -24,11 +24,11 @@ _el$3.addEventListener("change", (e) => { _$addEvent(_el$4, "change", handler); _el$5.addEventListener("change", handler); _el$6.addEventListener("change", hoisted1); -_el$7.$$click = () => console.log("delegated"); -_el$8.$$click = (id) => console.log("delegated", id); -_el$8.$$clickData = rowId; +_el$7._$$click = () => console.log("delegated"); +_el$8._$$click = (id) => console.log("delegated", id); +_el$8._$$clickData = rowId; _$addEvent(_el$9, "click", handler, true); -_el$10.$$click = handler; -_el$11.$$click = hoisted2; +_el$10._$$click = handler; +_el$11._$$click = hoisted2; const template = _el$; _$delegateEvents(["click"]); diff --git a/packages/compiler/__tests__/transform.test.js b/packages/compiler/__tests__/transform.test.js index 5c99aeeea..5dedc851b 100644 --- a/packages/compiler/__tests__/transform.test.js +++ b/packages/compiler/__tests__/transform.test.js @@ -325,7 +325,7 @@ describe("@solidjs/compiler transform", () => { }); expect(result.code).not.toContain("_$delegateEvents"); - expect(result.code).not.toContain("$$click"); + expect(result.code).not.toContain("_$$click"); expect(result.code).toContain('_el$.addEventListener("click",'); }); @@ -337,7 +337,7 @@ describe("@solidjs/compiler transform", () => { }); expect(result.code).toContain('import { delegateEvents as _$delegateEvents } from "r-dom";'); - expect(result.code).toContain("_el$.$$change ="); + expect(result.code).toContain("_el$._$$change ="); expect(result.code).toContain('_$delegateEvents(["change"]);'); }); @@ -713,7 +713,7 @@ describe("@solidjs/compiler transform", () => { }); expect(result.code).toContain('import { delegateEvents as _$delegateEvents } from "r-dom";'); - expect(result.code).toContain("_el$.$$click ="); + expect(result.code).toContain("_el$._$$click ="); expect(result.code).toContain("increment();"); expect(result.code).toContain('_$delegateEvents(["click"]);'); }); diff --git a/packages/compiler/src/dom/events.rs b/packages/compiler/src/dom/events.rs index abb3b07f9..37bb55cd0 100644 --- a/packages/compiler/src/dom/events.rs +++ b/packages/compiler/src/dom/events.rs @@ -42,14 +42,14 @@ impl<'a> AstDomTransform<'a, '_> { let mut statements = vec![self.delegated_assignment_statement( span, element_id, - &format!("$${event_name}"), + &format!("_$${event_name}"), bound, )]; if let Some(data) = data { statements.push(self.delegated_assignment_statement( span, element_id, - &format!("$${event_name}Data"), + &format!("_$${event_name}Data"), data, )); } @@ -61,7 +61,7 @@ impl<'a> AstDomTransform<'a, '_> { return vec![self.delegated_assignment_statement( span, element_id, - &format!("$${event_name}"), + &format!("_$${event_name}"), handler, )]; } diff --git a/packages/web/src/client.ts b/packages/web/src/client.ts index f40b5b160..008374501 100644 --- a/packages/web/src/client.ts +++ b/packages/web/src/client.ts @@ -191,6 +191,32 @@ export { DelegatedEvents } from "./constants.js"; +// === Delegated-event wire contract === +// +// Everything below is read off the DOM and the event object by whichever +// Solid runtime happens to be listening, so it is shared state between every +// Solid copy on a page: two bundles of the same major nested in each other, +// or a future major nested in this one. It is frozen. Changing any of it +// means choosing a new EVENT_KEY prefix so the runtimes stop seeing each +// other's handlers, not a new shape under the old one. +// +// - `node[EVENT_KEY + type]` is the delegated handler: a function or an +// object with `handleEvent`. `node[EVENT_KEY + type + "Data"]` is the +// optional bound data; when defined the handler is called `(data, e)`, +// otherwise `(e)`. Skipped while `node.disabled`. +// - `e[$$EVENT_OWNER]` marks an event a root has already walked: the owner +// node, or `true` for an unscoped container. A listener that sees a mark +// skips unless the mark is a descendant of its own container, in which +// case it resumes from the marked node's parent. +// - The walk climbs `_$host || parentNode || host`, stops on `cancelBubble`, +// and stops at the listener's own boundary or its direct child. +// +// v1 (`solid-js@1`) used `$$` + type for the key and delegated from +// `document`, so its listener saw every element on the page and fired any +// matching key it found. The prefix here is deliberately not `$$` so a v1 +// copy on the same page (an older widget, a devtools panel) cannot find +// these handlers, and this runtime cannot find v1's. +const EVENT_KEY = "_$$"; const $$EVENT_OWNER = "_$SOLID_EVENT_OWNER"; const $$EVENT_TUPLE = Symbol(); const hasOwn = Object.prototype.hasOwnProperty; @@ -724,7 +750,7 @@ export function addEvent( export function addEvent(node, name, handler, delegate) { if (delegate) { - const key = `$$${name}`; + const key = EVENT_KEY + name; let data; if (Array.isArray(handler)) { data = handler[1]; @@ -2460,7 +2486,7 @@ function eventHandler(e, container, state) { e[$$EVENT_OWNER] = owner || true; let node = resumeNode || e.target; - const key = `$$${e.type}`; + const key = EVENT_KEY + e.type; const oriTarget = e.target; const boundary = owner || container || e.currentTarget; const retarget = value => diff --git a/packages/web/test/delegated-event-cross-runtime.spec.tsx b/packages/web/test/delegated-event-cross-runtime.spec.tsx new file mode 100644 index 000000000..b832025b6 --- /dev/null +++ b/packages/web/test/delegated-event-cross-runtime.spec.tsx @@ -0,0 +1,224 @@ +/** + * @jsxImportSource @solidjs/web + * @vitest-environment jsdom + */ +import { afterEach, describe, expect, test, vi } from "vitest"; +import { render } from "@solidjs/web"; +import * as runtimeA from "../src/client.js"; +// A second module instance of the runtime: same code, separate module-level +// state (`delegatedContainers`, `delegatedEvents`). Stands in for a second +// bundle of the same major on one page — a third-party custom element, a +// devtools panel — and, since the wire contract is frozen, for a future +// major nested in this one. +// @ts-expect-error -- the query suffix is what makes Vite hand back a fresh instance +import * as runtimeB from "../src/client.js?copy=2"; + +// A faithful copy of `solid-js@1`'s delegated walker (dom-expressions +// `client.js`, `eventHandler`). v1 attaches this to `document` for every +// delegated type it has seen, keys handlers as `$$`, and consults +// nothing else on the event. It cannot be told to skip an element; the only +// way to keep it away from our handlers is a key it does not look for. +function v1EventHandler(e: any) { + let node = e.target; + const key = `$$${e.type}`; + const oriTarget = e.target; + const oriCurrentTarget = e.currentTarget; + const retarget = (value: any) => + Object.defineProperty(e, "target", { configurable: true, value }); + const handleNode = () => { + const handler = node[key]; + if (handler && !node.disabled) { + const data = node[`${key}Data`]; + data !== undefined ? handler.call(node, data, e) : handler.call(node, e); + if (e.cancelBubble) return; + } + node.host && + typeof node.host !== "string" && + !node.host._$host && + node.contains(e.target) && + retarget(node.host); + return true; + }; + const walkUpTree = () => { + while (handleNode() && (node = node._$host || node.parentNode || node.host)); + }; + Object.defineProperty(e, "currentTarget", { + configurable: true, + get() { + return node || document; + } + }); + if (e.composedPath) { + const path = e.composedPath(); + retarget(path[0]); + for (let i = 0; i < path.length - 2; i++) { + node = path[i]; + if (!handleNode()) break; + if (node._$host) { + node = node._$host; + walkUpTree(); + break; + } + if (node.parentNode === oriCurrentTarget) break; + } + } else walkUpTree(); + retarget(oriTarget); +} + +const cleanups: Array<() => void> = []; +afterEach(() => { + while (cleanups.length) cleanups.pop()!(); +}); + +function mountV1Walker(types: string[]) { + for (const type of types) document.addEventListener(type, v1EventHandler); + cleanups.push(() => { + for (const type of types) document.removeEventListener(type, v1EventHandler); + }); +} + +function container(parent: Node = document.body) { + const el = document.createElement("div"); + parent.appendChild(el); + cleanups.push(() => el.remove()); + return el; +} + +describe("delegated events beside a Solid 1 runtime", () => { + test("compiled handlers are keyed off the v1 `$$` namespace", () => { + const root = container(); + const dispose = render(() =>