diff --git a/packages/million/alias.ts b/packages/million/alias.ts new file mode 100644 index 0000000000..b64006d415 --- /dev/null +++ b/packages/million/alias.ts @@ -0,0 +1,99 @@ +/** + * From `getAttributeAlias.js` in React-DOM + * https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609ac7ff54a5c8/packages/react-dom-bindings/src/shared/getAttributeAlias.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +const aliases = new Map([ + ['acceptCharset', 'accept-charset'], + ['htmlFor', 'for'], + ['httpEquiv', 'http-equiv'], + // HTML and SVG attributes, but the SVG attribute is case sensitive.], + ['crossOrigin', 'crossorigin'], + // This is a list of all SVG attributes that need special casing. + // Regular attributes that just accept strings.], + ['accentHeight', 'accent-height'], + ['alignmentBaseline', 'alignment-baseline'], + ['arabicForm', 'arabic-form'], + ['baselineShift', 'baseline-shift'], + ['capHeight', 'cap-height'], + ['clipPath', 'clip-path'], + ['clipRule', 'clip-rule'], + ['colorInterpolation', 'color-interpolation'], + ['colorInterpolationFilters', 'color-interpolation-filters'], + ['colorProfile', 'color-profile'], + ['colorRendering', 'color-rendering'], + ['dominantBaseline', 'dominant-baseline'], + ['enableBackground', 'enable-background'], + ['fillOpacity', 'fill-opacity'], + ['fillRule', 'fill-rule'], + ['floodColor', 'flood-color'], + ['floodOpacity', 'flood-opacity'], + ['fontFamily', 'font-family'], + ['fontSize', 'font-size'], + ['fontSizeAdjust', 'font-size-adjust'], + ['fontStretch', 'font-stretch'], + ['fontStyle', 'font-style'], + ['fontVariant', 'font-variant'], + ['fontWeight', 'font-weight'], + ['glyphName', 'glyph-name'], + ['glyphOrientationHorizontal', 'glyph-orientation-horizontal'], + ['glyphOrientationVertical', 'glyph-orientation-vertical'], + ['horizAdvX', 'horiz-adv-x'], + ['horizOriginX', 'horiz-origin-x'], + ['imageRendering', 'image-rendering'], + ['letterSpacing', 'letter-spacing'], + ['lightingColor', 'lighting-color'], + ['markerEnd', 'marker-end'], + ['markerMid', 'marker-mid'], + ['markerStart', 'marker-start'], + ['overlinePosition', 'overline-position'], + ['overlineThickness', 'overline-thickness'], + ['paintOrder', 'paint-order'], + ['panose-1', 'panose-1'], + ['pointerEvents', 'pointer-events'], + ['renderingIntent', 'rendering-intent'], + ['shapeRendering', 'shape-rendering'], + ['stopColor', 'stop-color'], + ['stopOpacity', 'stop-opacity'], + ['strikethroughPosition', 'strikethrough-position'], + ['strikethroughThickness', 'strikethrough-thickness'], + ['strokeDasharray', 'stroke-dasharray'], + ['strokeDashoffset', 'stroke-dashoffset'], + ['strokeLinecap', 'stroke-linecap'], + ['strokeLinejoin', 'stroke-linejoin'], + ['strokeMiterlimit', 'stroke-miterlimit'], + ['strokeOpacity', 'stroke-opacity'], + ['strokeWidth', 'stroke-width'], + ['textAnchor', 'text-anchor'], + ['textDecoration', 'text-decoration'], + ['textRendering', 'text-rendering'], + ['transformOrigin', 'transform-origin'], + ['underlinePosition', 'underline-position'], + ['underlineThickness', 'underline-thickness'], + ['unicodeBidi', 'unicode-bidi'], + ['unicodeRange', 'unicode-range'], + ['unitsPerEm', 'units-per-em'], + ['vAlphabetic', 'v-alphabetic'], + ['vHanging', 'v-hanging'], + ['vIdeographic', 'v-ideographic'], + ['vMathematical', 'v-mathematical'], + ['vectorEffect', 'vector-effect'], + ['vertAdvY', 'vert-adv-y'], + ['vertOriginX', 'vert-origin-x'], + ['vertOriginY', 'vert-origin-y'], + ['wordSpacing', 'word-spacing'], + ['writingMode', 'writing-mode'], + ['xmlnsXlink', 'xmlns:xlink'], + ['xHeight', 'x-height'], +]); + +export default function getAttributeAlias(name: string): string { + return aliases.get(name) || name; +} diff --git a/packages/million/template.ts b/packages/million/template.ts index 1749a26939..d6e81c6f31 100644 --- a/packages/million/template.ts +++ b/packages/million/template.ts @@ -1,3 +1,4 @@ +import getAttributeAlias from './alias'; import { X_CHAR, VOID_ELEMENTS, @@ -61,8 +62,11 @@ export const renderToTemplate = ( if (name === 'key' || name === 'ref' || name === 'children') { continue; } + + const alias = getAttributeAlias(name); + if (alias) name = alias; + if (name === 'className') name = 'class'; - if (name === 'htmlFor') name = 'for'; if (name.startsWith('on')) { const isValueHole = '$' in value; diff --git a/test/alias.test.ts b/test/alias.test.ts new file mode 100644 index 0000000000..a0745d4aab --- /dev/null +++ b/test/alias.test.ts @@ -0,0 +1,41 @@ +import { describe, it } from 'vitest'; +import type { VElement } from '../packages/million'; +import { block as createBlock } from '../packages/million'; +import type { MillionProps } from '../packages/types'; + +const fn = (props?: MillionProps): VElement => { + return { + type: 'div', + props: { + children: [ + { + type: 'svg', + props: { + className: props?.bar, + strokeWidth: 1, + children: [ + { + type: 'path', + props: { + d: 'M9 12l2 2 4-4', + strikethroughThickness: props.thickness, + }, + }, + ], + }, + }, + ], + }, + }; +}; + +describe.concurrent('block', () => { + it('should resolve alias', ({ expect }) => { + const block = createBlock(fn); + const main = block({ bar: 'bar', thickness: 2 }); + main.m(); + expect(main.l?.outerHTML).toEqual( + '
', + ); + }); +});