From 8fd01e1edab65206b3fe00c21a4211524182da20 Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Thu, 13 Jul 2023 03:31:27 +1200 Subject: [PATCH] Add event types to react wrapper components (#1419) * Rename SlSlideChange for consistency with other events * Setup React event types for events used by Shoelace components Means that consumers of Shoelace via the React wrapper will be able to use callback methods with the correct event type, instead of having to rely on casting and friends when using Typescript. * Add docs demonstrating importing event types for React callbacks --- custom-elements-manifest.config.js | 1 + docs/pages/frameworks/react.md | 19 +++++++++++++++++++ scripts/make-react.js | 12 +++++++++++- src/events/events.ts | 2 +- src/events/sl-slide-change.ts | 6 +++--- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js index d2c73153c..e9b941472 100644 --- a/custom-elements-manifest.config.js +++ b/custom-elements-manifest.config.js @@ -116,6 +116,7 @@ export default { if (classDoc?.events) { classDoc.events.forEach(event => { event.reactName = `on${pascalCase(event.name)}`; + event.eventName = `${pascalCase(event.name)}Event`; }); } } diff --git a/docs/pages/frameworks/react.md b/docs/pages/frameworks/react.md index 5084bd158..b4a91473c 100644 --- a/docs/pages/frameworks/react.md +++ b/docs/pages/frameworks/react.md @@ -83,6 +83,25 @@ function MyComponent() { export default MyComponent; ``` +You can also import the event type for use in your callbacks, shown below. + +```tsx +import { useCallback, useState } from 'react'; +import { SlInput, SlInputEvent } from '@shoelace-style/shoelace/%NPMDIR%/react'; +import type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input'; + +function MyComponent() { + const [value, setValue] = useState(''); + const onInput = useCallback((event: SlInputEvent) => { + setValue(event.detail); + }, []); + + return setValue((event.target as SlInputElement).value)} />; +} + +export default MyComponent; +``` + ## Testing with Jest Testing with web components can be challenging if your test environment runs in a Node environment (i.e. it doesn't run in a real browser). Fortunately, [Jest](https://jestjs.io/) has made a number of strides to support web components and provide additional browser APIs. However, it's still not a complete replication of a browser environment. diff --git a/scripts/make-react.js b/scripts/make-react.js index d56491de4..da7691f39 100644 --- a/scripts/make-react.js +++ b/scripts/make-react.js @@ -25,7 +25,14 @@ components.map(component => { const componentDir = path.join(reactDir, tagWithoutPrefix); const componentFile = path.join(componentDir, 'index.ts'); const importPath = component.path; - const events = (component.events || []).map(event => `${event.reactName}: '${event.name}'`).join(',\n'); + const eventImports = (component.events || []) + .map(event => `import { ${event.eventName} } from '../../../src/events/events';`) + .join('\n'); + const eventNameImport = + (component.events || []).length > 0 ? `import { type EventName } from '@lit-labs/react';` : ``; + const events = (component.events || []) + .map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`) + .join(',\n'); fs.mkdirSync(componentDir, { recursive: true }); @@ -35,6 +42,9 @@ components.map(component => { import { createComponent } from '@lit-labs/react'; import Component from '../../${importPath}'; + ${eventNameImport} + ${eventImports} + export default createComponent({ tagName: '${component.tagName}', elementClass: Component, diff --git a/src/events/events.ts b/src/events/events.ts index 400c3ab62..913ee9870 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -28,7 +28,7 @@ export type { default as SlResizeEvent } from './sl-resize'; export type { default as SlSelectEvent } from './sl-select'; export type { default as SlSelectionChangeEvent } from './sl-selection-change'; export type { default as SlShowEvent } from './sl-show'; -export type { default as SlSlideChange } from './sl-slide-change'; +export type { default as SlSlideChangeEvent } from './sl-slide-change'; export type { default as SlStartEvent } from './sl-start'; export type { default as SlTabHideEvent } from './sl-tab-hide'; export type { default as SlTabShowEvent } from './sl-tab-show'; diff --git a/src/events/sl-slide-change.ts b/src/events/sl-slide-change.ts index cbbc2902a..36d545b81 100644 --- a/src/events/sl-slide-change.ts +++ b/src/events/sl-slide-change.ts @@ -1,11 +1,11 @@ import type SlCarouselItem from '../components/carousel-item/carousel-item'; -type SlSlideChange = CustomEvent<{ index: number; slide: SlCarouselItem }>; +type SlSlideChangeEvent = CustomEvent<{ index: number; slide: SlCarouselItem }>; declare global { interface GlobalEventHandlersEventMap { - 'sl-slide-change': SlSlideChange; + 'sl-slide-change': SlSlideChangeEvent; } } -export default SlSlideChange; +export default SlSlideChangeEvent;