Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions compat/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
useSyncExternalStore,
useTransition
} from './index';
import { assign } from './util';
import { assign, IS_NON_DIMENSIONAL } from './util';

export const REACT_ELEMENT_TYPE = Symbol.for('react.element');

Expand Down Expand Up @@ -117,7 +117,17 @@ function handleDomVNode(vnode) {
}

let lowerCased = i.toLowerCase();
if (i === 'defaultValue' && 'value' in props && props.value == null) {
if (i === 'style' && typeof value === 'object') {
for (let key in value) {
if (typeof value[key] === 'number' && !IS_NON_DIMENSIONAL.test(key)) {
value[key] += 'px';
}
}
} else if (
i === 'defaultValue' &&
'value' in props &&
props.value == null
) {
// `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
// `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
i = 'value';
Expand Down
3 changes: 3 additions & 0 deletions compat/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ export function shallowDiffers(a, b) {
for (let i in b) if (i !== '__source' && a[i] !== b[i]) return true;
return false;
}

export const IS_NON_DIMENSIONAL =
/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
205 changes: 205 additions & 0 deletions compat/test/browser/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -597,4 +597,209 @@ describe('compat render', () => {

expect(scratch.textContent).to.equal('foo');
});

it('should append "px" to unitless inline css values', () => {
// These are all CSS Properties that support a single <length> value
// that must have a unit. If we encounter a number we append "px" to it.
// The list is taken from: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
const unitless = {
'border-block': 2,
'border-block-end-width': 3,
'border-block-start-width': 4,
'border-block-width': 5,
'border-bottom-left-radius': 6,
'border-bottom-right-radius': 7,
'border-bottom-width': 8,
'border-end-end-radius': 9,
'border-end-start-radius': 10,
'border-image-outset': 11,
'border-image-width': 12,
'border-inline': 2,
'border-inline-end': 3,
'border-inline-end-width': 4,
'border-inline-start': 1,
'border-inline-start-width': 123,
'border-inline-width': 123,
'border-left': 123,
'border-left-width': 123,
'border-radius': 123,
'border-right': 123,
'border-right-width': 123,
'border-spacing': 123,
'border-start-end-radius': 123,
'border-start-start-radius': 123,
'border-top': 123,
'border-top-left-radius': 123,
'border-top-right-radius': 123,
'border-top-width': 123,
'border-width': 123,
bottom: 123,
'column-gap': 123,
'column-rule-width': 23,
'column-width': 23,
'flex-basis': 23,
'font-size': 123,
'grid-gap': 23,
'grid-auto-columns': 123,
'grid-auto-rows': 123,
'grid-template-columns': 23,
'grid-template-rows': 23,
height: 123,
'inline-size': 23,
inset: 23,
'inset-block-end': 12,
'inset-block-start': 12,
'inset-inline-end': 213,
'inset-inline-start': 213,
left: 213,
'letter-spacing': 213,
margin: 213,
'margin-block': 213,
'margin-block-end': 213,
'margin-block-start': 213,
'margin-bottom': 213,
'margin-inline': 213,
'margin-inline-end': 213,
'margin-inline-start': 213,
'margin-left': 213,
'margin-right': 213,
'margin-top': 213,
'mask-position': 23,
'mask-size': 23,
'max-block-size': 23,
'max-height': 23,
'max-inline-size': 23,
'max-width': 23,
'min-block-size': 23,
'min-height': 23,
'min-inline-size': 23,
'min-width': 23,
'object-position': 23,
'outline-offset': 23,
'outline-width': 123,
padding: 123,
'padding-block': 123,
'padding-block-end': 123,
'padding-block-start': 123,
'padding-bottom': 123,
'padding-inline': 123,
'padding-inline-end': 123,
'padding-inline-start': 123,
'padding-left': 123,
'padding-right': 123,
'padding-top': 123,
perspective: 123,
right: 123,
'scroll-margin': 123,
'scroll-margin-block': 123,
'scroll-margin-block-start': 123,
'scroll-margin-bottom': 123,
'scroll-margin-inline': 123,
'scroll-margin-inline-end': 123,
'scroll-margin-inline-start': 123,
'scroll-margin-inline-left': 123,
'scroll-margin-inline-right': 123,
'scroll-margin-inline-top': 123,
'scroll-padding': 123,
'scroll-padding-block': 123,
'scroll-padding-block-end': 123,
'scroll-padding-block-start': 123,
'scroll-padding-bottom': 123,
'scroll-padding-inline': 123,
'scroll-padding-inline-end': 123,
'scroll-padding-inline-start': 123,
'scroll-padding-left': 123,
'scroll-padding-right': 123,
'scroll-padding-top': 123,
'shape-margin': 123,
'text-decoration-thickness': 123,
'text-indent': 123,
'text-underline-offset': 123,
top: 123,
'transform-origin': 123,
translate: 123,
width: 123,
'word-spacing': 123
};

// These are all CSS properties that have valid numeric values.
// Our appending logic must not be applied here
const untouched = {
'-webkit-line-clamp': 2,
'animation-iteration-count': 3,
'column-count': 2,
// TODO: unsupported atm
// columns: 2,
flex: 1,
'flex-grow': 1,
'flex-shrink': 1,
'font-size-adjust': 123,
'font-weight': 12,
'grid-column': 2,
'grid-column-end': 2,
'grid-column-start': 2,
'grid-row': 2,
'grid-row-end': 2,
'grid-row-start': 2,
// TODO: unsupported atm
//'line-height': 2,
'mask-border-outset': 2,
'mask-border-slice': 2,
'mask-border-width': 2,
'max-zoom': 2,
'min-zoom': 2,
opacity: 123,
order: 123,
orphans: 2,
'grid-row-gap': 23,
scale: 23,
// TODO: unsupported atm
//'tab-size': 23,
widows: 123,
'z-index': 123,
zoom: 123
};

render(
<div
style={{
...unitless,
...untouched
}}
/>,
scratch
);

let style = scratch.firstChild.style;

// Check properties that MUST not be changed
for (const key in unitless) {
// Check if css property is supported
if (
window.CSS &&
typeof window.CSS.supports === 'function' &&
window.CSS.supports(key, unitless[key])
) {
expect(
String(style[key]).endsWith('px'),
`Should append px "${key}: ${unitless[key]}" === "${key}: ${style[key]}"`
).to.equal(true);
}
}

// Check properties that MUST not be changed
for (const key in untouched) {
// Check if css property is supported
if (
window.CSS &&
typeof window.CSS.supports === 'function' &&
window.CSS.supports(key, untouched[key])
) {
expect(
!String(style[key]).endsWith('px'),
`Should be left as is: "${key}: ${untouched[key]}" === "${key}: ${style[key]}"`
).to.equal(true);
}
}
});
});
27 changes: 9 additions & 18 deletions jsx-runtime/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { options, Fragment } from 'preact';
import { encodeEntities } from './utils';
import { IS_NON_DIMENSIONAL } from '../../src/constants';

