Skip to content
This repository was archived by the owner on Aug 9, 2023. It is now read-only.

Commit 638e615

Browse files
committed
Move core to lib/
1 parent 340007a commit 638e615

File tree

4 files changed

+335
-328
lines changed

4 files changed

+335
-328
lines changed

index.js

Lines changed: 3 additions & 325 deletions
Original file line numberDiff line numberDiff line change
@@ -1,328 +1,6 @@
11
/**
2-
* @typedef {import('hast').Element} Element
3-
* @typedef {import('hast').Root} Root
4-
* @typedef {import('hast').Text} Text
5-
* @typedef {import('hast').Root|import('hast').Content} Node
6-
*
7-
* @callback CreateElementLike
8-
* @param {string} name
9-
* @param {any} attributes
10-
* @param {Array<any>} [children]
11-
* @returns {any}
12-
*
13-
* @typedef Context
14-
* @property {html|svg} schema
15-
* @property {string|null} prefix
16-
* @property {number} key
17-
* @property {boolean} react
18-
* @property {boolean} vue
19-
* @property {boolean} vdom
20-
* @property {boolean} hyperscript
21-
*
22-
* @typedef Options
23-
* @property {string|null} [prefix]
24-
* @property {'html'|'svg'} [space]
2+
* @typedef {import('./lib/index.js').Options} Options
3+
* @typedef {import('./lib/index.js').CreateElementLike} CreateElementLike
254
*/
265

