I'm working in an Electron application. There are alot of events being passed around with specific listeners for each event. For instance BrowserWindow.on:
Electron.BrowserWindow.on(event, listener)
event can be any of 'always-on-top-changed', 'app-command', etc etc... . I want a list of all of these events in a single type that I can use, for example, to restrict other functions from passing in an invalid event.
I have tried:
type BrowserWindowOnEvents = Parameters<InstanceType<typeof Electron.BrowserWindow>['on']>[0];
But that only gives, at least for Intellisense, the last defined event in BrowserWindow class in electron.d.ts, which is currently 'will-resize'. I am expecting a list with all valid events.
When I have new BrowserWindow.on('') the Intellisense does provide me with the list of possibilities, but I want access to the list when creating a type definition not only when I have an instance of BrowserWindow available.
---
Here's a link to Electron's BrowserWindow
And in electron.d.ts this is how the methods are defined:
class BrowserWindow extends NodeEventEmitter {
// Docs: https://electronjs.org/docs/api/browser-window
/**
* Emitted when the window is set or unset to show always on top of other windows.
*/
on(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
once(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
addListener(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
removeListener(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
...
}
Turns out it is possible to do this, but it's not pretty. The problem is that the on method is declared with a separate overload for each event name, and type inference on overloaded functions only considers the last one (in this case the overload for 'will-resize').
There is a way to infer types from overloaded functions though (see this TypeScript issue):
type Args<F extends (...args: any[]) => unknown> = F extends {
(...args: infer A1): void
(...args: infer A2): void
} ? [A1, A2] : never
declare function f(event: 'n1', l: (n: number) => void): void
declare function f(event: 'n2', l: (s: string) => void): void
type TestArgs = Args<typeof f>
// type TestArgs = [
// [event: "n1", l: (n: number) => void],
// [event: "n2", l: (s: string) => void]
// ]
The downside is that this requires creating a function object with at least as many overloads as the function we wish to extract the parameter types from. The on method on BrowserWindow has 36 overloads, so let's use 40 to be on the safe side:
type BuildOverloads<
A01 extends unknown[], A02 extends unknown[], A03 extends unknown[],
A04 extends unknown[], A05 extends unknown[], A06 extends unknown[],
A07 extends unknown[], A08 extends unknown[], A09 extends unknown[],
A10 extends unknown[], A11 extends unknown[], A12 extends unknown[],
A13 extends unknown[], A14 extends unknown[], A15 extends unknown[],
A16 extends unknown[], A17 extends unknown[], A18 extends unknown[],
A19 extends unknown[], A20 extends unknown[], A21 extends unknown[],
A22 extends unknown[], A23 extends unknown[], A24 extends unknown[],
A25 extends unknown[], A26 extends unknown[], A27 extends unknown[],
A28 extends unknown[], A29 extends unknown[], A30 extends unknown[],
A31 extends unknown[], A32 extends unknown[], A33 extends unknown[],
A34 extends unknown[], A35 extends unknown[], A36 extends unknown[],
A37 extends unknown[], A38 extends unknown[], A39 extends unknown[],
A40 extends unknown[]
> = {
(...args: A01): unknown; (...args: A02): unknown; (...args: A03): unknown;
(...args: A04): unknown; (...args: A05): unknown; (...args: A06): unknown;
(...args: A07): unknown; (...args: A08): unknown; (...args: A09): unknown;
(...args: A10): unknown; (...args: A11): unknown; (...args: A12): unknown;
(...args: A13): unknown; (...args: A14): unknown; (...args: A15): unknown;
(...args: A16): unknown; (...args: A17): unknown; (...args: A18): unknown;
(...args: A19): unknown; (...args: A20): unknown; (...args: A21): unknown;
(...args: A22): unknown; (...args: A23): unknown; (...args: A24): unknown;
(...args: A25): unknown; (...args: A26): unknown; (...args: A27): unknown;
(...args: A28): unknown; (...args: A29): unknown; (...args: A30): unknown;
(...args: A31): unknown; (...args: A32): unknown; (...args: A33): unknown;
(...args: A34): unknown; (...args: A35): unknown; (...args: A36): unknown;
(...args: A37): unknown; (...args: A38): unknown; (...args: A39): unknown;
(...args: A40): unknown;
}
There's a minor issue with the solutions in the issue thread, in that they only work if one of the overloads takes either no parameters or only unknown parameters, which is not the case for on. The solution by jcalz doesn't have this problem, but it would require writing 41 * 40 = 1640 function signatures, which is impractical.
To remedy the problem we can simply add this extra overload by inferring on T & ((...args: unknown[]) => unknown) instead of T, and removing any unknown[] elements from the resulting list.
type ElementTypeWithoutUnknown<T extends unknown[]> =
{[K in keyof T]: unknown[] extends T[K] ? never : T[K]}[number]
Now we have everything we need to define GetOverloadParameters:
type GetOverloadParameters<T extends (...args: any) => unknown> =
T & ((...args: unknown[]) => unknown) extends BuildOverloads<
infer A01, infer A02, infer A03, infer A04, infer A05, infer A06, infer A07,
infer A08, infer A09, infer A10, infer A11, infer A12, infer A13, infer A14,
infer A15, infer A16, infer A17, infer A18, infer A19, infer A20, infer A21,
infer A22, infer A23, infer A24, infer A25, infer A26, infer A27, infer A28,
infer A29, infer A30, infer A31, infer A32, infer A33, infer A34, infer A35,
infer A36, infer A37, infer A38, infer A39, infer A40
> ?
ElementTypeWithoutUnknown<[
A01, A02, A03, A04, A05, A06, A07, A08, A09, A10, A11, A12, A13, A14, A15,
A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30,
A31, A32, A33, A34, A35, A36, A37, A38, A39, A40
]> : never
And we can apply it like this:
type EventName = GetOverloadParameters<Electron.BrowserWindow['on']>[0]
// type EventName = "always-on-top-changed" | "app-command" | .. | "will-resize"
(Note that the type Electron.BrowserWindow denotes the type of the object rather than the class, so there's no need to do InstanceType<typeof Electron.BrowserWindow>.)
Because the union of 36 event names is too big to show in a hover type, we can declare a couple of helpers to group them based on the number of dashes in the name (just to verify they are all present, you won't need to do this in your code):
type Dash0<S extends string> = S extends `${string}-${string}` ? never : S
type Dash1<S extends string> = S extends `${string}-${Dash0<infer _>}` ? S : never
type Dash2<S extends string> = S extends `${string}-${Dash1<infer _>}` ? S : never
type Dash3<S extends string> = S extends `${string}-${Dash2<infer _>}` ? S : never
type EventName1 = Dash0<EventName>
// EventName1 = "blur" | "close" | "closed" | "focus" | "hide" | "maximize" |
// "minimize" | "move" | "moved" | "resize" | "resized" |
// "responsive" | "restore" | "show" | "swipe" | "unmaximize" |
// "unresponsive"
type EventName2 = Dash1<EventName>
// EventName2 = "app-command" | "rotate-gesture" | "session-end" | "sheet-begin" |
// "sheet-end" | "will-move" | "will-resize"
type EventName3 = Dash2<EventName>
// EventName3 = "enter-full-screen" | "leave-full-screen" | "page-title-updated" |
// "ready-to-show" | "scroll-touch-begin" | "scroll-touch-edge" |
// "scroll-touch-end" | "system-context-menu"
type EventName4 = Dash3<EventName>
// EventName4 = "always-on-top-changed" | "enter-html-full-screen" |
// "leave-html-full-screen" | "new-window-for-tab"
TypeScript playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With