Your question is quite confusing, but I believe that what you want is to refine based on the event type (ie. “click”) rather than the event.

We cannot ever safely use a generic T that extends Event because this T could have any arbitrary additional properties. Consider the following example:

interface MyEvent extends Event {
 customProperty: string
};

const handler = (e: MyEvent) => console.log(e.cancelable);

No event listener would ever be able to execute the callback handler because their event lacks the property customProperty. That’s why you get the error that “Type ‘Event’ is not assignable to type ‘T’.”

We can however refine on event type because this is what addEventListener does.

addEventListener<K extends keyof HTMLElementEventMap>(
    type: K, 
    listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, 
    options?: boolean | AddEventListenerOptions
): void;
addEventListener(
    type: string, 
    listener: EventListenerOrEventListenerObject, 
    options?: boolean | AddEventListenerOptions
): void;

addEventListener can be called with a generic specifying the event type name. When that generic K is set, the type of the event that is passed as an argument to the callback is refined based on the HTMLElementEventMap.

We can make your tracker function depend on the same generic <K extends keyof HTMLElementEventMap>. Now your eventType must be K and your handler must take (event: HTMLElementEventMap[K]). Notice that we have narrowed the event, but we haven’t allowed for extra properties to be added via extends.

export function tracker<K extends keyof HTMLElementEventMap>(
  eventType: K,
  options?: boolean | AddEventListenerOptions
) {
  const stream: Stream<HTMLElementEventMap[K]> = Stream.empty();

  function tracker(node: HTMLElement) {
    const handler = (event: HTMLElementEventMap[K]) => stream.shamefullySendNext(event);
    node.addEventListener<K>( eventType, handler, options);

    return {
      destroy: () => {
        node.removeEventListener(eventType, handler);
        stream.shamefullySendComplete();
      },
    };
  }

  return {
    stream,
    tracker,
  };
}

Typescript Playground Link

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top