Skip to content

Instantly share code, notes, and snippets.

@difosfor
Last active September 21, 2023 02:25
Show Gist options
  • Save difosfor/ceeb01d03a8db7dc68d5cd4167d60637 to your computer and use it in GitHub Desktop.
Save difosfor/ceeb01d03a8db7dc68d5cd4167d60637 to your computer and use it in GitHub Desktop.
Typed LitElement events
import { customElement, LitElement } from 'lit-element';
type UnpackCustomEvent<T> = T extends CustomEvent<infer U> ? U : never;
// Define custom event types and details here
interface CustomEventMap {
/**
* Dispatched when an error occurs.
*/
'my-error': CustomEvent<{ error: Error }>;
/**
* Dispatched when initialized.
*/
'my-initialized': CustomEvent<{}>;
}
interface CombinedEventMap extends HTMLElementEventMap, CustomEventMap {}
// Add strict event type support to addEventListener etc.
interface MyElement {
addEventListener<K extends keyof CombinedEventMap>(
type: K,
listener: (this: MyElement, ev: CombinedEventMap[K]) => void,
options?: boolean | AddEventListenerOptions,
): void;
removeEventListener<K extends keyof CombinedEventMap>(
type: K,
listener: (this: MyElement, ev: CombinedEventMap[K]) => void,
options?: boolean | EventListenerOptions,
): void;
}
// Add custom element to TypeScript's map (defined by @customElement below)
declare global {
interface HTMLElementTagNameMap {
'my-element': MyElement;
}
}
/**
* MyElement class is defined as `<my-element>`.
*
* @fires my-error - Dispatched with `detail.error: Error` when an error occurs.
* @fires my-initialized - Dispatched when initialized.
*/
@customElement('my-element')
class MyElement extends LitElement {
/**
* A strict alternative to dispatchEvent for dispatching this element's custom events.
*
* @param type Custom event type
* @param detail Custom event detail
*/
private dispatch<K extends keyof CustomEventMap>(
type: K,
detail: UnpackCustomEvent<CustomEventMap[K]>,
) {
return this.dispatchEvent(new CustomEvent(type, { detail }));
}
public connectedCallback() {
if (super.connectedCallback) {
super.connectedCallback();
}
Promise.resolve().then(
() => this.dispatch('my-initialized', {}),
error => this.dispatch('my-error', { error }),
);
}
}
export default MyElement;
import { html } from 'lit-element';
import './my-element.ts';
function onInitialized() {
console.log('Initialized!');
}
function onError({ detail: { error } }: { detail: { error: Error } }) {
console.error(error);
}
// Using createElement: full typing support
const el = document.createElement('my-element');
el.addEventListener('my-initialized', onInitialized);
el.addEventListener('my-error', onError);
// Using lit-html; in VS Code with lit-plugin: event-type name completion, but listener type not validated
const foo = html`<my-element @my-initialized=${onInitialized} @my-error=${onError}></my-element>`;
@difosfor
Copy link
Author

This provides strictly typed listening and dispatching support to a custom LitElement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment