[change] StyleSheet performance rewrite

Improves StyleSheet benchmark performance by 13x. Removes undocumented
`StyleSheet.resolve` API.

Typical mean (and first) task duration.

[benchmark] DeepTree: depth=3, breadth=10, wrap=4)
  -master  2809.76ms (3117.64ms)
  -patch     211.2ms  (364.28ms)

[benchmark] DeepTree: depth=5, breadth=3, wrap=1)
  -master   421.25ms (428.15ms)
  -patch     32.46ms  (47.36ms)

This patch adds memoization of DOM prop resolution (~3-4x faster), and
re-introduces a `className`-based styling strategy (~3-4x faster).
Styles map to "atomic css" rules.

Fix #307
This commit is contained in:
Nicolas Gallagher
2016-12-31 17:11:22 -08:00
parent a2cafe56fc
commit d87f71ebc1
41 changed files with 2358 additions and 2290 deletions

View File

@@ -15,9 +15,9 @@ Each key of the object passed to `create` must define a style object.
Flattens an array of styles into a single style object.
**render**: function
**renderToString**: function
Returns a React `<style>` element for use in server-side rendering.
Returns a string of the stylesheet for use in server-side rendering.
## Properties

View File

@@ -1,6 +1,8 @@
const path = require('path')
const webpack = require('webpack')
const DEV = process.env.NODE_ENV !== 'production';
module.exports = {
module: {
loaders: [
@@ -19,7 +21,8 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
}),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(

View File

@@ -24,6 +24,7 @@
"dependencies": {
"animated": "^0.1.3",
"array-find-index": "^1.0.2",
"asap": "^2.0.5",
"babel-runtime": "^6.11.6",
"debounce": "^1.0.0",
"deep-assign": "^2.0.0",

View File

@@ -0,0 +1,45 @@
exports[`apis/AppRegistry/renderApplication getApplication 1`] = `
"<style id=\"react-native-stylesheet\">
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
body{margin:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}
.rn_pointerEvents\\:auto, .rn_pointerEvents\\:box-only, .rn_pointerEvents\\:box-none * {pointer-events:auto}.rn_pointerEvents\\:none, .rn_pointerEvents\\:box-only *, .rn_pointerEvents\\:box-none {pointer-events:none}
.rn-bottom\\:0px{bottom:0px;}
.rn-left\\:0px{left:0px;}
.rn-position\\:absolute{position:absolute;}
.rn-right\\:0px{right:0px;}
.rn-top\\:0px{top:0px;}
.rn-alignItems\\:stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;-webkit-box-align:stretch;align-items:stretch;}
.rn-backgroundColor\\:transparent{background-color:transparent;}
.rn-borderTopStyle\\:solid{border-top-style:solid;}
.rn-borderRightStyle\\:solid{border-right-style:solid;}
.rn-borderBottomStyle\\:solid{border-bottom-style:solid;}
.rn-borderLeftStyle\\:solid{border-left-style:solid;}
.rn-borderTopWidth\\:0px{border-top-width:0px;}
.rn-borderRightWidth\\:0px{border-right-width:0px;}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px;}
.rn-borderLeftWidth\\:0px{border-left-width:0px;}
.rn-boxSizing\\:border-box{-moz-box-sizing:border-box;box-sizing:border-box;}
.rn-color\\:inherit{color:inherit;}
.rn-display\\:flex{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;}
.rn-flexBasis\\:auto{-webkit-flex-basis:auto;-ms-preferred-size:auto;flex-basis:auto;}
.rn-flexDirection\\:column{-webkit-flex-direction:column;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;}
.rn-font\\:inherit{font:inherit;}
.rn-listStyle\\:none{list-style:none;}
.rn-marginTop\\:0px{margin-top:0px;}
.rn-marginRight\\:0px{margin-right:0px;}
.rn-marginBottom\\:0px{margin-bottom:0px;}
.rn-marginLeft\\:0px{margin-left:0px;}
.rn-minHeight\\:0px{min-height:0px;}
.rn-minWidth\\:0px{min-width:0px;}
.rn-paddingTop\\:0px{padding-top:0px;}
.rn-paddingRight\\:0px{padding-right:0px;}
.rn-paddingBottom\\:0px{padding-bottom:0px;}
.rn-paddingLeft\\:0px{padding-left:0px;}
.rn-position\\:relative{position:relative;}
.rn-textAlign\\:inherit{text-align:inherit;}
.rn-textDecoration\\:none{text-decoration:none;}
.rn-flexShrink\\:0{-webkit-flex-shrink:0px;-ms-flex-negative:0px;flex-shrink:0;}
</style>"
`;

View File

@@ -10,6 +10,6 @@ describe('apis/AppRegistry/renderApplication', () => {
const { element, stylesheet } = getApplication(component, {});
expect(element).toBeTruthy();
expect(stylesheet.type).toEqual('style');
expect(stylesheet).toMatchSnapshot();
});
});

View File

@@ -32,6 +32,6 @@ export function getApplication(RootComponent: Component, initialProps: Object):
rootComponent={RootComponent}
/>
);
const stylesheet = StyleSheet.render();
const stylesheet = StyleSheet.renderToString();
return { element, stylesheet };
}

