Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/light-jobs-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lit-labs/react': patch
---

Event callbacks can be typed by casting with EventHandler
45 changes: 45 additions & 0 deletions packages/labs/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,51 @@ React component.
/>
```

#### Typescript

Event callback types can be refined by type casting with `EventName`. The
type cast helps `createComponent` correlate typed callbacks to property names in
the event property map.

Non-casted event names will fallback to an event type of `Event`.

```ts
import type {EventName} from '@lit-labs/react';

import * as React from 'react';
import {createComponent} from '@lit-labs/react';
import {MyElement} from './my-element.js';

export const MyElementComponent = createComponent(
React,
'my-element',
MyElement,
{
onClick: 'pointerdown' as EventName<PointerEvent>,
onChange: 'input',
}
);
```

Event callbacks will match their type cast. In the example below, a
`PointerEvent` is expected in the `onClick` callback.

```tsx
<MyElementComponent
onClick={(e: PointerEvent) => {
console.log('DOM PointerEvent called!');
}}
onChange={(e: Event) => {
console.log(e);
}}
/>
```

NOTE: This type casting is not associated to any component property. Be
careful to use the corresponding type dispatched or bubbled from the
webcomponent. Incorrect types might result in additional properties, missing
properties, or properties of the wrong type.

## `useController`

Reactive Controllers allow developers to hook a component's lifecycle to bundle
Expand Down
28 changes: 20 additions & 8 deletions packages/labs/react/src/create-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,28 @@ const setRef = (ref: React.Ref<unknown>, value: Element | null) => {
}
};

type Events<S> = {
[P in keyof S]?: (e: Event) => unknown;
};

type StringValued<T> = {
[P in keyof T]: string;
};

type Constructor<T> = {new (): T};

/***
* Typecast that curries an Event type through a string. The goal of the type
* cast is to match a prop name to a typed event callback.
*/
export type EventName<T extends Event = Event> = string & {
__event_type: T;
};

type Events = Record<string, EventName | string>;

type EventProps<R extends Events> = {
[K in keyof R]: R[K] extends EventName
? (e: R[K]['__event_type']) => void
: (e: Event) => void;
};

/**
* Creates a React component for a custom element. Properties are distinguished
* from attributes automatically, and events can be configured so they are
Expand All @@ -115,11 +127,11 @@ type Constructor<T> = {new (): T};
* messages. Default value is inferred from the name of custom element class
* registered via `customElements.define`.
*/
export const createComponent = <I extends HTMLElement, E>(
export const createComponent = <I extends HTMLElement, E extends Events>(
React: typeof ReactModule,
tagName: string,
elementClass: Constructor<I>,
events?: StringValued<E>,
events?: E,
displayName?: string
) => {
const Component = React.Component;
Expand All @@ -132,8 +144,8 @@ export const createComponent = <I extends HTMLElement, E>(
type UserProps = React.PropsWithChildren<
React.PropsWithRef<
Partial<Omit<I, 'children'>> &
Events<E> &
React.HTMLAttributes<HTMLElement>
Partial<EventProps<E>> &
Omit<React.HTMLAttributes<HTMLElement>, keyof E>
>
>;

Expand Down
6 changes: 4 additions & 2 deletions packages/labs/react/src/test/create-component_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-3-Clause
*/

import type {EventName} from "../create-component.js";

import {ReactiveElement} from '@lit/reactive-element';
import {property} from '@lit/reactive-element/decorators/property.js';
import {customElement} from '@lit/reactive-element/decorators/custom-element.js';
Expand Down Expand Up @@ -67,7 +69,7 @@ suite('createComponent', () => {
});

const basicElementEvents = {
onFoo: 'foo',
onFoo: 'foo' as EventName<MouseEvent>,
onBar: 'bar',
};

Expand Down Expand Up @@ -241,7 +243,7 @@ suite('createComponent', () => {
let fooEvent: Event | undefined,
fooEvent2: Event | undefined,
barEvent: Event | undefined;
const onFoo = (e: Event) => {
const onFoo = (e: MouseEvent) => {
fooEvent = e;
};
const onFoo2 = (e: Event) => {
Expand Down