Skip to content

Commit dc1a006

Browse files
committed
Add tableCellAlignToStyle option
1 parent 32b3972 commit dc1a006

File tree

4 files changed

+206
-6
lines changed

4 files changed

+206
-6
lines changed

lib/handlers/element.js

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const own = {}.hasOwnProperty
2424
const cap = /[A-Z]/g
2525
const dashSomething = /-([a-z])/g
2626

27+
const tableCellElement = new Set(['td', 'th'])
28+
2729
/**
2830
* Turn a hast element into an estree node.
2931
*
@@ -51,6 +53,10 @@ export function element(node, state) {
5153
const attributes = []
5254
/** @type {string} */
5355
let prop
56+
/** @type {string | undefined} */
57+
let alignValue
58+
/** @type {Array<Property> | undefined} */
59+
let styleProperties
5460

5561
for (prop in props) {
5662
if (own.call(props, prop)) {
@@ -89,7 +95,7 @@ export function element(node, state) {
8995
: parseStyle(String(value), node.tagName)
9096

9197
if (state.stylePropertyNameCase === 'css') {
92-
styleObject = transformStyleToCssCasing(styleObject)
98+
styleObject = transformStylesToCssCasing(styleObject)
9399
}
94100

95101
/** @type {Array<Property>} */
@@ -114,12 +120,20 @@ export function element(node, state) {
114120
}
115121
}
116122

123+
styleProperties = cssProperties
117124
attributeValue = {
118125
type: 'JSXExpressionContainer',
119126
expression: {type: 'ObjectExpression', properties: cssProperties}
120127
}
121128
} else if (value === true) {
122129
attributeValue = null
130+
} else if (
131+
state.tableCellAlignToStyle &&
132+
tableCellElement.has(node.tagName) &&
133+
prop === 'align'
134+
) {
135+
alignValue = String(value)
136+
continue
123137
} else {
124138
attributeValue = {type: 'Literal', value: String(value)}
125139
}
@@ -154,6 +168,37 @@ export function element(node, state) {
154168
}
155169
}
156170

171+
if (alignValue !== undefined) {
172+
if (!styleProperties) {
173+
styleProperties = []
174+
attributes.push({
175+
type: 'JSXAttribute',
176+
name: {type: 'JSXIdentifier', name: 'style'},
177+
value: {
178+
type: 'JSXExpressionContainer',
179+
expression: {type: 'ObjectExpression', properties: styleProperties}
180+
}
181+
})
182+
}
183+
184+
const cssProp =
185+
state.stylePropertyNameCase === 'css'
186+
? transformStyleToCssCasing('textAlign')
187+
: 'textAlign'
188+
189+
styleProperties.push({
190+
type: 'Property',
191+
method: false,
192+
shorthand: false,
193+
computed: false,
194+
key: identifierName(cssProp)
195+
? {type: 'Identifier', name: cssProp}
196+
: {type: 'Literal', value: cssProp},
197+
value: {type: 'Literal', value: alignValue},
198+
kind: 'init'
199+
})
200+
}
201+
157202
// Restore parent schema.
158203
state.schema = parentSchema
159204

