Compare commits

...

21 Commits

Author SHA1 Message Date
Nicolas Gallagher
cc46c1d1a8 [change] Introduce static CSS base rules for core primitives
This patch addresses 2 related issues:

1) Browser layout times in Chrome increase when elements use a lot of CSS class
names. This begins to add up for larger trees.

2) React Native supports passing 'null' values for styles. This can remove base
styles defined using 'StyleSheet' in the implementation of components like View
and Text.

Both of these issues can be avoided, and some runtime logic and computation
removed, by moving the base styles to static CSS rules. Comparisons of the
"benchmark" UI tests (which only render View) indicate that total times in
Chrome are reduced by ~20% with almost all of those savings coming from a
~33% reduction in layout-related timings.

Ref #1136
2019-01-01 13:50:58 -08:00
Nicolas Gallagher
000b92e707 0.9.13 2018-12-31 17:31:46 -08:00
Nicolas Gallagher
d5f5dbccdb [fix] inline-style-prefixer API update
Fix #1217
2018-12-31 17:22:05 -08:00
Nicolas Gallagher
79456d5920 0.9.12 2018-12-31 10:34:04 -08:00
Nicolas Gallagher
2d1e303a6a [fix] ScrollView with stickyHeaderIndices regression
A ScrollView with stickyHeaderIndices would not render children that
weren't sticky.

