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

Commit 409cab7

Browse files
committed
Add support for SVG
Related to wooorm/property-information#6.
1 parent 5a19ac1 commit 409cab7

File tree

4 files changed

+385
-239
lines changed

4 files changed

+385
-239
lines changed

index.js

Lines changed: 85 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
'use strict'
22

33
var trim = require('trim')
4-
var paramCase = require('kebab-case')
5-
var information = require('property-information')
4+
var html = require('property-information/html')
5+
var svg = require('property-information/svg')
6+
var find = require('property-information/find')
67
var spaces = require('space-separated-tokens')
78
var commas = require('comma-separated-tokens')
89
var nan = require('is-nan')
10+
var ns = require('web-namespaces')
911
var is = require('unist-util-is')
1012

13+
var dashes = /-([a-z])/g
14+
1115
module.exports = wrapper
1216

13-
function wrapper(h, node, prefix) {
17+
function wrapper(h, node, options) {
18+
var settings = options || {}
19+
var prefix
1420
var r
1521
var v
1622

1723
if (typeof h !== 'function') {
1824
throw new Error('h is not a function')
1925
}
2026

27+
if (typeof settings === 'string' || typeof settings === 'boolean') {
28+
prefix = settings
29+
settings = {}
30+
} else {
31+
prefix = settings.prefix
32+
}
33+
2134
r = react(h)
2235
v = vdom(h)
2336

@@ -43,6 +56,7 @@ function wrapper(h, node, prefix) {
4356
}
4457

4558
return toH(h, node, {
59+
schema: settings.space === 'svg' ? svg : html,
4660
prefix: prefix,
4761
key: 0,
4862
react: r,
@@ -51,10 +65,12 @@ function wrapper(h, node, prefix) {
5165
})
5266
}
5367

54-
/* Transform a HAST node through a hyperscript interface
55-
* to *anything*! */
68+
// Transform a HAST node through a hyperscript interface
69+
// to *anything*!
5670
function toH(h, node, ctx) {
57-
var selector = node.tagName
71+
var parentSchema = ctx.schema
72+
var schema = parentSchema
73+
var name = node.tagName
5874
var properties
5975
var attributes
6076
var children
@@ -63,53 +79,44 @@ function toH(h, node, ctx) {
6379
var length
6480
var index
6581
var value
82+
var result
6683

67-
properties = node.properties
68-
attributes = {}
69-
70-
for (property in properties) {
71-
addAttribute(attributes, property, properties[property], ctx)
84+
if (parentSchema.space === 'html' && lower(name) === 'svg') {
85+
schema = svg
86+
ctx.schema = schema
7287
}
7388

74-
if (ctx.vdom === true) {
75-
selector = selector.toUpperCase()
89+
if (ctx.vdom === true && schema.space === 'html') {
90+
name = upper(name)
7691
}
7792

78-
if (ctx.hyperscript === true && attributes.id) {
79-
selector += '#' + attributes.id
80-
delete attributes.id
81-
}
93+
properties = node.properties
94+
attributes = {}
8295

83-
if ((ctx.hyperscript === true || ctx.vdom === true) && attributes.className) {
84-
selector += '.' + spaces.parse(attributes.className).join('.')
85-
delete attributes.className
96+
for (property in properties) {
97+
addAttribute(attributes, property, properties[property], ctx)
8698
}
8799

88-
if (typeof attributes.style === 'string') {
89-
/* VDOM expects a `string` style in `attributes`
90-
* See https://github.com/Matt-Esch/virtual-dom/blob/947ecf9/
91-
* docs/vnode.md#propertiesstyle-vs-propertiesattributesstyle */
92-
if (ctx.vdom === true) {
93-
if (!attributes.attributes) {
94-
attributes.attributes = {}
95-
}
96-
97-
attributes.attributes.style = attributes.style
98-
delete attributes.style
99-
/* React only accepts `style` as object. */
100-
} else if (ctx.react === true) {
101-
attributes.style = parseStyle(attributes.style)
102-
}
100+
if (
101+
typeof attributes.style === 'string' &&
102+
(ctx.vdom === true || ctx.react === true)
103+
) {
104+
// VDOM and React accept `style` as object.
105+
attributes.style = parseStyle(attributes.style)
103106
}
104107

105108
if (ctx.prefix) {
106109
ctx.key++
107110
attributes.key = ctx.prefix + ctx.key
108111
}
109112

113+
if (ctx.vdom && schema.space !== 'html') {
114+
attributes.namespace = ns[schema.space]
115+
}
116+
110117
elements = []
111-
children = node.children || []
112-
length = children.length
118+
children = node.children
119+
length = children ? children.length : 0
113120
index = -1
114121

115122
while (++index < length) {
@@ -122,20 +129,23 @@ function toH(h, node, ctx) {
122129
}
123130
}
124131

125-
/* Ensure no React warnings are triggered for
126-
* void elements having children passed in. */
127-
return elements.length === 0
128-
? h(selector, attributes)
129-
: h(selector, attributes, elements)
132+
// Ensure no React warnings are triggered for
133+
// void elements having children passed in.
134+
result =
135+
elements.length === 0 ? h(name, attributes) : h(name, attributes, elements)
136+
137+
// Restore parent schema.
138+
ctx.schema = parentSchema
139+
140+
return result
130141
}
131142

132-
/* Add `name` and its `value` to `props`. */
133-
function addAttribute(props, name, value, ctx) {
134-
var info = information(name) || {}
143+
function addAttribute(props, prop, value, ctx) {
144+
var schema = ctx.schema
145+
var info = find(schema, prop)
135146
var subprop
136147

137-
/* Ignore nully, `false`, `NaN`, and falsey known
138-
* booleans. */
148+
// Ignore nully, `false`, `NaN`, and falsey known booleans.
139149
if (
140150
value === null ||
141151
value === undefined ||
@@ -146,73 +156,51 @@ function addAttribute(props, name, value, ctx) {
146156
return
147157
}
148158

149-
if (info.name) {
150-
name = info.name
151-
} else if (ctx.react && !paramCasedReactProp(name)) {
152-
name = camelCase(name)
153-
} else {
154-
name = paramCase(name)
155-
}
156-
157159
if (value !== null && typeof value === 'object' && 'length' in value) {
158-
/* Accept `array`. Most props are space-separater. */
160+
// Accept `array`. Most props are space-separater.
159161
value = (info.commaSeparated ? commas : spaces).stringify(value)
160162
}
161163

162-
/* Treat `true` and truthy known booleans. */
164+
// Treat `true` and truthy known booleans.
163165
if (info.boolean && ctx.hyperscript === true) {
164166
value = ''
165167
}
166168

167-
if (info.name !== 'class' && (info.mustUseAttribute || !info.name)) {
169+
if (!info.mustUseProperty) {
168170
if (ctx.vdom === true) {
169171
subprop = 'attributes'
170172
} else if (ctx.hyperscript === true) {
171173
subprop = 'attrs'
172174
}
175+
}
173176

174-
if (subprop) {
175-
if (props[subprop] === undefined) {
176-
props[subprop] = {}
177-
}
178-
179-
props[subprop][name] = value
180-
181-
return
177+
if (subprop) {
178+
if (props[subprop] === undefined) {
179+
props[subprop] = {}
182180
}
183-
}
184181

185-
props[info.propertyName || name] = value
182+
props[subprop][info.attribute] = value
183+
} else {
184+
props[ctx.react && info.space ? info.property : info.attribute] = value
185+
}
186186
}
187187

188-
/* Check if `h` is `react.createElement`. It doesn’t accept
189-
* `class` as an attribute, it must be added through the
190-
* `selector`. */
188+
// Check if `h` is `react.createElement`.
191189
function react(h) {
192190
var node = h && h('div')
193191
return Boolean(
194192
node && ('_owner' in node || '_store' in node) && node.key === null
195193
)
196194
}
197195

198-
/* Check if `h` is `hyperscript`. It doesn’t accept
199-
* `class` as an attribute, it must be added through the
200-
* `selector`. */
196+
// Check if `h` is `hyperscript`.
201197
function hyperscript(h) {
202198
return Boolean(h && h.context && h.cleanup)
203199
}
204200

205-
/* Check if `h` is `virtual-dom/h`. It’s the only
206-
* hyperscript “compatible” interface needing `attributes`. */
201+
// Check if `h` is `virtual-dom/h`.
207202
function vdom(h) {
208-
try {
209-
return h('div').type === 'VirtualNode'
210-
} catch (err) {
211-
/* Empty */
212-
}
213-
214-
/* istanbul ignore next */
215-
return false
203+
return h && h('div').type === 'VirtualNode'
216204
}
217205

218206
function parseStyle(value) {
@@ -228,27 +216,30 @@ function parseStyle(value) {
228216
declaration = declarations[index]
229217
pos = declaration.indexOf(':')
230218
if (pos !== -1) {
231-
prop = camelCase(trim(declaration.slice(0, pos)))
219+
prop = styleCase(trim(declaration.slice(0, pos)))
232220
result[prop] = trim(declaration.slice(pos + 1))
233221
}
234222
}
235223

236224
return result
237225
}
238226

239-
function paramCasedReactProp(name) {
240-
var head = name.slice(0, 4)
241-
return (head === 'data' || head === 'aria') && name.length > 4
242-
}
243-
244-
function camelCase(val) {
227+
function styleCase(val) {
245228
if (val.slice(0, 4) === '-ms-') {
246229
val = 'ms-' + val.slice(4)
247230
}
248231

249-
return val.replace(/-([a-z])/g, replace)
232+
return val.replace(dashes, styleReplacer)
233+
}
234+
235+
function styleReplacer($0, $1) {
236+
return upper($1)
237+
}
238+
239+
function lower(value) {
240+
return value.toLowerCase()
250241
}
251242

252-
function replace($0, $1) {
253-
return $1.toUpperCase()
243+
function upper(value) {
244+
return value.toUpperCase()
254245
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
"dependencies": {
2828
"comma-separated-tokens": "^1.0.0",
2929
"is-nan": "^1.2.1",
30-
"kebab-case": "^1.0.0",
31-
"property-information": "^3.0.0",
30+
"property-information": "^4.0.0",
3231
"space-separated-tokens": "^1.0.0",
3332
"trim": "0.0.1",
34-
"unist-util-is": "^2.0.0"
33+
"unist-util-is": "^2.0.0",
34+
"web-namespaces": "^1.1.2"
3535
},
3636
"devDependencies": {
3737
"browserify": "^16.0.0",

readme.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,25 @@ Yields:
4646

4747
## API
4848

49-
### `toH(h, node[, prefix])`
49+
### `toH(h, node[, options|prefix])`
5050

5151
Transform [HAST][] to something else through a [hyperscript][] DSL.
5252

5353
###### Parameters
5454

5555
* `h` ([`Function`][h])
5656
* `node` ([`Element`][element])
57-
* `prefix` (`string` or `boolean`, optional)
57+
* `prefix` — Treated as `{prefix: prefix}`
58+
* `options.prefix` (`string` or `boolean`, optional)
5859
— Prefix to use as a prefix for keys passed in `attrs` to `h()`,
5960
this behaviour is turned off by passing `false`, turned on by passing
6061
a `string`. By default, `h-` is used as a prefix if the given `h`
6162
is detected as being `virtual-dom/h` or `React.createElement`
63+
* `options.space` (enum, `'svg'` or `'html'`, default: `'html'`)
64+
— Whether `node` is in the `'html'` or `'svg'` space.
65+
If an `svg` element is found when inside the HTML space, `toH` automatically
66+
switches to the SVG space when entering the element, and switches back when
67+
leaving
6268

6369
###### Returns
6470

0 commit comments

Comments
 (0)