@@ -235,24 +280,34 @@ function parseStyle(value, tagName) {
235280
* @param {Style} domCasing
236281
* @returns {Style}
237282
*/
238-
function transformStyleToCssCasing(domCasing) {
283+
function transformStylesToCssCasing(domCasing) {
239284
/** @type {Style} */
240285
const cssCasing = {}
241286
/** @type {string} */
242287
let from
243288

244289
for (from in domCasing) {
245290
if (own.call(domCasing, from)) {
246-
let to = from.replace(cap, toDash)
247-
// Handle `ms-xxx` -> `-ms-xxx`.
248-
if (to.slice(0, 3) === 'ms-') to = '-' + to
249-
cssCasing[to] = domCasing[from]
291+
cssCasing[transformStyleToCssCasing(from)] = domCasing[from]
250292
}
251293
}
252294

253295
return cssCasing
254296
}
255297

298+
/**
299+
* Transform a DOM casing style prop to a CSS casing style prop.
300+
*
301+
* @param {string} from
302+
* @returns {string}
303+
*/
304+
function transformStyleToCssCasing(from) {
305+
let to = from.replace(cap, toDash)
306+
// Handle `ms-xxx` -> `-ms-xxx`.
307+
if (to.slice(0, 3) === 'ms-') to = '-' + to
308+
return to
309+
}
310+
256311
/**
257312
* Make `$1` capitalized.
258313
*

lib/state.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
*
6464
* This casing is used for hast elements, not for embedded MDX JSX nodes
6565
* (components that someone authored manually).
66+
* @property {boolean | null | undefined} [tableCellAlignToStyle=true]
67+
* Turn obsolete `align` props on `td` and `th` into CSS `style` props
68+
* (default: `true`).
6669
*
6770
* @typedef {'html' | 'svg'} Space
6871
* Namespace.
@@ -97,6 +100,8 @@
97100
* Current schema.
98101
* @property {StylePropertyNameCase} stylePropertyNameCase
99102
* Casing to use for property names in `style` objects.
103+
* @property {boolean} tableCellAlignToStyle
104+
* Turn obsolete `align` props on `td` and `th` into CSS `style` props.
100105
*/
101106

102107
import {ok as assert} from 'devlop'
@@ -143,6 +148,7 @@ export function createState(options) {
143148
elementAttributeNameCase: options.elementAttributeNameCase || 'react',
144149
schema: options.space === 'svg' ? svg : html,
145150
stylePropertyNameCase: options.stylePropertyNameCase || 'dom',
151+
tableCellAlignToStyle: options.tableCellAlignToStyle !== false,
146152
// Results.
147153
comments: [],
148154
esm: [],

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ Configuration (TypeScript type).
268268
— specify casing to use for property names in `style` objects; this casing
269269
is used for hast elements, not for embedded MDX JSX nodes (components that
270270
someone authored manually)
271+
* `tableCellAlignToStyle` (`boolean`, default: `true`)
272+
— turn obsolete `align` props on `td` and `th` into CSS `style` props
271273
272274
### `Space`
273275

test.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,143 @@ test('toEstree', async function (t) {
680680
}
681681
)
682682

683+
await t.test(
684+
'should not transform `align` w/ `tableCellAlignToStyle: false`',
685+
async function () {
686+
assert.deepEqual(
687+
toEstree(h('th', {align: 'center'}), {tableCellAlignToStyle: false}),
688+
{
689+
type: 'Program',
690+
body: [
691+
{
692+
type: 'ExpressionStatement',
693+
expression: {
694+
type: 'JSXElement',
695+
openingElement: {
696+
type: 'JSXOpeningElement',
697+
attributes: [
698+
{
699+
type: 'JSXAttribute',
700+
name: {type: 'JSXIdentifier', name: 'align'},
701+
value: {type: 'Literal', value: 'center'}
702+
}
703+
],
704+
name: {type: 'JSXIdentifier', name: 'th'},
705+
selfClosing: true
706+
},
707+
closingElement: null,
708+
children: []
709+
}
710+
}
711+
],
712+
sourceType: 'module',
713+
comments: []
714+
}
715+
)
716+
}
717+
)
718+
719+
await t.test(
720+
'should transform `align` w/o `tableCellAlignToStyle`',
721+
async function () {
722+
assert.deepEqual(toEstree(h('th', {align: 'center'})), {
723+
type: 'Program',
724+
body: [
725+
{
726+
type: 'ExpressionStatement',
727+
expression: {
728+
type: 'JSXElement',
729+
openingElement: {
730+
type: 'JSXOpeningElement',
731+
attributes: [
732+
{
733+
type: 'JSXAttribute',
734+
name: {type: 'JSXIdentifier', name: 'style'},
735+
value: {
736+
type: 'JSXExpressionContainer',
737+
expression: {
738+
type: 'ObjectExpression',
739+
properties: [
740+
{
741+
type: 'Property',
742+
method: false,
743+
shorthand: false,
744+
computed: false,
745+
key: {type: 'Identifier', name: 'textAlign'},
746+
value: {type: 'Literal', value: 'center'},
747+
kind: 'init'
748+
}
749+
]
750+
}
751+
}
752+
}
753+
],
754+
name: {type: 'JSXIdentifier', name: 'th'},
755+
selfClosing: true
756+
},
757+
closingElement: null,
758+
children: []
759+
}
760+
}
761+
],
762+
sourceType: 'module',
763+
comments: []
764+
})
765+
}
766+
)
767+
768+
await t.test(
769+
"should support `tableCellAlignToStyle` w/ `stylePropertyNameCase: 'css'`",
770+
async function () {
771+
assert.deepEqual(
772+
toEstree(h('th', {align: 'center'}), {stylePropertyNameCase: 'css'}),
773+
{
774+
type: 'Program',
775+
body: [
776+
{
777+
type: 'ExpressionStatement',
778+
expression: {
779+
type: 'JSXElement',
780+
openingElement: {
781+
type: 'JSXOpeningElement',
782+
attributes: [
783+
{
784+
type: 'JSXAttribute',
785+
name: {type: 'JSXIdentifier', name: 'style'},
786+
value: {
787+
type: 'JSXExpressionContainer',
788+
expression: {
789+
type: 'ObjectExpression',
790+
properties: [
791+
{
792+
type: 'Property',
793+
method: false,
794+
shorthand: false,
795+
computed: false,
796+
key: {type: 'Literal', value: 'text-align'},
797+
value: {type: 'Literal', value: 'center'},
798+
kind: 'init'
799+
}
800+
]
801+
}
802+
}
803+
}
804+
],
805+
name: {type: 'JSXIdentifier', name: 'th'},
806+
selfClosing: true
807+
},
808+
closingElement: null,
809+
children: []
810+
}
811+
}
812+
],
813+
sourceType: 'module',
814+
comments: []
815+
}
816+
)
817+
}
818+
)
819+
683820
await t.test(
684821
'should use react casing for element attributes by default',
685822
async function () {

0 commit comments

Comments
 (0)