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..868ee48 100644
--- a/dist/LumeElement.d.ts
+++ b/dist/LumeElement.d.ts
@@ -204,14 +204,32 @@ 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 = {
- [Property in keyof Type]: NonNullable extends string ? Type[Property] : Type[Property] | string;
+export type WithStringValues = {
+ [Property in keyof Type]: PickFromUnion extends never ? // if the type does not include a type assignable to string
+ Type[Property] | string : Type[Property];
};
+type StringKeysOnly = OmitFromUnion;
+type OmitFromUnion = T extends TypeToOmit ? never : T;
+type PickFromUnion = T extends TypeToPick ? T : never;
+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..25b7cc4 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":"AAOA,OAAO,KAAK,EAAC,gBAAgB,EAAE,mBAAmB,EAAC,MAAM,aAAa,CAAA;AACtE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAA;AAe3C,QAAA,MAAM,IAAI,eAAiB,CAAA;;;;;;;;;;;;;;;AAI3B,cAAM,WAAY,SAAQ,gBAAsB;;IAC/C;;;;OAIG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,CAAK;IAE/B;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,SAAmB,EAAE,QAAQ,GAAE,qBAAsC;IAoB9F;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAEpC;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CAAC,yBAAyB,CAAC,EAAE,mBAAmB,CAAC;IAEvD,qFAAqF;IAC7E,CAAC,mBAAmB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;KAAC,CAAC,CAAA;IAEnG;;;;;;;;;;;OAWG;IACH,UAAkB,iBAAiB,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IA2F9D;;;;;OAKG;IACH,UAAkB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAErC;;;;OAIG;IACH,UAAkB,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;IAE/C;;;;;OAKG;IACH,iBAAyB,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;IAEtD;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAO;IAElC,mEAAmE;IACnE,aAAa,CAAC,EAAE,cAAc,CAAC;IAE/B,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAO;IAE1B;;;;OAIG;IACH,SAAS,KAAK,YAAY,IAAI,IAAI,CAMjC;IACD,SAAS,KAAK,YAAY,CAAC,CAAC,EAAE,IAAI,EAKjC;IAED,gHAAgH;IAChH,IAAI,IAAI,SAEP;IACD,IAAI,IAAI,CAAC,GAAG,MAAA,EAEX;IAED;;;;;;;;;;;;;OAaG;IACH,SAAS,KAAK,SAAS,IAAI,IAAI,CAE9B;IAEQ,YAAY,CAAC,OAAO,EAAE,cAAc;IAO7C,iBAAiB;IAYjB,oBAAoB;IAMpB,wBAAwB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IA6H3F,eAAe;CACf;AAGD,OAAO,EAAC,WAAW,IAAI,OAAO,EAAC,CAAA;AAE/B,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;AAElE,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,eAAe,CAAA;AACtC,KAAK,QAAQ,GAAG,GAAG,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAA;AAChD,KAAK,eAAe,GAAG,QAAQ,GAAG,QAAQ,EAAE,CAAA;AAC5C,KAAK,QAAQ,GAAG,eAAe,GAAG,CAAC,MAAM,eAAe,CAAC,CAAA;AAGzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,MAAM,iBAAiB,CAC5B,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,CACP,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,EAC/B,kBAAkB,GAAG,MAAM,oBAAoB,GAAG,SAAS,CAC3D,GACE;IAED,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CAC9C,GAEC,OAAO,CACR,cAAc,CACb,gBAAgB,CACf,IAAI,CACH,cAAc,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC,EAC9D,kBAAkB,CAClB,CACD,CACD,CACD,GAEC,oBAAoB,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,CAAC,IAAI,SAAS,MAAM,IAAI;KAElD,QAAQ,IAAI,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,SAAS,KAAK,GAE1E,AADA,2DAA2D;IAC3D,IAAI,CAAC,QAAQ,CAAC,GAAG,MAAM,GAEvB,IAAI,CAAC,QAAQ,CAAC;CACjB,CAAA;AAED,KAAK,cAAc,CAAC,CAAC,SAAS,WAAW,IAAI,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAE9E,KAAK,aAAa,CAAC,CAAC,EAAE,UAAU,IAAI,CAAC,SAAS,UAAU,GAAG,KAAK,GAAG,CAAC,CAAA;AACpE,KAAK,aAAa,CAAC,CAAC,EAAE,UAAU,IAAI,CAAC,SAAS,UAAU,GAAG,CAAC,GAAG,KAAK,CAAA;AAEpE,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,MAAM,SAAS,MAAM,IAAI;KACrD,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACtE,CAAA;AAED,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,GAAG,MAAM,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,CAAA;AAE1G,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;KAC/B,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,YAAY,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAClH,CAAA;AAED,KAAK,iBAAiB,CAAC,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAA;AAEjE,KAAK,UAAU,CAAC,CAAC,EAAE,MAAM,SAAS,MAAM,IAAI;KAC1C,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;CAClE,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAA"}
\ No newline at end of file
diff --git a/dist/jsx-types-react.test.d.ts b/dist/jsx-types-react.test.d.ts
new file mode 100644
index 0000000..135e9f6
--- /dev/null
+++ b/dist/jsx-types-react.test.d.ts
@@ -0,0 +1,17 @@
+import type { ReactElementAttributes } from './react.js';
+declare class SomeElement extends HTMLElement {
+ someProp: 'true' | 'false' | boolean;
+ get otherProp(): number;
+ set otherProp(_: this['__set__otherProp']);
+ /** do not use this property, its only for JSX types */
+ __set__otherProp: number | 'foo';
+}
+declare module 'react' {
+ namespace JSX {
+ interface IntrinsicElements {
+ 'some-element': ReactElementAttributes;
+ }
+ }
+}
+export {};
+//# sourceMappingURL=jsx-types-react.test.d.ts.map
\ No newline at end of file
diff --git a/dist/jsx-types-react.test.d.ts.map b/dist/jsx-types-react.test.d.ts.map
new file mode 100644
index 0000000..2f39cb6
--- /dev/null
+++ b/dist/jsx-types-react.test.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"jsx-types-react.test.d.ts","sourceRoot":"","sources":["../src/jsx-types-react.test.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,YAAY,CAAA;AAEtD,cAAM,WAAY,SAAQ,WAAW;IACpC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAO;IAE3C,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAI;IAE7C,uDAAuD;IACvD,gBAAgB,EAAG,MAAM,GAAG,KAAK,CAAA;CACjC;AAID,OAAO,QAAQ,OAAO,CAAC;IACtB,UAAU,GAAG,CAAC;QACb,UAAU,iBAAiB;YAC1B,cAAc,EAAE,sBAAsB,CAAC,WAAW,EAAE,UAAU,GAAG,WAAW,CAAC,CAAA;SAC7E;KACD;CACD"}
\ No newline at end of file
diff --git a/dist/jsx-types-react.test.jsx b/dist/jsx-types-react.test.jsx
new file mode 100644
index 0000000..633c3a6
--- /dev/null
+++ b/dist/jsx-types-react.test.jsx
@@ -0,0 +1,32 @@
+/* @jsxImportSource react */
+class SomeElement extends HTMLElement {
+ someProp = true;
+ get otherProp() {
+ return 0;
+ }
+ set otherProp(_) { }
+ /** do not use this property, its only for JSX types */
+ __set__otherProp;
+}
+SomeElement;
+describe('JSX types with ReactElementAttributes', () => {
+ it('derives JSX types from classes', () => {
+ ;
+ <>
+
+
+
+ {/* @ts-expect-error good, number is invalid */}
+
+ {/* @ts-expect-error good, 'blah' is invalid */}
+
+
+ {/* Additionally TypeScript will allow unknown dash-case props (as we didn't not define JS properties with these exact dash-cased names, React 19+ will set the element attributes, useful for setting the attributes but React has no way to specify to set attributes for names without dashes) */}
+
+ {/* @ts-expect-error foo doesn't exist. TypeScript will only check existence of properties without dashes */}
+
+ >;
+ });
+});
+export {};
+//# sourceMappingURL=jsx-types-react.test.jsx.map
\ No newline at end of file
diff --git a/dist/jsx-types-react.test.jsx.map b/dist/jsx-types-react.test.jsx.map
new file mode 100644
index 0000000..e2fb448
--- /dev/null
+++ b/dist/jsx-types-react.test.jsx.map
@@ -0,0 +1 @@
+{"version":3,"file":"jsx-types-react.test.jsx","sourceRoot":"","sources":["../src/jsx-types-react.test.tsx"],"names":[],"mappings":"AAAA,4BAA4B;AAI5B,MAAM,WAAY,SAAQ,WAAW;IACpC,QAAQ,GAA+B,IAAI,CAAA;IAE3C,IAAI,SAAS;QACZ,OAAO,CAAC,CAAA;IACT,CAAC;IACD,IAAI,SAAS,CAAC,CAA2B,IAAG,CAAC;IAE7C,uDAAuD;IACvD,gBAAgB,CAAiB;CACjC;AAED,WAAW,CAAA;AAUX,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACzC,CAAC;QAAA,EACA;GAAA,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAC9C;GAAA,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAC9C;GAAA,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAC9C;GAAA,CAAC,8CAA8C,CAC/C;GAAA,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAC5B;GAAA,CAAC,8CAA8C,CAC/C;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,EAE9B;;GAAA,CAAC,mSAAmS,CACpS;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAChD;GAAA,CAAC,2GAA2G,CAC5G;GAAA,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAC1B;EAAA,GAAG,CAAA;IACJ,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
\ No newline at end of file
diff --git a/dist/jsx-types-solid.test.d.ts b/dist/jsx-types-solid.test.d.ts
new file mode 100644
index 0000000..3df1786
--- /dev/null
+++ b/dist/jsx-types-solid.test.d.ts
@@ -0,0 +1,17 @@
+import type { ElementAttributes } from './LumeElement.js';
+declare class SomeElement extends HTMLElement {
+ someProp: 'true' | 'false' | boolean;
+ get otherProp(): number;
+ set otherProp(_: this['__set__otherProp']);
+ /** do not use this property, its only for JSX types */
+ __set__otherProp: number | 'foo';
+}
+declare module 'solid-js' {
+ namespace JSX {
+ interface IntrinsicElements {
+ 'some-element': ElementAttributes;
+ }
+ }
+}
+export {};
+//# sourceMappingURL=jsx-types-solid.test.d.ts.map
\ No newline at end of file
diff --git a/dist/jsx-types-solid.test.d.ts.map b/dist/jsx-types-solid.test.d.ts.map
new file mode 100644
index 0000000..7e97015
--- /dev/null
+++ b/dist/jsx-types-solid.test.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"jsx-types-solid.test.d.ts","sourceRoot":"","sources":["../src/jsx-types-solid.test.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAA;AAEvD,cAAM,WAAY,SAAQ,WAAW;IACpC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAO;IAE3C,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAI;IAE7C,uDAAuD;IACvD,gBAAgB,EAAG,MAAM,GAAG,KAAK,CAAA;CACjC;AAID,OAAO,QAAQ,UAAU,CAAC;IACzB,UAAU,GAAG,CAAC;QACb,UAAU,iBAAiB;YAC1B,cAAc,EAAE,iBAAiB,CAAC,WAAW,EAAE,UAAU,GAAG,WAAW,CAAC,CAAA;SACxE;KACD;CACD"}
\ No newline at end of file
diff --git a/dist/jsx-types-solid.test.jsx b/dist/jsx-types-solid.test.jsx
new file mode 100644
index 0000000..edf139c
--- /dev/null
+++ b/dist/jsx-types-solid.test.jsx
@@ -0,0 +1,32 @@
+/* @jsxImportSource solid-js */
+class SomeElement extends HTMLElement {
+ someProp = true;
+ get otherProp() {
+ return 0;
+ }
+ set otherProp(_) { }
+ /** do not use this property, its only for JSX types */
+ __set__otherProp;
+}
+SomeElement;
+describe('JSX types with ElementAttributes', () => {
+ it('derives JSX types from classes', () => {
+ ;
+ <>
+
+
+
+ {/* @ts-expect-error good, number is invalid */}
+
+ {/* @ts-expect-error good, 'blah' is invalid */}
+
+
+ {/* Additionally TypeScript will allow unknown dash-case props (the attr: can be used here to tell Solid to set the element attributes instead of the JS properties) */}
+
+ {/* @ts-expect-error foo doesn't exist. TypeScript will only check existence of properties without dashes */}
+
+ >;
+ });
+});
+export {};
+//# sourceMappingURL=jsx-types-solid.test.jsx.map
\ No newline at end of file
diff --git a/dist/jsx-types-solid.test.jsx.map b/dist/jsx-types-solid.test.jsx.map
new file mode 100644
index 0000000..bd4d335
--- /dev/null
+++ b/dist/jsx-types-solid.test.jsx.map
@@ -0,0 +1 @@
+{"version":3,"file":"jsx-types-solid.test.jsx","sourceRoot":"","sources":["../src/jsx-types-solid.test.tsx"],"names":[],"mappings":"AAAA,+BAA+B;AAI/B,MAAM,WAAY,SAAQ,WAAW;IACpC,QAAQ,GAA+B,IAAI,CAAA;IAE3C,IAAI,SAAS;QACZ,OAAO,CAAC,CAAA;IACT,CAAC;IACD,IAAI,SAAS,CAAC,CAA2B,IAAG,CAAC;IAE7C,uDAAuD;IACvD,gBAAgB,CAAiB;CACjC;AAED,WAAW,CAAA;AAUX,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACzC,CAAC;QAAA,EACA;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAChD;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAChD;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,EAChD;GAAA,CAAC,8CAA8C,CAC/C;GAAA,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAC7B;GAAA,CAAC,8CAA8C,CAC/C;GAAA,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,EAE/B;;GAAA,CAAC,sKAAsK,CACvK;GAAA,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAC1D;GAAA,CAAC,2GAA2G,CAC5G;GAAA,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAC1B;EAAA,GAAG,CAAA;IACJ,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
\ 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..7fa0013 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,34 @@ 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 = {
- [Property in keyof Type]: NonNullable extends string ? Type[Property] : Type[Property] | string
+export type WithStringValues = {
+ // [Property in keyof Type]: NonNullable extends string ? Type[Property] : Type[Property] | string
+ [Property in keyof Type]: PickFromUnion extends never
+ ? // if the type does not include a type assignable to string
+ Type[Property] | string
+ : // otherwise it does
+ Type[Property]
+}
+
+type StringKeysOnly = OmitFromUnion
+
+type OmitFromUnion = T extends TypeToOmit ? never : T
+type PickFromUnion = T extends TypeToPick ? T : never
+
+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/jsx-types-react.test.tsx b/src/jsx-types-react.test.tsx
new file mode 100644
index 0000000..d8bc68a
--- /dev/null
+++ b/src/jsx-types-react.test.tsx
@@ -0,0 +1,44 @@
+/* @jsxImportSource react */
+
+import type {ReactElementAttributes} from './react.js'
+
+class SomeElement extends HTMLElement {
+ someProp: 'true' | 'false' | boolean = true
+
+ get otherProp(): number {
+ return 0
+ }
+ set otherProp(_: this['__set__otherProp']) {}
+
+ /** do not use this property, its only for JSX types */
+ __set__otherProp!: number | 'foo'
+}
+
+SomeElement
+
+declare module 'react' {
+ namespace JSX {
+ interface IntrinsicElements {
+ 'some-element': ReactElementAttributes
+ }
+ }
+}
+
+describe('JSX types with ReactElementAttributes', () => {
+ it('derives JSX types from classes', () => {
+ ;<>
+
+
+
+ {/* @ts-expect-error good, number is invalid */}
+
+ {/* @ts-expect-error good, 'blah' is invalid */}
+
+
+ {/* Additionally TypeScript will allow unknown dash-case props (as we didn't not define JS properties with these exact dash-cased names, React 19+ will set the element attributes, useful for setting the attributes but React has no way to specify to set attributes for names without dashes) */}
+
+ {/* @ts-expect-error foo doesn't exist. TypeScript will only check existence of properties without dashes */}
+
+ >
+ })
+})
diff --git a/src/jsx-types-solid.test.tsx b/src/jsx-types-solid.test.tsx
new file mode 100644
index 0000000..913556f
--- /dev/null
+++ b/src/jsx-types-solid.test.tsx
@@ -0,0 +1,44 @@
+/* @jsxImportSource solid-js */
+
+import type {ElementAttributes} from './LumeElement.js'
+
+class SomeElement extends HTMLElement {
+ someProp: 'true' | 'false' | boolean = true
+
+ get otherProp(): number {
+ return 0
+ }
+ set otherProp(_: this['__set__otherProp']) {}
+
+ /** do not use this property, its only for JSX types */
+ __set__otherProp!: number | 'foo'
+}
+
+SomeElement
+
+declare module 'solid-js' {
+ namespace JSX {
+ interface IntrinsicElements {
+ 'some-element': ElementAttributes
+ }
+ }
+}
+
+describe('JSX types with ElementAttributes', () => {
+ it('derives JSX types from classes', () => {
+ ;<>
+
+
+
+ {/* @ts-expect-error good, number is invalid */}
+
+ {/* @ts-expect-error good, 'blah' is invalid */}
+
+
+ {/* Additionally TypeScript will allow unknown dash-case props (the attr: can be used here to tell Solid to set the element attributes instead of the JS properties) */}
+
+ {/* @ts-expect-error foo doesn't exist. TypeScript will only check existence of properties without dashes */}
+
+ >
+ })
+})
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