Fixes 1e202b6bd5
2018-12-31 10:26:33 -08:00
Nicolas Gallagher
209ff1fa40 0.9.11 2018-12-31 08:23:58 -08:00
Nicolas Gallagher
34d8160a43 [fix] CSS animation insertion for Android 5.1 HuaWei browser
Inserting unprefixed CSS keyframes rules causes a `SYNTAX_ERR: DOM Exception
12` error in Android 5.1. A similar issue with inserting rules containing
vendor-prefixed pseudo-selectors was patched by wrapping rule in `@media all
{}` blocks. This patch removes the media query wrapper from keyframe animations
(as it doesn't prevent the error) and relies on `CSSStyleSheet::insertRule`
being called within a try-catch block.

Fix #1199
Close #1210
2018-12-31 07:47:52 -08:00
Nicolas Gallagher
ada5651be2 Don't minify local benchmarks build 2018-12-24 13:35:21 +00:00
Nicolas Gallagher
9fe9d3c68a Update react-native-web dependencies 2018-12-24 13:03:49 +00:00
krister
1e202b6bd5 [add] ScrollView support for pagingEnabled
Available in browsers that support CSS snappoints.

Fix #1057
Close #1212

Co-authored-by: Nicolas Gallagher <nicolasgallagher@gmail.com>
2018-12-23 18:05:29 +00:00
Robert Sayre
2b77bfd853 [fix] improve style resolver performance
Script time in the benchmark was profiled by adding `console.profile` around
the timings for script time. The call to Array.join in the resolve function
stood out. Since the code already iterates over the array it can run slightly
faster by building the cache key in that loop instead.

Close #1213
2018-12-23 15:07:48 +00:00
Nicolas Gallagher
d0ac55aa4f Update benchmarks dependencies 2018-12-21 21:33:43 +00:00
Nicolas Gallagher
66dd1bd9ef Remove glamor and radium from benchmarks
Glamor is unmaintained. Radium is extremely slow. There is no need to compare
against these libraries.
2018-12-21 21:20:30 +00:00
Nicolas Gallagher
6add18c6f0 0.9.10 2018-12-21 21:04:26 +00:00
krister
30d7c31b65 [fix] ScrollView smooth scrolling
Rely on the `element.scroll()` programmatic API when available (or polyfilled).

Fix #1203
Fix #1173
Close #1208
2018-12-21 20:57:25 +00:00
Raibima Putra
f7e6b43422 Fix docs typo
Close #1209
2018-12-21 20:46:29 +00:00
Nicolas Gallagher
4b3f6efb21 Support style inspection in production 2018-12-10 17:01:23 -08:00
Nicolas Gallagher
85e098deec 0.9.9 2018-12-05 14:46:11 -08:00
Nicolas Gallagher
c949b0145a [fix] TextInput onKeyPress supports Escape key
Fix #1189
2018-11-27 12:15:59 -08:00
Charlie Croom
86b4cf5a51 [add] scaleZ and scale3d style transforms
Web-specific additions similar to the web-specific additions to `translate` styles.

Close #1184
2018-11-27 11:53:04 -08:00
Giuseppe
1b7ce4eec6 [fix] Fix regression in modality refactor
Fixes https://github.com/necolas/react-native-web/pull/1169#issuecomment-440590544

And removes the focus ring for press-after-keyboard edge cases.

Close #1186

Co-authored-by: Nicolas Gallagher <nicolasgallagher@gmail.com>
2018-11-27 11:52:46 -08:00
55 changed files with 1644 additions and 950 deletions

View File

@@ -140,7 +140,7 @@ React Native v0.55
| Picker | ✓ | |
| RefreshControl | ✘ | Not started ([#1027](https://github.com/necolas/react-native-web/issues/1027)). |
| SafeAreaView | ✓ | |
| ScrollView | ✓ | Missing momentum scroll events ([#1021](https://github.com/necolas/react-native-web/issues/1021)) and `pagingEnabled` ([#1057](https://github.com/necolas/react-native-web/issues/1057)). |
| ScrollView | ✓ | Missing momentum scroll events ([#1021](https://github.com/necolas/react-native-web/issues/1021)). |
| SectionList | ✓ | |
| Slider | ✘ | Not started ([#1022](https://github.com/necolas/react-native-web/issues/1022)). |
| StatusBar | (✓) | Mock. No equivalent web APIs. |

View File

@@ -36,7 +36,7 @@ target platform.
What follows is merely an _example_ of one basic way to package a web app using
[Webpack](https://webpack.js.org) and [Babel](https://babeljs.io/). (You can
also the React Native bundler, [Metro](https://github.com/facebook/metro), to
also use the React Native bundler, [Metro](https://github.com/facebook/metro), to
build web apps.)
Packaging web apps is subtly different to packaging React Native apps and is

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "0.9.8",
"version": "0.9.13",
"name": "react-native-web-monorepo",
"scripts": {
"clean": "del ./packages/*/dist",
@@ -49,15 +49,16 @@
"flow-bin": "^0.63.1",
"glob": "^7.1.2",
"husky": "^0.14.3",
"inline-style-prefixer": "^5.0.3",
"jest": "^22.4.3",
"jest-canvas-mock": "^1.0.2",
"lint-staged": "^7.1.0",
"npm-run-all": "^4.1.3",
"prettier": "^1.12.1",
"react": "^16.5.1",
"react-art": "^16.5.1",
"react-dom": "^16.5.1",
"react-test-renderer": "^16.5.1"
"react": "^16.7.0",
"react-art": "^16.7.0",
"react-dom": "^16.7.0",
"react-test-renderer": "^16.7.0"
},
"workspaces": [
"packages/*"

View File

@@ -1,6 +1,6 @@
{
"name": "babel-plugin-react-native-web",
"version": "0.9.8",
"version": "0.9.13",
"description": "Babel plugin for React Native for Web",
"main": "index.js",
"devDependencies": {

View File

@@ -1,37 +1,35 @@
{
"private": true,
"name": "benchmarks",
"version": "0.9.8",
"version": "0.9.13",
"scripts": {
"build": "mkdir -p dist && cp -f index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
"release": "yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
"release": "NODE_ENV=production yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
},
"dependencies": {
"aphrodite": "^2.2.2",
"aphrodite": "^2.2.3",
"classnames": "^2.2.6",
"d3-scale-chromatic": "^1.3.0",
"emotion": "^9.2.4",
"fela": "^6.1.9",
"glamor": "2.20.40",
"radium": "^0.24.0",
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-fela": "^7.3.1",
"d3-scale-chromatic": "^1.3.3",
"emotion": "^10.0.5",
"fela": "^10.0.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-fela": "^10.0.2",
"react-jss": "^8.6.1",
"react-native-web": "0.9.8",
"reactxp": "^1.3.0",
"styled-components": "^3.3.3",
"styled-jsx": "^2.2.7",
"styletron-engine-atomic": "^1.0.5",
"styletron-react": "^4.3.1"
"react-native-web": "0.9.13",
"reactxp": "^1.5.0",
"styled-components": "^4.1.3",
"styled-jsx": "^3.1.2",
"styletron-engine-atomic": "^1.0.13",
"styletron-react": "^4.4.4"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.9.8",
"css-loader": "^1.0.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"webpack": "^4.15.1",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8"
"babel-plugin-react-native-web": "0.9.13",
"css-loader": "^2.0.2",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.28.1",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.2"
}
}

View File

@@ -1,49 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={[
styles[`color${color}`],
fixed && styles.fixed,
layout === 'row' && styles.row,
outer && styles.outer
]}
/>
);
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: '#E0245E'
},
fixed: {
width: 6,
height: 6
}
};
export default Box;

View File

@@ -1,33 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { css } from 'glamor';
const Dot = ({ size, x, y, children, color }) => (
<div
className={css(styles.root, {
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
marginLeft: `${x}px`,
marginTop: `${y}px`
})}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};
export default Dot;

View File

@@ -1,2 +0,0 @@
import View from './View';
export default View;

View File

@@ -1,29 +0,0 @@
/* eslint-disable react/prop-types */
import { css } from 'glamor';
import React from 'react';
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={css(viewStyle, ...style)} />;
}
}
const viewStyle = {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
};
export default View;

View File

@@ -1,11 +0,0 @@
import Box from './Box';
import Dot from './Dot';
import Provider from './Provider';
import View from './View';
export default {
Box,
Dot,
Provider,
View
};

View File

@@ -1,50 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={[
styles[`color${color}`],
fixed && styles.fixed,
layout === 'row' && styles.row,
outer && styles.outer
]}
/>
);
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: '#E0245E'
},
fixed: {
width: 6,
height: 6
}
};
export default Radium(Box);

View File

@@ -1,36 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
const Dot = ({ size, x, y, children, color }) => (
<div
style={[
styles.root,
{
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
marginLeft: `${x}px`,
marginTop: `${y}px`
}
]}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};
export default Radium(Dot);

View File

@@ -1,2 +0,0 @@
import View from './View';
export default View;

View File

@@ -1,31 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} style={[styles.root, style]} />;
}
}
const styles = {
root: {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
}
};
export default Radium(View);

View File

@@ -1,11 +0,0 @@
import Box from './Box';
import Dot from './Dot';
import Provider from './Provider';
import View from './View';
export default {
Box,
Dot,
Provider,
View
};

View File

@@ -12,6 +12,9 @@ module.exports = {
path: path.resolve(appDirectory, 'dist'),
filename: 'bundle.js'
},
optimization: {
minimize: process.env.NODE_ENV === 'production'
},
module: {
rules: [
{

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "react-native-examples",
"version": "0.9.8",
"version": "0.9.13",
"scripts": {
"build": "mkdir -p dist && cp -f src/index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
"release": "yarn build && git checkout gh-pages && rm -rf ../../examples && mv dist ../../examples && git add -A && git commit -m \"Examples deploy\" && git push origin gh-pages && git checkout -"
@@ -10,10 +10,10 @@
"babel-runtime": "^6.26.0",
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-native-web": "0.9.8"
"react-native-web": "0.9.13"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.9.8",
"babel-plugin-react-native-web": "0.9.13",
"babel-plugin-transform-runtime": "^6.23.0",
"file-loader": "^1.1.11",
"webpack": "^4.8.1",

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.9.8",
"version": "0.9.13",
"description": "React Native for Web",
"module": "dist/index.js",
"main": "dist/cjs/index.js",
@@ -15,14 +15,14 @@
"dependencies": {
"array-find-index": "^1.0.2",
"create-react-class": "^15.6.2",
"debounce": "^1.1.0",
"deep-assign": "^2.0.0",
"fbjs": "^0.8.16",
"debounce": "^1.2.0",
"deep-assign": "^3.0.0",
"fbjs": "^1.0.0",
"hyphenate-style-name": "^1.0.2",
"inline-style-prefixer": "^4.0.2",
"inline-style-prefixer": "^5.0.3",
"normalize-css-color": "^1.0.2",
"prop-types": "^15.6.0",
"react-timer-mixin": "^0.13.3"
"react-timer-mixin": "^0.13.4"
},
"peerDependencies": {
"react": ">=16.5.1",

View File

@@ -15,35 +15,11 @@ html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlig
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-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
.rn-pointer{cursor:pointer;}
}
.rn-pointerEvents-12vffkv > *{pointer-events:auto}
.rn-pointerEvents-12vffkv{pointer-events:none !important}
.rn-alignItems-1oszu61{-ms-flex-align:stretch;-webkit-align-items:stretch;-webkit-box-align:stretch;align-items:stretch}
.rn-borderTopStyle-1efd50x{border-top-style:solid}
.rn-borderRightStyle-14skgim{border-right-style:solid}
.rn-borderBottomStyle-rull8r{border-bottom-style:solid}
.rn-borderLeftStyle-mm0ijv{border-left-style:solid}
.rn-borderTopWidth-13yce4e{border-top-width:0px}
.rn-borderRightWidth-fnigne{border-right-width:0px}
.rn-borderBottomWidth-ndvcnb{border-bottom-width:0px}
.rn-borderLeftWidth-gxnn5r{border-left-width:0px}
.rn-boxSizing-deolkf{box-sizing:border-box}
.rn-display-6koalj{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
.rn-flexShrink-1qe8dj5{-ms-flex-negative:0;-webkit-flex-shrink:0;flex-shrink:0}
.rn-flexBasis-1mlwlqe{-ms-flex-preferred-size:auto;-webkit-flex-basis:auto;flex-basis:auto}
.rn-flexDirection-eqz5dr{-ms-flex-direction:column;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
.rn-marginTop-1mnahxq{margin-top:0px}
.rn-marginRight-61z16t{margin-right:0px}
.rn-marginBottom-p1pxzi{margin-bottom:0px}
.rn-marginLeft-11wrixw{margin-left:0px}
.rn-minHeight-ifefl9{min-height:0px}
.rn-minWidth-bcqeeo{min-width:0px}
.rn-paddingTop-wk8lta{padding-top:0px}
.rn-paddingRight-9aemit{padding-right:0px}
.rn-paddingBottom-1mdbw0j{padding-bottom:0px}
.rn-paddingLeft-gy4na3{padding-left:0px}
.rn-position-bnwqim{position:relative}
.rn-zIndex-1lgpqti{z-index:0}
.rn-flexGrow-16y2uox{-ms-flex-positive:1;-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}
.rn-flexShrink-1wbh5a2{-ms-flex-negative:1;-webkit-flex-shrink:1;flex-shrink:1}
.rn-flexBasis-1ro0kt6{-ms-flex-preferred-size:0%;-webkit-flex-basis:0%;flex-basis:0%}"
@@ -64,5 +40,11 @@ html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlig
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;}
}</style>"
.rn-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
.rn-pointer{cursor:pointer;}
}
.rn-view-1d2if7t{-ms-flex-align:stretch;-ms-flex-direction:column;-ms-flex-negative:0;-ms-flex-preferred-size:auto;-webkit-align-items:stretch;-webkit-box-align:stretch;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-basis:auto;-webkit-flex-direction:column;-webkit-flex-shrink:0;align-items:stretch;background-color:transparent;border:0 solid black;box-sizing:border-box;color:inherit;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;flex-basis:auto;flex-direction:column;flex-shrink:0;font:inherit;list-style:none;margin:0px;min-height:0px;min-width:0px;padding:0px;position:relative;text-align:inherit;text-decoration:none;z-index:0}
.rn-hitSlop-1be6dej{bottom:0px;left:0px;position:absolute;right:0px;top:0px;z-index:-1}
.rn-text-1ogs4z5{background-color:transparent;border-width:0px;box-sizing:border-box;color:inherit;display:inline;font:14px system-ui, -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Roboto, Ubuntu, \\"Helvetica Neue\\", sans-serif;margin:0px;padding:0px;text-align:inherit;text-decoration:none;white-space:pre-wrap;word-wrap:break-word}
.rn-textinput-wwmj4d{-moz-appearance:textfield;-webkit-appearance:none;background-color:transparent;border-radius:0px;border:0 solid black;box-sizing:border-box;font-family:14px System;padding:0px;resize:none}</style>"
`;

View File

@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/ScrollView "pagingEnabled" prop 1`] = `undefined`;
exports[`components/ScrollView "pagingEnabled" prop 2`] = `"y mandatory"`;
exports[`components/ScrollView "pagingEnabled" prop 3`] = `"start"`;

View File

@@ -2,7 +2,9 @@
import React from 'react';
import ScrollView from '..';
import { mount } from 'enzyme';
import StyleSheet from '../../StyleSheet';
import View from '../../View';
import { mount, shallow } from 'enzyme';
describe('components/ScrollView', () => {
test('instance method setNativeProps', () => {
@@ -11,4 +13,32 @@ describe('components/ScrollView', () => {
instance.setNativeProps();
}).not.toThrow();
});
test('"children" prop', () => {
const component = shallow(
<ScrollView>
<View testID="child" />
</ScrollView>
);
expect(component.find({ testID: 'child' }).length).toBe(1);
component.setProps({ stickyHeaderIndices: [4] });
expect(component.find({ testID: 'child' }).length).toBe(1);
component.setProps({ pagingEnabled: true });
expect(component.find({ testID: 'child' }).length).toBe(1);
});
test('"pagingEnabled" prop', () => {
const getStyleProp = (component, prop) => StyleSheet.flatten(component.prop('style'))[prop];
// false
const component = shallow(<ScrollView children={'Child'} />);
expect(getStyleProp(component, 'scrollSnapType')).toMatchSnapshot();
// true
component.setProps({ pagingEnabled: true });
expect(getStyleProp(component, 'scrollSnapType')).toMatchSnapshot();
expect(getStyleProp(component.children().childAt(0), 'scrollSnapAlign')).toMatchSnapshot();
});
});

View File

@@ -137,10 +137,10 @@ const ScrollView = createReactClass({
onContentSizeChange,
refreshControl,
stickyHeaderIndices,
pagingEnabled,
/* eslint-disable */
keyboardDismissMode,
onScroll,
pagingEnabled,
/* eslint-enable */
...other
} = this.props;
@@ -164,11 +164,22 @@ const ScrollView = createReactClass({
};
}
const hasStickyHeaderIndices = !horizontal && Array.isArray(stickyHeaderIndices);
const children =
!horizontal && Array.isArray(stickyHeaderIndices)
hasStickyHeaderIndices || pagingEnabled
? React.Children.map(this.props.children, (child, i) => {
if (child && stickyHeaderIndices.indexOf(i) > -1) {
return <View style={styles.stickyHeader}>{child}</View>;
const isSticky = hasStickyHeaderIndices && stickyHeaderIndices.indexOf(i) > -1;
if (child != null && (isSticky || pagingEnabled)) {
return (
<View
style={StyleSheet.compose(
isSticky && styles.stickyHeader,
pagingEnabled && styles.pagingEnabledChild
)}
>
{child}
</View>
);
} else {
return child;
}
@@ -181,15 +192,21 @@ const ScrollView = createReactClass({
children={children}
collapsable={false}
ref={this._setInnerViewRef}
style={[horizontal && styles.contentContainerHorizontal, contentContainerStyle]}
style={StyleSheet.compose(
horizontal && styles.contentContainerHorizontal,
contentContainerStyle
)}
/>
);
const baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical;
const pagingEnabledStyle = horizontal
? styles.pagingEnabledHorizontal
: styles.pagingEnabledVertical;
const props = {
...other,
style: [baseStyle, this.props.style],
style: [baseStyle, pagingEnabled && pagingEnabledStyle, this.props.style],
onTouchStart: this.scrollResponderHandleTouchStart,
onTouchMove: this.scrollResponderHandleTouchMove,
onTouchEnd: this.scrollResponderHandleTouchEnd,
@@ -223,7 +240,7 @@ const ScrollView = createReactClass({
}
return (
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={props.style}>
<ScrollViewClass {...props} ref={this._setScrollViewRef}>
{contentContainer}
</ScrollViewClass>
);
@@ -294,6 +311,15 @@ const styles = StyleSheet.create({
position: 'sticky',
top: 0,
zIndex: 10
},
pagingEnabledHorizontal: {
scrollSnapType: 'x mandatory'
},
pagingEnabledVertical: {
scrollSnapType: 'y mandatory'
},
pagingEnabledChild: {
scrollSnapAlign: 'start'
}
});

View File

@@ -84,15 +84,19 @@ export default class ReactNativeStyleResolver {
// otherwise fallback to resolving
const flatArray = flattenArray(style);
let isArrayOfNumbers = true;
let cacheKey = '';
for (let i = 0; i < flatArray.length; i++) {
const id = flatArray[i];
if (typeof id !== 'number') {
isArrayOfNumbers = false;
} else {
if (isArrayOfNumbers) {
cacheKey += (id + '-');
}
this._injectRegisteredStyle(id);
}
}
const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
const key = isArrayOfNumbers ? createCacheKey(cacheKey) : null;
return this._resolveStyleIfNeeded(flatArray, key);
}

View File

@@ -15,9 +15,9 @@ import WebStyleSheet from './WebStyleSheet';
const emptyObject = {};
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
const createClassName = (prop, value) => {
const hashed = hash(prop + normalizeValue(value));
return process.env.NODE_ENV !== 'production' ? `rn-${prop}-${hashed}` : `rn-${hashed}`;
const createClassName = (name, value) => {
const hashed = hash(name + normalizeValue(value));
return process.env.NODE_ENV !== 'production' ? `rn-${name}-${hashed}` : `rn-${hashed}`;
};
const normalizeValue = value => (typeof value === 'object' ? JSON.stringify(value) : value);
@@ -69,6 +69,13 @@ export default class StyleSheetManager {
return className;
}
injectRule(name, body: string): void {
const className = createClassName(name, body);
const rule = `.${className}{${body}}`;
this._sheet.insertRuleOnce(rule);
return className;
}
_addToCache(className, prop, value) {
const cache = this._cache;
if (!cache.byProp[prop]) {

View File

@@ -103,10 +103,7 @@ StyleSheetValidation.addValidStylePropTypes({
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
objectPosition: string,
pointerEvents: string,
tableLayout: string,
/* @private */
MozAppearance: string,
WebkitAppearance: string
tableLayout: string
});
export default StyleSheetValidation;

View File

@@ -55,7 +55,15 @@ export default class WebStyleSheet {
// doesn't include styles injected via 'insertRule')
if (this._textContent.indexOf(rule) === -1 && this._sheet) {
const pos = position || this._sheet.cssRules.length;
this._sheet.insertRule(rule, pos);
try {
this._sheet.insertRule(rule, pos);
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`Failed to inject CSS: "${rule}". The browser may have rejecting unrecognized vendor prefixes. (This should have no user-facing impact.)`
);
}
}
}
}
}

View File

@@ -10,6 +10,8 @@ html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlig
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-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
.rn-pointer{cursor:pointer;}
}
.rn---test-property-ax3bxi{--test-property:test-value}",
}

View File

@@ -2,10 +2,10 @@
exports[`StyleSheet/createAtomicRules transforms custom "animationName" declaration 1`] = `
Array [
"@media all {@-webkit-keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}}",
"@media all {@keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}}",
"@media all {@-webkit-keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}}",
"@media all {@keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}}",
"@-webkit-keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
"@keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
"@-webkit-keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}",
"@keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}",
".test{-webkit-animation-name:rn-anim-2k74q5,rn-anim-zc91cv;animation-name:rn-anim-2k74q5,rn-anim-zc91cv}",
]
`;

View File

@@ -39,44 +39,17 @@ describe('StyleSheet/createReactDOMStyle', () => {
expect(createReactDOMStyle(style)).toMatchSnapshot();
});
describe('borderWidth styles', () => {
test('defaults to 0 when "null"', () => {
expect(createReactDOMStyle({ borderWidth: null })).toEqual({
borderTopWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '0px',
borderLeftWidth: '0px'
});
expect(createReactDOMStyle({ borderWidth: 2, borderRightWidth: null })).toEqual({
borderTopWidth: '2px',
borderRightWidth: '0px',
borderBottomWidth: '2px',
borderLeftWidth: '2px'
});
});
});
describe('flexbox styles', () => {
test('flex defaults', () => {
expect(createReactDOMStyle({ display: 'flex' })).toEqual({
display: 'flex',
flexShrink: 0,
flexBasis: 'auto'
});
});
test('flex: -1', () => {
expect(createReactDOMStyle({ display: 'flex', flex: -1 })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: -1 })).toEqual({
flexBasis: 'auto',
flexGrow: 0,
flexShrink: 1,
flexBasis: 'auto'
flexShrink: 1
});
});
test('flex: 0', () => {
expect(createReactDOMStyle({ display: 'flex', flex: 0 })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: 0 })).toEqual({
flexGrow: 0,
flexShrink: 0,
flexBasis: '0%'
@@ -84,8 +57,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
});
test('flex: 1', () => {
expect(createReactDOMStyle({ display: 'flex', flex: 1 })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: 1 })).toEqual({
flexGrow: 1,
flexShrink: 1,
flexBasis: '0%'
@@ -93,8 +65,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
});
test('flex: 10', () => {
expect(createReactDOMStyle({ display: 'flex', flex: 10 })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: 10 })).toEqual({
flexGrow: 10,
flexShrink: 1,
flexBasis: '0%'
@@ -103,15 +74,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
test('flexBasis overrides', () => {
// is flex-basis applied?
expect(createReactDOMStyle({ display: 'flex', flexBasis: '25%' })).toEqual({
display: 'flex',
flexShrink: 0,
expect(createReactDOMStyle({ flexBasis: '25%' })).toEqual({
flexBasis: '25%'
});
// can flex-basis override the 'flex' expansion?
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexBasis: '25%' })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: 1, flexBasis: '25%' })).toEqual({
flexGrow: 1,
flexShrink: 1,
flexBasis: '25%'
@@ -120,15 +88,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
test('flexShrink overrides', () => {
// is flex-shrink applied?
expect(createReactDOMStyle({ display: 'flex', flexShrink: 1 })).toEqual({
display: 'flex',
flexShrink: 1,
flexBasis: 'auto'
expect(createReactDOMStyle({ flexShrink: 1 })).toEqual({
flexShrink: 1
});
// can flex-shrink override the 'flex' expansion?
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({
display: 'flex',
expect(createReactDOMStyle({ flex: 1, flexShrink: 2 })).toEqual({
flexGrow: 1,
flexShrink: 2,
flexBasis: '0%'

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
export const monospaceFontStack = 'monospace, monospace';
export const systemFontStack =
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';

View File

@@ -29,7 +29,7 @@ const makeSteps = keyframes =>
const createKeyframesRules = (keyframes: Object): Array<String> => {
const identifier = createIdentifier(keyframes);
const rules = prefixes.map(prefix => {
return `@media all {@${prefix}keyframes ${identifier}{${makeSteps(keyframes)}}}`;
return `@${prefix}keyframes ${identifier}{${makeSteps(keyframes)}}`;
});
return { identifier, rules };
};

View File

@@ -7,6 +7,7 @@
* @noflow
*/
import { monospaceFontStack, systemFontStack } from './constants';
import normalizeColor from '../../modules/normalizeColor';
import normalizeValue from './normalizeValue';
import resolveShadowValue from './resolveShadowValue';
@@ -54,18 +55,6 @@ const colorProps = {
color: true
};
const borderWidthProps = {
borderWidth: true,
borderTopWidth: true,
borderRightWidth: true,
borderBottomWidth: true,
borderLeftWidth: true
};
const monospaceFontStack = 'monospace, monospace';
const systemFontStack =
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
const alphaSortProps = propsArray =>
propsArray.sort((a, b) => {
if (a < b) {
@@ -167,12 +156,6 @@ const createReducer = (style, styleProps) => {
return (resolvedStyle, prop) => {
let value = normalizeValue(prop, style[prop]);
// Make sure the default border width is explicitly set to '0' to avoid
// falling back to any unwanted user-agent styles.
if (borderWidthProps[prop]) {
value = value == null ? normalizeValue(null, 0) : value;
}
// Normalize color values
if (colorProps[prop]) {
value = normalizeColor(value);
@@ -203,21 +186,6 @@ const createReducer = (style, styleProps) => {
break;
}
case 'display': {
resolvedStyle.display = value;
// A flex container in React Native has these defaults which should be
// set only if there is no otherwise supplied flex style.
if (style.display === 'flex' && style.flex == null) {
if (style.flexShrink == null) {
resolvedStyle.flexShrink = 0;
}
if (style.flexBasis == null) {
resolvedStyle.flexBasis = 'auto';
}
}
break;
}
// The 'flex' property value in React Native must be a positive integer,
// 0, or -1.
case 'flex': {

View File

@@ -8,7 +8,6 @@
*/
import hyphenateStyleName from 'hyphenate-style-name';
import mapKeyValue from '../../modules/mapKeyValue';
import normalizeValue from './normalizeValue';
import prefixStyles from '../../modules/prefixStyles';
@@ -27,9 +26,15 @@ const createDeclarationString = (prop, val) => {
* createRuleBlock({ width: 20, color: 'blue' });
* // => 'color:blue;width:20px'
*/
const createRuleBlock = style =>
mapKeyValue(prefixStyles(style), createDeclarationString)
.sort()
.join(';');
const createRuleBlock = style => {
const prefixedStyle = prefixStyles(style);
return (
Object.keys(prefixedStyle)
.map(prop => createDeclarationString(prop, prefixedStyle[prop]))
// put short-form and vendor prefixed properties first
.sort()
.join(';')
);
};
export default createRuleBlock;

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
*/
import createRuleBlock from './createRuleBlock';
import styleResolver from './styleResolver';
import { systemFontStack } from './constants';
const css = {
create(rules) {
const result = {};
Object.keys(rules).forEach(key => {
const rule = rules[key];
if (rule.font && rule.font.indexOf('System') > -1) {
rule.font = rule.font.replace('System', systemFontStack);
}
if (rule.fontFamily === 'System') {
rule.fontFamily = systemFontStack;
}
const cssRule = createRuleBlock(rule);
const className = styleResolver.styleSheetManager.injectRule(key, cssRule);
result[key] = className;
});
return result;
}
};
export default css;

View File

@@ -1,11 +1,9 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import StyleSheet from './StyleSheet';
// allow component styles to be editable in React Dev Tools
if (process.env.NODE_ENV !== 'production') {
if (canUseDOM && window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = StyleSheet.flatten;
}
// allow original component styles to be inspected in React Dev Tools
if (canUseDOM && window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = StyleSheet.flatten;
}
export default StyleSheet;

View File

@@ -12,13 +12,34 @@ const safeRule = rule => `@media all{\n${rule}\n}`;
const resets = [
// minimal top-level reset
'html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}',
'html{' +
'-ms-text-size-adjust:100%;' +
'-webkit-text-size-adjust:100%;' +
'-webkit-tap-highlight-color:rgba(0,0,0,0);' +
'}',
'body{margin:0;}',
// minimal form pseudo-element reset
'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;}'
'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;' +
'}',
// Reset styles for heading, link, and list DOM elements
'.rn-reset{' +
'background-color:transparent;' +
'color:inherit;' +
'font:inherit;' +
'list-style:none;' +
'margin:0;' +
'text-align:inherit;' +
'text-decoration:none;' +
'}',
// For pressable elements
'.rn-pointer{cursor:pointer;}'
];
const reset = [safeRule(resets.join('\n'))];

View File

@@ -21,10 +21,12 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import hash from '../../vendor/hash';
const focusVisibleClass =
'rn-' + (process.env.NODE_ENV !== 'production' ? 'focusVisible-' : '') + hash('focus-visible');
const focusVisibleAttributeName =
'data-rn-' +
(process.env.NODE_ENV !== 'production' ? 'focusvisible-' : '') +
hash('focusvisible');
const rule = `:focus:not(.${focusVisibleClass}) { outline: none; }`;
const rule = `:focus:not([${focusVisibleAttributeName}]){outline: none;}`;
const modality = styleElement => {
if (!canUseDOM) {
@@ -71,7 +73,7 @@ const modality = styleElement => {
/**
* Computes whether the given element should automatically trigger the
* `focus-visible` class being added, i.e. whether it should always match
* `focus-visible` attribute being added, i.e. whether it should always match
* `:focus-visible` when focused.
*/
function focusTriggersKeyboardModality(el) {
@@ -95,22 +97,32 @@ const modality = styleElement => {
}
/**
* Add the `focus-visible` class to the given element if it was not added by
* Add the `focus-visible` attribute to the given element if it was not added by
* the author.
*/
function addFocusVisibleClass(el) {
if (el.classList.contains(focusVisibleClass)) {
function addFocusVisibleAttribute(el) {
if (el.hasAttribute(focusVisibleAttributeName)) {
return;
}
el.classList.add(focusVisibleClass);
el.setAttribute(focusVisibleAttributeName, true);
}
/**
* Remove the `focus-visible` class from the given element if it was not
* Remove the `focus-visible` attribute from the given element if it was not
* originally added by the author.
*/
function removeFocusVisibleClass(el) {
el.classList.remove(focusVisibleClass);
function removeFocusVisibleAttribute(el) {
el.removeAttribute(focusVisibleAttributeName);
}
/**
* Remove the `focus-visible` attribute from all elements in the document.
*/
function removeAllFocusVisibleAttributes() {
const list = document.querySelectorAll(`[${focusVisibleAttributeName}]`);
for (let i = 0; i < list.length; i += 1) {
removeFocusVisibleAttribute(list[i]);
}
}
/**
@@ -124,7 +136,7 @@ const modality = styleElement => {
}
if (isValidFocusTarget(document.activeElement)) {
addFocusVisibleClass(document.activeElement);
addFocusVisibleAttribute(document.activeElement);
}
hadKeyboardEvent = true;
@@ -136,13 +148,20 @@ const modality = styleElement => {
* This avoids the situation where a user presses a key on an already focused
* element, and then clicks on a different element, focusing it with a
* pointing device, while we still think we're in keyboard modality.
* It also avoids the situation where a user presses on an element within a
* previously keyboard-focused element (i.e., `e.target` is not the previously
* focused element, but one of its descendants) and we need to remove the
* focus ring because a `blur` event doesn't occur.
*/
function onPointerDown(e) {
if (hadKeyboardEvent === true) {
removeAllFocusVisibleAttributes();
}
hadKeyboardEvent = false;
}
/**
* On `focus`, add the `focus-visible` class to the target if:
* On `focus`, add the `focus-visible` attribute to the target if:
* - the target received focus as a result of keyboard navigation, or
* - the event target is an element that will likely require interaction
* via the keyboard (e.g. a text box)
@@ -154,19 +173,19 @@ const modality = styleElement => {
}
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
addFocusVisibleClass(e.target);
addFocusVisibleAttribute(e.target);
}
}
/**
* On `blur`, remove the `focus-visible` class from the target.
* On `blur`, remove the `focus-visible` attribute from the target.
*/
function onBlur(e) {
if (!isValidFocusTarget(e.target)) {
return;
}
if (e.target.classList.contains(focusVisibleClass)) {
if (e.target.hasAttribute(focusVisibleAttributeName)) {
// To detect a tab/window switch, we look for a blur event followed
// rapidly by a visibility change.
// If we don't see a visibility change within 100ms, it's probably a
@@ -177,20 +196,20 @@ const modality = styleElement => {
hadFocusVisibleRecently = false;
window.clearTimeout(hadFocusVisibleRecentlyTimeout);
}, 100);
removeFocusVisibleClass(e.target);
removeFocusVisibleAttribute(e.target);
}
}
/**
* If the user changes tabs, keep track of whether or not the previously
* focused element had .focus-visible.
* focused element had the focus-visible attribute.
*/
function onVisibilityChange(e) {
if (document.visibilityState === 'hidden') {
// If the tab becomes active again, the browser will handle calling focus
// on the element (Safari actually calls it twice).
// If this tab change caused a blur on an element with focus-visible,
// re-apply the class when the user switches back to the tab.
// re-apply the attribute when the user switches back to the tab.
if (hadFocusVisibleRecently) {
hadKeyboardEvent = true;
}

View File

@@ -2,7 +2,7 @@
exports[`components/Text prop "onPress" 1`] = `
<div
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-cursor-1loqt21 rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
className="rn-text-1ogs4z5 rn-cursor-1loqt21"
data-focusable={true}
dir="auto"
onClick={[Function]}
@@ -13,14 +13,14 @@ exports[`components/Text prop "onPress" 1`] = `
exports[`components/Text prop "selectable" 1`] = `
<div
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
className="rn-text-1ogs4z5"
dir="auto"
/>
`;
exports[`components/Text prop "selectable" 2`] = `
<div
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-userSelect-lrvibr rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
className="rn-text-1ogs4z5 rn-userSelect-lrvibr"
dir="auto"
/>
`;

View File

@@ -13,6 +13,7 @@ import applyNativeMethods from '../../modules/applyNativeMethods';
import { bool } from 'prop-types';
import { Component } from 'react';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import StyleSheet from '../StyleSheet';
import TextPropTypes from './TextPropTypes';
@@ -65,10 +66,10 @@ class Text extends Component<*> {
otherProps.onKeyDown = this._createEnterHandler(onPress);
}
otherProps.className = classes.text;
// allow browsers to automatically infer the language writing direction
otherProps.dir = dir !== undefined ? dir : 'auto';
otherProps.style = [
styles.initial,
this.context.isInAParentText === true && styles.isInAParentText,
style,
selectable === false && styles.notSelectable,
@@ -97,24 +98,24 @@ class Text extends Component<*> {
}
}
const styles = StyleSheet.create({
initial: {
const classes = css.create({
text: {
backgroundColor: 'transparent',
borderWidth: 0,
boxSizing: 'border-box',
color: 'inherit',
display: 'inline',
fontFamily: 'System',
fontSize: 14,
fontStyle: 'inherit',
fontVariant: ['inherit'],
fontWeight: 'inherit',
lineHeight: 'inherit',
font: '14px System',
margin: 0,
padding: 0,
textDecorationLine: 'none',
textAlign: 'inherit',
textDecoration: 'none',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word'
},
}
});
const styles = StyleSheet.create({
isInAParentText: {
// inherit parent font styles
fontFamily: 'inherit',

View File

@@ -180,6 +180,25 @@ describe('components/TextInput', () => {
});
describe('prop "onKeyPress"', () => {
test('arrow key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 37 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'ArrowLeft',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('backspace key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -199,25 +218,6 @@ describe('components/TextInput', () => {
);
});
test('tab key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyDown', { which: 9 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'Tab',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('enter key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -237,6 +237,25 @@ describe('components/TextInput', () => {
);
});
test('escape key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 27 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'Escape',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('space key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -256,17 +275,17 @@ describe('components/TextInput', () => {
);
});
test('arrow key', () => {
test('tab key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 37 });
input.simulate('keyDown', { which: 9 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'ArrowLeft',
key: 'Tab',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()

View File

@@ -14,8 +14,8 @@ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import { Component } from 'react';
import ColorPropType from '../ColorPropType';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import findNodeHandle from '../findNodeHandle';
import StyleSheet from '../StyleSheet';
import StyleSheetPropType from '../../modules/StyleSheetPropType';
import TextInputStylePropTypes from './TextInputStylePropTypes';
import TextInputState from '../../modules/TextInputState';
@@ -256,6 +256,7 @@ class TextInput extends Component<*> {
Object.assign(otherProps, {
autoCorrect: autoCorrect ? 'on' : 'off',
className: classes.textinput,
dir: 'auto',
onBlur: normalizeEventHandler(this._handleBlur),
onChange: normalizeEventHandler(this._handleChange),
@@ -266,7 +267,7 @@ class TextInput extends Component<*> {
readOnly: !editable,
ref: this._setNode,
spellCheck: spellCheck != null ? spellCheck : autoCorrect,
style: [styles.initial, style]
style
});
if (multiline) {
@@ -317,11 +318,13 @@ class TextInput extends Component<*> {
// Prevent key events bubbling (see #612)
e.stopPropagation();
// Backspace, Tab, Cmd+Enter, and Arrow keys only fire 'keydown' DOM events
// Backspace, Escape, Tab, Cmd+Enter, and Arrow keys only fire 'keydown'
// DOM events
if (
e.which === 8 ||
e.which === 9 ||
(e.which === 13 && e.metaKey) ||
e.which === 27 ||
e.which === 37 ||
e.which === 38 ||
e.which === 39 ||
@@ -348,6 +351,9 @@ class TextInput extends Component<*> {
case 13:
keyValue = 'Enter';
break;
case 27:
keyValue = 'Escape';
break;
case 32:
keyValue = ' ';
break;
@@ -423,18 +429,15 @@ class TextInput extends Component<*> {
};
}
const styles = StyleSheet.create({
initial: {
const classes = css.create({
textinput: {
MozAppearance: 'textfield',
WebkitAppearance: 'none',
backgroundColor: 'transparent',
borderColor: 'black',
border: '0 solid black',
borderRadius: 0,
borderStyle: 'solid',
borderWidth: 0,
boxSizing: 'border-box',
fontFamily: 'System',
fontSize: 14,
fontFamily: '14px System',
padding: 0,
resize: 'none'
}

View File

@@ -51,6 +51,8 @@ const ViewStylePropTypes = {
overscrollBehavior: overscrollBehaviorType,
overscrollBehaviorX: overscrollBehaviorType,
overscrollBehaviorY: overscrollBehaviorType,
scrollSnapAlign: string,
scrollSnapType: string,
WebkitMaskImage: string,
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
};

View File

@@ -10,6 +10,7 @@ import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import { bool } from 'prop-types';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import filterSupportedProps from './filterSupportedProps';
import invariant from 'fbjs/lib/invariant';
import StyleSheet from '../StyleSheet';
@@ -51,14 +52,18 @@ class View extends Component<ViewProps> {
const { isInAParentText } = this.context;
supportedProps.className = classes.view;
supportedProps.style = StyleSheet.compose(
styles.initial,
StyleSheet.compose(isInAParentText && styles.inline, this.props.style)
isInAParentText && styles.inline,
this.props.style
);
if (hitSlop) {
const hitSlopStyle = calculateHitSlopStyle(hitSlop);
const hitSlopChild = createElement('span', { style: [styles.hitSlop, hitSlopStyle] });
const hitSlopChild = createElement('span', {
className: classes.hitslop,
style: hitSlopStyle
});
supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
}
@@ -66,32 +71,45 @@ class View extends Component<ViewProps> {
}
}
const styles = StyleSheet.create({
// https://github.com/facebook/css-layout#default-values
initial: {
const classes = css.create({
view: {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
border: '0 solid black',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
minHeight: 0,
minWidth: 0,
padding: 0,
position: 'relative',
zIndex: 0,
// fix flexbox bugs
minHeight: 0,
minWidth: 0
},
inline: {
display: 'inline-flex'
// resets for if View is rendered as a link or list DOM element
backgroundColor: 'transparent',
color: 'inherit',
font: 'inherit',
listStyle: 'none',
textAlign: 'inherit',
textDecoration: 'none'
},
// this zIndex-ordering positions the hitSlop above the View but behind
// its children
hitSlop: {
...StyleSheet.absoluteFillObject,
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: -1
}
});
const styles = StyleSheet.create({
inline: {
display: 'inline-flex'
}
});
export default applyLayout(applyNativeMethods(View));

View File

@@ -375,9 +375,14 @@ const ScrollResponderMixin = {
({ x, y, animated } = x || emptyObject);
}
const node = this.scrollResponderGetScrollableNode();
UIManager.updateView(node, { style: { scrollBehavior: !animated ? 'auto' : 'smooth' } }, this);
node.scrollLeft = x || 0;
node.scrollTop = y || 0;
const left = x || 0;
const top = y || 0;
if (typeof node.scroll === 'function') {
node.scroll({ top, left, behavior: !animated ? 'auto' : 'smooth' });
} else {
node.scrollLeft = left;
node.scrollTop = top;
}
},
/**

View File

@@ -23,6 +23,8 @@ const TransformPropTypes = {
shape({ scale: number }),
shape({ scaleX: number }),
shape({ scaleY: number }),
shape({ scaleZ: number }),
shape({ scale3d: string }),
shape({ skewX: string }),
shape({ skewY: string }),
shape({ translateX: numberOrString }),

View File

@@ -2,10 +2,14 @@
exports[`modules/createDOMProps includes "rel" values for "a" elements (to securely open external links) 1`] = `" noopener noreferrer"`;
exports[`modules/createDOMProps includes cursor style for "button" role 1`] = `"rn-cursor-1loqt21"`;
exports[`modules/createDOMProps includes base reset style for browser-styled elements 1`] = `"rn-reset"`;
exports[`modules/createDOMProps includes reset styles for "a" elements 1`] = `"rn-backgroundColor-1niwhzg rn-color-homxoj rn-textDecoration-bauka4"`;
exports[`modules/createDOMProps includes base reset style for browser-styled elements 2`] = `"rn-reset"`;
exports[`modules/createDOMProps includes reset styles for "button" elements 1`] = `"rn-appearance-30o5oe rn-backgroundColor-1niwhzg rn-color-homxoj rn-fontFamily-poiln3 rn-fontSize-7cikom rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-textAlign-1ttztb7"`;
exports[`modules/createDOMProps includes base reset style for browser-styled elements 3`] = `"rn-reset"`;
exports[`modules/createDOMProps includes reset styles for "ul" elements 1`] = `"rn-listStyle-1ebb2ja"`;
exports[`modules/createDOMProps includes base reset style for browser-styled elements 4`] = `"rn-reset"`;
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"rn-pointer"`;
exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"rn-pointer"`;

View File

@@ -200,23 +200,15 @@ describe('modules/createDOMProps', () => {
expect(props.rel).toMatchSnapshot();
});
test('includes reset styles for "a" elements', () => {
const props = createDOMProps('a');
expect(props.className).toMatchSnapshot();
test('includes cursor style for pressable roles', () => {
expect(createDOMProps('span', { accessibilityRole: 'link' }).className).toMatchSnapshot();
expect(createDOMProps('span', { accessibilityRole: 'button' }).className).toMatchSnapshot();
});
test('includes reset styles for "button" elements', () => {
const props = createDOMProps('button');
expect(props.className).toMatchSnapshot();
});
test('includes cursor style for "button" role', () => {
const props = createDOMProps('span', { accessibilityRole: 'button' });
expect(props.className).toMatchSnapshot();
});
test('includes reset styles for "ul" elements', () => {
const props = createDOMProps('ul');
expect(props.className).toMatchSnapshot();
test('includes base reset style for browser-styled elements', () => {
expect(createDOMProps('a').className).toMatchSnapshot();
expect(createDOMProps('button').className).toMatchSnapshot();
expect(createDOMProps('li').className).toMatchSnapshot();
expect(createDOMProps('ul').className).toMatchSnapshot();
});
});

View File

@@ -13,40 +13,6 @@ import styleResolver from '../../exports/StyleSheet/styleResolver';
const emptyObject = {};
const resetStyles = StyleSheet.create({
ariaButton: {
cursor: 'pointer'
},
button: {
appearance: 'none',
backgroundColor: 'transparent',
color: 'inherit',
fontFamily: 'inherit',
fontSize: 'inherit',
fontStyle: 'inherit',
fontVariant: ['inherit'],
fontWeight: 'inherit',
lineHeight: 'inherit',
textAlign: 'inherit'
},
heading: {
fontFamily: 'inherit',
fontSize: 'inherit',
fontStyle: 'inherit',
fontVariant: ['inherit'],
fontWeight: 'inherit',
lineHeight: 'inherit'
},
link: {
backgroundColor: 'transparent',
color: 'inherit',
textDecorationLine: 'none'
},
list: {
listStyle: 'none'
}
});
const pointerEventsStyles = StyleSheet.create({
auto: {
pointerEvents: 'auto'
@@ -123,6 +89,8 @@ const createDOMProps = (component, props, styleResolver) => {
importantForAccessibility !== 'no-hide-descendants';
if (
role === 'link' ||
component === 'a' ||
component === 'button' ||
component === 'input' ||
component === 'select' ||
component === 'textarea'
@@ -146,30 +114,53 @@ const createDOMProps = (component, props, styleResolver) => {
// STYLE
// Resolve React Native styles to optimized browser equivalent
const reactNativeStyle = [
component === 'a' && resetStyles.link,
component === 'button' && resetStyles.button,
role === 'heading' && resetStyles.heading,
component === 'ul' && resetStyles.list,
role === 'button' && !disabled && resetStyles.ariaButton,
const reactNativeStyle = StyleSheet.compose(
pointerEvents && pointerEventsStyles[pointerEvents],
providedStyle,
placeholderTextColor && { placeholderTextColor }
];
StyleSheet.compose(
providedStyle,
placeholderTextColor && { placeholderTextColor }
)
);
const { className, style } = styleResolver(reactNativeStyle);
if (className && className.constructor === String) {
domProps.className = props.className ? `${props.className} ${className}` : className;
}
if (style) {
domProps.style = style;
}
// CLASSNAME
// Apply static style resets
let c;
// style interactive elements for mouse and mobile browsers
if ((role === 'button' || role === 'link') && !disabled) {
c = 'rn-pointer';
}
// style reset various elements (not all are used internally)
if (
component === 'a' ||
component === 'button' ||
component === 'li' ||
component === 'ul' ||
role === 'heading'
) {
c = 'rn-reset' + (c != null ? ' ' + c : '');
}
// style from createElement use
if (props.className != null) {
c = props.className + (c != null ? ' ' + c : '');
}
// style from React Native StyleSheets
if (className != null && className !== '') {
c = (c != null ? c + ' ' : '') + className;
}
if (c != null) {
domProps.className = c;
}
// OTHER
// Native element ID
if (nativeID && nativeID.constructor === String) {
domProps.id = nativeID;
}
// Link security and automation test ids
// Link security
if (component === 'a' && domProps.target === '_blank') {
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
}

View File

@@ -1,14 +0,0 @@
const hasOwnProperty = Object.prototype.hasOwnProperty;
const mapKeyValue = (obj, fn) => {
const result = [];
for (const key in obj) {
if (hasOwnProperty.call(obj, key)) {
const r = fn(key, obj[key]);
r && result.push(r);
}
}
return result;
};
export default mapKeyValue;

View File

@@ -7,7 +7,7 @@
* @flow
*/
import createPrefixer from 'inline-style-prefixer/static/createPrefixer';
import createPrefixer from 'inline-style-prefixer/lib/createPrefixer';
import staticData from './static';
const prefixAll = createPrefixer(staticData);

View File

@@ -1,14 +1,15 @@
import crossFade from 'inline-style-prefixer/static/plugins/crossFade';
import cursor from 'inline-style-prefixer/static/plugins/cursor';
import filter from 'inline-style-prefixer/static/plugins/filter';
import flex from 'inline-style-prefixer/static/plugins/flex';
import flexboxIE from 'inline-style-prefixer/static/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/static/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/static/plugins/gradient';
import imageSet from 'inline-style-prefixer/static/plugins/imageSet';
import position from 'inline-style-prefixer/static/plugins/position';
import sizing from 'inline-style-prefixer/static/plugins/sizing';
import transition from 'inline-style-prefixer/static/plugins/transition';
import backgroundClip from 'inline-style-prefixer/lib/plugins/backgroundClip';
import crossFade from 'inline-style-prefixer/lib/plugins/crossFade';
import cursor from 'inline-style-prefixer/lib/plugins/cursor';
import filter from 'inline-style-prefixer/lib/plugins/filter';
import flex from 'inline-style-prefixer/lib/plugins/flex';
import flexboxIE from 'inline-style-prefixer/lib/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/lib/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/lib/plugins/gradient';
import imageSet from 'inline-style-prefixer/lib/plugins/imageSet';
import position from 'inline-style-prefixer/lib/plugins/position';
import sizing from 'inline-style-prefixer/lib/plugins/sizing';
import transition from 'inline-style-prefixer/lib/plugins/transition';
const w = ['Webkit'];
const m = ['Moz'];
const ms = ['ms'];
@@ -18,6 +19,7 @@ const wmms = ['Webkit', 'Moz', 'ms'];
export default {
plugins: [
backgroundClip,
crossFade,
cursor,
filter,
@@ -120,6 +122,7 @@ export default {
flowInto: wms,
flowFrom: wms,
regionFragment: wms,
textOrientation: w,
textAlignLast: m,
tabSize: m,
wrapFlow: ms,
@@ -144,7 +147,7 @@ export default {
gridRowGap: ms,
gridArea: ms,
gridGap: ms,
textSizeAdjust: wms,
textSizeAdjust: ['ms', 'Webkit'],
borderImage: w,
borderImageOutset: w,
borderImageRepeat: w,

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "website",
"version": "0.9.8",
"version": "0.9.13",
"scripts": {
"build": "build-storybook -o ./dist -c ./storybook/.storybook",
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
@@ -12,10 +12,10 @@
"@storybook/react": "^3.4.3",
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-native-web": "0.9.8"
"react-native-web": "0.9.13"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.9.8",
"babel-plugin-react-native-web": "0.9.13",
"url-loader": "^1.0.1",
"webpack": "^4.8.1"
}

View File

@@ -101,6 +101,12 @@ const ScrollViewScreen = () => (
]}
/>
<DocItem
name="pagingEnabled"
typeInfo="?boolean = false"
description="When true, the scroll view snaps to individual items in the list when scrolling."
/>
<DocItem
name="scrollEnabled"
typeInfo="?boolean = true"

View File

@@ -1,6 +1,6 @@
'use strict';
const generator = require('inline-style-prefixer/generator');
const generator = require('inline-style-prefixer/lib/generator').default;
const path = require('path');
const browserList = {
@@ -19,8 +19,5 @@ const browserList = {
};
generator(browserList, {
staticPath: path.join(
__dirname,
'../../packages/react-native-web/src/modules/prefixStyles/static.js'
)
path: path.join(__dirname, '../../packages/react-native-web/src/modules/prefixStyles/static.js')
});

1488
yarn.lock

File diff suppressed because it is too large Load Diff