Skip to content

Improve DefineComponent typings to support precise event mapping for consuming Custom Elements #13518

Open
@JulianCataldo

Description

@JulianCataldo

Vue version

3.x

Link to minimal reproduction

https://play.vuejs.org/#eNqVU0uP2jAQ/ivTXLIrsXBoT2lA2m7pa6W2Kkg9YA4mmQQvjm3ZDgWh/PeOnSVQqVppD3l45vPkezin5N6Y8b7FJEtyV1hhPDj0rQHJVT1liXcsmTElGqOthxNYrKCDyuoGUtqWMsVUoZXz0LgapqF/k35BKTX81laWb9LbAJlMYIEIvNkIVH5cjmluKN5Lp0fgt8KB46rc6AMsFyCcSj0YUeyEqoHI+C1CLfWGS+Bt3dAI7oVWkE2Yyic9b2JJC4+NkdwjrQDy5nhXCi51DZmx2pCeDbckKJ8MHQLmk6tdyYg0k6JK1OMnpxUZcwqzWFLoxgiJ9ocJ3yZfMoid0OMk+M+3WPO2xdG5Xmyx2P2n/uQOocaSnxYd2j2yZOh5bmv0fXu++I4Heh+ajS5bSegXmr/QadkGjj3sQ6tKon2Fi2y/xkjJ4KWbHzwqdxYViAZkF/EsoZgfXpB+oft2/C7uY6ojF6+zJhP90SA8tM7rZi4xROjouMQp6RBGOszFfUAMSwL1iDttUBHseVJA5ScIxQw2WkvkCrrZ+35XF5/hHi6mIod5I7z7RAc4X87ODFaPIBTs8KgrWK7S/uPpep3BDWbXldXj+hamM9hrUT6PhTC5xEJyi9An0P8aYTQjgz3aihcIn+MBDlZqFeVfPBwMCDZ+xEqoaHnE5WcHTuEIh0ArrVnSpxPrr3i9iP83itVVBuuL3PgXAcRHjLX7C4T9Yfc=

Steps to reproduce

  1. 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;
};
  1. Try to create a wrapper type:
type EmitsFrom<T> = {
  [K in keyof T['events']]: (e: T['events'][K]) => void;
};
  1. Use it in DefineComponent:
DefineComponent<..., ..., ..., ..., ..., ..., ..., EmitsFrom<CustomElements['my-dialog']>>;
  1. Get a TS error because mapped types like { [K in keyof]: Fn } are not assignable to Vue’s Record<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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions