Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/delegated-event-key-namespace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@solidjs/web": patch
"@solidjs/babel-plugin": patch
"@solidjs/compiler": patch
---

Move delegated event handlers off the `$$<type>` 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 `_$$<type>` / `_$$<type>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`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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$;
})();
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" : "")
Expand Down
6 changes: 3 additions & 3 deletions packages/babel-plugin/src/dom/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
Expand All @@ -1070,7 +1070,7 @@ function transformAttributes(
t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(elem, t.identifier(`$$${ev}`)),
t.memberExpression(elem, t.identifier(`_$$${ev}`)),
handler
)
)
Expand All @@ -1080,7 +1080,7 @@ function transformAttributes(
t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(elem, t.identifier(`$$${ev}`)),
t.memberExpression(elem, t.identifier(`_$$${ev}`)),
handler
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
6 changes: 3 additions & 3 deletions packages/compiler/__tests__/transform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",');
});

Expand All @@ -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"]);');
});

Expand Down Expand Up @@ -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"]);');
});
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler/src/dom/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));
}
Expand All @@ -61,7 +61,7 @@ impl<'a> AstDomTransform<'a, '_> {
return vec![self.delegated_assignment_statement(
span,
element_id,
&format!("$${event_name}"),
&format!("_$${event_name}"),
handler,
)];
}
Expand Down
30 changes: 28 additions & 2 deletions packages/web/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 =>
Expand Down
Loading
Loading