Open
Description
Vue version
3.x
Link to minimal reproduction
Steps to reproduce
- Create a CE registry like:
type CustomElements = { 'my-dialog': { events: { 'dialog-open': CustomEvent<{ open: boolean }>; }; }; }; type EmitsFrom<T> = { [K in keyof T['events']]: (e: T['events'][K]) => void; };
- Try to create a wrapper type:
type EmitsFrom<T> = { [K in keyof T['events']]: (e: T['events'][K]) => void; };
- Use it in DefineComponent:
DefineComponent<..., ..., ..., ..., ..., ..., ..., EmitsFrom<CustomElements['my-dialog']>>;
- Get a TS error because mapped types like
{ [K in keyof]: Fn }
are not assignable to Vue’sRecord<string, Fn | null>
emits constraint. It's in the runtime core dts:
export type ObjectEmitsOptions = Record<string, ((...args: any[]) => any) | null>; export type EmitsOptions = ObjectEmitsOptions | string[];
What is expected?
Vue’s DefineComponent
should support mapped types in the emits slot so developers can preserve key-specific event typing (especially important when consuming pre-typed Custom Elements from a registry).
What is actually happening?
Due to the strict Record<string, Fn | null>
constraint in EmitsOptions
, generic mapped types are rejected, forcing developers to collapse their union (losing key-specific event typing).
This limits Vue’s TypeScript ergonomics when integrating Custom Elements or other external type registries.
Any additional comments?
TS in the sandbox doesn't seems to pick ambient typings.