View File

@@ -0,0 +1,15 @@
exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = `
Object {
"borderBottomWidth": "1px",
"borderLeftWidth": "1px",
"borderRightWidth": "1px",
"borderTopWidth": "1px",
"borderWidthLeft": "2px",
"borderWidthRight": "3px",
"boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px rgba(255,0,0,1)",
"display": "flex",
"marginBottom": "0px",
"marginTop": "0px",
"opacity": 0,
}
`;

View File

@@ -0,0 +1 @@
exports[`apis/StyleSheet/generateCss generates correct css 1`] = `"-webkit-transition-duration:0.1s;transition-duration:0.1s;position:absolute;border-width-right:3px;border-width-left:2px;box-shadow:1px 1px 1px 1px #000;"`;

View File

@@ -1,10 +1,14 @@
exports[`apis/StyleSheet resolve 1`] = `
Object {
"className": "test __style_df __style_pebn",
"style": Object {
"display": null,
"opacity": 1,
"pointerEvents": null,
},
}
exports[`apis/StyleSheet renderToString 1`] = `
"<style id=\"react-native-stylesheet\">
.rn-borderTopColor\\:red{border-top-color:red;}
.rn-borderRightColor\\:red{border-right-color:red;}
.rn-borderBottomColor\\:red{border-bottom-color:red;}
.rn-borderLeftColor\\:red{border-left-color:red;}
.rn-borderTopWidth\\:0px{border-top-width:0px;}
.rn-borderRightWidth\\:0px{border-right-width:0px;}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px;}
.rn-borderLeftWidth\\:0px{border-left-width:0px;}
.rn-left\\:50px{left:50px;}
.rn-position\\:absolute{position:absolute;}
</style>"
`;

View File

@@ -0,0 +1,179 @@
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 1`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 2`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 3`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 1`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 2`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
},
}
`;
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 3`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 1`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 2`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "200px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 3`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;

View File

@@ -2,11 +2,27 @@
import createReactDOMStyle from '../createReactDOMStyle';
const reactNativeStyle = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidth: 1,
borderWidthRight: 3,
display: 'flex',
marginVertical: 0,
opacity: 0,
shadowColor: 'red',
shadowOffset: { width: 1, height: 2 },
resizeMode: 'contain'
};
describe('apis/StyleSheet/createReactDOMStyle', () => {
test('converts ReactNative style to ReactDOM style', () => {
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot();
});
expect(createReactDOMStyle(reactNativeStyle)).toEqual(expectedStyle);
test('noop on DOM styles', () => {
const firstStyle = createReactDOMStyle(reactNativeStyle);
const secondStyle = createReactDOMStyle(firstStyle);
expect(firstStyle).toEqual(secondStyle);
});
});

View File

@@ -0,0 +1,16 @@
/* eslint-env jasmine, jest */
import generateCss from '../generateCss';
describe('apis/StyleSheet/generateCss', () => {
test('generates correct css', () => {
const style = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidthRight: 3,
position: 'absolute',
transitionDuration: '0.1s'
};
expect(generateCss(style)).toMatchSnapshot();
});
});

View File

@@ -1,7 +1,7 @@
/* eslint-env jasmine, jest */
import { getDefaultStyleSheet } from '../css';
import StyleSheet from '..';
import StyleRegistry from '../registry';
const isPlainObject = (x) => {
const toString = Object.prototype.toString;
@@ -16,7 +16,7 @@ const isPlainObject = (x) => {
describe('apis/StyleSheet', () => {
beforeEach(() => {
StyleSheet._reset();
StyleRegistry.reset();
});
test('absoluteFill', () => {
@@ -32,11 +32,6 @@ describe('apis/StyleSheet', () => {
const style = StyleSheet.create({ root: { opacity: 1 } });
expect(Number.isInteger(style.root) === true).toBeTruthy();
});
test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } });
expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet());
});
});
test('flatten', () => {
@@ -47,18 +42,17 @@ describe('apis/StyleSheet', () => {
expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy();
});
test('render', () => {
expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet());
});
test('resolve', () => {
expect(StyleSheet.resolve({
className: 'test',
style: {
display: 'flex',
opacity: 1,
pointerEvents: 'box-none'
test('renderToString', () => {
StyleSheet.create({
a: {
borderWidth: 0,
borderColor: 'red'
},
b: {
position: 'absolute',
left: 50
}
})).toMatchSnapshot();
});
expect(StyleSheet.renderToString()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,22 @@
/* eslint-env jasmine, jest */
import injector from '../injector';
describe('apis/StyleSheet', () => {
beforeEach(() => {
document.head.insertAdjacentHTML('afterbegin', `
<style id="react-native-stylesheet">
.rn-alignItems\\:stretch{align-items:stretch;}
.rn-position\\:top{position:top;}
</style>
`);
});
test('hydrates from SSR', () => {
const classList = injector.getAvailableClassNames();
expect(classList).toEqual([
'rn-alignItems\\:stretch',
'rn-position\\:top'
]);
});
});

View File

@@ -0,0 +1,13 @@
/* eslint-env jasmine, jest */
import prefixInlineStyles from '../prefixInlineStyles';
describe('apis/StyleSheet/prefixInlineStyles', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
};
expect(prefixInlineStyles(style)).toEqual({ display: 'flex' });
});
});

View File

@@ -0,0 +1,54 @@
/* eslint-env jasmine, jest */
import StyleRegistry from '../registry';
describe('apis/StyleSheet/registry', () => {
beforeEach(() => {
StyleRegistry.reset();
});
test('register', () => {
const style = { opacity: 0 };
const id = StyleRegistry.register(style);
expect(typeof id === 'number').toBe(true);
});
describe('resolve', () => {
const styleA = { borderWidth: 0, borderColor: 'red', width: 100 };
const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' };
const styleC = { width: 200 };
const testResolve = (a, b, c) => {
// no common properties, different resolving order, same result
const resolve1 = StyleRegistry.resolve([ a, b ]);
expect(resolve1).toMatchSnapshot();
const resolve2 = StyleRegistry.resolve([ b, a ]);
expect(resolve1).toEqual(resolve2);
// common properties, different resolving order, different result
const resolve3 = StyleRegistry.resolve([ a, b, c ]);
expect(resolve3).toMatchSnapshot();
const resolve4 = StyleRegistry.resolve([ c, a, b ]);
expect(resolve4).toMatchSnapshot();
expect(resolve3).not.toEqual(resolve4);
};
test('with stylesheet, resolves to className', () => {
const a = StyleRegistry.register(styleA);
const b = StyleRegistry.register(styleB);
const c = StyleRegistry.register(styleC);
testResolve(a, b, c);
});
test('with stylesheet, resolves to mixed', () => {
const a = styleA;
const b = StyleRegistry.register(styleB);
const c = StyleRegistry.register(styleC);
testResolve(a, b, c);
});
test('without stylesheet, resolves to inline styles', () => {
testResolve(styleA, styleB, styleC);
});
});
});

View File

@@ -1,13 +0,0 @@
/* eslint-env jasmine, jest */
import resolveVendorPrefixes from '../resolveVendorPrefixes';
describe('apis/StyleSheet/resolveVendorPrefixes', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
};
expect(resolveVendorPrefixes(style)).toEqual({ display: 'flex' });
});
});

View File

@@ -1,10 +1,6 @@
import expandStyle from './expandStyle';
import flattenStyle from './flattenStyle';
import i18nStyle from './i18nStyle';
import resolveVendorPrefixes from './resolveVendorPrefixes';
const createReactDOMStyle = (reactNativeStyle) => resolveVendorPrefixes(
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
);
const createReactDOMStyle = (flattenedReactNativeStyle) => expandStyle(i18nStyle(flattenedReactNativeStyle));
module.exports = createReactDOMStyle;

View File

@@ -1,42 +0,0 @@
const DISPLAY_FLEX_CLASSNAME = '__style_df';
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea';
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn';
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo';
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen';
/* eslint-disable max-len */
const CSS_RESET =
// reset unwanted styles
'/* React Native */\n' +
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
'body{margin:0}\n' +
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}';
const CSS_HELPERS =
// vendor prefix 'display:flex' until React supports fallback values for inline styles
`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` +
// implement React Native's pointer event values
`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` +
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
/* eslint-enable max-len */
const styleAsClassName = {
display: {
'flex': DISPLAY_FLEX_CLASSNAME
},
pointerEvents: {
'auto': POINTER_EVENTS_AUTO_CLASSNAME,
'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
'none': POINTER_EVENTS_NONE_CLASSNAME
}
};
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
export const getStyleAsHelperClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[prop][value];
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
@@ -10,10 +9,8 @@
* @providesModule flattenStyle
* @flow
*/
'use strict';
var ReactNativePropRegistry = require('../../modules/ReactNativePropRegistry');
var invariant = require('fbjs/lib/invariant');
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import invariant from 'fbjs/lib/invariant';
function getStyle(style) {
if (typeof style === 'number') {
@@ -22,22 +19,26 @@ function getStyle(style) {
return style;
}
function flattenStyle(style: ?StyleObj): ?Object {
function flattenStyle(style) {
if (!style) {
return undefined;
}
invariant(style !== true, 'style may be false but not true');
if (process.env.NODE !== 'production') {
invariant(style !== true, 'style may be false but not true');
}
if (!Array.isArray(style)) {
return getStyle(style);
}
var result = {};
for (var i = 0, styleLength = style.length; i < styleLength; ++i) {
var computedStyle = flattenStyle(style[i]);
const result = {};
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
const computedStyle = flattenStyle(style[i]);
if (computedStyle) {
for (var key in computedStyle) {
result[key] = computedStyle[key];
for (const key in computedStyle) {
const value = computedStyle[key];
result[key] = value;
}
}
}

View File

@@ -0,0 +1,38 @@
import hyphenate from './hyphenate';
import mapKeyValue from '../../modules/mapKeyValue';
import normalizeValue from './normalizeValue';
import prefixAll from 'inline-style-prefixer/static';
const RE_VENDOR = /^-/;
const sortVendorPrefixes = (a, b) => {
const vendorA = RE_VENDOR.test(a);
const vendorB = RE_VENDOR.test(b);
if (vendorA && vendorB || vendorA) {
return -1;
} else {
return 1;
}
};
const mapDeclaration = (prop, val) => {
const name = hyphenate(prop);
const value = normalizeValue(prop, val);
if (Array.isArray(val)) {
return val.map((v) => `${name}:${v};`).join('');
}
return `${name}:${value};`;
};
/**
* Generates valid CSS rule body from a JS object
*
* generateCss({ width: 20, color: 'blue' });
* // => 'width:20px;color:blue;'
*/
const generateCss = (style) => {
const prefixed = prefixAll(style);
return mapKeyValue(prefixed, mapDeclaration).sort(sortVendorPrefixes).join('');
};
module.exports = generateCss;

View File

@@ -0,0 +1,3 @@
const RE_1 = /([A-Z])/g;
const RE_2 = /^ms-/;
module.exports = (s) => s.replace(RE_1, '-$1').toLowerCase().replace(RE_2, '-ms-');

View File

@@ -1,89 +1,26 @@
import * as css from './css';
import createReactDOMStyle from './createReactDOMStyle';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import flattenStyle from './flattenStyle';
import React from 'react';
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import initialize from './initialize';
import injector from './injector';
import StyleRegistry from './registry';
let styleElement;
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
const STYLE_SHEET_ID = 'react-native-style__';
initialize();
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
const defaultStyleSheet = css.getDefaultStyleSheet();
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID);
// if not, inject the style sheet
if (!styleElement) {
document.head.insertAdjacentHTML(
'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
);
shouldInsertStyleSheet = false;
}
};
module.exports = {
/**
* For testing
* @private
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement);
styleElement = null;
shouldInsertStyleSheet = true;
}
},
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
absoluteFill: StyleRegistry.register(absoluteFillObject),
absoluteFillObject,
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet();
}
const result = {};
for (const key in styles) {
Object.keys(styles).forEach((key) => {
if (process.env.NODE_ENV !== 'production') {
require('./StyleSheetValidation').validateStyle(key, styles);
}
result[key] = ReactNativePropRegistry.register(styles[key]);
}
result[key] = StyleRegistry.register(styles[key]);
});
return result;
},
hairlineWidth: 1,
flatten: flattenStyle,
/* @platform web */
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
},
/**
* Accepts React props and converts style declarations to classNames when necessary
* @platform web
*/
resolve(props) {
let className = props.className || '';
const style = createReactDOMStyle(props.style);
for (const prop in style) {
const value = style[prop];
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
if (replacementClassName) {
className += ` ${replacementClassName}`;
style[prop] = null;
}
}
return { className, style };
}
renderToString: injector.getStyleSheetHtml
};

View File

@@ -0,0 +1,39 @@
import injector from './injector';
import StyleRegistry from './registry';
const initialize = () => {
injector.addRule(
'html-reset',
'html{' +
'font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;' +
'-webkit-tap-highlight-color:rgba(0,0,0,0)' +
'}'
);
injector.addRule(
'body-reset',
'body{margin:0}'
);
injector.addRule(
'button-reset',
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}'
);
injector.addRule(
'input-reset',
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}'
);
injector.addRule(
'pointer-events',
'.rn_pointerEvents\\:auto, .rn_pointerEvents\\:box-only, .rn_pointerEvents\\:box-none * {pointer-events:auto}' +
'.rn_pointerEvents\\:none, .rn_pointerEvents\\:box-only *, .rn_pointerEvents\\:box-none {pointer-events:none}'
);
StyleRegistry.initialize();
};
export default initialize;

