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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/runtime-helpers-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 9 additions & 1 deletion src/react/index.ts
Original file line number Diff line number Diff line change
@@ -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'
16 changes: 15 additions & 1 deletion src/react/react-jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props = Record<string, unknown>> = ComponentType<
Props & { children?: ReactNode }
PropsWithChildren<Props>
>

export type ReactJsxRenderable = ReactNode
export type ReactJsxRef<T> = Ref<T>
export type ReactJsxEventHandler<E extends SyntheticEvent> = EventHandler<E>
export type ReactJsxDomAttributes<T = unknown> = DOMAttributes<T>
export type ReactJsxIntrinsicElements = ReactJSX.IntrinsicElements
export type ReactJsxIntrinsicElement<Tag extends keyof ReactJsxIntrinsicElements> =
ReactJsxIntrinsicElements[Tag]

type ReactJsxContext = TemplateContext<ReactJsxComponent>

const isIterable = (value: unknown): value is Iterable<unknown> => {
Expand Down
54 changes: 54 additions & 0 deletions test/react-jsx.types.test-d.ts
Original file line number Diff line number Diff line change
@@ -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<A, B> =
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false

type Expect<T extends true> = T

type IntrinsicElementsEqual = Expect<
Equal<ReactJsxIntrinsicElements, React.JSX.IntrinsicElements>
>

type ButtonProps = ReactJsxIntrinsicElement<'button'>
type ButtonClick = Parameters<NonNullable<ButtonProps['onClick']>>[0]
type ButtonClickIsReactMouse = Expect<
Equal<ButtonClick, React.MouseEvent<HTMLButtonElement>>
>

type ButtonRefIsReactRef = Expect<
Equal<ButtonProps['ref'], ReactJsxRef<HTMLButtonElement> | undefined>
>

type DomAttributesMatch = Expect<
Equal<ReactJsxDomAttributes<HTMLDivElement>, React.DOMAttributes<HTMLDivElement>>
>

type EventHandlerType = ReactJsxEventHandler<React.SyntheticEvent<HTMLDivElement>>
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<DemoProps>
type DemoComponentProps = React.ComponentProps<DemoComponent>
type DemoPropsArePropsWithChildren = Expect<
Equal<DemoComponentProps, React.PropsWithChildren<DemoProps>>
>

type RenderableAllowsNull = Expect<null extends ReactJsxRenderable ? true : false>
Loading