let vnodeId = 0;

Expand All @@ -19,9 +18,9 @@ const isArray = Array.isArray;

/**
* JSX.Element factory used by Babel's {runtime:"automatic"} JSX transform
* @param {VNode['type']} type
* @param {VNode['props']} props
* @param {VNode['key']} [key]
* @param {import('../../src/internal').VNode['type']} type
* @param {import('preact').VNode['props']} props
* @param {import('preact').VNode['key']} [key]
* @param {unknown} [isStaticChildren]
* @param {unknown} [__source]
* @param {unknown} [__self]
Expand All @@ -46,7 +45,7 @@ function createVNode(type, props, key, isStaticChildren, __source, __self) {
}
}

/** @type {VNode & { __source: any; __self: any }} */
/** @type {import('../../src/internal').VNode & { __source: any; __self: any }} */
const vnode = {
type,
props: normalizedProps,
Expand All @@ -73,12 +72,13 @@ function createVNode(type, props, key, isStaticChildren, __source, __self) {
* Create a template vnode. This function is not expected to be
* used directly, but rather through a precompile JSX transform
* @param {string[]} templates
* @param {Array<string | null | VNode>} exprs
* @returns {VNode}
* @param {Array<string | null | import('preact').VNode>} exprs
* @returns {import('preact').VNode}
*/
function jsxTemplate(templates, ...exprs) {
const vnode = createVNode(Fragment, { tpl: templates, exprs });
// Bypass render to string top level Fragment optimization
// @ts-ignore
vnode.key = vnode._vnode;
return vnode;
}
Expand Down Expand Up @@ -112,16 +112,7 @@ function jsxAttr(name, value) {
: JS_TO_CSS[prop] ||
(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());

let suffix = ';';
if (
typeof val === 'number' &&
// Exclude custom-attributes
!name.startsWith('--') &&
!IS_NON_DIMENSIONAL.test(name)
) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
str = str + name + ':' + val + ';';
}
}
return name + '="' + str + '"';
Expand All @@ -144,7 +135,7 @@ function jsxAttr(name, value) {
* is not expected to be used directly, but rather through a
* precompile JSX transform
* @param {*} value
* @returns {string | null | VNode | Array<string | null | VNode>}
* @returns {string | null | import('preact').VNode | Array<string | null | import('preact').VNode>}
*/
function jsxEscape(value) {
if (
Expand Down
4 changes: 3 additions & 1 deletion jsx-runtime/test/browser/jsx-runtime.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ describe('precompiled JSX', () => {
});

it('should serialize style object', () => {
expect(jsxAttr('style', { padding: 3 })).to.equal('style="padding:3px;"');
expect(jsxAttr('style', { padding: '3px' })).to.equal(
'style="padding:3px;"'
);
});
});

Expand Down
6 changes: 2 additions & 4 deletions src/diff/props.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { IS_NON_DIMENSIONAL, SVG_NAMESPACE } from '../constants';
import { SVG_NAMESPACE } from '../constants';
import options from '../options';

function setStyle(style, key, value) {
if (key[0] == '-') {
style.setProperty(key, value == null ? '' : value);
} else if (value == null) {
style[key] = '';
} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
style[key] = value;
} else {
style[key] = value + 'px';
style[key] = value;
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/browser/style.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ describe('style attribute', () => {
backgroundPosition: '10px 10px',
'background-size': 'cover',
gridRowStart: 1,
padding: 5,
top: 100,
padding: '5px',
top: '100px',
left: '100%'
};

Expand Down
Loading