1 | /**
|
2 | * @typedef {import('hast').Root} Root
|
3 | * @typedef {import('hast').Element} Element
|
4 | * @typedef {import('react').ReactNode} ReactNode
|
5 | * @typedef {import('react').ReactElement<unknown>} ReactElement
|
6 | *
|
7 | * @callback CreateElementLike
|
8 | * @param {any} name
|
9 | * @param {any} props
|
10 | * @param {...ReactNode} children
|
11 | * @returns {ReactNode}
|
12 | *
|
13 | * @typedef SharedOptions
|
14 | * @property {CreateElementLike} createElement
|
15 | * How to create elements or components.
|
16 | * You should typically pass `React.createElement`.
|
17 | * @property {((props: any) => ReactNode)|undefined} [Fragment]
|
18 | * Create fragments instead of an outer `<div>` if available.
|
19 | * You should typically pass `React.Fragment`.
|
20 | * @property {string|undefined} [prefix='h-']
|
21 | * React key prefix
|
22 | *
|
23 | * @typedef {SharedOptions & (import("./complex-types").ComponentsWithNodeOptions|import("./complex-types").ComponentsWithoutNodeOptions)} Options
|
24 | */
|
25 |
|
26 | import {toH} from 'hast-to-hyperscript'
|
27 | // @ts-expect-error: hush.
|
28 | import tableCellStyle from '@mapbox/hast-util-table-cell-style'
|
29 |
|
30 | const own = {}.hasOwnProperty
|
31 | const tableElements = new Set([
|
32 | 'table',
|
33 | 'thead',
|
34 | 'tbody',
|
35 | 'tfoot',
|
36 | 'tr',
|
37 | 'th',
|
38 | 'td'
|
39 | ])
|
40 |
|
41 | /**
|
42 | * @type {import('unified').Plugin<[Options], Root, ReactElement>}
|
43 | */
|
44 | export default function rehypeReact(options) {
|
45 | if (!options || typeof options.createElement !== 'function') {
|
46 | throw new TypeError('createElement is not a function')
|
47 | }
|
48 |
|
49 | const createElement = options.createElement
|
50 |
|
51 | Object.assign(this, {Compiler: compiler})
|
52 |
|
53 | /** @type {import('unified').CompilerFunction<Root, ReactNode>} */
|
54 | function compiler(node) {
|
55 | /** @type {ReactNode} */
|
56 | // @ts-expect-error: assume `name` is a known element.
|
57 | let result = toH(h, tableCellStyle(node), options.prefix)
|
58 |
|
59 | if (node.type === 'root') {
|
60 | // Invert <https://github.com/syntax-tree/hast-to-hyperscript/blob/d227372/index.js#L46-L56>.
|
61 | result =
|
62 | result &&
|
63 | typeof result === 'object' &&
|
64 | 'type' in result &&
|
65 | 'props' in result &&
|
66 | result.type === 'div' &&
|
67 | (node.children.length !== 1 || node.children[0].type !== 'element')
|
68 | ? // `children` does exist.
|
69 | // type-coverage:ignore-next-line
|
70 | result.props.children
|
71 | : [result]
|
72 |
|
73 | return createElement(options.Fragment || 'div', {}, result)
|
74 | }
|
75 |
|
76 | return result
|
77 | }
|
78 |
|
79 | /**
|
80 | * @param {keyof JSX.IntrinsicElements} name
|
81 | * @param {Record<string, unknown>} props
|
82 | * @param {unknown[]} [children]
|
83 | * @returns {ReactNode}
|
84 | */
|
85 | function h(name, props, children) {
|
86 | // Currently, a warning is triggered by react for *any* white space in
|
87 | // tables.
|
88 | // So we remove the pretty lines for now.
|
89 | // See: <https://github.com/facebook/react/pull/7081>.
|
90 | // See: <https://github.com/facebook/react/pull/7515>.
|
91 | // See: <https://github.com/remarkjs/remark-react/issues/64>.
|
92 | if (children && tableElements.has(name)) {
|
93 | children = children.filter((child) => child !== '\n')
|
94 | }
|
95 |
|
96 | if (options.components && own.call(options.components, name)) {
|
97 | const component = options.components[name]
|
98 |
|
99 | if (options.passNode && typeof component === 'function') {
|
100 | // @ts-expect-error: `toH` passes the current node.
|
101 | // type-coverage:ignore-next-line
|
102 | props = Object.assign({node: this}, props)
|
103 | }
|
104 |
|
105 | return createElement(component, props, children)
|
106 | }
|
107 |
|
108 | return createElement(name, props, children)
|
109 | }
|
110 | }
|