-
Notifications
You must be signed in to change notification settings - Fork 12.8k
EventListener on a union of Element types should accept a specific event type as parameter #46819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Non-DOM version for clarity type A = {
listen(callback: (this: A, arg: string) => void): void;
listen(callback: (arg: unknown) => void): void;
isA: true;
}
type B = {
listen(callback: (this: B, arg: string) => void): void;
listen(callback: (arg: unknown) => void): void;
isB: true;
}
declare const a: A;
declare const b: B;
declare const ab: A | B;
a.listen(fn);
b.listen(fn);
ab.listen(fn);
function fn(arg: string) {} This happens because the signature unification step occurs first and collapses to the second overload. I can't think of a way to handle this in the general case that isn't combinatorially explosive; it's basically the same problem as #7294 in a different form. |
I also encountered this issue and after spending a nontrivial number of hours in the TypeScript code, found a solution to the immediate problem, as well as a more general case. From the original code posted above, without the error-burying comment, I tweaked it to put the listener definition first, and add a more explicit null check. The error remains. However, explicitly casting as an HTMLElement solves it. The event map used to give more specific events is defined here (with the click event listed here). That map is merged with others here and used on this line to add the more specific event listeners. The definitions of HTMLAnchorElement and HTMLButtonElement show that both directly extend HTMLElement, but both also have a definition for This is not an obvious solution because one would expect TypeScript to be able to figure out that In the much more general case, I think it would be reasonably efficient that if there’s an error encountered in matching a function on a union type, search the nearest common ancestor of the unioned types. Computing the nearest common ancestor type of a union type seems like it could be relatively efficient especially with the relatively short inheritance chains that are common in practice. In this case, it would have been easy to identify In the moderately general case, it seems like it should be possible to address this in the lib.dom.d.ts file that ships with TypeScript, using a generic such as interface HTMLEventListener<E extends HTMLElement> extends DocumentAndElementEventHandlers, GlobalEventHandlers {
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
} and then the individual elements could extend that type, either directly or through another layer with generic, such as interface HTMLButtonElement extends HTMLElement, HTMLEventListener<HTMLButtonElement> { or interface HTMLElementWithListeners<E extends HTMLElement> extends HTMLElement, HTMLEventListener<E> {
//...
interface HTMLButtonElement extends HTMLElementWithListeners<HTMLButtonElement> { In such a PR, the two interfaces extended by Summary: It's definitely possible to overcome this in a very specific case by casting, likely possible to overcome this in a moderately general case by changes to the DOM type library, and maybe possible to overcome this in a much more general (though still not universal) case by examining common ancestors of unioned types for signature matches where the existing searches fail. |
In the case of That would not work for me unfortunately. const listener = (e: PointerEvent) => {
console.log(e);
};
({} as SVGElement).addEventListener('pointerdown', listener); // ok
({} as HTMLElement).addEventListener('pointerdown', listener); // ok
({} as SVGElement | HTMLElement).addEventListener('pointerdown', listener); // error
({} as Element).addEventListener('pointerdown', listener); // error |
Closed as completed - was this fixed? |
The bulk "Close" action I applied to Design Limitation issues doesn't let you pick what kind of "Close" you're doing, so don't read too much into any issue's particular bit on that. I wish GitHub didn't make this distinction without a way to specify it in many UI actions! The correct state for Design Limitation issues is closed since we don't have any plausible way of fixing them, and "Open" issues are for representing "work left to be done". |
Bug Report
� Search Terms
HTMLElement, HTMLButtonElement, HTMLAnchorElement, HTMLElementEventNameMap, EventListener, MouseEvent, KeyboardEvent, Event, parameter, EventListenerOrEventListenerObject
� Version & Regression Information
strictFunctionTypes
(this affects functions as well as methods)⏯ Playground Link
Playground link with relevant code
� Code
� Actual behavior
� Expected behavior
No error, the parameter type
MouseEvent
should be inferred as congruent with the parameter type ofEventListener
The text was updated successfully, but these errors were encountered: