mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 17:34:05 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d880457f22 | ||
|
|
7663adebe8 | ||
|
|
1d1ebdb984 | ||
|
|
04b71784a4 | ||
|
|
a2402e6077 | ||
|
|
3d4a179ef6 | ||
|
|
56ced136d7 | ||
|
|
dfb878dc26 | ||
|
|
ead21b3aa2 | ||
|
|
2d1e303a6a | ||
|
|
209ff1fa40 | ||
|
|
34d8160a43 | ||
|
|
ada5651be2 | ||
|
|
9fe9d3c68a | ||
|
|
1e202b6bd5 | ||
|
|
2b77bfd853 | ||
|
|
d0ac55aa4f | ||
|
|
66dd1bd9ef | ||
|
|
6add18c6f0 | ||
|
|
30d7c31b65 | ||
|
|
f7e6b43422 | ||
|
|
4b3f6efb21 | ||
|
|
85e098deec | ||
|
|
c949b0145a | ||
|
|
86b4cf5a51 | ||
|
|
1b7ce4eec6 |
@@ -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. |
|
||||
|
||||
@@ -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
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"name": "react-native-web-monorepo",
|
||||
"scripts": {
|
||||
"clean": "del ./packages/*/dist",
|
||||
@@ -54,10 +54,10 @@
|
||||
"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/*"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "babel-plugin-react-native-web",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"description": "Babel plugin for React Native for Web",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "benchmarks",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"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.11",
|
||||
"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.11",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,2 +0,0 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -1,2 +0,0 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -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);
|
||||
@@ -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
|
||||
};
|
||||
@@ -12,6 +12,9 @@ module.exports = {
|
||||
path: path.resolve(appDirectory, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
optimization: {
|
||||
minimize: process.env.NODE_ENV === 'production'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "react-native-examples",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"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.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.9.8",
|
||||
"babel-plugin-react-native-web": "0.9.11",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"webpack": "^4.8.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"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",
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import AnimatedImplementation from '../../vendor/react-native/Animated/AnimatedImplementation';
|
||||
import FlatList from '../FlatList';
|
||||
import Image from '../Image';
|
||||
import SectionList from '../SectionList';
|
||||
import ScrollView from '../ScrollView';
|
||||
import Text from '../Text';
|
||||
import View from '../View';
|
||||
|
||||
const Animated = {
|
||||
...AnimatedImplementation,
|
||||
FlatList: AnimatedImplementation.createAnimatedComponent(FlatList),
|
||||
Image: AnimatedImplementation.createAnimatedComponent(Image),
|
||||
ScrollView: AnimatedImplementation.createAnimatedComponent(ScrollView),
|
||||
View: AnimatedImplementation.createAnimatedComponent(View),
|
||||
Text: AnimatedImplementation.createAnimatedComponent(Text)
|
||||
SectionList: AnimatedImplementation.createAnimatedComponent(SectionList),
|
||||
Text: AnimatedImplementation.createAnimatedComponent(Text),
|
||||
View: AnimatedImplementation.createAnimatedComponent(View)
|
||||
};
|
||||
|
||||
export default Animated;
|
||||
|
||||
@@ -12,6 +12,8 @@ exports[`components/Image prop "resizeMode" value "cover" 1`] = `"cover"`;
|
||||
|
||||
exports[`components/Image prop "resizeMode" value "none" 1`] = `"auto"`;
|
||||
|
||||
exports[`components/Image prop "resizeMode" value "repeat" 1`] = `"auto"`;
|
||||
|
||||
exports[`components/Image prop "resizeMode" value "stretch" 1`] = `"100% 100%"`;
|
||||
|
||||
exports[`components/Image prop "resizeMode" value "undefined" 1`] = `"cover"`;
|
||||
|
||||
@@ -141,13 +141,7 @@ describe('components/Image', () => {
|
||||
});
|
||||
|
||||
describe('prop "resizeMode"', () => {
|
||||
[
|
||||
Image.resizeMode.contain,
|
||||
Image.resizeMode.cover,
|
||||
Image.resizeMode.none,
|
||||
Image.resizeMode.stretch,
|
||||
undefined
|
||||
].forEach(resizeMode => {
|
||||
['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach(resizeMode => {
|
||||
test(`value "${resizeMode}"`, () => {
|
||||
const component = shallow(<Image resizeMode={resizeMode} />);
|
||||
expect(findImageSurfaceStyle(component).backgroundSize).toMatchSnapshot();
|
||||
@@ -214,7 +208,7 @@ describe('components/Image', () => {
|
||||
|
||||
describe('prop "style"', () => {
|
||||
test('supports "resizeMode" property', () => {
|
||||
const component = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />);
|
||||
const component = shallow(<Image style={{ resizeMode: 'contain' }} />);
|
||||
expect(findImageSurfaceStyle(component).backgroundSize).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -132,8 +132,6 @@ class Image extends Component<*, State> {
|
||||
return ImageLoader.prefetch(uri);
|
||||
}
|
||||
|
||||
static resizeMode = ImageResizeMode;
|
||||
|
||||
_filterId = 0;
|
||||
_imageRef = null;
|
||||
_imageRequestId = null;
|
||||
|
||||
@@ -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"`;
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import createReactClass from 'create-react-class';
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -284,7 +284,10 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
});
|
||||
|
||||
test('textShadowColor and textShadowRadius only', () => {
|
||||
expect(createReactDOMStyle({ textShadowColor: 'red', textShadowRadius: 5 })).toEqual({});
|
||||
expect(createReactDOMStyle({ textShadowColor: 'red', textShadowRadius: 0 })).toEqual({});
|
||||
expect(createReactDOMStyle({ textShadowColor: 'red', textShadowRadius: 5 })).toEqual({
|
||||
textShadow: '0px 0px 5px rgba(255,0,0,1.00)'
|
||||
});
|
||||
});
|
||||
|
||||
test('textShadowColor, textShadowOffset, textShadowRadius', () => {
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -117,12 +117,13 @@ const resolveTextDecoration = (resolvedStyle, style) => {
|
||||
const resolveTextShadow = (resolvedStyle, style) => {
|
||||
const { textShadowColor, textShadowOffset, textShadowRadius } = style;
|
||||
const { height, width } = textShadowOffset || defaultOffset;
|
||||
const radius = textShadowRadius || 0;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, textShadowRadius || 0);
|
||||
const blurRadius = normalizeValue(null, radius);
|
||||
const color = normalizeColor(textShadowColor);
|
||||
|
||||
if (color && (height !== 0 || width !== 0)) {
|
||||
if (color && (height !== 0 || width !== 0 || radius !== 0)) {
|
||||
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -317,11 +317,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 +350,9 @@ class TextInput extends Component<*> {
|
||||
case 13:
|
||||
keyValue = 'Enter';
|
||||
break;
|
||||
case 27:
|
||||
keyValue = 'Escape';
|
||||
break;
|
||||
case 32:
|
||||
keyValue = ' ';
|
||||
break;
|
||||
|
||||
@@ -51,6 +51,8 @@ const ViewStylePropTypes = {
|
||||
overscrollBehavior: overscrollBehaviorType,
|
||||
overscrollBehaviorX: overscrollBehaviorType,
|
||||
overscrollBehaviorY: overscrollBehaviorType,
|
||||
scrollSnapAlign: string,
|
||||
scrollSnapType: string,
|
||||
WebkitMaskImage: string,
|
||||
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
@@ -158,6 +158,7 @@ class AnimatedEvent {
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
_callListeners(...args) {
|
||||
this._listeners.forEach(listener => listener(...args));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import AnimatedModulo from './nodes/AnimatedModulo';
|
||||
import AnimatedMultiplication from './nodes/AnimatedMultiplication';
|
||||
import AnimatedNode from './nodes/AnimatedNode';
|
||||
import AnimatedProps from './nodes/AnimatedProps';
|
||||
import AnimatedSubtraction from './nodes/AnimatedSubtraction';
|
||||
import AnimatedTracking from './nodes/AnimatedTracking';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import AnimatedValueXY from './nodes/AnimatedValueXY';
|
||||
@@ -49,6 +50,13 @@ const add = function(
|
||||
return new AnimatedAddition(a, b);
|
||||
};
|
||||
|
||||
const subtract = function(
|
||||
a: AnimatedNode | number,
|
||||
b: AnimatedNode | number,
|
||||
): AnimatedSubtraction {
|
||||
return new AnimatedSubtraction(a, b);
|
||||
};
|
||||
|
||||
const divide = function(
|
||||
a: AnimatedNode | number,
|
||||
b: AnimatedNode | number,
|
||||
@@ -516,7 +524,7 @@ const AnimatedImplementation = {
|
||||
/**
|
||||
* 2D value class for driving 2D animations, such as pan gestures.
|
||||
*
|
||||
* See https://facebook.github.io/react-native/releases/next/docs/animatedvaluexy.html
|
||||
* See https://facebook.github.io/react-native/docs/animatedvaluexy.html
|
||||
*/
|
||||
ValueXY: AnimatedValueXY,
|
||||
/**
|
||||
@@ -563,6 +571,14 @@ const AnimatedImplementation = {
|
||||
*/
|
||||
add,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value composed by subtracting the second Animated
|
||||
* value from the first Animated value.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animated.html#subtract
|
||||
*/
|
||||
subtract,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value composed by dividing the first Animated value
|
||||
* by the second Animated value.
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import _bezier from './bezier';
|
||||
@@ -131,7 +133,7 @@ class Easing {
|
||||
* http://easings.net/#easeInSine
|
||||
*/
|
||||
static sin(t: number) {
|
||||
return 1 - Math.cos(t * Math.PI / 2);
|
||||
return 1 - Math.cos((t * Math.PI) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +166,7 @@ class Easing {
|
||||
*/
|
||||
static elastic(bounciness: number = 1): (t: number) => number {
|
||||
const p = bounciness * Math.PI;
|
||||
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
|
||||
return t => 1 - Math.pow(Math.cos((t * Math.PI) / 2), 3) * Math.cos(t * p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +181,7 @@ class Easing {
|
||||
if (s === undefined) {
|
||||
s = 1.70158;
|
||||
}
|
||||
return (t) => t * t * ((s + 1) * t - s);
|
||||
return t => t * t * ((s + 1) * t - s);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +219,7 @@ class Easing {
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
y2: number,
|
||||
): (t: number) => number {
|
||||
return _bezier(x1, y1, x2, y2);
|
||||
}
|
||||
@@ -225,19 +227,15 @@ class Easing {
|
||||
/**
|
||||
* Runs an easing function forwards.
|
||||
*/
|
||||
static in(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
static in(easing: (t: number) => number): (t: number) => number {
|
||||
return easing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an easing function backwards.
|
||||
*/
|
||||
static out(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => 1 - easing(1 - t);
|
||||
static out(easing: (t: number) => number): (t: number) => number {
|
||||
return t => 1 - easing(1 - t);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,10 +243,8 @@ class Easing {
|
||||
* forwards for half of the duration, then backwards for the rest of the
|
||||
* duration.
|
||||
*/
|
||||
static inOut(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => {
|
||||
static inOut(easing: (t: number) => number): (t: number) => number {
|
||||
return t => {
|
||||
if (t < 0.5) {
|
||||
return easing(t * 2) / 2;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,15 @@ const API = {
|
||||
const STYLES_WHITELIST = {
|
||||
opacity: true,
|
||||
transform: true,
|
||||
borderRadius: true,
|
||||
borderBottomEndRadius: true,
|
||||
borderBottomLeftRadius: true,
|
||||
borderBottomRightRadius: true,
|
||||
borderBottomStartRadius: true,
|
||||
borderTopEndRadius: true,
|
||||
borderTopLeftRadius: true,
|
||||
borderTopRightRadius: true,
|
||||
borderTopStartRadius: true,
|
||||
/* ios styles */
|
||||
shadowOpacity: true,
|
||||
shadowRadius: true,
|
||||
@@ -200,7 +209,7 @@ function validateTransform(configs: Array<Object>): void {
|
||||
}
|
||||
|
||||
function validateStyles(styles: Object): void {
|
||||
for (var key in styles) {
|
||||
for (const key in styles) {
|
||||
if (!STYLES_WHITELIST.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Style property '${key}' is not supported by native animated module`,
|
||||
@@ -210,7 +219,7 @@ function validateStyles(styles: Object): void {
|
||||
}
|
||||
|
||||
function validateInterpolation(config: Object): void {
|
||||
for (var key in config) {
|
||||
for (const key in config) {
|
||||
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Interpolation property '${key}' is not supported by native animated module`,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c
|
||||
@@ -4,7 +4,8 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -41,7 +42,7 @@ function fromBouncinessAndSpeed(
|
||||
}
|
||||
|
||||
function projectNormal(n, start, end) {
|
||||
return start + (n * (end - start));
|
||||
return start + n * (end - start);
|
||||
}
|
||||
|
||||
function linearInterpolation(t, start, end) {
|
||||
@@ -53,18 +54,20 @@ function fromBouncinessAndSpeed(
|
||||
}
|
||||
|
||||
function b3Friction1(x) {
|
||||
return (0.0007 * Math.pow(x, 3)) -
|
||||
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
|
||||
return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28;
|
||||
}
|
||||
|
||||
function b3Friction2(x) {
|
||||
return (0.000044 * Math.pow(x, 3)) -
|
||||
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
|
||||
return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
|
||||
}
|
||||
|
||||
function b3Friction3(x) {
|
||||
return (0.00000045 * Math.pow(x, 3)) -
|
||||
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
|
||||
return (
|
||||
0.00000045 * Math.pow(x, 3) -
|
||||
0.000332 * Math.pow(x, 2) +
|
||||
0.1078 * x +
|
||||
5.84
|
||||
);
|
||||
}
|
||||
|
||||
function b3Nobounce(tension) {
|
||||
@@ -77,14 +80,14 @@ function fromBouncinessAndSpeed(
|
||||
}
|
||||
}
|
||||
|
||||
var b = normalize(bounciness / 1.7, 0, 20);
|
||||
let b = normalize(bounciness / 1.7, 0, 20);
|
||||
b = projectNormal(b, 0, 0.8);
|
||||
var s = normalize(speed / 1.7, 0, 20);
|
||||
var bouncyTension = projectNormal(s, 0.5, 200);
|
||||
var bouncyFriction = quadraticOutInterpolation(
|
||||
const s = normalize(speed / 1.7, 0, 20);
|
||||
const bouncyTension = projectNormal(s, 0.5, 200);
|
||||
const bouncyFriction = quadraticOutInterpolation(
|
||||
b,
|
||||
b3Nobounce(bouncyTension),
|
||||
0.01
|
||||
0.01,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -266,7 +266,7 @@ class SpringAnimation extends Animation {
|
||||
position =
|
||||
this._toValue -
|
||||
envelope *
|
||||
((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) +
|
||||
(((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) +
|
||||
x0 * Math.cos(omega1 * t));
|
||||
// This looks crazy -- it's actually just the derivative of the
|
||||
// oscillation function
|
||||
@@ -274,7 +274,7 @@ class SpringAnimation extends Animation {
|
||||
zeta *
|
||||
omega0 *
|
||||
envelope *
|
||||
(Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 +
|
||||
((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 +
|
||||
x0 * Math.cos(omega1 * t)) -
|
||||
envelope *
|
||||
(Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
|
||||
|
||||
@@ -2,106 +2,144 @@
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* https://github.com/gre/bezier-easing
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
* @copyright 2014-2015 Gaëtan Renaudeau. MIT License.
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// These values are established by empiricism with tests (tradeoff: performance VS precision)
|
||||
var NEWTON_ITERATIONS = 4;
|
||||
var NEWTON_MIN_SLOPE = 0.001;
|
||||
var SUBDIVISION_PRECISION = 0.0000001;
|
||||
var SUBDIVISION_MAX_ITERATIONS = 10;
|
||||
// These values are established by empiricism with tests (tradeoff: performance VS precision)
|
||||
const NEWTON_ITERATIONS = 4;
|
||||
const NEWTON_MIN_SLOPE = 0.001;
|
||||
const SUBDIVISION_PRECISION = 0.0000001;
|
||||
const SUBDIVISION_MAX_ITERATIONS = 10;
|
||||
|
||||
var kSplineTableSize = 11;
|
||||
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
||||
const kSplineTableSize = 11;
|
||||
const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
||||
|
||||
var float32ArraySupported = typeof Float32Array === 'function';
|
||||
const float32ArraySupported = typeof Float32Array === 'function';
|
||||
|
||||
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
|
||||
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
|
||||
function C (aA1) { return 3.0 * aA1; }
|
||||
function A(aA1, aA2) {
|
||||
return 1.0 - 3.0 * aA2 + 3.0 * aA1;
|
||||
}
|
||||
function B(aA1, aA2) {
|
||||
return 3.0 * aA2 - 6.0 * aA1;
|
||||
}
|
||||
function C(aA1) {
|
||||
return 3.0 * aA1;
|
||||
}
|
||||
|
||||
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
||||
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
|
||||
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
||||
function calcBezier(aT, aA1, aA2) {
|
||||
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
|
||||
}
|
||||
|
||||
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
||||
function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); }
|
||||
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
||||
function getSlope(aT, aA1, aA2) {
|
||||
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
|
||||
}
|
||||
|
||||
function binarySubdivide (aX, aA, aB, mX1, mX2) {
|
||||
var currentX, currentT, i = 0;
|
||||
do {
|
||||
currentT = aA + (aB - aA) / 2.0;
|
||||
currentX = calcBezier(currentT, mX1, mX2) - aX;
|
||||
if (currentX > 0.0) {
|
||||
aB = currentT;
|
||||
} else {
|
||||
aA = currentT;
|
||||
}
|
||||
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
||||
return currentT;
|
||||
}
|
||||
function binarySubdivide(aX, aA, aB, mX1, mX2) {
|
||||
let currentX,
|
||||
currentT,
|
||||
i = 0;
|
||||
do {
|
||||
currentT = aA + (aB - aA) / 2.0;
|
||||
currentX = calcBezier(currentT, mX1, mX2) - aX;
|
||||
if (currentX > 0.0) {
|
||||
aB = currentT;
|
||||
} else {
|
||||
aA = currentT;
|
||||
}
|
||||
} while (
|
||||
Math.abs(currentX) > SUBDIVISION_PRECISION &&
|
||||
++i < SUBDIVISION_MAX_ITERATIONS
|
||||
);
|
||||
return currentT;
|
||||
}
|
||||
|
||||
function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
|
||||
for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
|
||||
var currentSlope = getSlope(aGuessT, mX1, mX2);
|
||||
function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
|
||||
for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
|
||||
const currentSlope = getSlope(aGuessT, mX1, mX2);
|
||||
if (currentSlope === 0.0) {
|
||||
return aGuessT;
|
||||
}
|
||||
var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
||||
const currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
||||
aGuessT -= currentX / currentSlope;
|
||||
}
|
||||
return aGuessT;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function bezier (mX1, mY1, mX2, mY2) {
|
||||
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { // eslint-disable-line yoda
|
||||
throw new Error('bezier x values must be in [0, 1] range');
|
||||
}
|
||||
function bezier(
|
||||
mX1: number,
|
||||
mY1: number,
|
||||
mX2: number,
|
||||
mY2: number,
|
||||
) {
|
||||
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
|
||||
throw new Error('bezier x values must be in [0, 1] range');
|
||||
}
|
||||
|
||||
// Precompute samples table
|
||||
var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
|
||||
if (mX1 !== mY1 || mX2 !== mY2) {
|
||||
for (var i = 0; i < kSplineTableSize; ++i) {
|
||||
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
// Precompute samples table
|
||||
const sampleValues = float32ArraySupported
|
||||
? new Float32Array(kSplineTableSize)
|
||||
: new Array(kSplineTableSize);
|
||||
if (mX1 !== mY1 || mX2 !== mY2) {
|
||||
for (let i = 0; i < kSplineTableSize; ++i) {
|
||||
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
|
||||
function getTForX (aX) {
|
||||
var intervalStart = 0.0;
|
||||
var currentSample = 1;
|
||||
var lastSample = kSplineTableSize - 1;
|
||||
function getTForX(aX) {
|
||||
let intervalStart = 0.0;
|
||||
let currentSample = 1;
|
||||
const lastSample = kSplineTableSize - 1;
|
||||
|
||||
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
|
||||
intervalStart += kSampleStepSize;
|
||||
}
|
||||
--currentSample;
|
||||
for (
|
||||
;
|
||||
currentSample !== lastSample && sampleValues[currentSample] <= aX;
|
||||
++currentSample
|
||||
) {
|
||||
intervalStart += kSampleStepSize;
|
||||
}
|
||||
--currentSample;
|
||||
|
||||
// Interpolate to provide an initial guess for t
|
||||
var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||
var guessForT = intervalStart + dist * kSampleStepSize;
|
||||
// Interpolate to provide an initial guess for t
|
||||
const dist =
|
||||
(aX - sampleValues[currentSample]) /
|
||||
(sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||
const guessForT = intervalStart + dist * kSampleStepSize;
|
||||
|
||||
var initialSlope = getSlope(guessForT, mX1, mX2);
|
||||
if (initialSlope >= NEWTON_MIN_SLOPE) {
|
||||
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
||||
} else if (initialSlope === 0.0) {
|
||||
return guessForT;
|
||||
} else {
|
||||
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
const initialSlope = getSlope(guessForT, mX1, mX2);
|
||||
if (initialSlope >= NEWTON_MIN_SLOPE) {
|
||||
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
||||
} else if (initialSlope === 0.0) {
|
||||
return guessForT;
|
||||
} else {
|
||||
return binarySubdivide(
|
||||
aX,
|
||||
intervalStart,
|
||||
intervalStart + kSampleStepSize,
|
||||
mX1,
|
||||
mX2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return function BezierEasing (x) {
|
||||
if (mX1 === mY1 && mX2 === mY2) {
|
||||
return x; // linear
|
||||
}
|
||||
// Because JavaScript number are imprecise, we should guarantee the extremes are right.
|
||||
if (x === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (x === 1) {
|
||||
return 1;
|
||||
}
|
||||
return calcBezier(getTForX(x), mY1, mY2);
|
||||
};
|
||||
};
|
||||
return function BezierEasing(x: number): number {
|
||||
if (mX1 === mY1 && mX2 === mY2) {
|
||||
return x; // linear
|
||||
}
|
||||
// Because JavaScript number are imprecise, we should guarantee the extremes are right.
|
||||
if (x === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (x === 1) {
|
||||
return 1;
|
||||
}
|
||||
return calcBezier(getTForX(x), mY1, mY2);
|
||||
};
|
||||
};
|
||||
|
||||
export default bezier;
|
||||
|
||||
@@ -17,7 +17,7 @@ import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
function createAnimatedComponent(Component: any): any {
|
||||
invariant(
|
||||
typeof Component === 'string' ||
|
||||
typeof Component !== 'function' ||
|
||||
(Component.prototype && Component.prototype.isReactComponent),
|
||||
'`createAnimatedComponent` does not support stateless functional components; ' +
|
||||
'use a class component instead.',
|
||||
|
||||
@@ -285,7 +285,7 @@ function checkValidInputRange(arr: Array<number>) {
|
||||
* mean this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
'inputRange must be monotonically increasing ' + arr,
|
||||
'inputRange must be monotonically non-decreasing ' + arr,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -349,13 +349,16 @@ class AnimatedInterpolation extends AnimatedWithChildren {
|
||||
__transformDataType(range: Array<any>) {
|
||||
// Change the string array type to number array
|
||||
// So we can reuse the same logic in iOS and Android platform
|
||||
/* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.70 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
return range.map(function(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
if (/deg$/.test(value)) {
|
||||
const degrees = parseFloat(value) || 0;
|
||||
const radians = degrees * Math.PI / 180.0;
|
||||
const radians = (degrees * Math.PI) / 180.0;
|
||||
return radians;
|
||||
} else {
|
||||
// Assume radians
|
||||
|
||||
@@ -32,7 +32,7 @@ class AnimatedModulo extends AnimatedWithChildren {
|
||||
|
||||
__getValue(): number {
|
||||
return (
|
||||
(this._a.__getValue() % this._modulus + this._modulus) % this._modulus
|
||||
((this._a.__getValue() % this._modulus) + this._modulus) % this._modulus
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,13 +96,13 @@ class AnimatedStyle extends AnimatedWithChildren {
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
|
||||
62
packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedSubtraction.js
vendored
Normal file
62
packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedSubtraction.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import AnimatedInterpolation from './AnimatedInterpolation';
|
||||
import AnimatedNode from './AnimatedNode';
|
||||
import AnimatedValue from './AnimatedValue';
|
||||
import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedSubtraction extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._a.__getValue() - this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'subtraction',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default AnimatedSubtraction;
|
||||
@@ -22,7 +22,6 @@ class AnimatedTransform extends AnimatedWithChildren {
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
@@ -31,6 +30,7 @@ class AnimatedTransform extends AnimatedWithChildren {
|
||||
}
|
||||
}
|
||||
});
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): $ReadOnlyArray<Object> {
|
||||
|
||||
@@ -50,6 +50,9 @@ let _uniqueId = 1;
|
||||
function _flush(rootNode: AnimatedValue): void {
|
||||
const animatedStyles = new Set();
|
||||
function findAnimatedStyles(node) {
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
if (typeof node.update === 'function') {
|
||||
animatedStyles.add(node);
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
|
||||
module.exports = {
|
||||
createInteractionHandle: function() {},
|
||||
clearInteractionHandle: function() {}
|
||||
clearInteractionHandle: function() {},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
import InteractionManager from '../../../exports/InteractionManager';
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import UnimplementedView from '../../../modules/UnimplementedView';
|
||||
import React from 'react';
|
||||
import StyleSheet from '../../../exports/StyleSheet';
|
||||
import View from '../../../exports/View';
|
||||
import VirtualizedList, { type Props as VirtualizedListProps } from '../VirtualizedList';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import type {
|
||||
ViewabilityConfig,
|
||||
ViewToken,
|
||||
ViewabilityConfigCallbackPair
|
||||
} from '../ViewabilityHelper';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
export type SeparatorsObj = {
|
||||
highlight: () => void,
|
||||
@@ -81,11 +84,19 @@ type OptionalProps<ItemT> = {
|
||||
* a rendered element.
|
||||
*/
|
||||
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListFooterComponent
|
||||
*/
|
||||
ListFooterComponentStyle?: any,
|
||||
/**
|
||||
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
*/
|
||||
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListHeaderComponent
|
||||
*/
|
||||
ListHeaderComponentStyle?: any,
|
||||
/**
|
||||
* Optional custom style for multi-item rows generated when numColumns > 1.
|
||||
*/
|
||||
@@ -340,6 +351,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
this._listRef.scrollToIndex(params);
|
||||
}
|
||||
}
|
||||
@@ -356,6 +368,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
this._listRef.scrollToItem(params);
|
||||
}
|
||||
}
|
||||
@@ -367,6 +380,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
*/
|
||||
scrollToOffset(params: {animated?: ?boolean, offset: number}) {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
this._listRef.scrollToOffset(params);
|
||||
}
|
||||
}
|
||||
@@ -378,6 +392,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
*/
|
||||
recordInteraction() {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
this._listRef.recordInteraction();
|
||||
}
|
||||
}
|
||||
@@ -389,6 +404,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
*/
|
||||
flashScrollIndicators() {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
this._listRef.flashScrollIndicators();
|
||||
}
|
||||
}
|
||||
@@ -398,51 +414,27 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
*/
|
||||
getScrollResponder() {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
return this._listRef.getScrollResponder();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollableNode() {
|
||||
if (this._listRef) {
|
||||
// $FlowFixMe Found when typing ListView
|
||||
return this._listRef.getScrollableNode();
|
||||
}
|
||||
}
|
||||
|
||||
setNativeProps(props: Object) {
|
||||
setNativeProps(props: {[string]: mixed}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this._checkProps(this.props);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<ItemT>) {
|
||||
invariant(
|
||||
nextProps.numColumns === this.props.numColumns,
|
||||
'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' +
|
||||
'changing the number of columns to force a fresh render of the component.',
|
||||
);
|
||||
invariant(
|
||||
nextProps.onViewableItemsChanged === this.props.onViewableItemsChanged,
|
||||
'Changing onViewableItemsChanged on the fly is not supported',
|
||||
);
|
||||
invariant(
|
||||
nextProps.viewabilityConfig === this.props.viewabilityConfig,
|
||||
'Changing viewabilityConfig on the fly is not supported',
|
||||
);
|
||||
invariant(
|
||||
nextProps.viewabilityConfigCallbackPairs ===
|
||||
this.props.viewabilityConfigCallbackPairs,
|
||||
'Changing viewabilityConfigCallbackPairs on the fly is not supported',
|
||||
);
|
||||
|
||||
this._checkProps(nextProps);
|
||||
}
|
||||
|
||||
constructor(props: Props<*>) {
|
||||
constructor(props: Props<ItemT>) {
|
||||
super(props);
|
||||
this._checkProps(this.props);
|
||||
if (this.props.viewabilityConfigCallbackPairs) {
|
||||
this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map(
|
||||
pair => ({
|
||||
@@ -465,6 +457,29 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props<ItemT>) {
|
||||
invariant(
|
||||
prevProps.numColumns === this.props.numColumns,
|
||||
'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' +
|
||||
'changing the number of columns to force a fresh render of the component.',
|
||||
);
|
||||
invariant(
|
||||
prevProps.onViewableItemsChanged === this.props.onViewableItemsChanged,
|
||||
'Changing onViewableItemsChanged on the fly is not supported',
|
||||
);
|
||||
invariant(
|
||||
prevProps.viewabilityConfig === this.props.viewabilityConfig,
|
||||
'Changing viewabilityConfig on the fly is not supported',
|
||||
);
|
||||
invariant(
|
||||
prevProps.viewabilityConfigCallbackPairs ===
|
||||
this.props.viewabilityConfigCallbackPairs,
|
||||
'Changing viewabilityConfigCallbackPairs on the fly is not supported',
|
||||
);
|
||||
|
||||
this._checkProps(this.props);
|
||||
}
|
||||
|
||||
_hasWarnedLegacy = false;
|
||||
_listRef: null | VirtualizedList;
|
||||
_virtualizedListPairs: Array<ViewabilityConfigCallbackPair> = [];
|
||||
@@ -505,8 +520,9 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
// comparison.
|
||||
if (!this._hasWarnedLegacy) {
|
||||
console.warn(
|
||||
'FlatList: Using legacyImplementation - some features not supported and performance ' +
|
||||
'may suffer',
|
||||
'FlatList: legacyImplementation is deprecated and will be removed in a ' +
|
||||
'future release - some features not supported and performance may suffer. ' +
|
||||
'Please migrate to the default implementation.',
|
||||
);
|
||||
this._hasWarnedLegacy = true;
|
||||
}
|
||||
@@ -524,7 +540,9 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
const ret = [];
|
||||
for (let kk = 0; kk < numColumns; kk++) {
|
||||
const item = data[index * numColumns + kk];
|
||||
item && ret.push(item);
|
||||
if (item != null) {
|
||||
ret.push(item);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
@@ -601,7 +619,11 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
'Expected array of items with numColumns > 1',
|
||||
);
|
||||
return (
|
||||
<View style={[{flexDirection: 'row'}, columnWrapperStyle]}>
|
||||
<View
|
||||
style={StyleSheet.compose(
|
||||
styles.row,
|
||||
columnWrapperStyle,
|
||||
)}>
|
||||
{item.map((it, kk) => {
|
||||
const element = renderItem({
|
||||
item: it,
|
||||
@@ -648,4 +670,8 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {flexDirection: 'row'},
|
||||
});
|
||||
|
||||
export default FlatList;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
facebook/react-native@0.55.4
|
||||
facebook/react-native@370bcffba748e895ad8afa825bfef40bff859c95
|
||||
facebook/react-native@v0.57.5
|
||||
facebook/react-native@44bfffb11666f2d1775dee5dd2890fc2255d2ab0
|
||||
|
||||
@@ -282,6 +282,7 @@ class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
|
||||
*/
|
||||
recordInteraction() {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
// $FlowFixMe Found when typing ListView
|
||||
listRef && listRef.recordInteraction();
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ class SwipeableFlatList<ItemT> extends React.Component<Props<ItemT>, State> {
|
||||
}}
|
||||
onScroll={this._onScroll}
|
||||
renderItem={this._renderItem}
|
||||
extraData={this.state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Batchinator from '../Batchinator';
|
||||
import FillRateHelper from '../FillRateHelper';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -34,6 +36,8 @@ type DangerouslyImpreciseStyleProp = any;
|
||||
|
||||
type Item = any;
|
||||
|
||||
type ViewStyleProp = any;
|
||||
|
||||
export type renderItemType = (info: any) => ?React.Element<any>;
|
||||
|
||||
type ViewabilityHelperCallbackTuple = {
|
||||
@@ -118,11 +122,19 @@ type OptionalProps = {
|
||||
* a rendered element.
|
||||
*/
|
||||
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListFooterComponent
|
||||
*/
|
||||
ListFooterComponentStyle?: ViewStyleProp,
|
||||
/**
|
||||
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
*/
|
||||
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListHeaderComponent
|
||||
*/
|
||||
ListHeaderComponentStyle?: ViewStyleProp,
|
||||
/**
|
||||
* A unique identifier for this list. If there are multiple VirtualizedLists at the same level of
|
||||
* nesting within another VirtualizedList, this key is necessary for virtualization to
|
||||
@@ -166,6 +178,12 @@ type OptionalProps = {
|
||||
* @platform android
|
||||
*/
|
||||
progressViewOffset?: number,
|
||||
/**
|
||||
* A custom refresh control element. When set, it overrides the default
|
||||
* <RefreshControl> component built internally. The onRefresh and refreshing
|
||||
* props are also ignored. Only works for vertical VirtualizedList.
|
||||
*/
|
||||
refreshControl?: ?React.Element<any>,
|
||||
/**
|
||||
* Set this true while waiting for new data from a refresh.
|
||||
*/
|
||||
@@ -205,6 +223,7 @@ type OptionalProps = {
|
||||
export type Props = RequiredProps & OptionalProps;
|
||||
|
||||
let _usedIndexForKey = false;
|
||||
let _keylessItemComponentName: string = '';
|
||||
|
||||
type Frame = {
|
||||
offset: number,
|
||||
@@ -399,7 +418,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
||||
return this._scrollRef.getScrollableNode();
|
||||
} else {
|
||||
return findNodeHandle(this._scrollRef);
|
||||
return ReactNative.findNodeHandle(this._scrollRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +429,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
disableVirtualization: process.env.NODE_ENV === 'test',
|
||||
disableVirtualization: false,
|
||||
horizontal: false,
|
||||
initialNumToRender: 10,
|
||||
keyExtractor: (item: Item, index: number) => {
|
||||
@@ -418,6 +437,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
return item.key;
|
||||
}
|
||||
_usedIndexForKey = true;
|
||||
if (item.type && item.type.displayName) {
|
||||
_keylessItemComponentName = item.type.displayName;
|
||||
}
|
||||
return String(index);
|
||||
},
|
||||
maxToRenderPerBatch: 10,
|
||||
@@ -740,12 +762,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
<VirtualizedCellWrapper
|
||||
cellKey={this._getCellKey() + '-header'}
|
||||
key="$header">
|
||||
<View onLayout={this._onLayoutHeader} style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
<View
|
||||
onLayout={this._onLayoutHeader}
|
||||
style={StyleSheet.compose(
|
||||
inversionStyle,
|
||||
this.props.ListHeaderComponentStyle,
|
||||
)}>
|
||||
{
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
element
|
||||
}
|
||||
</View>
|
||||
</VirtualizedCellWrapper>,
|
||||
);
|
||||
@@ -753,6 +779,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
const itemCount = this.props.getItemCount(data);
|
||||
if (itemCount > 0) {
|
||||
_usedIndexForKey = false;
|
||||
_keylessItemComponentName = '';
|
||||
const spacerKey = !horizontal ? 'height' : 'width';
|
||||
const lastInitialIndex = this.props.initialScrollIndex
|
||||
? -1
|
||||
@@ -776,8 +803,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
||||
const initBlock = this._getFrameMetricsApprox(lastInitialIndex);
|
||||
const stickyBlock = this._getFrameMetricsApprox(ii);
|
||||
const leadSpace =
|
||||
stickyBlock.offset - (initBlock.offset + initBlock.length);
|
||||
const leadSpace = stickyBlock.offset - initBlock.offset;
|
||||
cells.push(
|
||||
<View key="$sticky_lead" style={{[spacerKey]: leadSpace}} />,
|
||||
);
|
||||
@@ -822,6 +848,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
console.warn(
|
||||
'VirtualizedList: missing keys for items, make sure to specify a key property on each ' +
|
||||
'item or provide a custom keyExtractor.',
|
||||
_keylessItemComponentName,
|
||||
);
|
||||
this._hasWarned.keys = true;
|
||||
}
|
||||
@@ -843,23 +870,25 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
} else if (ListEmptyComponent) {
|
||||
const element = React.isValidElement(ListEmptyComponent) ? (
|
||||
const element: React.Element<any> = ((React.isValidElement(
|
||||
ListEmptyComponent,
|
||||
) ? (
|
||||
ListEmptyComponent
|
||||
) : (
|
||||
// $FlowFixMe
|
||||
<ListEmptyComponent />
|
||||
);
|
||||
)): any);
|
||||
cells.push(
|
||||
<View
|
||||
key="$empty"
|
||||
onLayout={this._onLayoutEmpty}
|
||||
style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
</View>,
|
||||
React.cloneElement(element, {
|
||||
key: '$empty',
|
||||
onLayout: event => {
|
||||
this._onLayoutEmpty(event);
|
||||
if (element.props.onLayout) {
|
||||
element.props.onLayout(event);
|
||||
}
|
||||
},
|
||||
style: [element.props.style, inversionStyle],
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (ListFooterComponent) {
|
||||
@@ -873,12 +902,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
<VirtualizedCellWrapper
|
||||
cellKey={this._getCellKey() + '-footer'}
|
||||
key="$footer">
|
||||
<View onLayout={this._onLayoutFooter} style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
<View
|
||||
onLayout={this._onLayoutFooter}
|
||||
style={StyleSheet.compose(
|
||||
inversionStyle,
|
||||
this.props.ListFooterComponentStyle,
|
||||
)}>
|
||||
{
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
element
|
||||
}
|
||||
</View>
|
||||
</VirtualizedCellWrapper>,
|
||||
);
|
||||
@@ -892,12 +925,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
onScrollEndDrag: this._onScrollEndDrag,
|
||||
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
||||
scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support
|
||||
invertStickyHeaders: this.props.invertStickyHeaders !== undefined
|
||||
? this.props.invertStickyHeaders
|
||||
: this.props.inverted,
|
||||
invertStickyHeaders:
|
||||
this.props.invertStickyHeaders !== undefined
|
||||
? this.props.invertStickyHeaders
|
||||
: this.props.inverted,
|
||||
stickyHeaderIndices,
|
||||
};
|
||||
if (inversionStyle) {
|
||||
/* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.70 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
scrollProps.style = [inversionStyle, this.props.style];
|
||||
}
|
||||
|
||||
@@ -908,6 +945,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
(this.props.renderScrollComponent || this._defaultRenderScrollComponent)(
|
||||
scrollProps,
|
||||
),
|
||||
// $FlowFixMe Invalid prop usage
|
||||
{
|
||||
ref: this._captureScrollRef,
|
||||
},
|
||||
@@ -989,9 +1027,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_defaultRenderScrollComponent = props => {
|
||||
const onRefresh = props.onRefresh;
|
||||
if (this._isNestedWithSameOrientation()) {
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
return <View {...props} />;
|
||||
} else if (props.onRefresh) {
|
||||
} else if (onRefresh) {
|
||||
invariant(
|
||||
typeof props.refreshing === 'boolean',
|
||||
'`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' +
|
||||
@@ -999,21 +1039,24 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
'`',
|
||||
);
|
||||
return (
|
||||
// $FlowFixMe Invalid prop usage
|
||||
<ScrollView
|
||||
{...props}
|
||||
refreshControl={
|
||||
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */
|
||||
<RefreshControl
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={props.onRefresh}
|
||||
progressViewOffset={props.progressViewOffset}
|
||||
/>
|
||||
props.refreshControl == null ? (
|
||||
<RefreshControl
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={onRefresh}
|
||||
progressViewOffset={props.progressViewOffset}
|
||||
/>
|
||||
) : (
|
||||
props.refreshControl
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// $FlowFixMe Invalid prop usage
|
||||
return <ScrollView {...props} />;
|
||||
}
|
||||
};
|
||||
@@ -1046,6 +1089,17 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
} else {
|
||||
this._frames[cellKey].inLayout = true;
|
||||
}
|
||||
|
||||
const childListKeys = this._cellKeysToChildListKeys.get(cellKey);
|
||||
if (childListKeys) {
|
||||
for (let childKey of childListKeys) {
|
||||
const childList = this._nestedChildLists.get(childKey);
|
||||
childList &&
|
||||
childList.ref &&
|
||||
childList.ref.measureLayoutRelativeToContainingList();
|
||||
}
|
||||
}
|
||||
|
||||
this._computeBlankness();
|
||||
}
|
||||
|
||||
@@ -1056,36 +1110,48 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
_measureLayoutRelativeToContainingList(): void {
|
||||
UIManager.measureLayout(
|
||||
findNodeHandle(this),
|
||||
findNodeHandle(
|
||||
this.context.virtualizedList.getOutermostParentListRef(),
|
||||
),
|
||||
error => {
|
||||
console.warn(
|
||||
"VirtualizedList: Encountered an error while measuring a list's" +
|
||||
' offset from its containing VirtualizedList.',
|
||||
);
|
||||
},
|
||||
(x, y, width, height) => {
|
||||
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
||||
this._scrollMetrics.contentLength = this._selectLength({width, height});
|
||||
measureLayoutRelativeToContainingList(): void {
|
||||
// TODO (T35574538): findNodeHandle sometimes crashes with "Unable to find
|
||||
// node on an unmounted component" during scrolling
|
||||
try {
|
||||
UIManager.measureLayout(
|
||||
ReactNative.findNodeHandle(this),
|
||||
ReactNative.findNodeHandle(
|
||||
this.context.virtualizedList.getOutermostParentListRef(),
|
||||
),
|
||||
error => {
|
||||
console.warn(
|
||||
"VirtualizedList: Encountered an error while measuring a list's" +
|
||||
' offset from its containing VirtualizedList.',
|
||||
);
|
||||
},
|
||||
(x, y, width, height) => {
|
||||
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
||||
this._scrollMetrics.contentLength = this._selectLength({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
const scrollMetrics = this._convertParentScrollMetrics(
|
||||
this.context.virtualizedList.getScrollMetrics(),
|
||||
);
|
||||
this._scrollMetrics.visibleLength = scrollMetrics.visibleLength;
|
||||
this._scrollMetrics.offset = scrollMetrics.offset;
|
||||
},
|
||||
);
|
||||
const scrollMetrics = this._convertParentScrollMetrics(
|
||||
this.context.virtualizedList.getScrollMetrics(),
|
||||
);
|
||||
this._scrollMetrics.visibleLength = scrollMetrics.visibleLength;
|
||||
this._scrollMetrics.offset = scrollMetrics.offset;
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'measureLayoutRelativeToContainingList threw an error',
|
||||
error.stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_onLayout = (e: Object) => {
|
||||
if (this._isNestedWithSameOrientation()) {
|
||||
// Need to adjust our scroll metrics to be relative to our containing
|
||||
// VirtualizedList before we can make claims about list item viewability
|
||||
this._measureLayoutRelativeToContainingList();
|
||||
this.measureLayoutRelativeToContainingList();
|
||||
} else {
|
||||
this._scrollMetrics.visibleLength = this._selectLength(
|
||||
e.nativeEvent.layout,
|
||||
@@ -1115,6 +1181,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
const itemCount = this.props.getItemCount(this.props.data);
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const frame = this._getFrameMetricsApprox(ii);
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
if (frame.inLayout) {
|
||||
framesInLayout.push(frame);
|
||||
}
|
||||
@@ -1323,18 +1392,26 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
const {offset, visibleLength, velocity} = this._scrollMetrics;
|
||||
const itemCount = this.props.getItemCount(this.props.data);
|
||||
let hiPri = false;
|
||||
if (first > 0 || last < itemCount - 1) {
|
||||
const scrollingThreshold =
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.63 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
(this.props.onEndReachedThreshold * visibleLength) / 2;
|
||||
// Mark as high priority if we're close to the start of the first item
|
||||
// But only if there are items before the first rendered item
|
||||
if (first > 0) {
|
||||
const distTop = offset - this._getFrameMetricsApprox(first).offset;
|
||||
hiPri =
|
||||
hiPri || distTop < 0 || (velocity < -2 && distTop < scrollingThreshold);
|
||||
}
|
||||
// Mark as high priority if we're close to the end of the last item
|
||||
// But only if there are items after the last rendered item
|
||||
if (last < itemCount - 1) {
|
||||
const distBottom =
|
||||
this._getFrameMetricsApprox(last).offset - (offset + visibleLength);
|
||||
const scrollingThreshold =
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.63 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
this.props.onEndReachedThreshold * visibleLength / 2;
|
||||
hiPri =
|
||||
Math.min(distTop, distBottom) < 0 ||
|
||||
(velocity < -2 && distTop < scrollingThreshold) ||
|
||||
hiPri ||
|
||||
distBottom < 0 ||
|
||||
(velocity > 2 && distBottom < scrollingThreshold);
|
||||
}
|
||||
// Only trigger high-priority updates if we've actually rendered cells,
|
||||
@@ -1561,7 +1638,7 @@ class CellRenderer extends React.Component<
|
||||
getItemLayout?: ?Function,
|
||||
renderItem: renderItemType,
|
||||
},
|
||||
prevCellKey: ?string
|
||||
prevCellKey: ?string,
|
||||
},
|
||||
$FlowFixMeState,
|
||||
> {
|
||||
@@ -1639,6 +1716,9 @@ class CellRenderer extends React.Component<
|
||||
separators: this._separators,
|
||||
});
|
||||
const onLayout =
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
getItemLayout && !parentProps.debug && !fillRateHelper.enabled()
|
||||
? undefined
|
||||
: this.props.onLayout;
|
||||
@@ -1702,15 +1782,6 @@ const styles = StyleSheet.create({
|
||||
horizontallyInverted: {
|
||||
transform: [{scaleX: -1}],
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
rowReverse: {
|
||||
flexDirection: 'row-reverse'
|
||||
},
|
||||
columnReverse: {
|
||||
flexDirection: 'column-reverse'
|
||||
}
|
||||
});
|
||||
|
||||
export default VirtualizedList;
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import View from '../../../exports/View';
|
||||
import VirtualizedList, { type Props as VirtualizedListProps } from '../VirtualizedList';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import type { ViewToken } from '../ViewabilityHelper';
|
||||
|
||||
type Item = any;
|
||||
@@ -33,12 +35,12 @@ type SectionBase = {
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
},
|
||||
}) => ?React.Element<any>,
|
||||
ItemSeparatorComponent?: ?React.ComponentType<*>,
|
||||
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
||||
keyExtractor?: (item: SectionItem, index: ?number) => string,
|
||||
|
||||
// TODO: support more optional/override props
|
||||
// FooterComponent?: ?ReactClass<*>,
|
||||
// HeaderComponent?: ?ReactClass<*>,
|
||||
// FooterComponent?: ?ReactClass<any>,
|
||||
// HeaderComponent?: ?ReactClass<any>,
|
||||
// onViewableItemsChanged?: ({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void,
|
||||
};
|
||||
|
||||
@@ -50,11 +52,11 @@ type OptionalProps<SectionT: SectionBase> = {
|
||||
/**
|
||||
* Rendered after the last item in the last section.
|
||||
*/
|
||||
ListFooterComponent?: ?(React.ComponentType<*> | React.Element<any>),
|
||||
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Rendered at the very beginning of the list.
|
||||
*/
|
||||
ListHeaderComponent?: ?(React.ComponentType<*> | React.Element<any>),
|
||||
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Default renderer for every item in every section.
|
||||
*/
|
||||
@@ -80,11 +82,11 @@ type OptionalProps<SectionT: SectionBase> = {
|
||||
* Rendered at the bottom of every Section, except the very last one, in place of the normal
|
||||
* ItemSeparatorComponent.
|
||||
*/
|
||||
SectionSeparatorComponent?: ?React.ComponentType<*>,
|
||||
SectionSeparatorComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Rendered at the bottom of every Item except the very last one in the last section.
|
||||
*/
|
||||
ItemSeparatorComponent?: ?React.ComponentType<*>,
|
||||
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Warning: Virtualization can drastically improve memory consumption for long lists, but trashes
|
||||
* the state of items when they scroll out of the render window, so make sure all relavent data is
|
||||
@@ -97,7 +99,7 @@ type OptionalProps<SectionT: SectionBase> = {
|
||||
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
|
||||
* sure to also set the `refreshing` prop correctly.
|
||||
*/
|
||||
onRefresh?: ?Function,
|
||||
onRefresh?: ?() => void,
|
||||
/**
|
||||
* Called when the viewability of rows changes, as defined by the
|
||||
* `viewabilityConfig` prop.
|
||||
@@ -130,10 +132,6 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
Props<SectionT>,
|
||||
State,
|
||||
> {
|
||||
props: Props<SectionT>;
|
||||
|
||||
state: State;
|
||||
|
||||
static defaultProps: DefaultProps = {
|
||||
...VirtualizedList.defaultProps,
|
||||
data: [],
|
||||
@@ -160,6 +158,48 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
return this._listRef;
|
||||
}
|
||||
|
||||
constructor(props: Props<SectionT>, context: Object) {
|
||||
super(props, context);
|
||||
this.state = this._computeState(props);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<SectionT>) {
|
||||
this.setState(this._computeState(nextProps));
|
||||
}
|
||||
|
||||
_computeState(props: Props<SectionT>): State {
|
||||
const offset = props.ListHeaderComponent ? 1 : 0;
|
||||
const stickyHeaderIndices = [];
|
||||
const itemCount = props.sections.reduce((v, section) => {
|
||||
stickyHeaderIndices.push(v + offset);
|
||||
return v + section.data.length + 2; // Add two for the section header and footer.
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
childProps: {
|
||||
...props,
|
||||
renderItem: this._renderItem,
|
||||
ItemSeparatorComponent: undefined, // Rendered with renderItem
|
||||
data: props.sections,
|
||||
getItemCount: () => itemCount,
|
||||
getItem,
|
||||
keyExtractor: this._keyExtractor,
|
||||
onViewableItemsChanged: props.onViewableItemsChanged
|
||||
? this._onViewableItemsChanged
|
||||
: undefined,
|
||||
stickyHeaderIndices: props.stickySectionHeadersEnabled
|
||||
? stickyHeaderIndices
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
|
||||
);
|
||||
}
|
||||
|
||||
_keyExtractor = (item: Item, index: number) => {
|
||||
const info = this._subExtractor(index);
|
||||
return (info && info.key) || String(index);
|
||||
@@ -303,7 +343,7 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
_getSeparatorComponent(
|
||||
index: number,
|
||||
info?: ?Object,
|
||||
): ?React.ComponentType<*> {
|
||||
): ?React.ComponentType<any> {
|
||||
info = info || this._subExtractor(index);
|
||||
if (!info) {
|
||||
return null;
|
||||
@@ -322,48 +362,6 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
return null;
|
||||
}
|
||||
|
||||
_computeState(props: Props<SectionT>): State {
|
||||
const offset = props.ListHeaderComponent ? 1 : 0;
|
||||
const stickyHeaderIndices = [];
|
||||
const itemCount = props.sections.reduce((v, section) => {
|
||||
stickyHeaderIndices.push(v + offset);
|
||||
return v + section.data.length + 2; // Add two for the section header and footer.
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
childProps: {
|
||||
...props,
|
||||
renderItem: this._renderItem,
|
||||
ItemSeparatorComponent: undefined, // Rendered with renderItem
|
||||
data: props.sections,
|
||||
getItemCount: () => itemCount,
|
||||
getItem,
|
||||
keyExtractor: this._keyExtractor,
|
||||
onViewableItemsChanged: props.onViewableItemsChanged
|
||||
? this._onViewableItemsChanged
|
||||
: undefined,
|
||||
stickyHeaderIndices: props.stickySectionHeadersEnabled
|
||||
? stickyHeaderIndices
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props: Props<SectionT>, context: Object) {
|
||||
super(props, context);
|
||||
this.state = this._computeState(props);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props<SectionT>) {
|
||||
this.setState(this._computeState(nextProps));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
|
||||
);
|
||||
}
|
||||
|
||||
_cellRefs = {};
|
||||
_listRef: VirtualizedList;
|
||||
_captureRef = ref => {
|
||||
@@ -374,25 +372,40 @@ class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
};
|
||||
}
|
||||
|
||||
type ItemWithSeparatorProps = {
|
||||
LeadingSeparatorComponent: ?React.ComponentType<*>,
|
||||
SeparatorComponent: ?React.ComponentType<*>,
|
||||
type ItemWithSeparatorCommonProps = $ReadOnly<{|
|
||||
leadingItem: ?Item,
|
||||
leadingSection: ?Object,
|
||||
section: Object,
|
||||
trailingItem: ?Item,
|
||||
trailingSection: ?Object,
|
||||
|}>;
|
||||
|
||||
type ItemWithSeparatorProps = $ReadOnly<{|
|
||||
...ItemWithSeparatorCommonProps,
|
||||
LeadingSeparatorComponent: ?React.ComponentType<any>,
|
||||
SeparatorComponent: ?React.ComponentType<any>,
|
||||
cellKey: string,
|
||||
index: number,
|
||||
item: Item,
|
||||
onUpdateSeparator: (cellKey: string, newProps: Object) => void,
|
||||
prevCellKey?: ?string,
|
||||
renderItem: Function,
|
||||
section: Object,
|
||||
leadingItem: ?Item,
|
||||
leadingSection: ?Object,
|
||||
trailingItem: ?Item,
|
||||
trailingSection: ?Object,
|
||||
|}>;
|
||||
|
||||
type ItemWithSeparatorState = {
|
||||
separatorProps: $ReadOnly<{|
|
||||
highlighted: false,
|
||||
...ItemWithSeparatorCommonProps,
|
||||
|}>,
|
||||
leadingSeparatorProps: $ReadOnly<{|
|
||||
highlighted: false,
|
||||
...ItemWithSeparatorCommonProps,
|
||||
|}>,
|
||||
};
|
||||
|
||||
class ItemWithSeparator extends React.Component<
|
||||
ItemWithSeparatorProps,
|
||||
$FlowFixMeState,
|
||||
ItemWithSeparatorState,
|
||||
> {
|
||||
state = {
|
||||
separatorProps: {
|
||||
@@ -426,7 +439,7 @@ class ItemWithSeparator extends React.Component<
|
||||
},
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => {
|
||||
const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props;
|
||||
if (select === 'leading' && LeadingSeparatorComponent) {
|
||||
if (select === 'leading' && LeadingSeparatorComponent != null) {
|
||||
this.setState(state => ({
|
||||
leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps},
|
||||
}));
|
||||
@@ -439,10 +452,13 @@ class ItemWithSeparator extends React.Component<
|
||||
},
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps(props: ItemWithSeparatorProps) {
|
||||
this.setState(state => ({
|
||||
static getDerivedStateFromProps(
|
||||
props: ItemWithSeparatorProps,
|
||||
prevState: ItemWithSeparatorState,
|
||||
): ?ItemWithSeparatorState {
|
||||
return {
|
||||
separatorProps: {
|
||||
...this.state.separatorProps,
|
||||
...prevState.separatorProps,
|
||||
leadingItem: props.item,
|
||||
leadingSection: props.leadingSection,
|
||||
section: props.section,
|
||||
@@ -450,14 +466,14 @@ class ItemWithSeparator extends React.Component<
|
||||
trailingSection: props.trailingSection,
|
||||
},
|
||||
leadingSeparatorProps: {
|
||||
...this.state.leadingSeparatorProps,
|
||||
...prevState.leadingSeparatorProps,
|
||||
leadingItem: props.leadingItem,
|
||||
leadingSection: props.leadingSection,
|
||||
section: props.section,
|
||||
trailingItem: props.item,
|
||||
trailingSection: props.trailingSection,
|
||||
},
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
updateSeparatorProps(newProps: Object) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "website",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.11",
|
||||
"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.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.9.8",
|
||||
"babel-plugin-react-native-web": "0.9.11",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.8.1"
|
||||
}
|
||||
|
||||
@@ -136,16 +136,6 @@ const ImageScreen = () => (
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Properties">
|
||||
<DocItem
|
||||
name="static resizeMode"
|
||||
typeInfo="object"
|
||||
example={{
|
||||
code: '<Image resizeMode={Image.resizeMode.contain} />'
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Methods">
|
||||
<DocItem
|
||||
name="static getSize"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user