1 | 'use strict'
|
---|
2 |
|
---|
3 | var find = require('property-information/find')
|
---|
4 | var normalize = require('property-information/normalize')
|
---|
5 | var parseSelector = require('hast-util-parse-selector')
|
---|
6 | var spaces = require('space-separated-tokens').parse
|
---|
7 | var commas = require('comma-separated-tokens').parse
|
---|
8 |
|
---|
9 | module.exports = factory
|
---|
10 |
|
---|
11 | var own = {}.hasOwnProperty
|
---|
12 |
|
---|
13 | function factory(schema, defaultTagName, caseSensitive) {
|
---|
14 | var adjust = caseSensitive ? createAdjustMap(caseSensitive) : null
|
---|
15 |
|
---|
16 | return h
|
---|
17 |
|
---|
18 | // Hyperscript compatible DSL for creating virtual hast trees.
|
---|
19 | function h(selector, properties) {
|
---|
20 | var node = parseSelector(selector, defaultTagName)
|
---|
21 | var children = Array.prototype.slice.call(arguments, 2)
|
---|
22 | var name = node.tagName.toLowerCase()
|
---|
23 | var property
|
---|
24 |
|
---|
25 | node.tagName = adjust && own.call(adjust, name) ? adjust[name] : name
|
---|
26 |
|
---|
27 | if (properties && isChildren(properties, node)) {
|
---|
28 | children.unshift(properties)
|
---|
29 | properties = null
|
---|
30 | }
|
---|
31 |
|
---|
32 | if (properties) {
|
---|
33 | for (property in properties) {
|
---|
34 | addProperty(node.properties, property, properties[property])
|
---|
35 | }
|
---|
36 | }
|
---|
37 |
|
---|
38 | addChild(node.children, children)
|
---|
39 |
|
---|
40 | if (node.tagName === 'template') {
|
---|
41 | node.content = {type: 'root', children: node.children}
|
---|
42 | node.children = []
|
---|
43 | }
|
---|
44 |
|
---|
45 | return node
|
---|
46 | }
|
---|
47 |
|
---|
48 | function addProperty(properties, key, value) {
|
---|
49 | var info
|
---|
50 | var property
|
---|
51 | var result
|
---|
52 |
|
---|
53 | // Ignore nullish and NaN values.
|
---|
54 | if (value === null || value === undefined || value !== value) {
|
---|
55 | return
|
---|
56 | }
|
---|
57 |
|
---|
58 | info = find(schema, key)
|
---|
59 | property = info.property
|
---|
60 | result = value
|
---|
61 |
|
---|
62 | // Handle list values.
|
---|
63 | if (typeof result === 'string') {
|
---|
64 | if (info.spaceSeparated) {
|
---|
65 | result = spaces(result)
|
---|
66 | } else if (info.commaSeparated) {
|
---|
67 | result = commas(result)
|
---|
68 | } else if (info.commaOrSpaceSeparated) {
|
---|
69 | result = spaces(commas(result).join(' '))
|
---|
70 | }
|
---|
71 | }
|
---|
72 |
|
---|
73 | // Accept `object` on style.
|
---|
74 | if (property === 'style' && typeof value !== 'string') {
|
---|
75 | result = style(result)
|
---|
76 | }
|
---|
77 |
|
---|
78 | // Class-names (which can be added both on the `selector` and here).
|
---|
79 | if (property === 'className' && properties.className) {
|
---|
80 | result = properties.className.concat(result)
|
---|
81 | }
|
---|
82 |
|
---|
83 | properties[property] = parsePrimitives(info, property, result)
|
---|
84 | }
|
---|
85 | }
|
---|
86 |
|
---|
87 | function isChildren(value, node) {
|
---|
88 | return (
|
---|
89 | typeof value === 'string' ||
|
---|
90 | 'length' in value ||
|
---|
91 | isNode(node.tagName, value)
|
---|
92 | )
|
---|
93 | }
|
---|
94 |
|
---|
95 | function isNode(tagName, value) {
|
---|
96 | var type = value.type
|
---|
97 |
|
---|
98 | if (tagName === 'input' || !type || typeof type !== 'string') {
|
---|
99 | return false
|
---|
100 | }
|
---|
101 |
|
---|
102 | if (typeof value.children === 'object' && 'length' in value.children) {
|
---|
103 | return true
|
---|
104 | }
|
---|
105 |
|
---|
106 | type = type.toLowerCase()
|
---|
107 |
|
---|
108 | if (tagName === 'button') {
|
---|
109 | return (
|
---|
110 | type !== 'menu' &&
|
---|
111 | type !== 'submit' &&
|
---|
112 | type !== 'reset' &&
|
---|
113 | type !== 'button'
|
---|
114 | )
|
---|
115 | }
|
---|
116 |
|
---|
117 | return 'value' in value
|
---|
118 | }
|
---|
119 |
|
---|
120 | function addChild(nodes, value) {
|
---|
121 | var index
|
---|
122 | var length
|
---|
123 |
|
---|
124 | if (typeof value === 'string' || typeof value === 'number') {
|
---|
125 | nodes.push({type: 'text', value: String(value)})
|
---|
126 | return
|
---|
127 | }
|
---|
128 |
|
---|
129 | if (typeof value === 'object' && 'length' in value) {
|
---|
130 | index = -1
|
---|
131 | length = value.length
|
---|
132 |
|
---|
133 | while (++index < length) {
|
---|
134 | addChild(nodes, value[index])
|
---|
135 | }
|
---|
136 |
|
---|
137 | return
|
---|
138 | }
|
---|
139 |
|
---|
140 | if (typeof value !== 'object' || !('type' in value)) {
|
---|
141 | throw new Error('Expected node, nodes, or string, got `' + value + '`')
|
---|
142 | }
|
---|
143 |
|
---|
144 | nodes.push(value)
|
---|
145 | }
|
---|
146 |
|
---|
147 | // Parse a (list of) primitives.
|
---|
148 | function parsePrimitives(info, name, value) {
|
---|
149 | var index
|
---|
150 | var length
|
---|
151 | var result
|
---|
152 |
|
---|
153 | if (typeof value !== 'object' || !('length' in value)) {
|
---|
154 | return parsePrimitive(info, name, value)
|
---|
155 | }
|
---|
156 |
|
---|
157 | length = value.length
|
---|
158 | index = -1
|
---|
159 | result = []
|
---|
160 |
|
---|
161 | while (++index < length) {
|
---|
162 | result[index] = parsePrimitive(info, name, value[index])
|
---|
163 | }
|
---|
164 |
|
---|
165 | return result
|
---|
166 | }
|
---|
167 |
|
---|
168 | // Parse a single primitives.
|
---|
169 | function parsePrimitive(info, name, value) {
|
---|
170 | var result = value
|
---|
171 |
|
---|
172 | if (info.number || info.positiveNumber) {
|
---|
173 | if (!isNaN(result) && result !== '') {
|
---|
174 | result = Number(result)
|
---|
175 | }
|
---|
176 | } else if (info.boolean || info.overloadedBoolean) {
|
---|
177 | // Accept `boolean` and `string`.
|
---|
178 | if (
|
---|
179 | typeof result === 'string' &&
|
---|
180 | (result === '' || normalize(value) === normalize(name))
|
---|
181 | ) {
|
---|
182 | result = true
|
---|
183 | }
|
---|
184 | }
|
---|
185 |
|
---|
186 | return result
|
---|
187 | }
|
---|
188 |
|
---|
189 | function style(value) {
|
---|
190 | var result = []
|
---|
191 | var key
|
---|
192 |
|
---|
193 | for (key in value) {
|
---|
194 | result.push([key, value[key]].join(': '))
|
---|
195 | }
|
---|
196 |
|
---|
197 | return result.join('; ')
|
---|
198 | }
|
---|
199 |
|
---|
200 | function createAdjustMap(values) {
|
---|
201 | var length = values.length
|
---|
202 | var index = -1
|
---|
203 | var result = {}
|
---|
204 | var value
|
---|
205 |
|
---|
206 | while (++index < length) {
|
---|
207 | value = values[index]
|
---|
208 | result[value.toLowerCase()] = value
|
---|
209 | }
|
---|
210 |
|
---|
211 | return result
|
---|
212 | }
|
---|