Skip to content

Commit 4d5e134

Browse files
committed
Add support for passing nodes to components
1 parent b660735 commit 4d5e134

File tree

4 files changed

+71
-16
lines changed

4 files changed

+71
-16
lines changed

lib/components.d.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type {Element} from 'hast'
2+
13
/**
24
* Basic functional component: given props, returns an element.
35
*
@@ -39,12 +41,14 @@ export type Component<ComponentProps> =
3941
| FunctionComponent<ComponentProps>
4042
| ClassComponent<ComponentProps>
4143

44+
export type ExtraProps = {node?: Element | undefined}
45+
4246
/**
4347
* Possible components to use.
4448
*
4549
* Each key is a tag name typed in `JSX.IntrinsicElements`.
46-
* Each value is a component accepting the corresponding props or a different
47-
* tag name.
50+
* Each value is either a different tag name, or a component accepting the
51+
* corresponding props (and an optional `node` prop if `passNode` is on).
4852
*
4953
* You can access props at `JSX.IntrinsicElements`.
5054
* For example, to find props for `a`, use `JSX.IntrinsicElements['a']`.
@@ -53,6 +57,6 @@ export type Component<ComponentProps> =
5357
// react into the `.d.ts` file.
5458
export type Components = {
5559
[TagName in keyof JSX.IntrinsicElements]:
56-
| Component<JSX.IntrinsicElements[TagName]>
60+
| Component<JSX.IntrinsicElements[TagName] & ExtraProps>
5761
| keyof JSX.IntrinsicElements
5862
}

lib/index.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* @callback Jsx
1919
* Create a production element.
2020
* @param {unknown} type
21-
* Element type: the `Fragment` symbol or a tag name (`string`).
21+
* Element type: `Fragment` symbol, tag name (`string`), component.
2222
* @param {Props} props
2323
* Element props and also includes `children`.
2424
* @param {string | undefined} key
@@ -29,7 +29,7 @@
2929
* @callback JsxDev
3030
* Create a development element.
3131
* @param {unknown} type
32-
* Element type: the `Fragment` symbol or a tag name (`string`).
32+
* Element type: `Fragment` symbol, tag name (`string`), component.
3333
* @param {Props} props
3434
* Element props and also includes `children`.
3535
* @param {string | undefined} key
@@ -67,7 +67,7 @@
6767
* @typedef {JSX.Element | string | null | undefined} Child
6868
* Child.
6969
*
70-
* @typedef {{children: Array<Child>, [prop: string]: Value | Array<Child>}} Props
70+
* @typedef {{children: Array<Child>, node?: Element | undefined, [prop: string]: Value | Element | undefined | Array<Child>}} Props
7171
* Properties and children.
7272
*
7373
* @callback Create
@@ -89,6 +89,8 @@
8989
* File path.
9090
* @property {Partial<Components>} components
9191
* Components to swap.
92+
* @property {boolean} passNode
93+
* Pass `node` to components.
9294
* @property {Schema} schema
9395
* Current schema.
9496
* @property {unknown} Fragment
@@ -108,6 +110,8 @@
108110
*
109111
* Passed in source info to `jsxDEV` when using the automatic runtime with
110112
* `development: true`.
113+
* @property {boolean | null | undefined} [passNode=false]
114+
* Pass the hast element node to components.
111115
* @property {Space | null | undefined} [space='html']
112116
* Whether `tree` is in the `'html'` or `'svg'` space.
113117
*
@@ -239,6 +243,7 @@ export function toJsxRuntime(tree, options) {
239243
const state = {
240244
Fragment: options.Fragment,
241245
schema: options.space === 'svg' ? svg : html,
246+
passNode: options.passNode || false,
242247
components: options.components || {},
243248
filePath,
244249
create
@@ -298,6 +303,11 @@ function one(state, node, key) {
298303
if (own.call(state.components, node.tagName)) {
299304
const key = /** @type {keyof JSX.IntrinsicElements} */ (node.tagName)
300305
type = state.components[key]
306+
307+
// If this is swapped out for a component:
308+
if (type !== 'string' && state.passNode) {
309+
props.node = node
310+
}
301311
} else {
302312
type = node.tagName
303313
}

readme.md

+18-10
Original file line numberDiff line numberDiff line change
@@ -144,25 +144,29 @@ Static JSX ([`Jsx`][jsx], required in production).
144144

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

147+
###### `development`
148+
149+
Whether to use `jsxDEV` when on or `jsx` and `jsxs` when off (`boolean`,
150+
default: `false`).
151+
147152
###### `components`
148153

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

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

154-
###### `development`
155-
156-
Whether to use `jsxDEV` when on or `jsx` and `jsxs` when off (`boolean`,
157-
default: `false`).
158-
159159
###### `filePath`
160160

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

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

166+
###### `passNode`
167+
168+
Pass the hast element node to components (`boolean`, default: `false`).
169+
166170
###### `space`
167171

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

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

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

192196
###### Type
193197

194198
```ts
199+
import type {Element} from 'hast'
200+
195201
type Components = {
196202
[TagName in keyof JSX.IntrinsicElements]:
197-
| Component<JSX.IntrinsicElements[TagName]>
203+
| Component<JSX.IntrinsicElements[TagName] & ExtraProps>
198204
| keyof JSX.IntrinsicElements
199205
}
200206

207+
type ExtraProps = {node?: Element | undefined}
208+
201209
type Component<ComponentProps> =
202210
// Function component:
203211
| ((props: ComponentProps) => JSX.Element | string | null | undefined)
@@ -222,7 +230,7 @@ Create a production element (TypeScript type).
222230
###### Parameters
223231
224232
* `type` (`unknown`)
225-
— element type: the `Fragment` symbol or a tag name (`string`)
233+
— element type: `Fragment` symbol, tag name (`string`), component
226234
* `props` ([`Props`][props])
227235
— element props and also includes `children`
228236
* `key` (`string` or `undefined`)
@@ -239,7 +247,7 @@ Create a development element (TypeScript type).
239247
###### Parameters
240248
241249
* `type` (`unknown`)
242-
— element type: the `Fragment` symbol or a tag name (`string`)
250+
— element type: `Fragment` symbol, tag name (`string`), component
243251
* `props` ([`Props`][props])
244252
— element props and also includes `children`
245253
* `key` (`string` or `undefined`)

test/index.js

+33
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,39 @@ test('components', () => {
400400
'a',
401401
'should support class components'
402402
)
403+
404+
assert.equal(
405+
renderToStaticMarkup(
406+
toJsxRuntime(h('b'), {
407+
...production,
408+
passNode: true,
409+
components: {
410+
b(props) {
411+
assert.ok(props.node)
412+
return 'a'
413+
}
414+
}
415+
})
416+
),
417+
'a',
418+
'should support components w/ `passNode: true`'
419+
)
420+
421+
assert.equal(
422+
renderToStaticMarkup(
423+
toJsxRuntime(h('b'), {
424+
...production,
425+
components: {
426+
b(props) {
427+
assert.equal(props.node, undefined)
428+
return 'a'
429+
}
430+
}
431+
})
432+
),
433+
'a',
434+
'should support components w/o `passNode`'
435+
)
403436
})
404437

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

0 commit comments

Comments
 (0)