Reputation: 1166
Problem
Given a TypeScript interface that is being treated as a map (relating unique key types to non-unique value types), can one extract the key types that map to a specified value type?
As a concrete example, starting with the WindowEventMap
in lib.dom.d.ts
...
interface WindowEventMap
extends GlobalEventHandlersEventMap, WindowEventHandlersEventMap {
"abort": UIEvent;
"afterprint": Event;
"beforeprint": Event;
"beforeunload": BeforeUnloadEvent;
"blur": FocusEvent;
// ...
}
There is a built-in operator keyof
for getting all the keys:
type FullKeyList = keyof WindowEventMap;
// gives: FullKeyList = "'abort' | 'afterprint' | 'beforeprint' | ..and 93 more"
Is there some expression that might filter only the keys which map to value type KeyboardEvent
?:
// Goal:
type SubKeyList = SomeExpression<WindowEventMap, KeyboardEvent>;
// would give: SubKeyList = "'keyup' | 'keypress' | 'keydown'"
Solution Attempt
TypeScript has the Conditional Type expression:
T extends U ? X : Y
So this is a reasonable try:
type Filter<K extends keyof M, V, M> = M[K] extends V ? K : never;
It filters individual types:
// Passes this test:
type FilteredExample1 = Filter<'keyup', KeyboardEvent, WindowEventMap>;
// gives FilteredExample1 = "'keyup'"
// Passes this test:
type FilteredExample2 = Filter<'blur', KeyboardEvent, WindowEventMap>;
// gives FilteredExample2 = "never"
Can it filter a union of types and return a new union? Starts off looking good:
// Passes this test:
type FilteredUnionExample1 = Filter<'keyup' | 'keydown', KeyboardEvent , WindowEventMap>;
// gives FilteredUnionExample1 = "'keyup' | 'keydown'"
But the union fails if one or more members of the union fails:
// Fails this test:
type FilteredUnionExample2 = Filter<'keyup' | 'keydown' | 'blur', KeyboardEvent, WindowEventMap>;
// gives FilteredUnionExample2 = "never" (and not sub-union "'keyup' | 'keydown'")
// And so, it also fails the end-goal usage:
type AllKeysUnion = keyof WindowEventMap;
type FilteredUnionExample3 = Filter<AllKeysUnion, KeyboardEvent , WindowEventMap>;
// gives FilteredUnionExample3 = "never" (not sub-union "'keyup' | 'keypress' | 'keydown'")
Is there a solution to this problem?
Upvotes: 4
Views: 1565
Reputation: 328272
I usually call this KeysMatching
:
type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];
type SubKeyList = KeysMatching<WindowEventMap, KeyboardEvent>
// type SubkeyList = "keydown" | "keypress" | "keyup"
Upvotes: 5