solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
31.94k stars 910 forks source link

Non-fancy event delegation syntax (on:click etc.) not working with Typescript? #1939

Open earslap opened 9 months ago

earslap commented 9 months ago

Describe the bug

The syntax for creating native event handlers (the ones prefixed with on: ) gives type errors.

Your Example Website or App

https://playground.solidjs.com/anonymous/c04dc47c-bac3-45bb-8114-9d520029b79f

Steps to Reproduce the Bug or Issue

Just visit https://playground.solidjs.com/anonymous/c04dc47c-bac3-45bb-8114-9d520029b79f and the error is visible.

Expected behavior

I expect the code to compile.

Same issue here: https://github.com/solidjs/solid/issues/1786#issuecomment-1691188568

ryansolid commented 9 months ago

The intention for this originally was for custom events so you would add them by extending the interface. I suppose they could be added to the base types.

earslap commented 9 months ago

To me, the documentation makes it sound like they are supported out of the box for the natively existing events. This also applies to oncapture: variants BTW. I think they work, just not compile without errors. Do you know of a simple example for how the interface extension might ideally be done at application level? Maybe I can add a simple example to the docs at the very least. The info I found so far resides here: https://docs.solidjs.com/references/api-reference/special-jsx-attributes/on-and-oncapture

mdynnl commented 9 months ago

@earslap you can refer back to the old docs before the news docs is ready

https://www.solidjs.com/guides/typescript#special-jsx-attributes-and-directives

class NameEvent extends CustomEvent {
  type: 'Name';
  detail: {name: string};

  constructor(name: string) {
    super('Name', {detail: {name}});
  }
}

declare module "solid-js" {
  namespace JSX {
    interface CustomEvents { // on:Name
      "Name": NameEvent;
    }
    interface CustomCaptureEvents { // oncapture:Name
      "Name": NameEvent;
    }
  }
}

<div on:Name={(event) => console.log('name is', event.detail.name)}/>
sparecycles commented 1 month ago

Any reason not to have a default catch-all, e.g.,

declare module "solid-js" {
  namespace JSX {
    interface CustomEvents {
      [on: string]: Event;
    }
    interface CustomCaptureEvents {
      [oncapture: string]: Event;
    }
  }
}

?

KotoriK commented 2 weeks ago

I agree with that the type definition should be right out of the box since it is the only way now to disable Event Delegation (refers to the doc). Moreover, Event Delegation disabling is not a uncommon demand. For the use case of embedding components on other pages, global injecting may leads to unpredictable behaviours since it pollutes global environment.

From user side, an probably elegant types might be:

// global.d.ts
import { JSX } from "solid-js";
type GetEventName<T extends string> = T extends `on${infer R}` ? R : never;
type ExtractEvent<T> = T extends JSX.EventHandlerUnion<any, infer E> ? E : Event;
type EventPair<T> = {
  [K in keyof Required<JSX.CustomEventHandlersLowerCase<T>>]: [GetEventName<K>,ExtractEvent<Required<JSX.CustomEventHandlersLowerCase<T>>[K]>];
}[keyof JSX.CustomEventHandlersLowerCase<T>]
type FromEntries<T extends [any,any]> = {
    [K in T[0]]: T extends [K,infer V] ? V : never
}
type HTMLElementEventsMap = FromEntries<EventPair<HTMLElement>>
declare module "solid-js" {
    namespace JSX {
      interface CustomEvents extends HTMLElementEventsMap {
      }
    }
  }

These declarations extract official definition automatically. However, things can be done better from lib side. I would like to help if you need.