From d4cb15172432b96ab58a09a392b60ecaad3d2f2b Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Mon, 7 Oct 2024 21:33:57 -0700 Subject: [PATCH] **feat:** mprove the `ElementAttributes` and `ReactElementAttributes` JSX type helpers. This greatly improves JSX types for Custom Elements in React JSX, whereas before they only accepted string types for dash-cased attributes. If you have a `get`ter/`set`ter property in your element class, you can now define a dummy property prefixed with `__set__` to specify the type of the `set`ter, and this will be picked up and lead to improved types in JSX. For example, you can start using like so: Before: ```js @element('some-element') class SomeElement extends Element { @attribute get someProp(): number {...} @attribute set someProp(n: number | 'foo' | 'bar') {...} } declare module 'react' { namespace JSX { interface IntrinsicElements { 'some-element': ReactElementAttributes } } } ``` and this JSX would have a type error: ```jsx return // Error: string is not assignable to number ``` After: ```js @element('some-element') class SomeElement extends Element { @attribute get someProp(): number {...} @attribute set someProp(n: this['__set__someProp']) {...} /** don't use this property, it is for JSX types. */ __set__someProp!: number | 'foo' | 'bar' } // ... the same React JSX definition as before ... ``` and now JSX prop types will allow setting the *setter* types: ```jsx return // No error, yay! ``` Note, the property is camelCase instead of dash-case now. **BREAKING:** This may introduce type errors into existing JSX templates, tested with React 19 (not tested with React 18 or below), but it is an inevitable upgrade for the better. To migrate, there's likely nothing to do in Solid JSX, but in React JSX the selected properties are no longer converted to dash-case, so you'll want to use the original JS property names in React JSX templates. For example this, ```jsx return ``` becomes ```jsx return ``` If you have any issues, please reach out on GitHub or Discord! https://discord.gg/VmvkFcWrsx --- README.md | 70 +++++++++++++++++++++++++++++++++------ dist/LumeElement.d.ts | 20 +++++++++-- dist/LumeElement.d.ts.map | 2 +- dist/react.d.ts | 11 +++--- dist/react.d.ts.map | 2 +- package.json | 7 +++- src/LumeElement.ts | 55 +++++++++++++++++++++++++----- src/react.ts | 30 ++++++++++------- 8 files changed, 156 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 4557770..b8a2fbd 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ npm install --save-dev @babel/cli @babel/core @babel/plugin-proposal-decorators If using TypeScript, set `allowJs` in `tsconfig.json` to allow compiling JS files, f.e.: -```json +```js { "compilerOptions": { "allowJs": true, @@ -247,7 +247,8 @@ If using TypeScript, set `allowJs` in `tsconfig.json` to allow compiling JS file } ``` -and running `npx tsc`. +and running `npx tsc`. See the [TypeScript](#typescript) section below for configuring JSX +types for various frameworks (Solid, React, Preact, etc). If using Babel, add the decorators plugin to `.babelrc`, f.e. @@ -581,9 +582,13 @@ Load the required JSX types in one of two ways: project, but if you have files with different types of JSX, you'll want to use option 1 instead). - ```json + ```js { "compilerOptions": { + /* Solid.js Config */ + // Note, you need to use an additional tool such as Babel, Vite, etc, to + // compile Solid JSX. `npm create solid` will scaffold things for you. + "jsx": "preserve", "jsxImportSource": "solid-js" } } @@ -687,8 +692,10 @@ const el2 = (...) as any as HTMLDivElement #### Type definitions for custom elements +### With Solid JSX + To give your Custom Elements type checking for use with DOM APIs, and type -checking in JSX, use the following template. +checking in Solid JSX, we can add the element type definition to `JSX.IntrinsicElements`: ```tsx /* @jsxImportSource solid-js */ @@ -697,11 +704,9 @@ checking in JSX, use the following template. // anywhere in non-JSX parts of the code, you also need to import it from // solid-js: import {Element, element, stringAttribute, numberAttribute, /*...,*/ JSX} from 'solid-js' -// ^ We imported JSX so that... // Define the attributes that your element accepts export interface CoolElementAttributes extends JSX.HTMLAttributes { - // ^ ...we can use it in this non-JSX code. 'cool-type'?: 'beans' | 'hair' 'cool-factor'?: number // ^ NOTE: These should be dash-case versions of your class's attribute properties. @@ -777,14 +782,27 @@ return ( Defining the types of custom elements for React JSX is similar as for Solid JSX above, but with some small differences for React JSX: +```js +// tsconfig.json +{ + "compilerOptions": { + /* React Config */ + "jsx": "react-jsx", + "jsxImportSource": "react" // React >=19 (Omit for React <=18) + } +} +``` + ```ts import type {HTMLAttributes} from 'react' // Define the attributes that your element accepts, almost the same as before: export interface CoolElementAttributes extends HTMLAttributes { - 'cool-type'?: 'beans' | 'hair' - 'cool-factor'?: number - // ^ NOTE: These should be dash-case versions of your class's attribute properties. + coolType?: 'beans' | 'hair' + coolFactor?: number + // ^ NOTE: These are the names of the class's properties verbatim, not + // dash-cased as with Solid. React works differently than Solid's: it will + // map the exact prop name to the JS property. } // Add your element to the list of known HTML elements, like before. @@ -812,7 +830,7 @@ declare global { > attribute types: ```ts -import type {ReactElementAttributes} from '@lume/element/src/react' +import type {ReactElementAttributes} from '@lume/element/dist/react' // This definition is now shorter than before, and automatically maps the property names to dash-case. export type CoolElementAttributes = ReactElementAttributes @@ -827,6 +845,17 @@ declare global { } ``` +Now when you use `` in React JSX, it will be type checked: + +```jsx +return ( + +) +``` + > [!Note] > You may want to define React JSX types for your elements in separate files, and > have only React users import those files if they need the types, and similar if you make @@ -834,6 +863,27 @@ declare global { > yet, but you can manually augment JSX as in the examples above on a > per-framework basis, contributions welcome!). +### With Preact JSX + +It works the same as the previous section for React JSX. Define the element +types with the same `ReactElementAttributes` helper as described above. In your +TypeScript `compilerOptions` make sure you link to the React compatibility +layer: + +```json +{ + "compilerOptions": { + /* Preact Config */ + "jsx": "react-jsx", + "jsxImportSource": "preact", + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + } +} +``` + ## API ### `Element` diff --git a/dist/LumeElement.d.ts b/dist/LumeElement.d.ts index a6ccb30..2e8bdc5 100644 --- a/dist/LumeElement.d.ts +++ b/dist/LumeElement.d.ts @@ -204,14 +204,30 @@ type Template = TemplateContent | (() => TemplateContent); * let coolEl = * ``` */ -export type ElementAttributes = WithStringValues>>> & AdditionalProperties & Omit, SelectedProperties | keyof AdditionalProperties>; +export type ElementAttributes, SetterTypePrefix>, AdditionalProperties extends object = {}> = Omit, SelectedProperties | keyof AdditionalProperties | 'onerror'> & { + onerror?: ((error: ErrorEvent) => void) | null; +} & Partial, SetterTypePrefix>, SelectedProperties>>>> & AdditionalProperties; /** * Make all non-string properties union with |string because they can all * receive string values from string attributes like opacity="0.5" (those values * are converted to the types of values they should be, f.e. reading a * `@numberAttribute` property always returns a `number`) */ -type WithStringValues = { +export type WithStringValues = { [Property in keyof Type]: NonNullable extends string ? Type[Property] : Type[Property] | string; }; +type StringKeysOnly = UnionWithout; +type UnionWithout = T extends TypeToRemove ? never : T; +export type RemovePrefixes = { + [K in keyof T as K extends string ? RemovePrefix : K]: T[K]; +}; +type RemovePrefix = T extends `${Prefix}${infer Rest}` ? Rest : T; +export type RemoveAccessors = { + [K in keyof T as K extends RemovePrefix>, SetterTypePrefix> ? never : K]: T[K]; +}; +type SetterTypeKeysFor = keyof PrefixPick; +type PrefixPick = { + [K in keyof T as K extends `${Prefix}${string}` ? K : never]: T[K]; +}; +export type SetterTypePrefix = '__set__'; //# sourceMappingURL=LumeElement.d.ts.map \ No newline at end of file diff --git a/dist/LumeElement.d.ts.map b/dist/LumeElement.d.ts.map index 48391c1..c09d2be 100644 --- a/dist/LumeElement.d.ts.map +++ b/dist/LumeElement.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"LumeElement.d.ts","sourceRoot":"","sources":["../src/LumeElement.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"LumeElement.d.ts","sourceRoot":"","sources":["../src/LumeElement.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/react.d.ts b/dist/react.d.ts index 44a4165..d87c077 100644 --- a/dist/react.d.ts +++ b/dist/react.d.ts @@ -1,14 +1,13 @@ import type { HTMLAttributes as ReactHTMLAttributes, DetailedHTMLProps as ReactDetailedHTMLProps } from 'react'; -import type { DashCasedProps } from './utils'; +import type { RemoveAccessors, RemovePrefixes, SetterTypePrefix, WithStringValues } from './LumeElement.js'; /** * Similar to ElementAttributes, but for defining element attribute types for * React JSX. See LUME Element's [TypeScript * docs](https://docs.lume.io/#/guide/making-elements?id=typescript) for * details. */ -export type ReactElementAttributes = ReactDetailedHTMLProps>>> & ReactHTMLAttributes, ElementType>; -type ToStringValues = { - [Property in keyof Type]: Type[Property] extends string ? Type[Property] : Type[Property] extends boolean ? boolean | string : string; -}; -export {}; +export type ReactElementAttributes, SetterTypePrefix>, AdditionalProperties extends object = {}> = Omit, ElementType>, SelectedProperties | keyof AdditionalProperties> & { + /** The 'has' attribute from the 'element-behaviors' package. If element-behaviors is installed and imported (it is if you're using `lume` 3D elements) then this specifies which behaviors to instantiate on the given element. */ + has?: string; +} & Partial, SetterTypePrefix>, SelectedProperties>>> & AdditionalProperties; //# sourceMappingURL=react.d.ts.map \ No newline at end of file diff --git a/dist/react.d.ts.map b/dist/react.d.ts.map index 53127c1..c33566f 100644 --- a/dist/react.d.ts.map +++ b/dist/react.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,IAAI,mBAAmB,EAAE,iBAAiB,IAAI,sBAAsB,EAAC,MAAM,OAAO,CAAA;AAC7G,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,CAAC,WAAW,EAAE,kBAAkB,SAAS,MAAM,WAAW,IAAI,sBAAsB,CACrH,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,WAAW,CAAC,EACjH,WAAW,CACX,CAAA;AAED,KAAK,cAAc,CAAC,IAAI,SAAS,MAAM,IAAI;KACzC,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,MAAM,GACpD,IAAI,CAAC,QAAQ,CAAC,GACd,IAAI,CAAC,QAAQ,CAAC,SAAS,OAAO,GAC9B,OAAO,GAAG,MAAM,GAChB,MAAM;CACT,CAAA"} \ No newline at end of file +{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,IAAI,mBAAmB,EAAE,iBAAiB,IAAI,sBAAsB,EAAC,MAAM,OAAO,CAAA;AAC7G,OAAO,KAAK,EAAC,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAA;AAGzG;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,CACjC,WAAW,SAAS,WAAW,EAC/B,kBAAkB,SAAS,MAAM,cAAc,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC,EAC/F,oBAAoB,SAAS,MAAM,GAAG,EAAE,IACrC,IAAI,CACN,sBAAsB,CAAC,mBAAmB,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,EACrE,kBAAkB,GAAG,MAAM,oBAAoB,CAC/C,GAEC;IACD,mOAAmO;IACnO,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ,GAEC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAEnH,oBAAoB,CAAA"} \ No newline at end of file diff --git a/package.json b/package.json index 07fc684..a5f80ff 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ }, "devDependencies": { "@lume/cli": "^0.14.0", - "@types/react": "^17.0.0", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "ncp": "^2.0.0", "prettier": "3.0.3", "typescript": "^5.0.0" @@ -44,6 +45,10 @@ "peerDependencies": { "@types/react": "*" }, + "overrides": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" + }, "repository": { "type": "git", "url": "git+ssh://git@github.com/lume/element.git" diff --git a/src/LumeElement.ts b/src/LumeElement.ts index da2c336..4350530 100644 --- a/src/LumeElement.ts +++ b/src/LumeElement.ts @@ -456,13 +456,12 @@ export {LumeElement as Element} export type AttributeHandlerMap = Record -// This is TypeScript-specific. Eventually Hegel would like to have better -// support for JSX. We'd need to figure how to supports types for both systems. import type {JSX} from './jsx-runtime' type JSXOrDOM = JSX.Element | globalThis.Element type TemplateContent = JSXOrDOM | JSXOrDOM[] type Template = TemplateContent | (() => TemplateContent) +// prettier-ignore /** * A helper for defining the JSX types of an element's attributes. * @@ -503,12 +502,30 @@ type Template = TemplateContent | (() => TemplateContent) * ``` */ export type ElementAttributes< - ElementType, - SelectedProperties extends keyof ElementType, + ElementType extends HTMLElement, + SelectedProperties extends keyof RemovePrefixes, SetterTypePrefix>, AdditionalProperties extends object = {}, -> = WithStringValues>>> & - AdditionalProperties & - Omit, SelectedProperties | keyof AdditionalProperties> +> = Omit< + JSX.HTMLAttributes, + SelectedProperties | keyof AdditionalProperties | 'onerror' +> + & { + // Fixes the onerror JSX prop type (https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1821) + onerror?: ((error: ErrorEvent) => void) | null + } + + & Partial< + DashCasedProps< + WithStringValues< + Pick< + RemovePrefixes, SetterTypePrefix>, + SelectedProperties + > + > + > + > + + & AdditionalProperties /** * Make all non-string properties union with |string because they can all @@ -516,6 +533,28 @@ export type ElementAttributes< * are converted to the types of values they should be, f.e. reading a * `@numberAttribute` property always returns a `number`) */ -type WithStringValues = { +export type WithStringValues = { [Property in keyof Type]: NonNullable extends string ? Type[Property] : Type[Property] | string } + +type StringKeysOnly = UnionWithout + +type UnionWithout = T extends TypeToRemove ? never : T + +export type RemovePrefixes = { + [K in keyof T as K extends string ? RemovePrefix : K]: T[K] +} + +type RemovePrefix = T extends `${Prefix}${infer Rest}` ? Rest : T + +export type RemoveAccessors = { + [K in keyof T as K extends RemovePrefix>, SetterTypePrefix> ? never : K]: T[K] +} + +type SetterTypeKeysFor = keyof PrefixPick + +type PrefixPick = { + [K in keyof T as K extends `${Prefix}${string}` ? K : never]: T[K] +} + +export type SetterTypePrefix = '__set__' diff --git a/src/react.ts b/src/react.ts index 3b9ca92..11e59d0 100644 --- a/src/react.ts +++ b/src/react.ts @@ -1,21 +1,27 @@ import type {HTMLAttributes as ReactHTMLAttributes, DetailedHTMLProps as ReactDetailedHTMLProps} from 'react' -import type {DashCasedProps} from './utils' +import type {RemoveAccessors, RemovePrefixes, SetterTypePrefix, WithStringValues} from './LumeElement.js' +// prettier-ignore /** * Similar to ElementAttributes, but for defining element attribute types for * React JSX. See LUME Element's [TypeScript * docs](https://docs.lume.io/#/guide/making-elements?id=typescript) for * details. */ -export type ReactElementAttributes = ReactDetailedHTMLProps< - DashCasedProps>>> & ReactHTMLAttributes, - ElementType -> +export type ReactElementAttributes< + ElementType extends HTMLElement, + SelectedProperties extends keyof RemovePrefixes, SetterTypePrefix>, + AdditionalProperties extends object = {}, +> = Omit< + ReactDetailedHTMLProps, ElementType>, + SelectedProperties | keyof AdditionalProperties + > -type ToStringValues = { - [Property in keyof Type]: Type[Property] extends string - ? Type[Property] - : Type[Property] extends boolean - ? boolean | string - : string -} + & { + /** The 'has' attribute from the 'element-behaviors' package. If element-behaviors is installed and imported (it is if you're using `lume` 3D elements) then this specifies which behaviors to instantiate on the given element. */ + has?: string + } + + & Partial, SetterTypePrefix>, SelectedProperties>>> + + & AdditionalProperties