Description
I'm currently writing a small library to make working with the Web MIDI API slightly nicer, and I'd like to write event handling code that correctly types the event handler signature (similar to how eg. lib.dom.d.ts
types Document.addEventListener
generically with keys/values from the DocumentEventMap
interface.)
MIDI events can be dispatched frequently and it's often desirable to handle them with minimum latency, so I'm using const enums to provide an abstraction over MIDI event data (passed in a UInt8Array
by the Web MIDI API and decoded with bit shifts and masks) without the additional overhead of object lookups.
Since const enums are fully known at design time, it seems like it should be possible to use their keys as keys in interface declarations like lib.dom.d.ts
does it:
const enum ChannelMessage {
'noteoff' = 0x8,
'noteon' = 0x9,
'keyaftertouch' = 0xA,
// ...
}
interface ChannelMessageEventMap {
[ChannelMessage.noteoff] : NoteEvent
[ChannelMessage.noteon] : NoteEvent
[ChannelMessage.keyaftertouch] : AftertouchEvent
// ...
}
addEventListener<K extends keyof ChannelMessageEventMap>(type: K, listener: (ev: ChannelMessageEventMap[K]) => any): void
Unfortunately, this doesn't currently work -- const enum keys can't be used in interfaces. I thought in a pinch I could simply use the enum values as keys in the event map interface instead, that is:
interface ChannelMessageEventMap {
0x8 : NoteEvent
0x9 : NoteEvent
0xA : AftertouchEvent
// ...
}
The above compiles -- but the following usage of addEventListener
won't (assuming the signature declared above):
addEventListener(ChannelMessage.noteoff, ev => { /* ... */ })
// ^^^^^^^^^^^^^^^^^^^^^^
// ERROR: Argument of type 'ChannelMessage.'noteoff'' is not assignable to parameter of type '"8" | "9" | "10" | ...
Even though const enum values are fully known and inlined at compile time, it seems they aren't compatible with the value literal types themselves.
It's not clear to me if there's any other way to make this work with const enums, unfortunately -- their usefulness as compile-time constants seems greatly reduced by their incompatibility and the inability to use them in other places you'd be able to use a constant value or a type literal. I guess I maybe have to bite the bullet and map to string keys after all?