View File

@@ -0,0 +1,91 @@
/**
* Based on
* https://github.com/lelandrichardson/react-primitives/blob/master/src/StyleSheet/injector.js
*/
import asap from 'asap';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
const hasOwnProperty = Object.prototype.hasOwnProperty;
const CLASSNAME_REXEP = /\.rn-([^{;\s]+)/g;
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
let registry = {};
let isDirty = false;
let styleNode = null;
const getStyleText = () => {
/* eslint prefer-template:0 */
let result = '\n';
for (const key in registry) {
if (hasOwnProperty.call(registry, key)) {
result += registry[key] + '\n';
}
}
return result;
};
// TODO: SSR support
const getAvailableClassNames = () => {
if (ExecutionEnvironment.canUseDOM) {
if (!styleNode) {
styleNode = document.getElementById(STYLE_ELEMENT_ID);
}
if (styleNode) {
const text = styleNode.textContent;
return text.match(CLASSNAME_REXEP).map((name) => name.slice(1));
} else {
return [];
}
} else {
return [];
}
};
const createStyleHTML = (text) => `<style id="${STYLE_ELEMENT_ID}">${text}</style>`;
const frame = () => {
if (!isDirty || !ExecutionEnvironment.canUseDOM) { return; }
isDirty = false;
styleNode = styleNode || document.getElementById(STYLE_ELEMENT_ID);
if (!styleNode) {
document.head.insertAdjacentHTML('afterbegin', createStyleHTML());
styleNode = document.getElementById(STYLE_ELEMENT_ID);
}
const css = getStyleText();
if (styleNode.styleSheet) {
styleNode.styleSheet.cssText = css;
} else {
/* eslint no-cond-assign:0 */
let last;
while (last = styleNode.lastChild) {
styleNode.removeChild(last);
}
styleNode.appendChild(document.createTextNode(css));
}
};
const addRule = (key, rule) => {
if (!registry[key]) {
registry[key] = rule;
if (!isDirty) {
isDirty = true;
if (ExecutionEnvironment.canUseDOM) {
asap(frame);
}
}
}
};
const getStyleSheetHtml = () => createStyleHTML(getStyleText());
module.exports = {
addRule,
getAvailableClassNames,
getStyleSheetHtml,
reset: () => { registry = {}; }
};

View File

@@ -1,6 +1,6 @@
import prefixAll from 'inline-style-prefixer/static';
const resolveVendorPrefixes = (style) => {
const prefixInlineStyles = (style) => {
const prefixedStyles = prefixAll(style);
// React@15 removed undocumented support for fallback values in
@@ -15,4 +15,4 @@ const resolveVendorPrefixes = (style) => {
return prefixedStyles;
};
module.exports = resolveVendorPrefixes;
module.exports = prefixInlineStyles;

View File

@@ -0,0 +1,178 @@
/**
* WARNING: changes to this file in particular can cause significant changes to
* the results of render performance benchmarks.
*/
import createReactDOMStyle from './createReactDOMStyle';
import flattenArray from '../../modules/flattenArray';
import flattenStyle from './flattenStyle';
import generateCss from './generateCss';
import injector from './injector';
import mapKeyValue from '../../modules/mapKeyValue';
import prefixInlineStyles from './prefixInlineStyles';
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
const prefix = 'r';
const SPACE_REGEXP = /\s/g;
const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#]/g;
/**
* Creates an HTML class name for use on elements
*/
const createClassName = (prop, value) => {
const val = `${value}`.replace(SPACE_REGEXP, '-');
return `rn-${prop}:${val}`;
};
/**
* Inject a CSS rule for a given declaration and record the availability of the
* resulting class name.
*/
let injectedClassNames = {};
const injectClassNameIfNeeded = (prop, value) => {
const className = createClassName(prop, value);
if (!injectedClassNames[className]) {
// create a valid CSS selector for a given HTML class name
const selector = className.replace(ESCAPE_SELECTOR_CHARS_REGEXP, '\\$&');
const body = generateCss({ [prop]: value });
const css = `.${selector}{${body}}`;
// this adds the rule to the buffer to be injected into the document
injector.addRule(className, css);
injectedClassNames[className] = true;
}
return className;
};
/**
* Converts a React Native style object to HTML class names
*/
let resolvedPropsCache = {};
const registerStyle = (id, flatStyle) => {
const style = createReactDOMStyle(flatStyle);
const className = mapKeyValue(style, (prop, value) => {
if (value != null) {
return injectClassNameIfNeeded(prop, value);
}
}).join(' ').trim();
const key = `${prefix}${id}`;
resolvedPropsCache[key] = { className };
return id;
};
/**
* Resolves a React Native style object to DOM props
*/
const resolveProps = (reactNativeStyle) => {
const flatStyle = flattenStyle(reactNativeStyle);
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
console.groupCollapsed('[render] deoptimized: resolving uncached styles');
console.log('source style\n', reactNativeStyle);
console.log('flattened style\n', flatStyle);
}
const domStyle = createReactDOMStyle(flatStyle);
const style = {};
const _className = mapKeyValue(domStyle, (prop, value) => {
if (value != null) {
const singleClassName = createClassName(prop, value);
if (injectedClassNames[singleClassName]) {
return singleClassName;
} else {
style[prop] = value;
}
}
})
// improves debugging in devtools and snapshots
.join('\n')
.trim();
const className = `\n${_className}`;
const props = {
className,
style: prefixInlineStyles(style)
};
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
console.log('DOM props\n', props);
console.groupEnd();
}
return props;
};
/**
* Caching layer over 'resolveProps'
*/
const resolvePropsIfNeeded = (key, style) => {
if (!key || !resolvedPropsCache[key]) {
// slow: convert style object to props and cache
resolvedPropsCache[key] = resolveProps(style);
}
return resolvedPropsCache[key];
};
/**
* Web style registry
*/
const StyleRegistry = {
initialize() {
const classNames = injector.getAvailableClassNames();
classNames.forEach((className) => { injectedClassNames[className] = true; });
},
reset() {
injectedClassNames = {};
resolvedPropsCache = {};
injector.reset();
},
register(style) {
const id = ReactNativePropRegistry.register(style);
return registerStyle(id, style);
},
resolve(reactNativeStyle) {
if (!reactNativeStyle) {
return undefined;
}
// fast and cachable
if (typeof reactNativeStyle === 'number') {
const key = `${prefix}${reactNativeStyle}`;
return resolvePropsIfNeeded(key, reactNativeStyle);
}
// convert a RN style object to DOM props
if (!Array.isArray(reactNativeStyle)) {
return resolveProps(reactNativeStyle);
}
// flatten the array
// [ 1, [ 2, 3 ], { prop: value }, 4, 5 ] => [ 1, 2, 3, { prop: value }, 4, 5 ];
const flatArray = flattenArray(reactNativeStyle);
let isArrayOfNumbers = true;
for (let i = 0; i < flatArray.length; i++) {
if (typeof flatArray[i] !== 'number') {
isArrayOfNumbers = false;
break;
}
}
if (isArrayOfNumbers) {
// cache resolved props
const key = `${prefix}${flatArray.join('-')}`;
return resolvePropsIfNeeded(key, flatArray);
} else {
// resolve
return resolveProps(flatArray);
}
}
};
module.exports = StyleRegistry;

View File

@@ -3,11 +3,13 @@ import normalizeValue from './normalizeValue';
const defaultOffset = { height: 0, width: 0 };
const applyOpacity = (colorNumber, opacity) => {
const r = (colorNumber & 0xff000000) >>> 24;
const g = (colorNumber & 0x00ff0000) >>> 16;
const b = (colorNumber & 0x0000ff00) >>> 8;
const a = (((colorNumber & 0x000000ff) >>> 0) / 255).toFixed(2);
const applyOpacity = (color, opacity = 1) => {
const nullableColor = normalizeColor(color);
const colorInt = nullableColor === null ? 0x00000000 : nullableColor;
const r = Math.round(((colorInt & 0xff000000) >>> 24));
const g = Math.round(((colorInt & 0x00ff0000) >>> 16));
const b = Math.round(((colorInt & 0x0000ff00) >>> 8));
const a = (((colorInt & 0x000000ff) >>> 0) / 255).toFixed(2);
return `rgba(${r},${g},${b},${a * opacity})`;
};
@@ -17,10 +19,7 @@ const resolveBoxShadow = (resolvedStyle, style) => {
const offsetX = normalizeValue(null, width);
const offsetY = normalizeValue(null, height);
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
// rgba color
const opacity = style.shadowOpacity != null ? style.shadowOpacity : 1;
const colorNumber = normalizeColor(style.shadowColor) || 0x00000000;
const color = applyOpacity(colorNumber, opacity);
const color = applyOpacity(style.shadowColor, style.shadowOpacity);
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;

View File

@@ -19,7 +19,7 @@ const resolveTransform = (resolvedStyle, style) => {
const transform = style.transform.map(mapTransform).join(' ');
resolvedStyle.transform = transform;
} else if (style.transformMatrix) {
const transform = convertTransformMatrix(style.transformMatrix)
const transform = convertTransformMatrix(style.transformMatrix);
resolvedStyle.transform = transform;
}
};

View File

@@ -1,5 +1,7 @@
import createReactDOMStyle from '../StyleSheet/createReactDOMStyle';
import flattenStyle from '../StyleSheet/flattenStyle';
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
import prefixInlineStyles from '../StyleSheet/prefixInlineStyles';
const _measureLayout = (node, relativeToNativeNode, callback) => {
const relativeNode = relativeToNativeNode || node.parentNode;
@@ -41,14 +43,11 @@ const UIManager = {
const value = props[prop];
switch (prop) {
case 'style':
// convert styles to DOM-styles
CSSPropertyOperations.setValueForStyles(
node,
createReactDOMStyle(value),
component._reactInternalInstance
);
case 'style': {
const style = prefixInlineStyles(createReactDOMStyle(flattenStyle(value)));
CSSPropertyOperations.setValueForStyles(node, style, component._reactInternalInstance);
break;
}
case 'class':
case 'className': {
const nativeProp = 'class';

File diff suppressed because it is too large Load Diff

View File

@@ -131,8 +131,8 @@ class Image extends Component {
styles.initial,
imageSizeStyle,
originalStyle,
backgroundImage && { backgroundImage },
resizeModeStyles[finalResizeMode]
resizeModeStyles[finalResizeMode],
backgroundImage && { backgroundImage }
]);
// View doesn't support 'resizeMode' as a style
delete style.resizeMode;

View File

@@ -1,26 +1,14 @@
exports[`components/Text prop "children" 1`] = `
<span
className=""
className="rn-borderTopWidth:0px rn-borderRightWidth:0px rn-borderBottomWidth:0px rn-borderLeftWidth:0px rn-color:inherit rn-display:inline rn-font:inherit rn-marginTop:0px rn-marginRight:0px rn-marginBottom:0px rn-marginLeft:0px rn-paddingTop:0px rn-paddingRight:0px rn-paddingBottom:0px rn-paddingLeft:0px rn-textDecoration:none rn-whiteSpace:pre-wrap rn-wordWrap:break-word"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
Array [
3,
undefined,
false,
false,
undefined,
]
}>
children
</span>
@@ -28,86 +16,67 @@ exports[`components/Text prop "children" 1`] = `
exports[`components/Text prop "onPress" 1`] = `
<span
className=""
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-cursor:pointer
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"cursor": "pointer",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
}
style={Object {}}
tabIndex={0} />
`;
exports[`components/Text prop "selectable" 1`] = `
<span
className=""
className="rn-borderTopWidth:0px rn-borderRightWidth:0px rn-borderBottomWidth:0px rn-borderLeftWidth:0px rn-color:inherit rn-display:inline rn-font:inherit rn-marginTop:0px rn-marginRight:0px rn-marginBottom:0px rn-marginLeft:0px rn-paddingTop:0px rn-paddingRight:0px rn-paddingBottom:0px rn-paddingLeft:0px rn-textDecoration:none rn-whiteSpace:pre-wrap rn-wordWrap:break-word"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
Array [
3,
undefined,
false,
false,
undefined,
]
} />
`;
exports[`components/Text prop "selectable" 2`] = `
<span
className=""
style={
Object {
"MozUserSelect": "none",
"WebkitUserSelect": "none",
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"msUserSelect": "none",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"userSelect": "none",
"wordWrap": "break-word",
}
} />
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-userSelect:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
style={Object {}} />
`;

View File

@@ -78,6 +78,7 @@ const styles = StyleSheet.create({
margin: 0,
padding: 0,
textDecorationLine: 'none',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word'
},
notSelectable: {

View File

@@ -1,525 +1,399 @@
exports[`components/View prop "children" 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
}>
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}}>
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
data-testid="1"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
style={Object {}} />
</div>
`;
exports[`components/View prop "pointerEvents" 1`] = `
<div
className=" __style_df __style_pebo"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"pointerEvents": null,
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"pointerEvents": "box-only",
}
} />
`;
exports[`components/View prop "style" 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
`;
exports[`components/View prop "style" 2`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexGrow": 1,
"WebkitFlexShrink": 1,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexGrow": 1,
"flexShrink": 1,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 1,
"msFlexPositive": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View prop "style" 3`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 1,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 1,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View prop "style" 4`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexGrow": 1,
"WebkitFlexShrink": 2,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexGrow": 1,
"flexShrink": 2,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 2,
"msFlexPositive": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View rendered element is a "div" by default 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
`;
exports[`components/View rendered element is a "span" when inside <View accessibilityRole="button" /> 1`] = `
<button
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="button"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
}
style={Object {}}
type="button">
<span
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
</button>
`;

View File

@@ -1,75 +1,45 @@
exports[`modules/createDOMElement prop "accessibilityLabel" 1`] = `
<span
aria-label="accessibilityLabel"
className=""
style={Object {}} />
aria-label="accessibilityLabel" />
`;
exports[`modules/createDOMElement prop "accessibilityLiveRegion" 1`] = `
<span
aria-live="polite"
className=""
style={Object {}} />
aria-live="polite" />
`;
exports[`modules/createDOMElement prop "accessibilityRole" button 1`] = `
<button
className=""
role="button"
style={Object {}}
type="button" />
`;
exports[`modules/createDOMElement prop "accessibilityRole" link and target="_blank" 1`] = `
<a
className=""
rel=" noopener noreferrer"
role="link"
style={Object {}}
target="_blank" />
`;
exports[`modules/createDOMElement prop "accessibilityRole" roles 1`] = `
<header
className=""
role="banner"
style={Object {}} />
role="banner" />
`;
exports[`modules/createDOMElement prop "accessible" 1`] = `
<span
className=""
style={Object {}} />
`;
exports[`modules/createDOMElement prop "accessible" 1`] = `<span />`;
exports[`modules/createDOMElement prop "accessible" 2`] = `
<span
className=""
style={Object {}} />
`;
exports[`modules/createDOMElement prop "accessible" 2`] = `<span />`;
exports[`modules/createDOMElement prop "accessible" 3`] = `
<span
aria-hidden={true}
className=""
style={Object {}} />
aria-hidden={true} />
`;
exports[`modules/createDOMElement prop "testID" 1`] = `
<span
className=""
data-testid="Example.testID"
style={Object {}} />
data-testid="Example.testID" />
`;
exports[`modules/createDOMElement renders correct DOM element 1`] = `
<span
className=""
style={Object {}} />
`;
exports[`modules/createDOMElement renders correct DOM element 1`] = `<span />`;
exports[`modules/createDOMElement renders correct DOM element 2`] = `
<main
className=""
style={Object {}} />
`;
exports[`modules/createDOMElement renders correct DOM element 2`] = `<main />`;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import StyleSheet from '../../apis/StyleSheet';
import StyleRegistry from '../../apis/StyleSheet/registry';
const emptyObject = {};
@@ -33,7 +33,7 @@ const createDOMElement = (component, rnProps = emptyObject) => {
const accessibilityComponent = accessibilityRole && roleComponents[accessibilityRole];
const Component = accessibilityComponent || component;
Object.assign(domProps, StyleSheet.resolve(domProps));
const { className, style } = StyleRegistry.resolve(domProps.style) || emptyObject;
if (!accessible) { domProps['aria-hidden'] = true; }
if (accessibilityLabel) { domProps['aria-label'] = accessibilityLabel; }
@@ -47,6 +47,12 @@ const createDOMElement = (component, rnProps = emptyObject) => {
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
}
}
if (className && className !== '') {
domProps.className = domProps.className ? `${domProps.className} ${className}` : className;
}
if (style) {
domProps.style = style;
}
if (type) { domProps.type = type; }
return (

View File

@@ -0,0 +1,18 @@
function flattenArray(array) {
function flattenDown(array, result) {
for (let i = 0; i < array.length; i++) {
const value = array[i];
if (Array.isArray(value)) {
flattenDown(value, result);
} else if (value != null && value !== false) {
result.push(value);
}
}
return result;
}
return flattenDown(array, []);
}
module.exports = flattenArray;

View File

@@ -0,0 +1,13 @@
const hasOwnProperty = Object.prototype.hasOwnProperty;
const mapKeyValue = (obj, fn) => {
const result = [];
for (const key in obj) {
if (hasOwnProperty.call(obj, key)) {
result.push(fn(key, obj[key]));
}
}
return result;
};
module.exports = mapKeyValue;

View File

@@ -292,7 +292,7 @@ arrify@^1.0.0, arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
asap@~2.0.3:
asap@^2.0.5, asap@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"