S
SolidJS2mo ago
sponge

Delegated custom events

Should delegated events work with custom events sent via HTMLElement dispatchEvent? The (new) docs mention:
For any other events, such as custom events or events you wish not to be delegated, the on: attribute will add an event listener as-is.
If you need to attach an event listener to an element that is not supported by Solid's event delegation, such as a custom event in a custom element, you can use the on:__ form.
I'm wanting to use delegated events so I can have a component that uses onMyEvent, and also allow users of that component to add their own onMyEvent on the same component. Even though the docs imply I need to use the on: form to attach a native event, the delegated events seem to work correctly. TS does complain about the JSX, though (I have setup the event in JSX.CustomEvents/CustomCaptureEvents): Property 'onMyEvent' does not exist on type 'HTMLAttributes<HTMLDivElement>'. Did you mean '"on:myevent"'? Should I be able to use delegated events for this custom event, and if I can, how do I get TS to stop complaining about that property not existing?
5 Replies
Dsaquel
Dsaquel2mo ago
If you need to attach an event listener to an element that is not supported by Solid's event delegation, such as a custom event in a custom element, you can use the on:__ form.
delegated events works only with solid's event delegation. Now for your custom event you need to type your own event: https://docs.solidjs.com/configuration/typescript#advanced-jsx-attributes-and-directives
Sarguel
Sarguel2mo ago
Hello, you can see event delegation order illustrated with this example: https://playground.solidjs.com/anonymous/f57f8222-23e3-4472-8573-205f6e5c0a29 To answer your question the custom event foo in the example also work with the solid delegation form ( without : ) but if you go and see in the output tab you will see that solid compile it to a dom listener event instead of the solid delegated event primitive
_el$.addEventListener("foo", () => console.log("HELLO FOO from SOLID ??"));
#instead of
_el$.$$foo = () => console.log("HELLO FOO from SOLID ??");
_el$.addEventListener("foo", () => console.log("HELLO FOO from SOLID ??"));
#instead of
_el$.$$foo = () => console.log("HELLO FOO from SOLID ??");
I suspect that if the event doesn't get recognized it get transformed to a delegate event. I don't know if this is a documented behaviour or even if it is by design. We would need someone that know better. In the meantime I would advise you to use the on: form
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Sarguel
Sarguel2mo ago
maybe relevant code from dom-expression:
} else if (prop.slice(0, 3) === "on:") {
const e = prop.slice(3);
prev && node.removeEventListener(e, prev);
value && node.addEventListener(e, value);
} else if (prop.slice(0, 10) === "oncapture:") {
const e = prop.slice(10);
prev && node.removeEventListener(e, prev, true);
value && node.addEventListener(e, value, true);
} else if (prop.slice(0, 2) === "on") {
const name = prop.slice(2).toLowerCase();
const delegate = DelegatedEvents.has(name); // <------------
if (!delegate && prev) {
const h = Array.isArray(prev) ? prev[0] : prev;
node.removeEventListener(name, h);
}
if (delegate || value) {
addEventListener(node, name, value, delegate); // <-------------
delegate && delegateEvents([name]);
}
} else if (prop.slice(0, 3) === "on:") {
const e = prop.slice(3);
prev && node.removeEventListener(e, prev);
value && node.addEventListener(e, value);
} else if (prop.slice(0, 10) === "oncapture:") {
const e = prop.slice(10);
prev && node.removeEventListener(e, prev, true);
value && node.addEventListener(e, value, true);
} else if (prop.slice(0, 2) === "on") {
const name = prop.slice(2).toLowerCase();
const delegate = DelegatedEvents.has(name); // <------------
if (!delegate && prev) {
const h = Array.isArray(prev) ? prev[0] : prev;
node.removeEventListener(name, h);
}
if (delegate || value) {
addEventListener(node, name, value, delegate); // <-------------
delegate && delegateEvents([name]);
}
and
export function addEventListener(node, name, handler, delegate) {
if (delegate) { // <--------------------- false
if (Array.isArray(handler)) {
node[`$$${name}`] = handler[0];
node[`$$${name}Data`] = handler[1];
} else node[`$$${name}`] = handler;
} else if (Array.isArray(handler)) {
const handlerFn = handler[0];
node.addEventListener(name, (handler[0] = e => handlerFn.call(node, handler[1], e)));
} else node.addEventListener(name, handler); // <----------------------
}
export function addEventListener(node, name, handler, delegate) {
if (delegate) { // <--------------------- false
if (Array.isArray(handler)) {
node[`$$${name}`] = handler[0];
node[`$$${name}Data`] = handler[1];
} else node[`$$${name}`] = handler;
} else if (Array.isArray(handler)) {
const handlerFn = handler[0];
node.addEventListener(name, (handler[0] = e => handlerFn.call(node, handler[1], e)));
} else node.addEventListener(name, handler); // <----------------------
}
refs: - https://github.com/ryansolid/dom-expressions/blob/ae71a417aa13b33517082628aff09513629df8b2/packages/dom-expressions/src/client.js#L320-L338 - https://github.com/ryansolid/dom-expressions/blob/ae71a417aa13b33517082628aff09513629df8b2/packages/dom-expressions/src/client.js#L127-L137
GitHub
dom-expressions/packages/dom-expressions/src/client.js at ae71a417a...
A Fine-Grained Runtime for Performant DOM Rendering - ryansolid/dom-expressions
Sarguel
Sarguel2mo ago
const delegate = DelegatedEvents.has(name);
const delegate = DelegatedEvents.has(name);
so if the DelegatedEvents doesn't has your custom event it won't delegate but just fallback to the addEventListener on the node. for final reference:
// list of Element events that will be delegated
const DelegatedEvents = /*#__PURE__*/ new Set([
"beforeinput",
"click",
"dblclick",
"contextmenu",
"focusin",
"focusout",
"input",
"keydown",
"keyup",
"mousedown",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"pointerdown",
"pointermove",
"pointerout",
"pointerover",
"pointerup",
"touchend",
"touchmove",
"touchstart"
]);
// list of Element events that will be delegated
const DelegatedEvents = /*#__PURE__*/ new Set([
"beforeinput",
"click",
"dblclick",
"contextmenu",
"focusin",
"focusout",
"input",
"keydown",
"keyup",
"mousedown",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"pointerdown",
"pointermove",
"pointerout",
"pointerover",
"pointerup",
"touchend",
"touchmove",
"touchstart"
]);
sponge
sponge2mo ago
thanks for the input here! i ended up using onmount/oncleanup addeventlistener for the base component so the users of that component can use on:. but it seems like on without the colon should work it just won't be delegated