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.