Skip to content

Commit

Permalink
Add support for passing nodes to components
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jan 18, 2023
1 parent b660735 commit 4d5e134
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 16 deletions.
10 changes: 7 additions & 3 deletions lib/components.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {Element} from 'hast'

/**
* Basic functional component: given props, returns an element.
*
Expand Down Expand Up @@ -39,12 +41,14 @@ export type Component<ComponentProps> =
| FunctionComponent<ComponentProps>
| ClassComponent<ComponentProps>

export type ExtraProps = {node?: Element | undefined}

/**
* Possible components to use.
*
* Each key is a tag name typed in `JSX.IntrinsicElements`.
* Each value is a component accepting the corresponding props or a different
* tag name.
* Each value is either a different tag name, or a component accepting the
* corresponding props (and an optional `node` prop if `passNode` is on).
*
* You can access props at `JSX.IntrinsicElements`.
* For example, to find props for `a`, use `JSX.IntrinsicElements['a']`.
Expand All @@ -53,6 +57,6 @@ export type Component<ComponentProps> =
// react into the `.d.ts` file.
export type Components = {
[TagName in keyof JSX.IntrinsicElements]:
| Component<JSX.IntrinsicElements[TagName]>
| Component<JSX.IntrinsicElements[TagName] & ExtraProps>
| keyof JSX.IntrinsicElements
}
16 changes: 13 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* @callback Jsx
* Create a production element.
* @param {unknown} type
* Element type: the `Fragment` symbol or a tag name (`string`).
* Element type: `Fragment` symbol, tag name (`string`), component.
* @param {Props} props
* Element props and also includes `children`.
* @param {string | undefined} key
Expand All @@ -29,7 +29,7 @@
* @callback JsxDev
* Create a development element.
* @param {unknown} type
* Element type: the `Fragment` symbol or a tag name (`string`).
* Element type: `Fragment` symbol, tag name (`string`), component.
* @param {Props} props
* Element props and also includes `children`.
* @param {string | undefined} key
Expand Down Expand Up @@ -67,7 +67,7 @@
* @typedef {JSX.Element | string | null | undefined} Child
* Child.
*
* @typedef {{children: Array<Child>, [prop: string]: Value | Array<Child>}} Props
* @typedef {{children: Array<Child>, node?: Element | undefined, [prop: string]: Value | Element | undefined | Array<Child>}} Props
* Properties and children.
*
* @callback Create
Expand All @@ -89,6 +89,8 @@
* File path.
* @property {Partial<Components>} components
* Components to swap.
* @property {boolean} passNode
* Pass `node` to components.
* @property {Schema} schema
* Current schema.
* @property {unknown} Fragment
Expand All @@ -108,6 +110,8 @@
*
* Passed in source info to `jsxDEV` when using the automatic runtime with
* `development: true`.
* @property {boolean | null | undefined} [passNode=false]
* Pass the hast element node to components.
* @property {Space | null | undefined} [space='html']
* Whether `tree` is in the `'html'` or `'svg'` space.
*
Expand Down Expand Up @@ -239,6 +243,7 @@ export function toJsxRuntime(tree, options) {
const state = {
Fragment: options.Fragment,
schema: options.space === 'svg' ? svg : html,
passNode: options.passNode || false,
components: options.components || {},
filePath,
create
Expand Down Expand Up @@ -298,6 +303,11 @@ function one(state, node, key) {
if (own.call(state.components, node.tagName)) {
const key = /** @type {keyof JSX.IntrinsicElements} */ (node.tagName)
type = state.components[key]

// If this is swapped out for a component:
if (type !== 'string' && state.passNode) {
props.node = node
}
} else {
type = node.tagName
}
Expand Down
28 changes: 18 additions & 10 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,29 @@ Static JSX ([`Jsx`][jsx], required in production).

Development JSX ([`JsxDev`][jsxdev], required in development).

###### `development`

Whether to use `jsxDEV` when on or `jsx` and `jsxs` when off (`boolean`,
default: `false`).

###### `components`

Components to use ([`Partial<Components>`][components], optional).

Each key is the name of an HTML (or SVG) element to override.
The value is the component to render instead.

###### `development`

Whether to use `jsxDEV` when on or `jsx` and `jsxs` when off (`boolean`,
default: `false`).

###### `filePath`

File path to the original source file (`string`, optional).

Passed in source info to `jsxDEV` when using the automatic runtime with
`development: true`.

###### `passNode`

Pass the hast element node to components (`boolean`, default: `false`).

###### `space`

Whether `tree` is in the `'html'` or `'svg'` space ([`Space`][space], default:
Expand All @@ -183,21 +187,25 @@ it.
Possible components to use (TypeScript type).

Each key is a tag name typed in `JSX.IntrinsicElements`.
Each value is a component accepting the corresponding props or a different tag
name.
Each value is either a different tag name, or a component accepting the
corresponding props (and an optional `node` prop if `passNode` is on).

You can access props at `JSX.IntrinsicElements`.
For example, to find props for `a`, use `JSX.IntrinsicElements['a']`.

###### Type

```ts
import type {Element} from 'hast'

type Components = {
[TagName in keyof JSX.IntrinsicElements]:
| Component<JSX.IntrinsicElements[TagName]>
| Component<JSX.IntrinsicElements[TagName] & ExtraProps>
| keyof JSX.IntrinsicElements
}

type ExtraProps = {node?: Element | undefined}

type Component<ComponentProps> =
// Function component:
| ((props: ComponentProps) => JSX.Element | string | null | undefined)
Expand All @@ -222,7 +230,7 @@ Create a production element (TypeScript type).
###### Parameters
* `type` (`unknown`)
— element type: the `Fragment` symbol or a tag name (`string`)
— element type: `Fragment` symbol, tag name (`string`), component
* `props` ([`Props`][props])
— element props and also includes `children`
* `key` (`string` or `undefined`)
Expand All @@ -239,7 +247,7 @@ Create a development element (TypeScript type).
###### Parameters
* `type` (`unknown`)
— element type: the `Fragment` symbol or a tag name (`string`)
— element type: `Fragment` symbol, tag name (`string`), component
* `props` ([`Props`][props])
— element props and also includes `children`
* `key` (`string` or `undefined`)
Expand Down
33 changes: 33 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,39 @@ test('components', () => {
'a',
'should support class components'
)

assert.equal(
renderToStaticMarkup(
toJsxRuntime(h('b'), {
...production,
passNode: true,
components: {
b(props) {
assert.ok(props.node)
return 'a'
}
}
})
),
'a',
'should support components w/ `passNode: true`'
)

assert.equal(
renderToStaticMarkup(
toJsxRuntime(h('b'), {
...production,
components: {
b(props) {
assert.equal(props.node, undefined)
return 'a'
}
}
})
),
'a',
'should support components w/o `passNode`'
)
})

test('react specific: filter whitespace in tables', () => {
Expand Down

0 comments on commit 4d5e134

Please sign in to comment.