27-
import {html, svg, find, hastToReact} from 'property-information'
28-
import {stringify as spaces} from 'space-separated-tokens'
29-
import {stringify as commas} from 'comma-separated-tokens'
30-
import style from 'style-to-object'
31-
import {webNamespaces} from 'web-namespaces'
32-
33-
const toReact = /** @type {Record<string, string>} */ (hastToReact)
34-
35-
const own = {}.hasOwnProperty
36-
37-
/**
38-
* @template {CreateElementLike} H
39-
* @param {H} h
40-
* @param {Node} tree
41-
* @param {string|boolean|Options} [options]
42-
* @returns {ReturnType<H>}
43-
*/
44-
// eslint-disable-next-line complexity
45-
export function toH(h, tree, options) {
46-
if (typeof h !== 'function') {
47-
throw new TypeError('h is not a function')
48-
}
49-
50-
const r = react(h)
51-
const v = vue(h)
52-
const vd = vdom(h)
53-
/** @type {string|boolean|null|undefined} */
54-
let prefix
55-
/** @type {Element} */
56-
let node
57-
58-
if (typeof options === 'string' || typeof options === 'boolean') {
59-
prefix = options
60-
options = {}
61-
} else {
62-
if (!options) options = {}
63-
prefix = options.prefix
64-
}
65-
66-
if (tree && tree.type === 'root') {
67-
const head = tree.children[0]
68-
// @ts-expect-error Allow `doctypes` in there, we’ll filter them out later.
69-
node =
70-
tree.children.length === 1 && head.type === 'element'
71-
? head
72-
: {
73-
type: 'element',
74-
tagName: 'div',
75-
properties: {},
76-
children: tree.children
77-
}
78-
} else if (tree && tree.type === 'element') {
79-
node = tree
80-
} else {
81-
throw new Error(
82-
'Expected root or element, not `' + ((tree && tree.type) || tree) + '`'
83-
)
84-
}
85-
86-
return transform(h, node, {
87-
schema: options.space === 'svg' ? svg : html,
88-
prefix:
89-
prefix === undefined || prefix === null
90-
? r || v || vd
91-
? 'h-'
92-
: null
93-
: typeof prefix === 'string'
94-
? prefix
95-
: prefix
96-
? 'h-'
97-
: null,
98-
key: 0,
99-
react: r,
100-
vue: v,
101-
vdom: vd,
102-
hyperscript: hyperscript(h)
103-
})
104-
}
105-
106-
/**
107-
* Transform a hast node through a hyperscript interface to *anything*!
108-
*
109-
* @template {CreateElementLike} H
110-
* @param {H} h
111-
* @param {Element} node
112-
* @param {Context} ctx
113-
*/
114-
function transform(h, node, ctx) {
115-
const parentSchema = ctx.schema
116-
let schema = parentSchema
117-
let name = node.tagName
118-
/** @type {Record<string, unknown>} */
119-
const attributes = {}
120-
/** @type {Array<ReturnType<H>|string>} */
121-
const nodes = []
122-
let index = -1
123-
/** @type {string} */
124-
let key
125-
126-
if (parentSchema.space === 'html' && name.toLowerCase() === 'svg') {
127-
schema = svg
128-
ctx.schema = schema
129-
}
130-
131-
for (key in node.properties) {
132-
if (node.properties && own.call(node.properties, key)) {
133-
addAttribute(attributes, key, node.properties[key], ctx, name)
134-
}
135-
}
136-
137-
if (ctx.vdom) {
138-
if (schema.space === 'html') {
139-
name = name.toUpperCase()
140-
} else if (schema.space) {
141-
attributes.namespace = webNamespaces[schema.space]
142-
}
143-
}
144-
145-
if (ctx.prefix) {
146-
ctx.key++
147-
attributes.key = ctx.prefix + ctx.key
148-
}
149-
150-
if (node.children) {
151-
while (++index < node.children.length) {
152-
const value = node.children[index]
153-
154-
if (value.type === 'element') {
155-
nodes.push(transform(h, value, ctx))
156-
} else if (value.type === 'text') {
157-
nodes.push(value.value)
158-
}
159-
}
160-
}
161-
162-
// Restore parent schema.
163-
ctx.schema = parentSchema
164-
165-
// Ensure no React warnings are triggered for void elements having children
166-
// passed in.
167-
return nodes.length > 0
168-
? h.call(node, name, attributes, nodes)
169-
: h.call(node, name, attributes)
170-
}
171-
172-
/**
173-
* @param {Record<string, unknown>} props
174-
* @param {string} prop
175-
* @param {unknown} value
176-
* @param {Context} ctx
177-
* @param {string} name
178-
*/
179-
// eslint-disable-next-line complexity, max-params
180-
function addAttribute(props, prop, value, ctx, name) {
181-
const info = find(ctx.schema, prop)
182-
/** @type {string|undefined} */
183-
let subprop
184-
185-
// Ignore nullish and `NaN` values.
186-
// Ignore `false` and falsey known booleans for hyperlike DSLs.
187-
if (
188-
value === undefined ||
189-
value === null ||
190-
(typeof value === 'number' && Number.isNaN(value)) ||
191-
(value === false && (ctx.vue || ctx.vdom || ctx.hyperscript)) ||
192-
(!value && info.boolean && (ctx.vue || ctx.vdom || ctx.hyperscript))
193-
) {
194-
return
195-
}
196-
197-
if (Array.isArray(value)) {
198-
// Accept `array`.
199-
// Most props are space-separated.
200-
value = info.commaSeparated ? commas(value) : spaces(value)
201-
}
202-
203-
// Treat `true` and truthy known booleans.
204-
if (info.boolean && ctx.hyperscript) {
205-
value = ''
206-
}
207-
208-
// VDOM, Vue, and React accept `style` as object.
209-
if (
210-
info.property === 'style' &&
211-
typeof value === 'string' &&
212-
(ctx.react || ctx.vue || ctx.vdom)
213-
) {
214-
value = parseStyle(value, name)
215-
}
216-
217-
// Vue 3 (used in our tests) doesn’t need this anymore.
218-
// Some major in the future we can drop Vue 2 support.
219-
/* c8 ignore next 2 */
220-
if (ctx.vue) {
221-
if (info.property !== 'style') subprop = 'attrs'
222-
} else if (!info.mustUseProperty) {
223-
if (ctx.vdom) {
224-
if (info.property !== 'style') subprop = 'attributes'
225-
} else if (ctx.hyperscript) {
226-
subprop = 'attrs'
227-
}
228-
}
229-
230-
if (subprop) {
231-
props[subprop] = Object.assign(props[subprop] || {}, {
232-
[info.attribute]: value
233-
})
234-
} else if (info.space && ctx.react) {
235-
props[toReact[info.property] || info.property] = value
236-
} else {
237-
props[info.attribute] = value
238-
}
239-
}
240-
241-
/**
242-
* Check if `h` is `react.createElement`.
243-
*
244-
* @param {CreateElementLike} h
245-
* @returns {boolean}
246-
*/
247-
function react(h) {
248-
const node = /** @type {unknown} */ (h('div', {}))
249-
return Boolean(
250-
node &&
251-
// @ts-expect-error Looks like a React node.
252-
('_owner' in node || '_store' in node) &&
253-
// @ts-expect-error Looks like a React node.
254-
(node.key === undefined || node.key === null)
255-
)
256-
}
257-
258-
/**
259-
* Check if `h` is `hyperscript`.
260-
*
261-
* @param {CreateElementLike} h
262-
* @returns {boolean}
263-
*/
264-
function hyperscript(h) {
265-
return 'context' in h && 'cleanup' in h
266-
}
267-
268-
/**
269-
* Check if `h` is `virtual-dom/h`.
270-
*
271-
* @param {CreateElementLike} h
272-
* @returns {boolean}
273-
*/
274-
function vdom(h) {
275-
const node = /** @type {unknown} */ (h('div', {}))
276-
// @ts-expect-error Looks like a vnode.
277-
return node.type === 'VirtualNode'
278-
}
279-
280-
/**
281-
* Check if `h` is Vue.
282-
*
283-
* @param {CreateElementLike} h
284-
* @returns {boolean}
285-
*/
286-
function vue(h) {
287-
// Vue 3 (used in our tests) doesn’t need this anymore.
288-
// Some major in the future we can drop Vue 2 support.
289-
/* c8 ignore next 3 */
290-
const node = /** @type {unknown} */ (h('div', {}))
291-
// @ts-expect-error Looks like a Vue node.
292-
return Boolean(node && node.context && node.context._isVue)
293-
}
294-
295-
/**
296-
* @param {string} value
297-
* @param {string} tagName
298-
* @returns {Record<string, string>}
299-
*/
300-
function parseStyle(value, tagName) {
301-
/** @type {Record<string, string>} */
302-
const result = {}
303-
304-
try {
305-
style(value, (name, value) => {
306-
if (name.slice(0, 4) === '-ms-') name = 'ms-' + name.slice(4)
307-
308-
result[
309-
name.replace(
310-
/-([a-z])/g,
311-
/**
312-
* @param {string} _
313-
* @param {string} $1
314-
* @returns {string}
315-
*/
316-
(_, $1) => $1.toUpperCase()
317-
)
318-
] = value
319-
})
320-
} catch (error_) {
321-
const error = /** @type {Error} */ (error_)
322-
error.message =
323-
tagName + '[style]' + error.message.slice('undefined'.length)
324-
throw error
325-
}
326-
327-
return result
328-
}
6+
export {toH} from './lib/index.js'

0 commit comments

Comments
 (0)