diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ece21e9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.7.2] - 2026-01-11 + +### Changed + +- Improve `reactJsx` typings by exporting React-aligned intrinsic element, event, and ref helper types (backward compatible; loader react mode unchanged). diff --git a/README.md b/README.md index 76a0244..b779b35 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,8 @@ createRoot(document.getElementById('root')!).render(reactJsx`<${App} />`) The React runtime shares the same template semantics as `jsx`, except it returns React elements (via `React.createElement`) so you can embed other React components with `<${MyComponent} />` and use hooks/state as usual. The helper lives in a separate subpath so DOM-only consumers never pay the React dependency cost. +Intrinsic props, events, and refs follow React’s JSX intrinsic element typings (React 18/19), and helper types like `ReactJsxIntrinsicElements`, `ReactJsxRef`, and `ReactJsxDomAttributes` are exported from `@knighted/jsx/react` when you need annotations. + ### DOM-specific props - `style` accepts either a string or an object. Object values handle CSS custom properties (`--token`) automatically. diff --git a/docs/runtime-helpers-overview.md b/docs/runtime-helpers-overview.md index 0c57d83..059142b 100644 --- a/docs/runtime-helpers-overview.md +++ b/docs/runtime-helpers-overview.md @@ -41,6 +41,7 @@ document.querySelector('#mount')?.append(badge) - Returns a React element tree. You still render via `createRoot`, `renderToString`, etc. - Identical template semantics to `jsx`, but expressions inside braces become arguments to `React.createElement` instead of DOM setters. +- Intrinsic props, events, and `ref` follow React’s JSX intrinsic element typings, and helper types are exported from `@knighted/jsx/react` for annotations when needed. - Hooks/state work because you typically invoke `reactJsx` inside components or pass the elements to React. Example: diff --git a/docs/typescript.md b/docs/typescript.md index 8759495..4b466e4 100644 --- a/docs/typescript.md +++ b/docs/typescript.md @@ -38,7 +38,7 @@ Use one `tsconfig.json` when the whole project can share the same JSX compiler o - `jsxImportSource` points TypeScript at the packaged runtime typings so `.tsx` helpers get DOM-friendly diagnostics. - The language-service plugin enforces DOM vs React rules for tagged templates in `.ts` files. Add extra keys in `tagModes` if you alias `jsx`/`reactJsx` to different identifiers. -- React components still compile and run through React’s own runtime; the setting only affects type checking. +- React components still compile and run through React’s own runtime; the setting only affects type checking. The `reactJsx` helper re-uses React’s intrinsic element typings (props/events/refs), and the `@knighted/jsx/react` entry exports helper types if you need explicit annotations in your code. ## Mixed React build + DOM helper configs diff --git a/package-lock.json b/package-lock.json index 2ceb5fe..43e4573 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@knighted/jsx", - "version": "1.7.1", + "version": "1.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@knighted/jsx", - "version": "1.7.1", + "version": "1.7.2", "license": "MIT", "dependencies": { "@napi-rs/wasm-runtime": "^1.1.0", diff --git a/package.json b/package.json index 8bda33d..7fd8532 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/jsx", - "version": "1.7.1", + "version": "1.7.2", "description": "Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.", "keywords": [ "jsx runtime", diff --git a/src/react/index.ts b/src/react/index.ts index 461c61c..6512956 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -1,2 +1,10 @@ export { reactJsx } from './react-jsx.js' -export type { ReactJsxComponent } from './react-jsx.js' +export type { + ReactJsxComponent, + ReactJsxDomAttributes, + ReactJsxEventHandler, + ReactJsxIntrinsicElement, + ReactJsxIntrinsicElements, + ReactJsxRef, + ReactJsxRenderable, +} from './react-jsx.js' diff --git a/src/react/react-jsx.ts b/src/react/react-jsx.ts index 170e46b..9d58d2f 100644 --- a/src/react/react-jsx.ts +++ b/src/react/react-jsx.ts @@ -21,14 +21,28 @@ import { Fragment, createElement, type ComponentType, + type DOMAttributes, + type EventHandler, + type JSX as ReactJSX, + type PropsWithChildren, type ReactElement, type ReactNode, + type Ref, + type SyntheticEvent, } from 'react' export type ReactJsxComponent> = ComponentType< - Props & { children?: ReactNode } + PropsWithChildren > +export type ReactJsxRenderable = ReactNode +export type ReactJsxRef = Ref +export type ReactJsxEventHandler = EventHandler +export type ReactJsxDomAttributes = DOMAttributes +export type ReactJsxIntrinsicElements = ReactJSX.IntrinsicElements +export type ReactJsxIntrinsicElement = + ReactJsxIntrinsicElements[Tag] + type ReactJsxContext = TemplateContext const isIterable = (value: unknown): value is Iterable => { diff --git a/test/react-jsx.types.test-d.ts b/test/react-jsx.types.test-d.ts new file mode 100644 index 0000000..898d635 --- /dev/null +++ b/test/react-jsx.types.test-d.ts @@ -0,0 +1,54 @@ +/* + * This file is a type-only test suite: declarations are intentionally "unused" + * so TypeScript will fail the build if the reactJsx typings drift. Linting is + * secondary here, so we disable unused-var checks. + */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type * as React from 'react' +import type { + ReactJsxComponent, + ReactJsxDomAttributes, + ReactJsxEventHandler, + ReactJsxIntrinsicElement, + ReactJsxIntrinsicElements, + ReactJsxRef, + ReactJsxRenderable, +} from '../src/react/index.js' + +type Equal = + (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false + +type Expect = T + +type IntrinsicElementsEqual = Expect< + Equal +> + +type ButtonProps = ReactJsxIntrinsicElement<'button'> +type ButtonClick = Parameters>[0] +type ButtonClickIsReactMouse = Expect< + Equal> +> + +type ButtonRefIsReactRef = Expect< + Equal | undefined> +> + +type DomAttributesMatch = Expect< + Equal, React.DOMAttributes> +> + +type EventHandlerType = ReactJsxEventHandler> +const _eventHandler: EventHandlerType | undefined = undefined + +// @ts-expect-error href is not allowed on button +const invalidButton: ButtonProps = { href: '#' } + +type DemoProps = { label: string } +type DemoComponent = ReactJsxComponent +type DemoComponentProps = React.ComponentProps +type DemoPropsArePropsWithChildren = Expect< + Equal> +> + +type RenderableAllowsNull = Expect