{"id":16641806,"url":"https://github.com/samthor/typed-event-types","last_synced_at":"2025-10-17T21:03:10.140Z","repository":{"id":46327138,"uuid":"514736743","full_name":"samthor/typed-event-types","owner":"samthor","description":"Typed events for EventTarget subclasses","archived":false,"fork":false,"pushed_at":"2022-07-18T00:29:47.000Z","size":17,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-02T08:11:19.504Z","etag":null,"topics":["eventtarget","types","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samthor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-17T03:24:52.000Z","updated_at":"2024-08-23T04:49:37.000Z","dependencies_parsed_at":"2022-09-05T08:01:41.252Z","dependency_job_id":null,"html_url":"https://github.com/samthor/typed-event-types","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samthor%2Ftyped-event-types","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samthor%2Ftyped-event-types/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samthor%2Ftyped-event-types/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samthor%2Ftyped-event-types/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samthor","download_url":"https://codeload.github.com/samthor/typed-event-types/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238960267,"owners_count":19559230,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eventtarget","types","typescript"],"created_at":"2024-10-12T07:47:57.075Z","updated_at":"2025-10-17T21:03:05.075Z","avatar_url":"https://github.com/samthor.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"This package provides TypeScript types that help you add typed events to your `EventTarget` subclasses.\n\n## Usage\n\nTo create a subclass of `EventTarget` that has custom events:\n\n```ts\nimport type { AddEvents } from 'typed-event-types';\n\ninterface WhateverEventMap {\n  whatever: CustomEvent\u003cnumber\u003e;\n}\n\nclass ObjectWithEvents extends (EventTarget as AddEvents\u003ctypeof EventTarget, WhateverEventMap\u003e) {}\nconst o = new ObjectWithEvents();\n\no.addEventListener('whatever', (e) =\u003e {\n  console.info(e.detail);  // \u003c-- TS now knows this is a number\n});\n```\n\nThis also works for Custom Elements (or anything else that _already has_ events), and keeps all the existing events:\n\n```ts\nclass ElementWithMoreEvents extends (HTMLElement as AddEvents\u003ctypeof HTMLElement, WhateverEventMap\u003e) {}\nconst e = new ElementWithMoreEvents();\n\ne.addEventListener('whatever', (e) =\u003e {\n  console.info(e.detail);  // \u003c-- TS now knows this is a number\n});\ne.addEventListener('click', (e) =\u003e {\n  console.info(e.movementX);  // \u003c-- TS still knows this is a MouseEvent\n});\n```\n\nGreat!\n\nYou can also extend your types _again_, because that's the point:\n\n```ts\nclass ExtendedEvenMoreEvents extends (ElementWithMoreEvents as AddEvents\u003ctypeof ElementWithMoreEvents, {\n  anotherEvent: CustomEvent\u003cstring\u003e;\n}\u003e) {}\nconst ee = new ExtendedEvenMoreEvents();\nee.addEventListener('anotherEvent', (e) =\u003e {\n  console.info(e.detail);  // \u003c-- TS now knows this is a string\n});\nee.addEventListener('whatever', (e) =\u003e {\n  console.info(e.detail);  // \u003c-- TS still knows this is a number\n});\n```\n\n## Background\n\nThis has historically been a hard problem.\n\nTypeScript internally solves this by adding/replacing the `addEventListener` call on all its internal interfaces whenever it needs to add more types.\nBut this isn't _additive_, you need to replace all the events every time.\n\nFor example, on the media elements, TypeScript does this:\n\n```ts\ninterface HTMLMediaElementEventMap extends HTMLElementEventMap {\n    \"encrypted\": MediaEncryptedEvent;\n    \"waitingforkey\": Event;\n}\ninterface HTMLMediaElement extends HTMLElement {\n    addEventListener\u003cK extends keyof HTMLMediaElementEventMap\u003e(type: K, listener: (this: HTMLMediaElement, ev: HTMLMediaElementEventMap[K]) =\u003e any, options?: boolean | AddEventListenerOptions): void;\n}\n```\n\n\u0026hellip;which is fine, but annoying for developers to do _every time_: you can't extend something easily as you need to know what set of events your parent had.\n\nThe approach in this package solves this by adding specific `addEventListener` etc calls which accept a type literal, which TypeScript happily hoists and merges with the other keys.\n(TypeScript could use this approach internally, too—it would be a bit more verbose in the _generated_ types, but make the built-in types much easier to write.)\n\nSo how does this work?\nWell\u0026hellip; check out the source.\n\n## Attributions\n\nThis internally includes `UnionToIntersection` [from here](https://fettblog.eu/typescript-union-to-intersection/).\nThanks, [@ddprrt](https://twitter.com/ddprrt).\n\n## Contributions\n\n### Unknown This\n\nBecause of the way the `addEventListener` etc calls are added, we don't know what `this` is at the time.\nListeners added this way should get the `this` of the `EventTarget`.\n\nThis is a limitation but _only if you're using_ `function() { ... }`, which, \u0026hellip;you probably aren't, anymore.\nSo if you're writing inline handlers, use `() =\u003e { ... }`.\nTo be clear, this isn't _dangerous_: we just say that `this` is `EventTarget`, which is true, but not very useful.\nTypeScript will complain at you.\n\nThis doesn't effect self-referential cases like this:\n\n```ts\nclass ElementWithMoreEvents extends (HTMLElement as AddEvents\u003ctypeof HTMLElement, WhateverEventMap\u003e) {\n  constructor() {\n    super();\n    this.addEventListener('whatever', this.unboundHandler);\n  }\n  unboundHandler() {\n    // this will be from the caller, and for events, it's set correctly\n  }\n}\n```\n\nMaybe you can help fix\u0026hellip; `this`? 👀\n\n### Verbose Syntax\n\nThe syntax is pretty verbose.\nI've tried to make it slightly nicer to work with a builder function—it doesn't do anything, just returns the argument but casts it—but it ends up being a bit unsafe.\n\nWant to contribute?\n[@-me](https://twitter.com/samthor) or you know, do GitHub things.\n\n## Usage\n\nInstall via \"typed-event-types\".\n\nBut!\nIf you want to take this code and include it in a TS helper library—I'd love that!\nIt is a bit niche, as it specifically targets the DOM.\nJust attribute me (Apache-2.0) or tell me where to contribute the PR.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamthor%2Ftyped-event-types","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamthor%2Ftyped-event-types","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamthor%2Ftyped-event-types/lists"}