@starptech/prettyhtml-hastscript
Version:
Hyperscript compatible DSL for creating virtual HAST trees
179 lines (137 loc) • 4.12 kB
JavaScript
var find = require('property-information/find')
var parseSelector = require('hast-util-parse-selector')
var spaces = require('space-separated-tokens').parse
var commas = require('comma-separated-tokens').parse
module.exports = factory
function factory(schema, defaultTagName) {
return h
/* Hyperscript compatible DSL for creating virtual HAST trees. */
function h(selector, properties, children) {
var node = parseSelector(selector, defaultTagName)
var property
if (!children && properties && !properties[Symbol.for('hast.isProp')] && isChildren(properties, node)) {
children = properties
properties = null
}
if (properties) {
for (property in properties) {
addProperty(node.properties, property, properties[property])
}
}
addChild(node.children, children)
return node
}
function addProperty(properties, key, value) {
var info
var property
var result
/* Ignore nully and NaN values. */
// eslint-disable-next-line no-self-compare
if (value === null || value === undefined || value !== value) {
return
}
info = find(schema, key)
property = info.property
result = value
/* Handle list values. */
if (typeof result === 'string') {
if (info.spaceSeparated) {
result = spaces(result)
} else if (info.commaSeparated) {
result = commas(result)
} else if (info.commaOrSpaceSeparated) {
result = spaces(commas(result).join(' '))
}
}
/* Accept `object` on style. */
if (property === 'style' && typeof value !== 'string') {
result = style(result)
}
/* Class-names (which can be added both on the `selector` and here). */
if (property === 'className' && properties.className) {
result = properties.className.concat(result)
}
properties[property] = parsePrimitives(info, property, result)
}
}
// Value can be: string for text node, array for chilNodes
function isChildren(value, node) {
return typeof value === 'string' || 'length' in value || isNode(node.tagName, value)
}
function isNode(tagName, value) {
var type = value.type
if (tagName === 'input' || !type || typeof type !== 'string') {
return false
}
if (typeof value.children === 'object' && 'length' in value.children) {
return true
}
type = type.toLowerCase()
if (tagName === 'button') {
return type !== 'menu' && type !== 'submit' && type !== 'reset' && type !== 'button'
}
return 'value' in value
}
function addChild(nodes, value) {
var index
var length
if (value === null || value === undefined) {
return
}
if (typeof value === 'string' || typeof value === 'number') {
nodes.push({ type: 'text', value: String(value) })
return
}
if (typeof value === 'object' && 'length' in value) {
index = -1
length = value.length
while (++index < length) {
addChild(nodes, value[index])
}
return
}
if (typeof value !== 'object' || !('type' in value)) {
throw new Error('Expected node, nodes, or string, got `' + value + '`')
}
nodes.push(value)
}
/* Parse a (list of) primitives. */
function parsePrimitives(info, name, value) {
var index
var length
var result
if (typeof value !== 'object' || !('length' in value)) {
return parsePrimitive(info, name, value)
}
length = value.length
index = -1
result = []
while (++index < length) {
result[index] = parsePrimitive(info, name, value[index])
}
return result
}
/* Parse a single primitives. */
function parsePrimitive(info, name, value) {
var result = value
if (info.number || info.positiveNumber) {
if (!isNaN(result) && result !== '') {
result = Number(result)
}
} else if (info.boolean || info.overloadedBoolean) {
/* Accept `boolean` and `string`. */
if (typeof result === 'string' && result === '') {
result = true
}
}
return result
}
function style(value) {
var result = []
var key
for (key in value) {
result.push([key, value[key]].join(': '))
}
return result.join('; ')
}