Skip to content

Commit a12dc92

Browse files
committed
Add support for passing props as HTML attributes
Previously, all properties from elements were passed using React casing. This does not work, particularly for complex cases such as SVG and XML, in any other JSX runtime. Instead, all other JSX runtimes converge on allowing HTML casing. This adds an option for that. Related-to: #6. Related-to: #7.
1 parent d3afe85 commit a12dc92

File tree

5 files changed

+72
-24
lines changed

5 files changed

+72
-24
lines changed

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/**
2+
* @typedef {import('./lib/state.js').ElementAttributeNameCase} ElementAttributeNameCase
23
* @typedef {import('./lib/state.js').Handle} Handle
34
* @typedef {import('./lib/state.js').Options} Options
45
* @typedef {import('./lib/state.js').Space} Space

lib/handlers/element.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,10 @@ export function element(node, state) {
6565
continue
6666
}
6767

68-
prop = info.space
69-
? hastToReact[info.property] || info.property
70-
: info.attribute
68+
prop =
69+
state.elementAttributeNameCase === 'react' && info.space
70+
? hastToReact[info.property] || info.property
71+
: info.attribute
7172

7273
if (Array.isArray(value)) {
7374
// Accept `array`.

lib/state.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,34 @@
4747
* @returns {JsxChild | null | undefined | void}
4848
* estree node.
4949
*
50+
* @typedef {'react' | 'html'} ElementAttributeNameCase
51+
* Specify casing to use for attribute names.
52+
*
53+
* React casing is for example `className`, `strokeLinecap`, `xmlLang`.
54+
* HTML casing is for example `class`, `stroke-linecap`, `xml:lang`.
55+
*
5056
* @typedef Options
5157
* Configuration.
58+
* @property {ElementAttributeNameCase | null | undefined} [elementAttributeNameCase='react']
59+
* Casing to use for attribute names.
60+
*
61+
* This casing is used for hast elements, not for embedded MDX JSX nodes
62+
* (components that someone authored manually).
63+
* @property {Record<string, Handle | null | undefined> | null | undefined} [handlers={}]
64+
* Custom handlers.
5265
* @property {Space | null | undefined} [space='html']
5366
* Which space the document is in.
5467
*
5568
* When an `<svg>` element is found in the HTML space, this package already
5669
* automatically switches to and from the SVG space when entering and exiting
5770
* it.
58-
* @property {Record<string, Handle | null | undefined> | null | undefined} [handlers={}]
59-
* Custom handlers.
6071
*
6172
* @typedef State
6273
* Info passed around about the current state.
6374
* @property {Schema} schema
6475
* Current schema.
76+
* @property {ElementAttributeNameCase} elementAttributeNameCase
77+
* Casing to use for attribute names.
6578
* @property {Array<Comment>} comments
6679
* List of estree comments.
6780
* @property {Array<Directive | Statement | ModuleDeclaration>} esm
@@ -117,6 +130,7 @@ export function createState(options) {
117130
return {
118131
// Current space.
119132
schema: options.space === 'svg' ? svg : html,
133+
elementAttributeNameCase: options.elementAttributeNameCase || 'react',
120134
// Results.
121135
comments: [],
122136
esm: [],

readme.md

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
* [API](#api)
2020
* [`toEstree(tree[, options])`](#toestreetree-options)
2121
* [`defaultHandlers`](#defaulthandlers)
22+
* [`ElementAttributeNameCase`](#elementattributenamecase)
2223
* [`Handle`](#handle)
2324
* [`Options`](#options)
24-
* [`Space`](#space-1)
25+
* [`Space`](#space)
2526
* [`State`](#state)
2627
* [Types](#types)
2728
* [Compatibility](#compatibility)
@@ -193,6 +194,19 @@ Default handlers for elements (`Record<string, Handle>`).
193194

194195
Each key is a node type, each value is a [`Handle`][handle].
195196

197+
### `ElementAttributeNameCase`
198+
199+
Specify casing to use for attribute names (TypeScript type).
200+
201+
React casing is for example `className`, `strokeLinecap`, `xmlLang`.
202+
HTML casing is for example `class`, `stroke-linecap`, `xml:lang`.
203+
204+
###### Type
205+
206+
```ts
207+
type ElementAttributeNameCase = 'react' | 'html'
208+
```
209+
196210
### `Handle`
197211
198212
Turn a hast node into an estree node (TypeScript type).
@@ -214,23 +228,20 @@ You can also add more results to `state.esm` and `state.comments`.
214228
215229
Configuration (TypeScript type).
216230
217-
##### Fields
218-
219-
###### `space`
220-
221-
Which space the document is in ([`Space`][space], default: `'html'`).
222-
223-
When an `<svg>` element is found in the HTML space, this package already
224-
automatically switches to and from the SVG space when entering and exiting
225-
it.
226-
227-
###### `handlers`
228-
229-
Object mapping node types to functions handling the corresponding nodes
230-
(`Record<string, Handle>`, optional).
231+
###### Fields
231232
232-
Merged into the defaults.
233-
See [`Handle`][handle].
233+
* `elementAttributeNameCase`
234+
([`ElementAttributeNameCase`][api-element-attribute-name-case], default:
235+
`'react'`)
236+
— specify casing to use for attribute names; this casing is used for hast
237+
elements, not for embedded MDX JSX nodes (components that someone authored
238+
manually)
239+
* `handlers` (`Record<string, Handle>`, optional)
240+
— custom handlers
241+
* `space` ([`Space`][space], default: `'html'`).
242+
which space the document is in; when an `<svg>` element is found in the
243+
HTML space, this package already automatically switches to and from the SVG
244+
space when entering and exiting it
234245
235246
### `Space`
236247
@@ -250,6 +261,9 @@ Info passed around about the current state (TypeScript type).
250261
251262
* `schema` ([`Schema`][schema])
252263
— current schema
264+
* `elementAttributeNameCase`
265+
([`ElementAttributeNameCase`][api-element-attribute-name-case])
266+
— casing to use for attribute names
253267
* `comments` (`Array<EstreeComment>`)
254268
— list of estree comments
255269
* `esm` (`Array<EstreeNode>`)
@@ -271,7 +285,9 @@ Info passed around about the current state (TypeScript type).
271285
## Types
272286
273287
This package is fully typed with [TypeScript][].
274-
It exports the additional types [`Handle`][handle], [`Options`][options],
288+
It exports the additional types
289+
[`ElementAttributeNameCase`][api-element-attribute-name-case],
290+
[`Handle`][handle], [`Options`][options],
275291
[`Space`][space], and [`State`][state].
276292
277293
## Compatibility
@@ -381,7 +397,9 @@ abide by its terms.
381397
382398
[toestree]: #toestreetree-options
383399
384-
[space]: #space-1
400+
[api-element-attribute-name-case]: #elementattributenamecase
401+
402+
[space]: #space
385403
386404
[options]: #options
387405

test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,20 @@ test('toEstree', () => {
597597
},
598598
'should ignore text line endings between table elements'
599599
)
600+
601+
assert.equal(
602+
toJs(toEstree(h('#a.b.c', 'd')), {handlers: jsx}).value,
603+
'<div id="a" className="b c">{"d"}</div>;\n',
604+
'should use react casing for element attributes by default'
605+
)
606+
607+
assert.equal(
608+
toJs(toEstree(h('#a.b.c', 'd'), {elementAttributeNameCase: 'html'}), {
609+
handlers: jsx
610+
}).value,
611+
'<div id="a" class="b c">{"d"}</div>;\n',
612+
"should support `elementAttributeNameCase: 'html'`"
613+
)
600614
})
601615

602616
test('integration (babel)', () => {

0 commit comments

Comments
 (0)