diff --git a/apps/examples/src/App.js b/apps/examples/src/App.js index 3cd1ca0..cd130de 100644 --- a/apps/examples/src/App.js +++ b/apps/examples/src/App.js @@ -185,31 +185,21 @@ export default function App(): React.MixedElement { - {/* variables & themes */} - - Global variables - - - - Direct theming - - - - - Inherit theming - - - - - - Nested theming - - - + + + setAnimate(!animate)}> + {animate ? 'Reset' : 'Start'} + {/* block layout emulation */} - + display:block emulation @@ -253,8 +243,8 @@ export default function App(): React.MixedElement { - {/* positioning (static by default) */} - + {/* CSS positioning (static by default) */} + @@ -265,115 +255,16 @@ export default function App(): React.MixedElement { - {/* visibility */} - - - - - + {/* CSS text */} + + + + line-height (unitless) + - - - {/* event emulation */} - - { - console.log(e.type, e.target.value); - }} - onKeyDown={(e) => { - console.log(e.type, e.key); - }} - onInput={(e) => { - console.log(e.type, e.target.value); - }} - /> - { - console.log(e.type, e.target.value); - }} - onKeyDown={(e) => { - console.log(e.type, e.key); - }} - onInput={(e) => { - console.log(e.type, e.target.value); - }} - /> - { - setClickData((data) => ({ - color: data.color === 'red' ? 'blue' : 'red', - text: 'click' - })); - setClickEventData({ - altKey: e.altKey, - button: e.button, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - pageX: e.pageX, - pageY: e.pageY, - shiftKey: e.shiftKey - }); - }} - style={[styles.h100, styles.dynamicBg(clickData.color)]} - > - {clickData.text} - - - - - {clickEventData.altKey ? '✅' : '🚫'} altKey - - - - - {clickEventData.ctrlKey ? '✅' : '🚫'} ctrlKey - - - - - {clickEventData.metaKey ? '✅' : '🚫'} metaKey - - - - - {clickEventData.shiftKey ? '✅' : '🚫'} shiftKey - - - - - - button: {clickEventData.button} - - - pageX: {clickEventData.pageX} - - - pageY: {clickEventData.pageY} - - - + + line-height (em) - - { - setImageLoadText(`${e.type}: loaded`); - }} - width={150} - height={150} - src="http://placehold.jp/150x150.png" - style={styles.objContain} - /> - {imageLoadText} - { - setImageErrorText(`${e.type}: errored`); - }} - width={150} - height={150} - src="http://error" - style={styles.objContain} - /> - {imageErrorText} {/* CSS transitions shim */} @@ -492,21 +383,145 @@ export default function App(): React.MixedElement { Toggle - - - setAnimate(!animate)}> - {animate ? 'Reset' : 'Start'} - + + {/* visibility */} + + + + + + + + + {/* variables & themes */} + + Global variables + + + + Direct theming + + + + + Inherit theming + + + + + + Nested theming + + + - + + {/* hover */} + + + {/* event emulation */} + + { + console.log(e.type, e.target.value); + }} + onKeyDown={(e) => { + console.log(e.type, e.key); + }} + onInput={(e) => { + console.log(e.type, e.target.value); + }} + /> + { + console.log(e.type, e.target.value); + }} + onKeyDown={(e) => { + console.log(e.type, e.key); + }} + onInput={(e) => { + console.log(e.type, e.target.value); + }} + /> + { + setClickData((data) => ({ + color: data.color === 'red' ? 'blue' : 'red', + text: 'click' + })); + setClickEventData({ + altKey: e.altKey, + button: e.button, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + pageX: e.pageX, + pageY: e.pageY, + shiftKey: e.shiftKey + }); + }} + style={[styles.h100, styles.dynamicBg(clickData.color)]} + > + {clickData.text} + + + + + {clickEventData.altKey ? '✅' : '🚫'} altKey + + + + + {clickEventData.ctrlKey ? '✅' : '🚫'} ctrlKey + + + + + {clickEventData.metaKey ? '✅' : '🚫'} metaKey + + + + + {clickEventData.shiftKey ? '✅' : '🚫'} shiftKey + + + + + + button: {clickEventData.button} + + + pageX: {clickEventData.pageX} + + + pageY: {clickEventData.pageY} + + + + + + { + setImageLoadText(`${e.type}: loaded`); + }} + width={150} + height={150} + src="http://placehold.jp/150x150.png" + style={styles.objContain} + /> + {imageLoadText} + { + setImageErrorText(`${e.type}: errored`); + }} + width={150} + height={150} + src="http://error" + style={styles.objContain} + /> + {imageErrorText} + ); @@ -671,5 +686,21 @@ const styles = css.create({ }, visibilityVisible: { visibility: 'visible' + }, + lineHeightUnitless: { + lineHeight: 2, + borderWidth: 1, + borderColor: 'black', + borderStyle: 'solid' + }, + lineHeightEm: { + lineHeight: '2em', + borderWidth: 1, + borderColor: 'black', + borderStyle: 'solid' + }, + text: { + fontSize: '2em', + backgroundColor: 'rgba(255,0,0,0.25)' } }); diff --git a/packages/react-strict-dom/src/native/stylex/index.js b/packages/react-strict-dom/src/native/stylex/index.js index df5f188..4fc4b5a 100644 --- a/packages/react-strict-dom/src/native/stylex/index.js +++ b/packages/react-strict-dom/src/native/stylex/index.js @@ -220,7 +220,7 @@ function processStyle(style: S): S { const propNames = Object.keys(result); for (let i = 0; i < propNames.length; i++) { const propName = propNames[i]; - let styleValue = result[propName]; + const styleValue = result[propName]; if ( CSSMediaQuery.isMediaQueryString(propName) && @@ -242,19 +242,6 @@ function processStyle(style: S): S { continue; } - // Polyfill unitless lineHeight - // React Native treats unitless as a 'px' value - // Web treats unitless as an 'em' value - if (propName === 'lineHeight') { - if ( - typeof styleValue === 'number' || - (typeof styleValue === 'string' && - CSSLengthUnitValue.parse(styleValue) == null) - ) { - styleValue = styleValue + 'em'; - } - } - if (typeof styleValue === 'string') { if (stringContainsVariables(styleValue)) { result[propName] = CSSUnparsedValue.parse(propName, styleValue); @@ -354,6 +341,34 @@ function resolveStyle( continue; } + // Polyfill unitless lineHeight + // React Native treats unitless as a 'px' value + // Web treats unitless as fontSize multiplier + if (propName === 'lineHeight') { + if ( + typeof styleValue === 'number' || + (typeof styleValue === 'string' && + CSSLengthUnitValue.parse(styleValue) == null) + ) { + const lineHeightValue = parseFloat(styleValue); + // Only convert unitless lineHeight if fontSize exists + if (style.fontSize != null) { + if (style.fontSize instanceof CSSLengthUnitValue) { + const { value: fontSizeValue, unit: fontSizeUnit } = style.fontSize; + const value = new CSSLengthUnitValue( + lineHeightValue * fontSizeValue, + fontSizeUnit + ); + stylesToReprocess[propName] = value; + result[propName] = value; + } else if (typeof style.fontSize === 'number') { + result[propName] = lineHeightValue * style.fontSize; + } + continue; + } + } + } + // resolve length units if (styleValue instanceof CSSLengthUnitValue) { result[propName] = styleValue.resolvePixelValue(options); diff --git a/packages/react-strict-dom/tests/__snapshots__/css-test.native.js.snap-native b/packages/react-strict-dom/tests/__snapshots__/css-test.native.js.snap-native index 28393d0..1fac9f5 100644 --- a/packages/react-strict-dom/tests/__snapshots__/css-test.native.js.snap-native +++ b/packages/react-strict-dom/tests/__snapshots__/css-test.native.js.snap-native @@ -272,6 +272,7 @@ exports[`properties: general line-height: rem 1`] = ` exports[`properties: general line-height: unitless number 1`] = ` { "style": { + "fontSize": 16, "lineHeight": 24, }, } @@ -280,6 +281,7 @@ exports[`properties: general line-height: unitless number 1`] = ` exports[`properties: general line-height: unitless string 1`] = ` { "style": { + "fontSize": 16, "lineHeight": 24, }, } diff --git a/packages/react-strict-dom/tests/__snapshots__/html-test.native.js.snap-native b/packages/react-strict-dom/tests/__snapshots__/html-test.native.js.snap-native index 6025fcb..bcb78d8 100644 --- a/packages/react-strict-dom/tests/__snapshots__/html-test.native.js.snap-native +++ b/packages/react-strict-dom/tests/__snapshots__/html-test.native.js.snap-native @@ -1291,11 +1291,11 @@ exports[`html style polyfills default inherited text styles 1`] = ` "color": "red", "direction": "rtl", "fontFamily": "Arial", - "fontSize": "1em", + "fontSize": 32, "fontStyle": "italic", "fontVariant": "variant", "fontWeight": "300", - "lineHeight": 1.6, + "lineHeight": 48, "position": "static", "textAlign": "right", "textTransform": "uppercase", @@ -1303,7 +1303,29 @@ exports[`html style polyfills default inherited text styles 1`] = ` } } > - Text should inherit div styles + Text should + + inherit + + div styles { test('line-height', () => { const styles = css.create({ numeric: { + fontSize: 16, lineHeight: 1.5 }, string: { + fontSize: 16, lineHeight: '1.5' }, rem: { diff --git a/packages/react-strict-dom/tests/html-test.native.js b/packages/react-strict-dom/tests/html-test.native.js index bb9ced8..0e405a4 100644 --- a/packages/react-strict-dom/tests/html-test.native.js +++ b/packages/react-strict-dom/tests/html-test.native.js @@ -114,26 +114,39 @@ describe('html', () => { }); test('default inherited text styles', () => { - const inheritableStyles = { - color: 'red', - cursor: 'pointer', - direction: 'rtl', - fontFamily: 'Arial', - fontSize: '1em', - fontStyle: 'italic', - fontVariant: 'variant', - fontWeight: 'bold', - letterSpace: '10px', - lineHeight: 1.6, - textAlign: 'right', - textIndent: '10px', - textTransform: 'uppercase', - whiteSpace: '' - }; + const styles = css.create({ + inheritable: { + color: 'red', + cursor: 'pointer', + direction: 'rtl', + fontFamily: 'Arial', + fontSize: '1em', + fontStyle: 'italic', + fontVariant: 'variant', + fontWeight: 'bold', + letterSpace: '10px', + lineHeight: 1.5, + textAlign: 'right', + textIndent: '10px', + textTransform: 'uppercase', + whiteSpace: 'pre' + }, + alsoInheritable: { + fontWeight: 300 + }, + text: { + fontSize: '2em' + } + }); + // This also tests that unitless line-height is correctly inherited, including + // through nested text elements. const root = create( - - - Text should inherit div styles + + + + Text should inherit div + styles +