mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
312
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc04bf3d96 | ||
|
|
8e34e7bd5f | ||
|
|
68f6799b7c | ||
|
|
2c130e6a9f | ||
|
|
21423bdf22 | ||
|
|
e5332504cc | ||
|
|
292f035ae8 | ||
|
|
a6c4ae0e75 | ||
|
|
e4b5e9ef85 | ||
|
|
732831dc27 | ||
|
|
b150fdfdce | ||
|
|
73f2007679 | ||
|
|
da2a8b365c | ||
|
|
143ecf4c62 | ||
|
|
c8d6cb9e49 | ||
|
|
0661c6f254 | ||
|
|
5f4a5388ac | ||
|
|
445dac2743 | ||
|
|
2d230bee78 | ||
|
|
382abb4b45 | ||
|
|
b0e4b89b58 | ||
|
|
0b8525847f | ||
|
|
09a923599e | ||
|
|
e7b7951c96 | ||
|
|
a91d0d8c35 | ||
|
|
47206fdd3c | ||
|
|
b02efd9e96 | ||
|
|
31894136f4 | ||
|
|
9b6639ad12 | ||
|
|
11f838f349 | ||
|
|
34787cc0eb | ||
|
|
d40bea925f | ||
|
|
692d3f72fb | ||
|
|
cb9b266b8e | ||
|
|
ccb4e67756 | ||
|
|
ad9b66fc6f | ||
|
|
1fe8f89bc8 | ||
|
|
a1e053cc50 | ||
|
|
221286be13 | ||
|
|
07972cf3bf | ||
|
|
09401ed56f | ||
|
|
c885357cc9 | ||
|
|
6dd9d16833 | ||
|
|
cad2d9b072 | ||
|
|
e3cfcdfb9c | ||
|
|
e2c87b59be | ||
|
|
aafccdf622 | ||
|
|
bfece1d800 | ||
|
|
e43c061e95 | ||
|
|
e02bafd9cf | ||
|
|
c198911561 | ||
|
|
320aea8bca | ||
|
|
e57655274d | ||
|
|
c41b5a02f2 | ||
|
|
d9f98191eb | ||
|
|
fc86f25f9a | ||
|
|
1d353b4ef7 | ||
|
|
98b925ceec | ||
|
|
820026f69b | ||
|
|
9f87728f5d | ||
|
|
999851a389 | ||
|
|
fc68dfbed6 | ||
|
|
ec8118b637 | ||
|
|
8ea6cea39a | ||
|
|
0c44b9e9b0 | ||
|
|
7cdd4d48c8 | ||
|
|
18c795bfcc | ||
|
|
3f16aa5559 | ||
|
|
a616a2d99d | ||
|
|
6b29fe78c4 | ||
|
|
5f1408858a | ||
|
|
51e258e6e7 | ||
|
|
78e2030e95 | ||
|
|
50f200ba90 | ||
|
|
ad0fe15e2e | ||
|
|
88fb45ddf9 | ||
|
|
8f363b2c49 | ||
|
|
684e03590b | ||
|
|
10230707cb | ||
|
|
d7f671135c | ||
|
|
dfd3b22275 | ||
|
|
40fdd6d91c | ||
|
|
5da0a9909e | ||
|
|
dec62ffc3e | ||
|
|
c00abe53c3 | ||
|
|
f1cbb11620 | ||
|
|
dc97e3fb4e | ||
|
|
e6941990ef | ||
|
|
1954fd4cef | ||
|
|
2781739fbe | ||
|
|
bf1b67ee5f | ||
|
|
dedffdc235 | ||
|
|
6db97d638d | ||
|
|
9afb71fde8 | ||
|
|
20224b74a4 | ||
|
|
8548ca09f1 | ||
|
|
e0eee0bb49 | ||
|
|
9b06a3193e | ||
|
|
361f03badb | ||
|
|
3e96fb1de3 | ||
|
|
6796abbe55 | ||
|
|
a8a29a92a0 | ||
|
|
9108f98ca7 | ||
|
|
6e13adbf56 | ||
|
|
6c2beaffdc | ||
|
|
79bf089bc5 | ||
|
|
26f01faf1b | ||
|
|
1ef21ece92 | ||
|
|
1054644631 | ||
|
|
352c3d97b3 | ||
|
|
f8ae461bc1 | ||
|
|
465a68db1f | ||
|
|
2be921c88a | ||
|
|
2c9e113f8e | ||
|
|
ed04d77c2c | ||
|
|
1ab96fd077 | ||
|
|
51afde821d | ||
|
|
b437d61475 | ||
|
|
3525998884 | ||
|
|
e301a36606 | ||
|
|
0fd061ec3d | ||
|
|
c28595e3fb | ||
|
|
6312d67bcb | ||
|
|
e7c1cf5b7d | ||
|
|
304dc6bd77 | ||
|
|
1d7c990424 | ||
|
|
c42080eaaf | ||
|
|
ecd5829bf8 | ||
|
|
12ab236629 | ||
|
|
d494282fd1 | ||
|
|
25d19e3830 | ||
|
|
33057aacbf | ||
|
|
d666f30665 | ||
|
|
0797dae500 | ||
|
|
185948604c | ||
|
|
16747dd807 | ||
|
|
960e5db0ed | ||
|
|
c848c3820b | ||
|
|
2f193b9bc1 | ||
|
|
937568b472 | ||
|
|
afa47924d6 | ||
|
|
2b1795c5ad | ||
|
|
1081b21f3e | ||
|
|
a8c45ac1c3 | ||
|
|
3364488af0 | ||
|
|
6ba8e29c89 | ||
|
|
2bf41672f8 | ||
|
|
719f0005f6 | ||
|
|
f954f3d9b6 | ||
|
|
75eb55096e | ||
|
|
7ee051db28 | ||
|
|
14538aa038 | ||
|
|
58aca39105 | ||
|
|
1a5489bb70 | ||
|
|
22ab231c2e | ||
|
|
bb832e1238 | ||
|
|
162d92da0e | ||
|
|
30352ecbf2 | ||
|
|
c0505cedf1 | ||
|
|
83faa4b608 | ||
|
|
e2dff82160 | ||
|
|
1faf40b02a | ||
|
|
a38f5b6b6a | ||
|
|
08b8216e4f | ||
|
|
076eaec805 | ||
|
|
e2628f93b5 | ||
|
|
6aea140a33 | ||
|
|
44310da324 | ||
|
|
d4cda7168f | ||
|
|
dbd7a4e3a1 | ||
|
|
7516fa56d7 | ||
|
|
ecccd06662 | ||
|
|
a6607968c4 | ||
|
|
1ee602b655 | ||
|
|
8e9322c65e | ||
|
|
2b89416ac0 | ||
|
|
4429a8dde6 | ||
|
|
b6cfad27f8 | ||
|
|
d795fa1b2c | ||
|
|
0ae11f12f3 | ||
|
|
e9f657f2bd | ||
|
|
94d9f00dd6 | ||
|
|
09ba07e4fa | ||
|
|
c5004d5dd0 | ||
|
|
502604074c | ||
|
|
4566f01fbd | ||
|
|
4f74b752df | ||
|
|
31fe832d04 | ||
|
|
119959252e | ||
|
|
31e1f37314 | ||
|
|
5c5410459e | ||
|
|
1ae54b5108 | ||
|
|
7d1981ef82 | ||
|
|
5c53f89dd8 | ||
|
|
1230549c77 | ||
|
|
5550a25628 | ||
|
|
bc718fd729 | ||
|
|
ec68536e08 | ||
|
|
70ca432e5a | ||
|
|
70e0455522 | ||
|
|
d217921b5e | ||
|
|
112e3767ce | ||
|
|
b1e64a448a | ||
|
|
a04322fa1b | ||
|
|
ac3f345b07 | ||
|
|
abfa63c67e | ||
|
|
d0ad6ad413 | ||
|
|
01adc71ade | ||
|
|
a2b0ee0fc3 | ||
|
|
407ec0023f | ||
|
|
75508c12ff | ||
|
|
60783aa6ba | ||
|
|
ebcf5fd241 | ||
|
|
3c4057cbb4 | ||
|
|
a26d87628c | ||
|
|
e82ceb24eb | ||
|
|
3ba8e6a901 | ||
|
|
8aabce3f1c | ||
|
|
3e4a8928b3 | ||
|
|
8377b30b8d | ||
|
|
86328e9266 | ||
|
|
56969d51fa | ||
|
|
fa147f02e5 | ||
|
|
ce5ae53669 | ||
|
|
398746fa09 | ||
|
|
fd34018d08 | ||
|
|
0801d94faa | ||
|
|
3b93ba9af7 | ||
|
|
8fa55ca7b5 | ||
|
|
5d515113e7 | ||
|
|
eaac3b57ee | ||
|
|
a555551aaf | ||
|
|
4fd427e249 | ||
|
|
f3f44eee59 | ||
|
|
c143313a20 | ||
|
|
d27ff20588 | ||
|
|
390c8cf96e | ||
|
|
2cc1195035 | ||
|
|
2083516f45 | ||
|
|
e48ad9609c | ||
|
|
559805d0b0 | ||
|
|
7464d5e1a2 | ||
|
|
71401c1185 | ||
|
|
0d3b7fe472 | ||
|
|
879edf0d05 | ||
|
|
c9a39b3bd3 | ||
|
|
9250250364 | ||
|
|
383915caeb | ||
|
|
bc22a4da7e | ||
|
|
5c57601769 | ||
|
|
534bbfac8f | ||
|
|
d67628eb5a | ||
|
|
c25f356675 | ||
|
|
ce6fb337a1 | ||
|
|
71ea94b51e | ||
|
|
0431164bbd | ||
|
|
e428fb4a8b | ||
|
|
51c0e81557 | ||
|
|
7707905550 | ||
|
|
c78ddf2c3d | ||
|
|
c4818d9394 | ||
|
|
64825389df | ||
|
|
96d3be18bf | ||
|
|
29d9c35e12 | ||
|
|
f7044419be | ||
|
|
1dd825d261 | ||
|
|
8dd15f2c87 | ||
|
|
53d5504f40 | ||
|
|
1d30f833a6 | ||
|
|
0a3e6e0e76 | ||
|
|
63f7efcd32 | ||
|
|
2c32acb755 | ||
|
|
50b11aa09b | ||
|
|
40b5486eea | ||
|
|
94116dbad5 | ||
|
|
2e4284215c | ||
|
|
947d77dab5 | ||
|
|
ac19ddeea8 | ||
|
|
e8df8d9fd5 | ||
|
|
b6cf4eeed6 | ||
|
|
01afc16bfc | ||
|
|
f908cce679 | ||
|
|
6407071424 | ||
|
|
223eab930b | ||
|
|
82edc131b0 | ||
|
|
a1321aedd7 | ||
|
|
4990c5c5b3 | ||
|
|
9d4cc7cce4 | ||
|
|
2984628357 | ||
|
|
80bc07fd60 | ||
|
|
3b6d2cd782 | ||
|
|
f847fbe021 | ||
|
|
c639a1f802 | ||
|
|
a4947d1778 | ||
|
|
32a0ee0975 | ||
|
|
e38641cd73 | ||
|
|
a0a7d97c7b | ||
|
|
5840a9051d | ||
|
|
62b20ce582 | ||
|
|
d062cc252e | ||
|
|
cbb8d6f628 | ||
|
|
4a7ebdf757 | ||
|
|
3360999431 | ||
|
|
ba2e486b33 | ||
|
|
a9e8b7bca5 | ||
|
|
f978109281 | ||
|
|
71c812ae0e | ||
|
|
f42f2dff37 | ||
|
|
81c2f3b189 | ||
|
|
f7c89b4187 | ||
|
|
4f3da46146 | ||
|
|
236e9e4d01 |
+9
-1
@@ -3,4 +3,12 @@
|
||||
**/staticBundle.js
|
||||
**/main.js
|
||||
Libraries/vendor/**/*
|
||||
packager/src/worker-farm/**/*
|
||||
Libraries/Renderer/*
|
||||
website/node_modules
|
||||
pr-inactivity-bookmarklet.js
|
||||
question-bookmarklet.js
|
||||
flow/
|
||||
website/core/metadata.js
|
||||
website/core/metadata-blog.js
|
||||
website/src/react-native/docs/
|
||||
website/src/react-native/blog/
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"root": true,
|
||||
|
||||
"parser": "babel-eslint",
|
||||
|
||||
"ecmaFeatures": {
|
||||
@@ -11,6 +13,8 @@
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"flowtype",
|
||||
"prettier",
|
||||
"react"
|
||||
],
|
||||
|
||||
@@ -49,33 +53,17 @@
|
||||
"setTimeout": false,
|
||||
"window": false,
|
||||
"XMLHttpRequest": false,
|
||||
"pit": false,
|
||||
|
||||
// Flow global types.
|
||||
"ReactComponent": false,
|
||||
"ReactClass": false,
|
||||
"ReactElement": false,
|
||||
"ReactPropsCheckType": false,
|
||||
"ReactPropsChainableTypeChecker": false,
|
||||
"ReactPropTypes": false,
|
||||
"SyntheticEvent": false,
|
||||
"$Either": false,
|
||||
"$All": false,
|
||||
"$ArrayBufferView": false,
|
||||
"$Tuple": false,
|
||||
"$Supertype": false,
|
||||
"$Subtype": false,
|
||||
"$Shape": false,
|
||||
"$Diff": false,
|
||||
"$Keys": false,
|
||||
"$Enum": false,
|
||||
"$Exports": false,
|
||||
"$FlowIssue": false,
|
||||
"$FlowFixMe": false,
|
||||
"$FixMe": false
|
||||
"pit": false
|
||||
},
|
||||
|
||||
"rules": {
|
||||
// Flow Plugin
|
||||
// The following rules are made available via `eslint-plugin-flowtype`
|
||||
"flowtype/define-flow-type": 1,
|
||||
"flowtype/use-flow-type": 1,
|
||||
|
||||
// General
|
||||
|
||||
"comma-dangle": 0, // disallow trailing commas in object literals
|
||||
"no-cond-assign": 1, // disallow assignment in conditional expressions
|
||||
"no-console": 0, // disallow use of console (off by default in the node environment)
|
||||
@@ -176,6 +164,10 @@
|
||||
"no-restricted-modules": 1, // restrict usage of specified node modules (off by default)
|
||||
"no-sync": 0, // disallow use of synchronous methods (off by default)
|
||||
|
||||
// Prettier Plugin
|
||||
// https://github.com/prettier/eslint-plugin-prettier
|
||||
"prettier/prettier": [2, "fb", "@format"],
|
||||
|
||||
// Stylistic Issues
|
||||
// These rules are purely matters of style and are quite subjective.
|
||||
|
||||
@@ -241,6 +233,6 @@
|
||||
"react/prop-types": 0,
|
||||
"react/react-in-jsx-scope": 1,
|
||||
"react/self-closing-comp": 1,
|
||||
"react/wrap-multilines": 0,
|
||||
"react/wrap-multilines": 0
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -38,12 +38,12 @@ suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.47.0
|
||||
^0.49.1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
docs/* @hramos
|
||||
blog/* @hramos
|
||||
Libraries/Animated/* @janicduplessis
|
||||
Libraries/NativeAnimation/* @janicduplessis
|
||||
ReactAndroid/src/main/java/com/facebook/react/animated/* @janicduplessis
|
||||
website/* @hramos
|
||||
website/showcase.json @hramos
|
||||
package.json @hramos @ericnakagawa
|
||||
website/package.json @hramos @ericnakagawa
|
||||
@@ -16,20 +16,20 @@
|
||||
If you answered "No":
|
||||
|
||||
We use GitHub Issues exclusively for tracking bugs in React Native. If you're looking for help,
|
||||
check out the How to Get In Touch section of the following guide:
|
||||
https://facebook.github.io/react-native/docs/contributing.html#how-to-get-in-touch
|
||||
check out the How to Get In Touch section of the following guide:
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#how-to-get-in-touch
|
||||
|
||||
Now scroll below!
|
||||
-->
|
||||
|
||||
|
||||
### Have you read the Bugs section of the How to Contribute guide?
|
||||
### Have you read the Bugs section of the Contributing to React Native Guide?
|
||||
|
||||
(Write your answer here.)
|
||||
|
||||
<!--
|
||||
Please read through the bug reporting guidelines thoroughly:
|
||||
https://facebook.github.io/react-native/docs/contributing.html#bugs
|
||||
https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#bugs
|
||||
-->
|
||||
|
||||
### Environment
|
||||
@@ -41,15 +41,15 @@
|
||||
1. `react-native -v`:
|
||||
2. `node -v`:
|
||||
3. `npm -v`:
|
||||
4. `yarn --version`<!-- (if you use Yarn) -->:
|
||||
4. `yarn --version` (if you use Yarn):
|
||||
|
||||
Then, specify:
|
||||
|
||||
<!-- (What platform are you building for? Choose any from iOS, Android, AppleTV.) -->
|
||||
- Target Platform:
|
||||
- Target Platform:
|
||||
|
||||
<!-- Which operating system are you using? Specify macOS, Windows, or Linux, along with specific release versions -->
|
||||
- Development Operating System:
|
||||
- Development Operating System:
|
||||
|
||||
<!-- Include any additional relevant information. Are you using Xcode or Android Studio to build native code? Is the issue specific to a particular iOS or Android SDK? -->
|
||||
- Build tools:
|
||||
|
||||
@@ -1,32 +1,9 @@
|
||||
Thanks for submitting a PR! Please read these instructions carefully:
|
||||
<!--
|
||||
Thank you for sending the PR!
|
||||
|
||||
- [ ] Explain the **motivation** for making this change.
|
||||
- [ ] Provide a **test plan** demonstrating that the code is solid.
|
||||
- [ ] Match the **code formatting** of the rest of the codebase.
|
||||
- [ ] Target the `master` branch, NOT a "stable" branch.
|
||||
If you changed any code, please provide us with clear instructions on how you verified your changes work. In other words, a test plan is *required*. Bonus points for screenshots and videos!
|
||||
|
||||
## Motivation (required)
|
||||
Please read the Contribution Guidelines at https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md to learn more about contributing to React Native.
|
||||
|
||||
What existing problem does the pull request solve?
|
||||
|
||||
## Test Plan (required)
|
||||
|
||||
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI or updates the website. See [What is a Test Plan?][1] to learn more.
|
||||
|
||||
If you have added code that should be tested, add tests.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Sign the [CLA][2], if you haven't already.
|
||||
|
||||
Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it.
|
||||
|
||||
Make sure all **tests pass** on both [Travis][3] and [Circle CI][4]. PRs that break tests are unlikely to be merged.
|
||||
|
||||
For more info, see the ["Pull Requests"][5] section of our "Contributing" guidelines.
|
||||
|
||||
[1]: https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9
|
||||
[2]: https://code.facebook.com/cla
|
||||
[3]: https://travis-ci.org/facebook/react-native
|
||||
[4]: http://circleci.com/gh/facebook/react-native
|
||||
[5]: https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#pull-requests
|
||||
Happy contributing!
|
||||
-->
|
||||
|
||||
@@ -57,3 +57,6 @@ node_modules
|
||||
|
||||
/coverage
|
||||
/third-party
|
||||
|
||||
/website/src
|
||||
/website/build
|
||||
|
||||
+9
-15
@@ -3,25 +3,20 @@ language: objective-c
|
||||
osx_image: xcode8.3
|
||||
|
||||
install:
|
||||
- mkdir -p /Users/travis/build/facebook/.nvm
|
||||
- export NVM_DIR="/Users/travis/build/facebook/.nvm"
|
||||
- brew install nvm
|
||||
- source $(brew --prefix nvm)/nvm.sh
|
||||
# TODO npm 2 started stalling on Travis, t11852928
|
||||
|
||||
# Use node 6 because that is what runs on land-blocking tests
|
||||
- nvm install 6
|
||||
- nvm install 7
|
||||
- rm -Rf "${TMPDIR}/jest_preprocess_cache"
|
||||
- wget https://github.com/yarnpkg/yarn/releases/download/v0.16.0/yarn-0.16.0.js
|
||||
- export yarn="node $(pwd)/yarn-0.16.0.js"
|
||||
- $yarn install
|
||||
- brew install yarn --ignore-dependencies
|
||||
- yarn install
|
||||
|
||||
script:
|
||||
- if [[ "$TEST_TYPE" = objc-ios ]]; then travis_retry travis_wait ./scripts/objc-test-ios.sh test; fi
|
||||
- if [[ "$TEST_TYPE" = objc-tvos ]]; then travis_retry travis_wait ./scripts/objc-test-tvos.sh; fi
|
||||
- if [[ "$TEST_TYPE" = objc-ios ]]; then travis_retry travis_wait 30 ./scripts/objc-test-ios.sh test; fi
|
||||
- if [[ "$TEST_TYPE" = objc-tvos ]]; then travis_retry travis_wait 30 ./scripts/objc-test-tvos.sh; fi
|
||||
- if [[ "$TEST_TYPE" = e2e-objc ]]; then node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; fi
|
||||
- if [[ ( "$TEST_TYPE" = podspecs ) && ( "$TRAVIS_PULL_REQUEST" = "false" ) ]]; then gem install cocoapods && ./scripts/process-podspecs.sh; fi
|
||||
- if [[ ( "$TEST_TYPE" = podspecs ) && ( "$TRAVIS_PULL_REQUEST" = "false" ) ]]; then gem install cocoapods && travis_wait 30 ./scripts/process-podspecs.sh; fi
|
||||
|
||||
cache:
|
||||
- cocoapods
|
||||
- yarn
|
||||
|
||||
matrix:
|
||||
- fast_finish: true # Fail the whole build as soon as one test type fails. Should help with Travis capacity issues (very long queues).
|
||||
@@ -41,7 +36,6 @@ branches:
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- mkonicek@fb.com
|
||||
- douglowder@mac.com # Doug Lowder built and maintains Apple TV specific code and wants to be notified about tvOS failures.
|
||||
- eloy@artsy.net # Eloy Durán maintains the podspecs test and wants to be notified about failures.
|
||||
on_failure: change
|
||||
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceRoot}/Libraries/react-native/react-native-implementation.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
+56
-80
@@ -1,48 +1,16 @@
|
||||
# Contributing to React Native
|
||||
|
||||
<!-- generated_contributing_start -->
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody using Facebook's mobile apps. If you're interested in contributing to React Native, hopefully this document makes the process for contributing clear.
|
||||
React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody on https://facebook.com. We're still working out the kinks to make contributing to this project as easy and transparent as possible, but we're not quite there yet. Hopefully this document makes the process for contributing clear and preempts some questions you may have.
|
||||
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the [React Native blog](https://facebook.github.io/react-native/blog). You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
|
||||
## [Code of Conduct](https://code.facebook.com/codeofconduct)
|
||||
## Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
## How to contribute
|
||||
|
||||
There are many ways to contribute to React Native, and many of them do not involve writing any code. Here's a few ideas to get started:
|
||||
|
||||
* Simply start using React Native. Go through the [Getting Started](http://facebook.github.io/react-native/docs/getting-started.html) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](http://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
* Look through the [open issues](https://github.com/facebook/react-native/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](http://facebook.github.io/react-native/docs/contributing.html#triaging-issues-and-pull-requests).
|
||||
* If you find an issue you would like to fix, [open a pull request](http://facebook.github.io/react-native/docs/contributing.html#your-first-pull-request). Issues tagged as [`Good First Task`](https://github.com/facebook/react-native/labels/Good%20First%20Task) are a good place to get started.
|
||||
* Read through the [React Native docs](http://facebook.github.io/react-native/docs). If you find anything that is confusing or can be improved, you can make edits by clicking "Improve this page" at the bottom of most docs.
|
||||
* Browse [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) and answer questions. This will help you get familiarized with common pitfalls or misunderstandings, which can be useful when contributing updates to the documentation.
|
||||
* Take a look at the [features requested](https://react-native.canny.io/feature-requests) by others in the community and consider opening a pull request if you see something you want to work on.
|
||||
|
||||
Contributions are very welcome. If you think you need help planning your contribution, please hop into [#react-native](https://discord.gg/0ZcbPKXt5bZjGY5n) and let people know you're looking for a mentor.
|
||||
|
||||
### Triaging issues and pull requests
|
||||
|
||||
One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in.
|
||||
|
||||
* Ask for more information if the issue does not provide all the details required by the template.
|
||||
* Suggest [labels](https://github.com/facebook/react-native/labels) that can help categorize issues.
|
||||
* Flag issues that are stale or that should be closed.
|
||||
* Ask for test plans and review code.
|
||||
|
||||
Adding labels, closing and reopening issues, and merging pull requests is, as you may expect, limited to a subset of contributors. Simply commenting on the issue or pull request can still go a long way towards helping us keep the number of outstanding issues under control.
|
||||
|
||||
Once you have become an active contributor in the community, you may gain access to the Facebook GitHub Bot, allowing you to perform some of these operations yourself. You can learn more about the bot in the [maintainer's guide](docs/maintainers.html#facebook-github-bot).
|
||||
|
||||
|
||||
## Our development process
|
||||
## Our Development Process
|
||||
|
||||
Some of the core team will be working directly on [GitHub](https://github.com/facebook/react-native). These changes will be public from the beginning. Other changesets will come via a bridge with Facebook's internal source control. This is a necessity as it allows engineers at Facebook outside of the core team to move fast and contribute from an environment they are comfortable in.
|
||||
|
||||
When a change made on GitHub is approved, it will first be imported into Facebook's internal source control. The change will eventually sync back to GitHub as a single commit once it has passed all internal tests.
|
||||
|
||||
### Branch organization
|
||||
## Branch Organization
|
||||
|
||||
We will do our best to keep `master` in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to [communicate these changes](https://github.com/facebook/react-native/releases) and version appropriately so you can lock into a specific version if need be.
|
||||
|
||||
@@ -50,61 +18,69 @@ To see what changes are coming and provide better feedback to React Native contr
|
||||
|
||||
## Bugs
|
||||
|
||||
We use [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you a are certain this is a new, unreported bug, you can submit a [bug report](http://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues).
|
||||
#### Where to Find Known Issues
|
||||
|
||||
If you have questions about using React Native, the [help page](http://facebook.github.io/react-native/support.html) list various resources that should help you get started.
|
||||
We are using [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. Before filing a new task, try to make sure your problem doesn't already exist.
|
||||
|
||||
We also have a [place where you can request features or enhancements](https://react-native.canny.io/feature-requests). If you see anything you'd like to be implemented, vote it up and explain your use case.
|
||||
Questions and feature requests are tracked elsewhere:
|
||||
|
||||
## Reporting new issues
|
||||
- Have a question? [Ask on Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
- If you have a question regarding future plans, check out the [roadmap](https://github.com/facebook/react-native/wiki/Roadmap).
|
||||
- Have a feature request that is not covered in the roadmap? [Add it here](https://react-native.canny.io/feature-requests).
|
||||
|
||||
When [opening a new issue](https://github.com/facebook/react-native/issues/new), always make sure to fill out the [issue template](https://raw.githubusercontent.com/facebook/react-native/master/.github/ISSUE_TEMPLATE.md). **This step is very important!** Not doing so may result in your issue getting closed. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template.
|
||||
#### Reporting New Issues
|
||||
|
||||
* **One issue, one bug:** Please report a single bug per issue.
|
||||
* **Provide a Snack:** The best way to get attention on your issue is to provide a reduced test case. You can use [Snack](https://snack.expo.io/) to demonstrate the issue.
|
||||
* **Provide reproduction steps:** List all the steps necessary to reproduce the issue. Provide a Snack or upload a sample project to GitHub. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort.
|
||||
* **Try out the latest version:** Verify that the issue can be reproduced locally by updating your project to use [React Native from `master`](http://facebook.github.io/react-native/versions.html). The bug may have already been fixed!
|
||||
The best way to get your bug fixed is to provide a reduced test case. Please provide either a [Sketch](https://sketch.expo.io/) or a public repository with a runnable example.
|
||||
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) or reaching out to the community through [other channels](https://facebook.github.io/react-native/support.html).
|
||||
Please report a single bug per issue. Always provide reproduction steps. You can use Snack in many cases to demonstrate an issue: https://snack.expo.io/. If the bug cannot be reproduced using Snack, verify that the issue can be reproduced locally by targeting the latest release candidate. Ideally, check if the issue is present in master as well.
|
||||
|
||||
### Security bugs
|
||||
Do not forget to include sample code that reproduces the issue. Only open issues for bugs affecting either the latest stable release, or the current release candidate, or master (see http://facebook.github.io/react-native/versions.html). If it is not clear from your report that the issue can be reproduced in one of these releases, your issue will be closed.
|
||||
|
||||
We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native
|
||||
|
||||
#### Security Bugs
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues; go through the process outlined on that page.
|
||||
|
||||
## Pull requests
|
||||
## How to Get in Touch
|
||||
|
||||
### Your first pull request
|
||||
Many React Native users are active on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native).
|
||||
|
||||
So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at.
|
||||
If you want to get a general sense of what React Native folks talk about, check out the [React Native Community](https://www.facebook.com/groups/react.native.community) Facebook group.
|
||||
|
||||
Working on your first Pull Request? You can learn how from this free video series:
|
||||
There is also [an active community of React and React Native users on the Discord chat platform](https://discord.gg/0ZcbPKXt5bZjGY5n) in case you need help.
|
||||
|
||||
[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
The React Native team sends out periodical updates through the following channels:
|
||||
|
||||
We have a list of [beginner friendly issues](https://github.com/facebook/react-native/labels/Good%20First%20Task) to help you get your feet wet in the React Native codebase and familiar with our contribution process. This is a great place to get started.
|
||||
* [Blog](https://facebook.github.io/react-native/blog/)
|
||||
* [Twitter](https://www.twitter.com/reactnative)
|
||||
|
||||
### Proposing a change
|
||||
Core contributors to React Native meet monthly and post their meeting notes on the React Native blog. You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group.
|
||||
|
||||
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, we have a [place to track feature requests](https://react-native.canny.io/feature-requests).
|
||||
## Proposing a Change
|
||||
|
||||
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend [filing an issue](https://github.com/facebook/react-native/issues/new). This lets us reach an agreement on your proposal before you put significant effort into it.
|
||||
|
||||
If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
|
||||
|
||||
### Sending a pull request
|
||||
## Pull Requests
|
||||
|
||||
If you send a pull request, please do it against the master branch. We maintain stable branches for stable releases separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable version.
|
||||
|
||||
The core team will be monitoring for pull requests. When we get one, we'll run some Facebook-specific integration tests on it first. From here, we'll need to get another person to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process.
|
||||
|
||||
Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it.
|
||||
|
||||
**Before submitting a pull request**, please make sure the following is done:
|
||||
*Before* submitting a pull request, please make sure the following is done…
|
||||
|
||||
1. Fork [the repository](https://github.com/facebook/react-native) and create your branch from `master`.
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. Add the copyright notice to the top of any new files you've added.
|
||||
3. Describe your [**test plan**](https://facebook.github.io/react-native/docs/contributing.html#test-plan) in your commit.
|
||||
4. Ensure [**tests pass**](https://facebook.github.io/react-native/docs/contributing.html#contrinuous-integration-tests) on both Travis and Circle CI.
|
||||
5. Make sure your code lints (`npm run lint`).
|
||||
6. If you haven't already, [sign the CLA](https://code.facebook.com/cla).
|
||||
|
||||
All pull requests should be opened against the `master` branch.
|
||||
3. Describe your **test plan** in your commit.
|
||||
4. Ensure **tests pass** on Travis and Circle CI.
|
||||
5. Make sure your code lints (`node linter.js <files touched>`).
|
||||
6. If you haven't already, sign the CLA: https://code.facebook.com/cla
|
||||
7. Squash your commits (`git rebase -i`).
|
||||
One intent alongside one commit makes it clearer for people to review and easier to understand your intention.
|
||||
|
||||
> **Note:** It is not necessary to keep clicking `Merge master to your branch` on the PR page. You would want to merge master if there are conflicts or tests are failing. The Facebook-GitHub-Bot ultimately squashes all commits to a single one before merging your PR.
|
||||
|
||||
@@ -112,15 +88,18 @@ All pull requests should be opened against the `master` branch.
|
||||
|
||||
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI or updates the website.
|
||||
|
||||
* If you've added code that should be tested, add tests!
|
||||
* If you've changed APIs, update the documentation.
|
||||
* If you've updated the docs, verify the website locally and submit screenshots if applicable (see [website/README.md](https://github.com/facebook/react-native/blob/master/website/README.md))
|
||||
- If you've added code that should be tested, add tests!
|
||||
- If you've changed APIs, update the documentation.
|
||||
- If you've updated the docs, verify the website locally and submit screenshots if applicable (see `website/README.md`)
|
||||
|
||||
See [What is a Test Plan?](https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9) to learn more.
|
||||
See "What is a Test Plan?" to learn more:
|
||||
https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9
|
||||
|
||||
#### Continuous integration tests
|
||||
|
||||
Make sure all **tests pass** on both [Travis][travis] and [Circle CI][circle]. PRs that break tests are unlikely to be merged. Learn more about [testing your changes here](https://facebook.github.io/react-native/docs/testing.html).
|
||||
Make sure all **tests pass** on both [Travis][travis] and [Circle CI][circle]. PRs that break tests are unlikely to be merged.
|
||||
|
||||
You can learn more about running tests and contributing to React Native here: https://facebook.github.io/react-native/docs/testing.html
|
||||
|
||||
[travis]: https://travis-ci.org/facebook/react-native
|
||||
[circle]: http://circleci.com/gh/facebook/react-native
|
||||
@@ -132,10 +111,10 @@ When adding a new breaking change, follow this template in your pull request:
|
||||
```
|
||||
### New breaking change here
|
||||
|
||||
* **Who does this affect**:
|
||||
* **How to migrate**:
|
||||
* **Why make this breaking change**:
|
||||
* **Severity (number of people affected x effort)**:
|
||||
- **Who does this affect**:
|
||||
- **How to migrate**:
|
||||
- **Why make this breaking change**:
|
||||
- **Severity (number of people affected x effort)**:
|
||||
```
|
||||
|
||||
If your pull request is merged, a core contributor will update the [list of breaking changes](https://github.com/facebook/react-native/wiki/Breaking-Changes) which is then used to populate the release notes.
|
||||
@@ -157,17 +136,15 @@ Copy and paste this to the top of your new file(s):
|
||||
|
||||
If you've added a new module, add a `@providesModule <moduleName>` at the end of the comment. This will allow the haste package manager to find it.
|
||||
|
||||
#### Contributor License Agreement (CLA)
|
||||
### Contributor License Agreement (CLA)
|
||||
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla).
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username.
|
||||
|
||||
### What happens next?
|
||||
|
||||
The core team will be monitoring for pull requests. Read [what to expect from maintainers](https://facebook.github.io/react-native/docs/maintainers.html#handling-pull-requests) to understand what may happen after you open a pull request.
|
||||
Complete your CLA here: https://code.facebook.com/cla
|
||||
|
||||
## Style Guide
|
||||
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
|
||||
Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `node linter.js <files touched>`.
|
||||
|
||||
However, there are still some styles that the linter cannot pick up.
|
||||
|
||||
@@ -217,4 +194,3 @@ However, there are still some styles that the linter cannot pick up.
|
||||
## License
|
||||
|
||||
By contributing to React Native, you agree that your contributions will be licensed under its BSD license.
|
||||
<!-- generated_contributing_end -->
|
||||
|
||||
@@ -3,7 +3,6 @@ FROM containership/android-base:latest
|
||||
# set default environment variables
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
|
||||
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
|
||||
ENV REACT_NATIVE_MAX_WORKERS=1
|
||||
|
||||
# add ReactAndroid directory
|
||||
ADD .buckconfig /app/.buckconfig
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM library/node:6.9.2
|
||||
|
||||
ENV YARN_VERSION=0.19.1
|
||||
ENV YARN_VERSION=0.27.5
|
||||
|
||||
# install dependencies
|
||||
RUN apt-get update && apt-get install ocaml libelf-dev -y
|
||||
|
||||
@@ -17,7 +17,6 @@ AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)
|
||||
ANDROID_NPM_DEPS="appium@1.5.1 mocha@2.4.5 wd@0.3.11 colors@1.0.3 pretty-data2@0.40.1"
|
||||
CLI_PACKAGE=$ROOT/react-native-cli/react-native-cli-*.tgz
|
||||
PACKAGE=$ROOT/react-native-*.tgz
|
||||
REACT_NATIVE_MAX_WORKERS=1
|
||||
|
||||
# solve issue with max user watches limit
|
||||
echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches
|
||||
@@ -231,13 +230,13 @@ function e2e_suite() {
|
||||
# js tests
|
||||
if [ $RUN_JS -ne 0 ]; then
|
||||
# Check the packager produces a bundle (doesn't throw an error)
|
||||
REACT_NATIVE_MAX_WORKERS=1 react-native bundle --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
|
||||
react-native bundle --max-workers 1 --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Could not build android bundle"
|
||||
return 1
|
||||
fi
|
||||
|
||||
REACT_NATIVE_MAX_WORKERS=1 react-native bundle --platform ios --dev true --entry-file index.ios.js --bundle-output ios-bundle.js
|
||||
react-native bundle --max-workers 1 --platform ios --dev true --entry-file index.ios.js --bundle-output ios-bundle.js
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Could not build iOS bundle"
|
||||
return 1
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule AccessibilityManagerTest
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const { View } = ReactNative;
|
||||
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
const {
|
||||
TestModule,
|
||||
AccessibilityManager,
|
||||
} = ReactNative.NativeModules;
|
||||
|
||||
|
||||
class AccessibilityManagerTest extends React.Component {
|
||||
componentDidMount() {
|
||||
AccessibilityManager.setAccessibilityContentSizeMultipliers({
|
||||
'extraSmall': 1.0,
|
||||
'small': 2.0,
|
||||
'medium': 3.0,
|
||||
'large': 4.0,
|
||||
'extraLarge': 5.0,
|
||||
'extraExtraLarge': 6.0,
|
||||
'extraExtraExtraLarge': 7.0,
|
||||
'accessibilityMedium': 8.0,
|
||||
'accessibilityLarge': 9.0,
|
||||
'accessibilityExtraLarge': 10.0,
|
||||
'accessibilityExtraExtraLarge': 11.0,
|
||||
'accessibilityExtraExtraExtraLarge': 12.0,
|
||||
});
|
||||
RCTDeviceEventEmitter.addListener('didUpdateDimensions', update => {
|
||||
TestModule.markTestPassed(update.window.fontScale === 4.0);
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
return <View />;
|
||||
}
|
||||
}
|
||||
|
||||
AccessibilityManagerTest.displayName = 'AccessibilityManagerTest';
|
||||
|
||||
module.exports = AccessibilityManagerTest;
|
||||
@@ -35,6 +35,7 @@ var TESTS = [
|
||||
require('./PromiseTest'),
|
||||
require('./SyncMethodTest'),
|
||||
require('./WebSocketTest'),
|
||||
require('./AccessibilityManagerTest'),
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
|
||||
@@ -392,7 +392,7 @@ class Shape extends React.Component {
|
||||
render() {
|
||||
var props = this.props;
|
||||
var path = props.d || childrenAsString(props.children);
|
||||
var d = new Path(path).toJSON();
|
||||
var d = (path instanceof Path ? path : new Path(path)).toJSON();
|
||||
return (
|
||||
<NativeShape
|
||||
fill={extractBrush(props.fill, props)}
|
||||
@@ -486,7 +486,8 @@ function extractAlignment(alignment) {
|
||||
class Text extends React.Component {
|
||||
render() {
|
||||
var props = this.props;
|
||||
var textPath = props.path ? new Path(props.path).toJSON() : null;
|
||||
var path = props.path;
|
||||
var textPath = path ? (path instanceof Path ? path : new Path(path)).toJSON() : null;
|
||||
var textFrame = extractFontAndLines(
|
||||
props.font,
|
||||
childrenAsString(props.children)
|
||||
|
||||
@@ -2825,9 +2825,9 @@ module.exports = {
|
||||
*
|
||||
*```javascript
|
||||
* onScroll={Animated.event(
|
||||
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
|
||||
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}],
|
||||
* {listener}, // Optional async listener
|
||||
* )
|
||||
* )}
|
||||
* ...
|
||||
* onPanResponderMove: Animated.event([
|
||||
* null, // raw event arg ignored
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
var Animated = require('Animated');
|
||||
describe('Animated tests', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.disableAutomock()
|
||||
.setMock('Text', {})
|
||||
.setMock('View', {})
|
||||
.setMock('Image', {})
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('Easing');
|
||||
|
||||
var Easing = require('Easing');
|
||||
describe('Easing', () => {
|
||||
it('should work with linear', () => {
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.unmock('Interpolation')
|
||||
.unmock('Easing')
|
||||
.unmock('normalizeColor');
|
||||
|
||||
var Interpolation = require('Interpolation');
|
||||
var Easing = require('Easing');
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
jest.unmock('bezier');
|
||||
var bezier = require('bezier');
|
||||
|
||||
var identity = function (x) { return x; };
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
'use strict';
|
||||
|
||||
const ErrorUtils = require('ErrorUtils');
|
||||
const JSTimersExecution = require('JSTimersExecution');
|
||||
const Systrace = require('Systrace');
|
||||
|
||||
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
@@ -41,6 +40,9 @@ const TRACE_TAG_REACT_APPS = 1 << 17;
|
||||
|
||||
const DEBUG_INFO_LIMIT = 32;
|
||||
|
||||
// Work around an initialization order issue
|
||||
let JSTimers = null;
|
||||
|
||||
class MessageQueue {
|
||||
_lazyCallableModules: {[key: string]: void => Object};
|
||||
_queue: [Array<number>, Array<number>, Array<any>, number];
|
||||
@@ -151,8 +153,9 @@ class MessageQueue {
|
||||
};
|
||||
}
|
||||
|
||||
_getCallableModule(name: string) {
|
||||
return this._lazyCallableModules[name]();
|
||||
getCallableModule(name: string) {
|
||||
const getValue = this._lazyCallableModules[name];
|
||||
return getValue ? getValue() : null;
|
||||
}
|
||||
|
||||
enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
|
||||
@@ -234,8 +237,11 @@ class MessageQueue {
|
||||
}
|
||||
|
||||
__callImmediates() {
|
||||
Systrace.beginEvent('JSTimersExecution.callImmediates()');
|
||||
JSTimersExecution.callImmediates();
|
||||
Systrace.beginEvent('JSTimers.callImmediates()');
|
||||
if (!JSTimers) {
|
||||
JSTimers = require('JSTimers');
|
||||
}
|
||||
JSTimers.callImmediates();
|
||||
Systrace.endEvent();
|
||||
}
|
||||
|
||||
@@ -246,7 +252,7 @@ class MessageQueue {
|
||||
if (this.__spy) {
|
||||
this.__spy({ type: TO_JS, module, method, args});
|
||||
}
|
||||
const moduleMethods = this._getCallableModule(module);
|
||||
const moduleMethods = this.getCallableModule(module);
|
||||
invariant(
|
||||
!!moduleMethods,
|
||||
'Module %s is not a registered callable module (calling %s)',
|
||||
|
||||
@@ -15,6 +15,8 @@ const BatchedBridge = require('BatchedBridge');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {ExtendedError} from 'parseErrorStack';
|
||||
|
||||
type ModuleConfig = [
|
||||
string, /* name */
|
||||
?Object, /* constants */
|
||||
@@ -113,13 +115,13 @@ function arrayContains<T>(array: Array<T>, value: T): boolean {
|
||||
return array.indexOf(value) !== -1;
|
||||
}
|
||||
|
||||
function createErrorFromErrorData(errorData: {message: string}): Error {
|
||||
function createErrorFromErrorData(errorData: {message: string}): ExtendedError {
|
||||
const {
|
||||
message,
|
||||
...extraErrorInfo
|
||||
} = errorData;
|
||||
const error = new Error(message);
|
||||
(error:any).framesToPop = 1;
|
||||
} = errorData || {};
|
||||
const error : ExtendedError = new Error(message);
|
||||
error.framesToPop = 1;
|
||||
return Object.assign(error, extraErrorInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// const MessageQueueTestConfig = require('MessageQueueTestConfig');
|
||||
jest.unmock('MessageQueue');
|
||||
jest.unmock('defineLazyObjectProperty');
|
||||
|
||||
let MessageQueue;
|
||||
let MessageQueueTestModule;
|
||||
let queue;
|
||||
@@ -84,4 +80,34 @@ describe('MessageQueue', function() {
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(0)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when calling with unknown module', () => {
|
||||
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
|
||||
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
|
||||
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return lazily registered module', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
queue.registerLazyCallableModule(name, () => dummyModule);
|
||||
|
||||
expect(queue.getCallableModule(name)).toEqual(dummyModule);
|
||||
});
|
||||
|
||||
it('should not initialize lazily registered module before it was used for the first time', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
expect(factory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should initialize lazily registered module only once', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.getCallableModule(name);
|
||||
queue.getCallableModule(name);
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('BatchedBridge');
|
||||
jest.unmock('defineLazyObjectProperty');
|
||||
jest.unmock('MessageQueue');
|
||||
jest.unmock('NativeModules');
|
||||
jest
|
||||
.enableAutomock()
|
||||
.unmock('BatchedBridge')
|
||||
.unmock('defineLazyObjectProperty')
|
||||
.unmock('MessageQueue')
|
||||
.unmock('NativeModules');
|
||||
|
||||
let BatchedBridge;
|
||||
let NativeModules;
|
||||
|
||||
@@ -97,25 +97,25 @@ class Button extends React.Component {
|
||||
} = this.props;
|
||||
const buttonStyles = [styles.button];
|
||||
const textStyles = [styles.text];
|
||||
const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
|
||||
if (color && Platform.OS === 'ios') {
|
||||
textStyles.push({color: color});
|
||||
} else if (color) {
|
||||
buttonStyles.push({backgroundColor: color});
|
||||
if (color) {
|
||||
if (Platform.OS === 'ios') {
|
||||
textStyles.push({color: color});
|
||||
} else {
|
||||
buttonStyles.push({backgroundColor: color});
|
||||
}
|
||||
}
|
||||
const accessibilityTraits = ['button'];
|
||||
if (disabled) {
|
||||
buttonStyles.push(styles.buttonDisabled);
|
||||
textStyles.push(styles.textDisabled);
|
||||
accessibilityTraits.push('disabled');
|
||||
}
|
||||
invariant(
|
||||
typeof title === 'string',
|
||||
'The title prop of a Button must be a string',
|
||||
);
|
||||
const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title;
|
||||
const accessibilityTraits = ['button'];
|
||||
if (disabled) {
|
||||
accessibilityTraits.push('disabled');
|
||||
}
|
||||
const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
|
||||
return (
|
||||
<Touchable
|
||||
accessibilityComponentType="button"
|
||||
@@ -132,32 +132,27 @@ class Button extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Material design blue from https://material.google.com/style/color.html#color-color-palette
|
||||
let defaultBlue = '#2196F3';
|
||||
if (Platform.OS === 'ios') {
|
||||
// Measured default tintColor from iOS 10
|
||||
defaultBlue = '#0C42FD';
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: Platform.select({
|
||||
ios: {},
|
||||
android: {
|
||||
elevation: 4,
|
||||
backgroundColor: defaultBlue,
|
||||
// Material design blue from https://material.google.com/style/color.html#color-color-palette
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 2,
|
||||
},
|
||||
}),
|
||||
text: Platform.select({
|
||||
ios: {
|
||||
color: defaultBlue,
|
||||
// iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
|
||||
color: '#007AFF',
|
||||
textAlign: 'center',
|
||||
padding: 8,
|
||||
fontSize: 18,
|
||||
},
|
||||
android: {
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
padding: 8,
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
@@ -100,7 +100,9 @@ let Keyboard = {
|
||||
* - `keyboardDidChangeFrame`
|
||||
*
|
||||
* Note that if you set `android:windowSoftInputMode` to `adjustResize` or `adjustNothing`,
|
||||
* only `keyboardDidShow` and `keyboardDidHide` events will available on Android.
|
||||
* only `keyboardDidShow` and `keyboardDidHide` events will be available on Android.
|
||||
* `keyboardWillShow` as well as `keyboardWillHide` are generally not available on Android
|
||||
* since there is no native corresponding event.
|
||||
*
|
||||
* @param {function} callback function to be called when the event fires.
|
||||
*/
|
||||
|
||||
@@ -22,13 +22,8 @@ const View = require('View');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
import type {ViewLayout, ViewLayoutEvent} from 'ViewPropTypes';
|
||||
|
||||
type Rect = {
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
type ScreenRect = {
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
@@ -41,11 +36,6 @@ type KeyboardChangeEvent = {
|
||||
duration?: number,
|
||||
easing?: string,
|
||||
};
|
||||
type LayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: Rect,
|
||||
}
|
||||
};
|
||||
|
||||
const viewRef = 'VIEW';
|
||||
|
||||
@@ -87,7 +77,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
},
|
||||
|
||||
subscriptions: ([]: Array<EmitterSubscription>),
|
||||
frame: (null: ?Rect),
|
||||
frame: (null: ?ViewLayout),
|
||||
|
||||
relativeKeyboardHeight(keyboardFrame: ScreenRect): number {
|
||||
const frame = this.frame;
|
||||
@@ -123,7 +113,7 @@ const KeyboardAvoidingView = createReactClass({
|
||||
this.setState({bottom: height});
|
||||
},
|
||||
|
||||
onLayout(event: LayoutEvent) {
|
||||
onLayout(event: ViewLayoutEvent) {
|
||||
this.frame = event.nativeEvent.layout;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule MaskedViewIOS
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('../../setupBabel')();
|
||||
module.exports = require('metro-bundler/build/JSTransformer/worker');
|
||||
module.exports = require('UnimplementedView');
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule MaskedViewIOS
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
const ReactPropTypes = React.PropTypes;
|
||||
|
||||
import type { ViewProps } from 'ViewPropTypes';
|
||||
|
||||
type Props = ViewProps & {
|
||||
children: any,
|
||||
/**
|
||||
* Should be a React element to be rendered and applied as the
|
||||
* mask for the child element.
|
||||
*/
|
||||
maskElement: React.Element<*>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the child view with a mask specified in the `maskElement` prop.
|
||||
*
|
||||
* ```
|
||||
* import React from 'react';
|
||||
* import { MaskedView, Text, View } from 'react-native';
|
||||
*
|
||||
* class MyMaskedView extends React.Component {
|
||||
* render() {
|
||||
* return (
|
||||
* <MaskedView
|
||||
* style={{ flex: 1 }}
|
||||
* maskElement={
|
||||
* <View style={styles.maskContainerStyle}>
|
||||
* <Text style={styles.maskTextStyle}>
|
||||
* Basic Mask
|
||||
* </Text>
|
||||
* </View>
|
||||
* }
|
||||
* >
|
||||
* <View style={{ flex: 1, backgroundColor: 'blue' }} />
|
||||
* </MaskedView>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The above example will render a view with a blue background that fills its
|
||||
* parent, and then mask that view with text that says "Basic Mask".
|
||||
*
|
||||
* The alpha channel of the view rendered by the `maskElement` prop determines how
|
||||
* much of the view's content and background shows through. Fully or partially
|
||||
* opaque pixels allow the underlying content to show through but fully
|
||||
* transparent pixels block that content.
|
||||
*
|
||||
*/
|
||||
class MaskedViewIOS extends React.Component {
|
||||
props: Props;
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
maskElement: ReactPropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
_hasWarnedInvalidRenderMask = false;
|
||||
|
||||
render() {
|
||||
const { maskElement, children, ...otherViewProps } = this.props;
|
||||
|
||||
if (!React.isValidElement(maskElement)) {
|
||||
if (!this._hasWarnedInvalidRenderMask) {
|
||||
console.warn(
|
||||
'MaskedView: Invalid `maskElement` prop was passed to MaskedView. ' +
|
||||
'Expected a React Element. No mask will render.'
|
||||
);
|
||||
this._hasWarnedInvalidRenderMask = true;
|
||||
}
|
||||
return <View {...otherViewProps}>{children}</View>;
|
||||
}
|
||||
|
||||
return (
|
||||
<RCTMaskedView {...otherViewProps}>
|
||||
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
||||
{maskElement}
|
||||
</View>
|
||||
{children}
|
||||
</RCTMaskedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const RCTMaskedView = requireNativeComponent('RCTMaskedView', {
|
||||
name: 'RCTMaskedView',
|
||||
displayName: 'RCTMaskedView',
|
||||
propTypes: {
|
||||
...ViewPropTypes,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = MaskedViewIOS;
|
||||
@@ -436,7 +436,7 @@ var NavigatorIOS = createReactClass({
|
||||
titleTextColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Bboolean value that indicates whether the navigation bar is
|
||||
* Boolean value that indicates whether the navigation bar is
|
||||
* translucent.
|
||||
*/
|
||||
translucent: PropTypes.bool,
|
||||
|
||||
@@ -33,6 +33,9 @@ const flattenStyle = require('flattenStyle');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const processDecelerationRate = require('processDecelerationRate');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
|
||||
|
||||
/**
|
||||
* Component that wraps platform ScrollView while providing
|
||||
@@ -256,6 +259,8 @@ const ScrollView = createReactClass({
|
||||
* When true, the scroll view stops on multiples of the scroll view's size
|
||||
* when scrolling. This can be used for horizontal pagination. The default
|
||||
* value is false.
|
||||
*
|
||||
* Note: Vertical pagination is not supported on Android.
|
||||
*/
|
||||
pagingEnabled: PropTypes.bool,
|
||||
/**
|
||||
@@ -313,8 +318,10 @@ const ScrollView = createReactClass({
|
||||
/**
|
||||
* When set, causes the scroll view to stop at multiples of the value of
|
||||
* `snapToInterval`. This can be used for paginating through children
|
||||
* that have lengths smaller than the scroll view. Used in combination
|
||||
* with `snapToAlignment`.
|
||||
* that have lengths smaller than the scroll view. Typically used in
|
||||
* combination with `snapToAlignment` and `decelerationRate="fast"`.
|
||||
* Overrides less configurable `pagingEnabled` prop.
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
snapToInterval: PropTypes.number,
|
||||
@@ -390,6 +397,15 @@ const ScrollView = createReactClass({
|
||||
'always',
|
||||
'never',
|
||||
]),
|
||||
/**
|
||||
* When true, ScrollView will emit updateChildFrames data in scroll events,
|
||||
* otherwise will not compute or emit child frame data. This only exists
|
||||
* to support legacy issues, `onLayout` should be used instead to retrieve
|
||||
* frame data.
|
||||
* The default value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
DEPRECATED_sendUpdatedChildFrames: PropTypes.bool,
|
||||
},
|
||||
|
||||
mixins: [ScrollResponder.Mixin],
|
||||
@@ -585,8 +601,8 @@ const ScrollView = createReactClass({
|
||||
this._scrollViewRef = ref;
|
||||
},
|
||||
|
||||
_innerViewRef: (null: ?View),
|
||||
_setInnerViewRef: function(ref: ?View) {
|
||||
_innerViewRef: (null: ?NativeMethodsMixinType),
|
||||
_setInnerViewRef: function(ref: ?NativeMethodsMixinType) {
|
||||
this._innerViewRef = ref;
|
||||
},
|
||||
|
||||
@@ -596,6 +612,10 @@ const ScrollView = createReactClass({
|
||||
if (Platform.OS === 'ios') {
|
||||
ScrollViewClass = RCTScrollView;
|
||||
ScrollContentContainerViewClass = RCTScrollContentView;
|
||||
warning(
|
||||
!this.props.snapToInterval || !this.props.pagingEnabled,
|
||||
'snapToInterval is currently ignored when pagingEnabled is true.'
|
||||
);
|
||||
} else if (Platform.OS === 'android') {
|
||||
if (this.props.horizontal) {
|
||||
ScrollViewClass = AndroidHorizontalScrollView;
|
||||
@@ -669,7 +689,13 @@ const ScrollView = createReactClass({
|
||||
{...contentSizeChangeProps}
|
||||
ref={this._setInnerViewRef}
|
||||
style={contentContainerStyle}
|
||||
removeClippedSubviews={this.props.removeClippedSubviews}
|
||||
removeClippedSubviews={
|
||||
// Subview clipping causes issues with sticky headers on Android and
|
||||
// would be hard to fix properly in a performant way.
|
||||
Platform.OS === 'android' && hasStickyHeaders ?
|
||||
false :
|
||||
this.props.removeClippedSubviews
|
||||
}
|
||||
collapsable={false}>
|
||||
{children}
|
||||
</ScrollContentContainerViewClass>;
|
||||
@@ -684,6 +710,9 @@ const ScrollView = createReactClass({
|
||||
this.props.alwaysBounceVertical :
|
||||
!this.props.horizontal;
|
||||
|
||||
const DEPRECATED_sendUpdatedChildFrames =
|
||||
!!this.props.DEPRECATED_sendUpdatedChildFrames;
|
||||
|
||||
const baseStyle = this.props.horizontal ? styles.baseHorizontal : styles.baseVertical;
|
||||
const props = {
|
||||
...this.props,
|
||||
@@ -712,6 +741,7 @@ const ScrollView = createReactClass({
|
||||
scrollEventThrottle: hasStickyHeaders ? 1 : this.props.scrollEventThrottle,
|
||||
sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ?
|
||||
true : false,
|
||||
DEPRECATED_sendUpdatedChildFrames,
|
||||
};
|
||||
|
||||
const { decelerationRate } = this.props;
|
||||
@@ -808,6 +838,7 @@ if (Platform.OS === 'android') {
|
||||
(ScrollView: ReactClass<any>),
|
||||
nativeOnlyProps,
|
||||
);
|
||||
// $FlowFixMe (bvaughn) Update ComponentInterface in ViewPropTypes to include a string type (for Fiber host components) in a follow-up.
|
||||
RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,10 @@ type DefaultProps = {
|
||||
/**
|
||||
* Merges the prop stack with the default values.
|
||||
*/
|
||||
function mergePropsStack(propsStack: Array<Object>, defaultValues: Object): Object {
|
||||
function mergePropsStack(
|
||||
propsStack: Array<Object>,
|
||||
defaultValues: Object
|
||||
): Object {
|
||||
return propsStack.reduce((prev, cur) => {
|
||||
for (const prop in cur) {
|
||||
if (cur[prop] != null) {
|
||||
@@ -80,20 +83,29 @@ function mergePropsStack(propsStack: Array<Object>, defaultValues: Object): Obje
|
||||
*/
|
||||
function createStackEntry(props: any): any {
|
||||
return {
|
||||
backgroundColor: props.backgroundColor != null ? {
|
||||
value: props.backgroundColor,
|
||||
animated: props.animated,
|
||||
} : null,
|
||||
barStyle: props.barStyle != null ? {
|
||||
value: props.barStyle,
|
||||
animated: props.animated,
|
||||
} : null,
|
||||
backgroundColor:
|
||||
props.backgroundColor != null
|
||||
? {
|
||||
value: props.backgroundColor,
|
||||
animated: props.animated,
|
||||
}
|
||||
: null,
|
||||
barStyle:
|
||||
props.barStyle != null
|
||||
? {
|
||||
value: props.barStyle,
|
||||
animated: props.animated,
|
||||
}
|
||||
: null,
|
||||
translucent: props.translucent,
|
||||
hidden: props.hidden != null ? {
|
||||
value: props.hidden,
|
||||
animated: props.animated,
|
||||
transition: props.showHideTransition,
|
||||
} : null,
|
||||
hidden:
|
||||
props.hidden != null
|
||||
? {
|
||||
value: props.hidden,
|
||||
animated: props.animated,
|
||||
transition: props.showHideTransition,
|
||||
}
|
||||
: null,
|
||||
networkActivityIndicatorVisible: props.networkActivityIndicatorVisible,
|
||||
};
|
||||
}
|
||||
@@ -141,7 +153,7 @@ class StatusBar extends React.Component {
|
||||
props: {
|
||||
hidden?: boolean,
|
||||
animated?: boolean,
|
||||
backgroundColor?: $FlowFixMe,
|
||||
backgroundColor?: string,
|
||||
translucent?: boolean,
|
||||
barStyle?: 'default' | 'light-content' | 'dark-content',
|
||||
networkActivityIndicatorVisible?: boolean,
|
||||
@@ -215,7 +227,9 @@ class StatusBar extends React.Component {
|
||||
*/
|
||||
static setNetworkActivityIndicatorVisible(visible: boolean) {
|
||||
if (Platform.OS !== 'ios') {
|
||||
console.warn('`setNetworkActivityIndicatorVisible` is only available on iOS');
|
||||
console.warn(
|
||||
'`setNetworkActivityIndicatorVisible` is only available on iOS'
|
||||
);
|
||||
return;
|
||||
}
|
||||
StatusBar._defaultProps.networkActivityIndicatorVisible = visible;
|
||||
@@ -276,11 +290,7 @@ class StatusBar extends React.Component {
|
||||
/**
|
||||
* Sets the color of the status bar text.
|
||||
*/
|
||||
barStyle: PropTypes.oneOf([
|
||||
'default',
|
||||
'light-content',
|
||||
'dark-content',
|
||||
]),
|
||||
barStyle: PropTypes.oneOf(['default', 'light-content', 'dark-content']),
|
||||
/**
|
||||
* If the network activity indicator should be visible.
|
||||
*
|
||||
@@ -293,10 +303,7 @@ class StatusBar extends React.Component {
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
showHideTransition: PropTypes.oneOf([
|
||||
'fade',
|
||||
'slide',
|
||||
]),
|
||||
showHideTransition: PropTypes.oneOf(['fade', 'slide']),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -343,38 +350,52 @@ class StatusBar extends React.Component {
|
||||
clearImmediate(StatusBar._updateImmediate);
|
||||
StatusBar._updateImmediate = setImmediate(() => {
|
||||
const oldProps = StatusBar._currentValues;
|
||||
const mergedProps = mergePropsStack(StatusBar._propsStack, StatusBar._defaultProps);
|
||||
const mergedProps = mergePropsStack(
|
||||
StatusBar._propsStack,
|
||||
StatusBar._defaultProps
|
||||
);
|
||||
|
||||
// Update the props that have changed using the merged values from the props stack.
|
||||
if (Platform.OS === 'ios') {
|
||||
if (!oldProps || oldProps.barStyle.value !== mergedProps.barStyle.value) {
|
||||
if (
|
||||
!oldProps ||
|
||||
oldProps.barStyle.value !== mergedProps.barStyle.value
|
||||
) {
|
||||
StatusBarManager.setStyle(
|
||||
mergedProps.barStyle.value,
|
||||
mergedProps.barStyle.animated,
|
||||
mergedProps.barStyle.animated
|
||||
);
|
||||
}
|
||||
if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
|
||||
StatusBarManager.setHidden(
|
||||
mergedProps.hidden.value,
|
||||
mergedProps.hidden.animated ?
|
||||
mergedProps.hidden.transition :
|
||||
'none',
|
||||
mergedProps.hidden.animated ? mergedProps.hidden.transition : 'none'
|
||||
);
|
||||
}
|
||||
|
||||
if (!oldProps || oldProps.networkActivityIndicatorVisible !== mergedProps.networkActivityIndicatorVisible) {
|
||||
if (
|
||||
!oldProps ||
|
||||
oldProps.networkActivityIndicatorVisible !==
|
||||
mergedProps.networkActivityIndicatorVisible
|
||||
) {
|
||||
StatusBarManager.setNetworkActivityIndicatorVisible(
|
||||
mergedProps.networkActivityIndicatorVisible
|
||||
);
|
||||
}
|
||||
} else if (Platform.OS === 'android') {
|
||||
if (!oldProps || oldProps.barStyle.value !== mergedProps.barStyle.value) {
|
||||
if (
|
||||
!oldProps ||
|
||||
oldProps.barStyle.value !== mergedProps.barStyle.value
|
||||
) {
|
||||
StatusBarManager.setStyle(mergedProps.barStyle.value);
|
||||
}
|
||||
if (!oldProps || oldProps.backgroundColor.value !== mergedProps.backgroundColor.value) {
|
||||
if (
|
||||
!oldProps ||
|
||||
oldProps.backgroundColor.value !== mergedProps.backgroundColor.value
|
||||
) {
|
||||
StatusBarManager.setColor(
|
||||
processColor(mergedProps.backgroundColor.value),
|
||||
mergedProps.backgroundColor.animated,
|
||||
mergedProps.backgroundColor.animated
|
||||
);
|
||||
}
|
||||
if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
|
||||
|
||||
@@ -53,7 +53,7 @@ var TextInputState = {
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {number} textFieldID id of the text field to focus
|
||||
* @param {number} textFieldID id of the text field to unfocus
|
||||
* Unfocuses the specified text field
|
||||
* noop if it wasn't focused
|
||||
*/
|
||||
|
||||
@@ -7,38 +7,36 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule TouchableHighlight
|
||||
* @noflow
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var ColorPropType = require('ColorPropType');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const ColorPropType = require('ColorPropType');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const PropTypes = require('prop-types');
|
||||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
var View = require('View');
|
||||
const React = require('React');
|
||||
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const TimerMixin = require('react-timer-mixin');
|
||||
const Touchable = require('Touchable');
|
||||
const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
const View = require('View');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
|
||||
var createReactClass = require('create-react-class');
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
var keyOf = require('fbjs/lib/keyOf');
|
||||
var merge = require('merge');
|
||||
const createReactClass = require('create-react-class');
|
||||
const ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
const ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
const keyOf = require('fbjs/lib/keyOf');
|
||||
const merge = require('merge');
|
||||
|
||||
type Event = Object;
|
||||
import type {Event} from 'TouchableWithoutFeedback';
|
||||
|
||||
var DEFAULT_PROPS = {
|
||||
const DEFAULT_PROPS = {
|
||||
activeOpacity: 0.85,
|
||||
underlayColor: 'black',
|
||||
};
|
||||
|
||||
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches.
|
||||
@@ -110,7 +108,6 @@ var TouchableHighlight = createReactClass({
|
||||
* @platform ios
|
||||
*/
|
||||
tvParallaxProperties: PropTypes.object,
|
||||
|
||||
},
|
||||
|
||||
mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin],
|
||||
|
||||
@@ -26,7 +26,7 @@ const {
|
||||
AccessibilityTraits,
|
||||
} = require('ViewAccessibility');
|
||||
|
||||
type Event = Object;
|
||||
export type Event = Object;
|
||||
|
||||
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
@@ -37,7 +37,6 @@ const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
* TouchableWithoutFeedback supports only one child.
|
||||
* If you wish to have several child components, wrap them in a View.
|
||||
*/
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
const TouchableWithoutFeedback = createReactClass({
|
||||
displayName: 'TouchableWithoutFeedback',
|
||||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
@@ -90,6 +89,7 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
* reactivated! Move it back and forth several times while the scroll view
|
||||
* is disabled. Ensure you pass in a constant to reduce memory allocations.
|
||||
*/
|
||||
// $FlowFixMe: Expected a React PropType instead
|
||||
pressRetentionOffset: EdgeInsetsPropType,
|
||||
/**
|
||||
* This defines how far your touch can start away from the button. This is
|
||||
@@ -99,6 +99,7 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
* of sibling views always takes precedence if a touch hits two overlapping
|
||||
* views.
|
||||
*/
|
||||
// $FlowFixMe: Expected a React PropType instead
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
},
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
const Text = require('Text');
|
||||
|
||||
@@ -9,32 +9,21 @@
|
||||
* @providesModule View
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const NativeModules = require('NativeModules');
|
||||
const Platform = require('Platform');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('React');
|
||||
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
const NativeMethodsMixin = require("NativeMethodsMixin");
|
||||
const Platform = require("Platform");
|
||||
const PropTypes = require("prop-types");
|
||||
const React = require("React");
|
||||
const ReactNativeStyleAttributes = require("ReactNativeStyleAttributes");
|
||||
const ReactNativeViewAttributes = require("ReactNativeViewAttributes");
|
||||
const ViewPropTypes = require("ViewPropTypes");
|
||||
|
||||
const createReactClass = require('create-react-class');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
const createReactClass = require("create-react-class");
|
||||
const invariant = require("fbjs/lib/invariant");
|
||||
const requireNativeComponent = require("requireNativeComponent");
|
||||
|
||||
const {
|
||||
AccessibilityComponentTypes,
|
||||
AccessibilityTraits,
|
||||
} = require('ViewAccessibility');
|
||||
|
||||
const forceTouchAvailable = (NativeModules.PlatformConstants &&
|
||||
NativeModules.PlatformConstants.forceTouchAvailable) || false;
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type { ViewProps } from "ViewPropTypes";
|
||||
|
||||
export type Props = ViewProps;
|
||||
|
||||
@@ -86,7 +75,7 @@ export type Props = ViewProps;
|
||||
*/
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
const View = createReactClass({
|
||||
displayName: 'View',
|
||||
displayName: "View",
|
||||
// TODO: We should probably expose the mixins, viewConfig, and statics publicly. For example,
|
||||
// one of the props is of type AccessibilityComponentType. That is defined as a const[] above,
|
||||
// but it is not rendered by the docs, since `statics` below is not rendered. So its Possible
|
||||
@@ -96,143 +85,61 @@ const View = createReactClass({
|
||||
// `propTypes` should not be accessed directly on View since this wrapper only
|
||||
// exists for DEV mode. However it's important for them to be declared.
|
||||
// If the object passed to `createClass` specifies `propTypes`, Flow will
|
||||
// create a static type from it. This property will be over-written below with
|
||||
// a warn-on-use getter though.
|
||||
// TODO (bvaughn) Remove the warn-on-use comment after April 1.
|
||||
// create a static type from it.
|
||||
propTypes: ViewPropTypes,
|
||||
|
||||
// ReactElementValidator will (temporarily) use this private accessor when
|
||||
// detected to avoid triggering the warning message.
|
||||
// TODO (bvaughn) Remove this after April 1 ReactNative RC is tagged.
|
||||
statics: {
|
||||
__propTypesSecretDontUseThesePlease: ViewPropTypes
|
||||
},
|
||||
|
||||
/**
|
||||
* `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
|
||||
* make `this` look like an actual native component class.
|
||||
*/
|
||||
viewConfig: {
|
||||
uiViewClassName: 'RCTView',
|
||||
uiViewClassName: "RCTView",
|
||||
validAttributes: ReactNativeViewAttributes.RCTView
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
isInAParentText: PropTypes.bool,
|
||||
isInAParentText: PropTypes.bool
|
||||
},
|
||||
|
||||
render: function() {
|
||||
invariant(
|
||||
!(this.context.isInAParentText && Platform.OS === 'android'),
|
||||
'Nesting of <View> within <Text> is not supported on Android.');
|
||||
!(this.context.isInAParentText && Platform.OS === "android"),
|
||||
"Nesting of <View> within <Text> is not supported on Android."
|
||||
);
|
||||
|
||||
// WARNING: This method will not be used in production mode as in that mode we
|
||||
// replace wrapper component View with generated native wrapper RCTView. Avoid
|
||||
// adding functionality this component that you'd want to be available in both
|
||||
// dev and prod modes.
|
||||
return <RCTView {...this.props} />;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// Warn about unsupported use of View static properties as these will no longer
|
||||
// be supported with React fiber. This warning message will go away in the next
|
||||
// ReactNative release. Use defineProperty() rather than createClass() statics
|
||||
// because the mixin process auto-triggers the 1-time warning message.
|
||||
// TODO (bvaughn) Remove this after April 1 ReactNative RC is tagged.
|
||||
function mixinStatics (target) {
|
||||
let warnedAboutAccessibilityTraits = false;
|
||||
let warnedAboutAccessibilityComponentType = false;
|
||||
let warnedAboutForceTouchAvailable = false;
|
||||
let warnedAboutPropTypes = false;
|
||||
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/285
|
||||
Object.defineProperty(target, 'AccessibilityTraits', {
|
||||
get: function() {
|
||||
warning(
|
||||
warnedAboutAccessibilityTraits,
|
||||
'View.AccessibilityTraits has been deprecated and will be ' +
|
||||
'removed in a future version of ReactNative. Use ' +
|
||||
'ViewAccessibility.AccessibilityTraits instead.'
|
||||
);
|
||||
warnedAboutAccessibilityTraits = true;
|
||||
return AccessibilityTraits;
|
||||
}
|
||||
});
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/285
|
||||
Object.defineProperty(target, 'AccessibilityComponentType', {
|
||||
get: function() {
|
||||
warning(
|
||||
warnedAboutAccessibilityComponentType,
|
||||
'View.AccessibilityComponentType has been deprecated and will be ' +
|
||||
'removed in a future version of ReactNative. Use ' +
|
||||
'ViewAccessibility.AccessibilityComponentTypes instead.'
|
||||
);
|
||||
warnedAboutAccessibilityComponentType = true;
|
||||
return AccessibilityComponentTypes;
|
||||
}
|
||||
});
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/285
|
||||
Object.defineProperty(target, 'forceTouchAvailable', {
|
||||
get: function() {
|
||||
warning(
|
||||
warnedAboutForceTouchAvailable,
|
||||
'View.forceTouchAvailable has been deprecated and will be removed ' +
|
||||
'in a future version of ReactNative. Use ' +
|
||||
'NativeModules.PlatformConstants.forceTouchAvailable instead.'
|
||||
);
|
||||
warnedAboutForceTouchAvailable = true;
|
||||
return forceTouchAvailable;
|
||||
}
|
||||
});
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/285
|
||||
Object.defineProperty(target, 'propTypes', {
|
||||
get: function() {
|
||||
warning(
|
||||
warnedAboutPropTypes,
|
||||
'View.propTypes has been deprecated and will be removed in a future ' +
|
||||
'version of ReactNative. Use ViewPropTypes instead.'
|
||||
);
|
||||
warnedAboutPropTypes = true;
|
||||
return ViewPropTypes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const RCTView = requireNativeComponent('RCTView', View, {
|
||||
const RCTView = requireNativeComponent("RCTView", View, {
|
||||
nativeOnly: {
|
||||
nativeBackgroundAndroid: true,
|
||||
nativeForegroundAndroid: true,
|
||||
nativeForegroundAndroid: true
|
||||
}
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
const UIManager = require('UIManager');
|
||||
const viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {};
|
||||
const UIManager = require("UIManager");
|
||||
const viewConfig =
|
||||
(UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {};
|
||||
for (const prop in viewConfig.nativeProps) {
|
||||
const viewAny: any = View; // Appease flow
|
||||
if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) {
|
||||
throw new Error(
|
||||
'View is missing propType for native prop `' + prop + '`'
|
||||
"View is missing propType for native prop `" + prop + "`"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (bvaughn) Remove feature flags once all static View accessors are gone.
|
||||
// We temporarily wrap fiber native views with the create-class View above,
|
||||
// Because external code sometimes accesses static properties of this view.
|
||||
let ViewToExport = RCTView;
|
||||
if (
|
||||
__DEV__ ||
|
||||
ReactNativeFeatureFlags.useFiber
|
||||
) {
|
||||
mixinStatics(View);
|
||||
if (__DEV__) {
|
||||
ViewToExport = View;
|
||||
} else {
|
||||
// TODO (bvaughn) Remove this mixin once all static View accessors are gone.
|
||||
mixinStatics((RCTView : any));
|
||||
}
|
||||
|
||||
// TODO (bvaughn) Temporarily mask Flow warnings for View property accesses.
|
||||
// We're wrapping the string type (Fiber) for now to avoid any actual problems.
|
||||
module.exports = ((ViewToExport : any) : typeof View);
|
||||
// No one should depend on the DEV-mode createClass View wrapper.
|
||||
module.exports = ((ViewToExport: any): typeof RCTView);
|
||||
|
||||
@@ -36,6 +36,19 @@ import type {TVViewProps} from 'TVViewPropTypes';
|
||||
|
||||
const stylePropType = StyleSheetPropType(ViewStylePropTypes);
|
||||
|
||||
export type ViewLayout = {
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export type ViewLayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: ViewLayout,
|
||||
}
|
||||
}
|
||||
|
||||
// There's no easy way to create a different type if(Platform.isTVOS):
|
||||
// so we must include TVViewProps
|
||||
export type ViewProps = {
|
||||
@@ -50,6 +63,7 @@ export type ViewProps = {
|
||||
onMagicTap?: Function,
|
||||
testID?: string,
|
||||
nativeID?: string,
|
||||
onLayout?: (event: ViewLayoutEvent) => void,
|
||||
onResponderGrant?: Function,
|
||||
onResponderMove?: Function,
|
||||
onResponderReject?: Function,
|
||||
@@ -218,8 +232,6 @@ module.exports = {
|
||||
* Used to locate this view from native classes.
|
||||
*
|
||||
* > This disables the 'layout-only view removal' optimization for this view!
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nativeID: PropTypes.string,
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class ViewPagerAndroid extends React.Component {
|
||||
onPageScrollStateChanged?: Function,
|
||||
onPageSelected?: Function,
|
||||
pageMargin?: number,
|
||||
peekEnabled?: boolean,
|
||||
keyboardDismissMode?: 'none' | 'on-drag',
|
||||
scrollEnabled?: boolean,
|
||||
};
|
||||
@@ -137,6 +138,12 @@ class ViewPagerAndroid extends React.Component {
|
||||
* The default value is true.
|
||||
*/
|
||||
scrollEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether enable showing peekFraction or not. If this is true, the preview of
|
||||
* last and next page will show in current screen. Defaults to false.
|
||||
*/
|
||||
peekEnabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -367,6 +367,10 @@ class WebView extends React.Component {
|
||||
]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
scalesPageToFit: true,
|
||||
};
|
||||
|
||||
state = {
|
||||
viewState: WebViewState.IDLE,
|
||||
lastErrorEvent: (null: ?ErrorEvent),
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
var parseErrorStack = require('parseErrorStack');
|
||||
|
||||
function getFakeError() {
|
||||
|
||||
@@ -18,20 +18,22 @@ export type StackFrame = {
|
||||
methodName: string,
|
||||
};
|
||||
|
||||
var stacktraceParser = require('stacktrace-parser');
|
||||
export type ExtendedError = Error & {
|
||||
framesToPop?: number,
|
||||
};
|
||||
|
||||
function parseErrorStack(e: Error): Array<StackFrame> {
|
||||
function parseErrorStack(e: ExtendedError): Array<StackFrame> {
|
||||
if (!e || !e.stack) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var stack = Array.isArray(e.stack) ? e.stack : stacktraceParser.parse(e.stack);
|
||||
const stacktraceParser = require('stacktrace-parser');
|
||||
const stack = Array.isArray(e.stack) ? e.stack : stacktraceParser.parse(e.stack);
|
||||
|
||||
var framesToPop = typeof e.framesToPop === 'number' ? e.framesToPop : 0;
|
||||
let framesToPop = typeof e.framesToPop === 'number' ? e.framesToPop : 0;
|
||||
while (framesToPop--) {
|
||||
stack.shift();
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type {ExtendedError} from 'parseErrorStack';
|
||||
|
||||
/**
|
||||
* Handles the developer-visible aspect of errors and exceptions
|
||||
*/
|
||||
let exceptionID = 0;
|
||||
function reportException(e: Error, isFatal: bool) {
|
||||
function reportException(e: ExtendedError, isFatal: bool) {
|
||||
const {ExceptionsManager} = require('NativeModules');
|
||||
if (ExceptionsManager) {
|
||||
const parseErrorStack = require('parseErrorStack');
|
||||
@@ -84,7 +86,7 @@ function reactConsoleErrorHandler() {
|
||||
// (Note: Logic duplicated in polyfills/console.js.)
|
||||
return;
|
||||
}
|
||||
const error : any = new Error('console.error: ' + str);
|
||||
const error : ExtendedError = new Error('console.error: ' + str);
|
||||
error.framesToPop = 1;
|
||||
reportException(error, /* isFatal */ false);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
/* eslint-disable strict */
|
||||
/* globals window: true */
|
||||
|
||||
|
||||
/**
|
||||
* Sets up global variables typical in most JavaScript environments.
|
||||
*
|
||||
@@ -96,28 +95,21 @@ if (!global.process.env.NODE_ENV) {
|
||||
global.process.env.NODE_ENV = __DEV__ ? 'development' : 'production';
|
||||
}
|
||||
|
||||
// Set up profile
|
||||
const Systrace = require('Systrace');
|
||||
Systrace.setEnabled(global.__RCTProfileIsProfiling || false);
|
||||
// Setup the Systrace profiling hooks if necessary
|
||||
if (global.__RCTProfileIsProfiling) {
|
||||
const Systrace = require('Systrace');
|
||||
Systrace.setEnabled(true);
|
||||
}
|
||||
|
||||
if (__DEV__ && global.performance === undefined) {
|
||||
const Systrace = require('Systrace');
|
||||
global.performance = Systrace.getUserTimingPolyfill();
|
||||
}
|
||||
|
||||
// Set up console
|
||||
const ExceptionsManager = require('ExceptionsManager');
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
|
||||
// TODO: Move these around to solve the cycle in a cleaner way
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
BatchedBridge.registerLazyCallableModule('Systrace', () => require('Systrace'));
|
||||
BatchedBridge.registerLazyCallableModule('JSTimersExecution', () => require('JSTimersExecution'));
|
||||
BatchedBridge.registerLazyCallableModule('HeapCapture', () => require('HeapCapture'));
|
||||
BatchedBridge.registerLazyCallableModule('SamplingProfiler', () => require('SamplingProfiler'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTLog', () => require('RCTLog'));
|
||||
|
||||
if (__DEV__) {
|
||||
if (!global.__RCTProfileIsProfiling) {
|
||||
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
|
||||
}
|
||||
}
|
||||
|
||||
// Set up error handler
|
||||
if (!global.__fbDisableExceptionsManager) {
|
||||
const handleError = (e, isFatal) => {
|
||||
@@ -135,29 +127,11 @@ if (!global.__fbDisableExceptionsManager) {
|
||||
ErrorUtils.setGlobalHandler(handleError);
|
||||
}
|
||||
|
||||
// Set up timers
|
||||
const defineLazyTimer = name => {
|
||||
defineProperty(global, name, () => require('JSTimers')[name]);
|
||||
};
|
||||
defineLazyTimer('setTimeout');
|
||||
defineLazyTimer('setInterval');
|
||||
defineLazyTimer('setImmediate');
|
||||
defineLazyTimer('clearTimeout');
|
||||
defineLazyTimer('clearInterval');
|
||||
defineLazyTimer('clearImmediate');
|
||||
defineLazyTimer('requestAnimationFrame');
|
||||
defineLazyTimer('cancelAnimationFrame');
|
||||
defineLazyTimer('requestIdleCallback');
|
||||
defineLazyTimer('cancelIdleCallback');
|
||||
|
||||
// Set up alert
|
||||
if (!global.alert) {
|
||||
global.alert = function(text) {
|
||||
// Require Alert on demand. Requiring it too early can lead to issues
|
||||
// with things like Platform not being fully initialized.
|
||||
require('Alert').alert('Alert', '' + text);
|
||||
};
|
||||
}
|
||||
// Set up collections
|
||||
// We can't make these lazy because `Map` checks for `global.Map` (which wouldc
|
||||
// not exist if it were lazily defined).
|
||||
defineProperty(global, 'Map', () => require('Map'), true);
|
||||
defineProperty(global, 'Set', () => require('Set'), true);
|
||||
|
||||
// Set up Promise
|
||||
// The native Promise implementation throws the following error:
|
||||
@@ -173,6 +147,21 @@ defineProperty(global, 'regeneratorRuntime', () => {
|
||||
return global.regeneratorRuntime;
|
||||
});
|
||||
|
||||
// Set up timers
|
||||
const defineLazyTimer = name => {
|
||||
defineProperty(global, name, () => require('JSTimers')[name]);
|
||||
};
|
||||
defineLazyTimer('setTimeout');
|
||||
defineLazyTimer('setInterval');
|
||||
defineLazyTimer('setImmediate');
|
||||
defineLazyTimer('clearTimeout');
|
||||
defineLazyTimer('clearInterval');
|
||||
defineLazyTimer('clearImmediate');
|
||||
defineLazyTimer('requestAnimationFrame');
|
||||
defineLazyTimer('cancelAnimationFrame');
|
||||
defineLazyTimer('requestIdleCallback');
|
||||
defineLazyTimer('cancelIdleCallback');
|
||||
|
||||
// Set up XHR
|
||||
// The native XMLHttpRequest in Chrome dev tools is CORS aware and won't
|
||||
// let you fetch anything from the internet
|
||||
@@ -185,6 +174,15 @@ defineProperty(global, 'Request', () => require('fetch').Request);
|
||||
defineProperty(global, 'Response', () => require('fetch').Response);
|
||||
defineProperty(global, 'WebSocket', () => require('WebSocket'));
|
||||
|
||||
// Set up alert
|
||||
if (!global.alert) {
|
||||
global.alert = function(text) {
|
||||
// Require Alert on demand. Requiring it too early can lead to issues
|
||||
// with things like Platform not being fully initialized.
|
||||
require('Alert').alert('Alert', '' + text);
|
||||
};
|
||||
}
|
||||
|
||||
// Set up Geolocation
|
||||
let navigator = global.navigator;
|
||||
if (navigator === undefined) {
|
||||
@@ -195,35 +193,31 @@ if (navigator === undefined) {
|
||||
defineProperty(navigator, 'product', () => 'ReactNative', true);
|
||||
defineProperty(navigator, 'geolocation', () => require('Geolocation'));
|
||||
|
||||
// Set up collections
|
||||
// We can't make these lazy because `Map` checks for `global.Map` (which wouldc
|
||||
// not exist if it were lazily defined).
|
||||
defineProperty(global, 'Map', () => require('Map'), true);
|
||||
defineProperty(global, 'Set', () => require('Set'), true);
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
BatchedBridge.registerLazyCallableModule('Systrace', () => require('Systrace'));
|
||||
BatchedBridge.registerLazyCallableModule('JSTimers', () => require('JSTimers'));
|
||||
BatchedBridge.registerLazyCallableModule('HeapCapture', () => require('HeapCapture'));
|
||||
BatchedBridge.registerLazyCallableModule('SamplingProfiler', () => require('SamplingProfiler'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTLog', () => require('RCTLog'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTDeviceEventEmitter', () => require('RCTDeviceEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTNativeAppEventEmitter', () => require('RCTNativeAppEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('PerformanceLogger', () => require('PerformanceLogger'));
|
||||
|
||||
// Set up devtools
|
||||
if (__DEV__) {
|
||||
if (!global.__RCTProfileIsProfiling) {
|
||||
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
|
||||
|
||||
// not when debugging in chrome
|
||||
// TODO(t12832058) This check is broken
|
||||
if (!window.document) {
|
||||
require('setupDevtools');
|
||||
}
|
||||
|
||||
require('RCTDebugComponentOwnership');
|
||||
|
||||
// In order to use Cmd+P to record/dump perf data, we need to make sure
|
||||
// this module is available in the bundle
|
||||
require('RCTRenderingPerf');
|
||||
|
||||
// Set up inspector
|
||||
const JSInspector = require('JSInspector');
|
||||
JSInspector.registerAgent(require('NetworkAgent'));
|
||||
}
|
||||
}
|
||||
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
BatchedBridge.registerLazyCallableModule('RCTDeviceEventEmitter', () => require('RCTDeviceEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('RCTNativeAppEventEmitter', () => require('RCTNativeAppEventEmitter'));
|
||||
BatchedBridge.registerLazyCallableModule('PerformanceLogger', () => require('PerformanceLogger'));
|
||||
|
||||
@@ -7,64 +7,39 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule JSTimers
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Note that the module JSTimers is split into two in order to solve a cycle
|
||||
// in dependencies. NativeModules > BatchedBridge > MessageQueue > JSTimersExecution
|
||||
const JSTimersExecution = require('JSTimersExecution');
|
||||
const Platform = require('Platform');
|
||||
const Systrace = require('Systrace');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
const {Timing} = require('NativeModules');
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
|
||||
import type {JSTimerType} from 'JSTimersExecution';
|
||||
import type {ExtendedError} from 'parseErrorStack';
|
||||
|
||||
// Returns a free index if one is available, and the next consecutive index otherwise.
|
||||
function _getFreeIndex(): number {
|
||||
let freeIndex = JSTimersExecution.timerIDs.indexOf(null);
|
||||
if (freeIndex === -1) {
|
||||
freeIndex = JSTimersExecution.timerIDs.length;
|
||||
}
|
||||
return freeIndex;
|
||||
}
|
||||
/**
|
||||
* JS implementation of timer functions. Must be completely driven by an
|
||||
* external clock signal, all that's stored here is timerID, timer type, and
|
||||
* callback.
|
||||
*/
|
||||
|
||||
function _allocateCallback(func: Function, type: JSTimerType): number {
|
||||
const id = JSTimersExecution.GUID++;
|
||||
const freeIndex = _getFreeIndex();
|
||||
JSTimersExecution.timerIDs[freeIndex] = id;
|
||||
JSTimersExecution.callbacks[freeIndex] = func;
|
||||
JSTimersExecution.types[freeIndex] = type;
|
||||
if (__DEV__) {
|
||||
const parseErrorStack = require('parseErrorStack');
|
||||
const e = (new Error() : any);
|
||||
e.framesToPop = 1;
|
||||
const stack = parseErrorStack(e);
|
||||
if (stack) {
|
||||
JSTimersExecution.identifiers[freeIndex] = stack.shift();
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
export type JSTimerType =
|
||||
| 'setTimeout'
|
||||
| 'setInterval'
|
||||
| 'requestAnimationFrame'
|
||||
| 'setImmediate'
|
||||
| 'requestIdleCallback';
|
||||
|
||||
function _freeCallback(timerID: number) {
|
||||
// JSTimersExecution.timerIDs contains nulls after timers have been removed;
|
||||
// ignore nulls upfront so indexOf doesn't find them
|
||||
if (timerID == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = JSTimersExecution.timerIDs.indexOf(timerID);
|
||||
// See corresponding comment in `callTimers` for reasoning behind this
|
||||
if (index !== -1) {
|
||||
JSTimersExecution._clearIndex(index);
|
||||
const type = JSTimersExecution.types[index];
|
||||
if (type !== 'setImmediate' && type !== 'requestIdleCallback') {
|
||||
Timing.deleteTimer(timerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
// These timing contants should be kept in sync with the ones in native ios and
|
||||
// android `RCTTiming` module.
|
||||
const FRAME_DURATION = 1000 / 60;
|
||||
const IDLE_CALLBACK_FRAME_DEADLINE = 1;
|
||||
|
||||
const MAX_TIMER_DURATION_MS = 60 * 1000;
|
||||
const IS_ANDROID = Platform.OS === 'android';
|
||||
@@ -74,6 +49,152 @@ const ANDROID_LONG_TIMER_MESSAGE =
|
||||
'module awake, and timers can only be called when the app is in the foreground. ' +
|
||||
'See https://github.com/facebook/react-native/issues/12981 for more info.';
|
||||
|
||||
// Parallel arrays
|
||||
const callbacks: Array<?Function> = [];
|
||||
const types: Array<?JSTimerType> = [];
|
||||
const timerIDs: Array<?number> = [];
|
||||
let immediates: Array<number> = [];
|
||||
let requestIdleCallbacks: Array<number> = [];
|
||||
const requestIdleCallbackTimeouts: {[number]: number} = {};
|
||||
const identifiers: Array<null | {methodName: string}> = [];
|
||||
|
||||
let GUID = 1;
|
||||
let errors: ?Array<Error> = null;
|
||||
|
||||
let hasEmittedTimeDriftWarning = false;
|
||||
|
||||
// Returns a free index if one is available, and the next consecutive index otherwise.
|
||||
function _getFreeIndex(): number {
|
||||
let freeIndex = timerIDs.indexOf(null);
|
||||
if (freeIndex === -1) {
|
||||
freeIndex = timerIDs.length;
|
||||
}
|
||||
return freeIndex;
|
||||
}
|
||||
|
||||
function _allocateCallback(func: Function, type: JSTimerType): number {
|
||||
const id = GUID++;
|
||||
const freeIndex = _getFreeIndex();
|
||||
timerIDs[freeIndex] = id;
|
||||
callbacks[freeIndex] = func;
|
||||
types[freeIndex] = type;
|
||||
if (__DEV__) {
|
||||
const parseErrorStack = require('parseErrorStack');
|
||||
const error: ExtendedError = new Error();
|
||||
error.framesToPop = 1;
|
||||
const stack = parseErrorStack(error);
|
||||
if (stack) {
|
||||
identifiers[freeIndex] = stack.shift();
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the callback associated with the ID. Also unregister that callback
|
||||
* if it was a one time timer (setTimeout), and not unregister it if it was
|
||||
* recurring (setInterval).
|
||||
*/
|
||||
function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
|
||||
warning(
|
||||
timerID <= GUID,
|
||||
'Tried to call timer with ID %s but no such timer exists.',
|
||||
timerID,
|
||||
);
|
||||
|
||||
// timerIndex of -1 means that no timer with that ID exists. There are
|
||||
// two situations when this happens, when a garbage timer ID was given
|
||||
// and when a previously existing timer was deleted before this callback
|
||||
// fired. In both cases we want to ignore the timer id, but in the former
|
||||
// case we warn as well.
|
||||
const timerIndex = timerIDs.indexOf(timerID);
|
||||
if (timerIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = types[timerIndex];
|
||||
const callback = callbacks[timerIndex];
|
||||
if (!callback || !type) {
|
||||
console.error('No callback found for timerID ' + timerID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const identifier = identifiers[timerIndex] || {};
|
||||
Systrace.beginEvent('Systrace.callTimer: ' + identifier.methodName);
|
||||
}
|
||||
|
||||
// Clear the metadata
|
||||
if (
|
||||
type === 'setTimeout' ||
|
||||
type === 'setImmediate' ||
|
||||
type === 'requestAnimationFrame' ||
|
||||
type === 'requestIdleCallback'
|
||||
) {
|
||||
_clearIndex(timerIndex);
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
type === 'setTimeout' ||
|
||||
type === 'setInterval' ||
|
||||
type === 'setImmediate'
|
||||
) {
|
||||
callback();
|
||||
} else if (type === 'requestAnimationFrame') {
|
||||
callback(performanceNow());
|
||||
} else if (type === 'requestIdleCallback') {
|
||||
callback({
|
||||
timeRemaining: function() {
|
||||
// TODO: Optimisation: allow running for longer than one frame if
|
||||
// there are no pending JS calls on the bridge from native. This
|
||||
// would require a way to check the bridge queue synchronously.
|
||||
return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime));
|
||||
},
|
||||
didTimeout: !!didTimeout,
|
||||
});
|
||||
} else {
|
||||
console.error('Tried to call a callback with invalid type: ' + type);
|
||||
}
|
||||
} catch (e) {
|
||||
// Don't rethrow so that we can run all timers.
|
||||
if (!errors) {
|
||||
errors = [e];
|
||||
} else {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
}
|
||||
|
||||
function _clearIndex(i: number) {
|
||||
timerIDs[i] = null;
|
||||
callbacks[i] = null;
|
||||
types[i] = null;
|
||||
identifiers[i] = null;
|
||||
}
|
||||
|
||||
function _freeCallback(timerID: number) {
|
||||
// timerIDs contains nulls after timers have been removed;
|
||||
// ignore nulls upfront so indexOf doesn't find them
|
||||
if (timerID == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = timerIDs.indexOf(timerID);
|
||||
// See corresponding comment in `callTimers` for reasoning behind this
|
||||
if (index !== -1) {
|
||||
_clearIndex(index);
|
||||
const type = types[index];
|
||||
if (type !== 'setImmediate' && type !== 'requestIdleCallback') {
|
||||
Timing.deleteTimer(timerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JS implementation of timer functions. Must be completely driven by an
|
||||
* external clock signal, all that's stored here is timerID, timer type, and
|
||||
@@ -84,13 +205,24 @@ const JSTimers = {
|
||||
* @param {function} func Callback to be invoked after `duration` ms.
|
||||
* @param {number} duration Number of milliseconds.
|
||||
*/
|
||||
setTimeout: function(func: Function, duration: number, ...args?: any): number {
|
||||
setTimeout: function(
|
||||
func: Function,
|
||||
duration: number,
|
||||
...args?: any
|
||||
): number {
|
||||
if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) {
|
||||
console.warn(
|
||||
ANDROID_LONG_TIMER_MESSAGE + '\n' + '(Saw setTimeout with duration ' +
|
||||
duration + 'ms)');
|
||||
ANDROID_LONG_TIMER_MESSAGE +
|
||||
'\n' +
|
||||
'(Saw setTimeout with duration ' +
|
||||
duration +
|
||||
'ms)',
|
||||
);
|
||||
}
|
||||
const id = _allocateCallback(() => func.apply(undefined, args), 'setTimeout');
|
||||
const id = _allocateCallback(
|
||||
() => func.apply(undefined, args),
|
||||
'setTimeout',
|
||||
);
|
||||
Timing.createTimer(id, duration || 0, Date.now(), /* recurring */ false);
|
||||
return id;
|
||||
},
|
||||
@@ -99,13 +231,24 @@ const JSTimers = {
|
||||
* @param {function} func Callback to be invoked every `duration` ms.
|
||||
* @param {number} duration Number of milliseconds.
|
||||
*/
|
||||
setInterval: function(func: Function, duration: number, ...args?: any): number {
|
||||
setInterval: function(
|
||||
func: Function,
|
||||
duration: number,
|
||||
...args?: any
|
||||
): number {
|
||||
if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) {
|
||||
console.warn(
|
||||
ANDROID_LONG_TIMER_MESSAGE + '\n' + '(Saw setInterval with duration ' +
|
||||
duration + 'ms)');
|
||||
ANDROID_LONG_TIMER_MESSAGE +
|
||||
'\n' +
|
||||
'(Saw setInterval with duration ' +
|
||||
duration +
|
||||
'ms)',
|
||||
);
|
||||
}
|
||||
const id = _allocateCallback(() => func.apply(undefined, args), 'setInterval');
|
||||
const id = _allocateCallback(
|
||||
() => func.apply(undefined, args),
|
||||
'setInterval',
|
||||
);
|
||||
Timing.createTimer(id, duration || 0, Date.now(), /* recurring */ true);
|
||||
return id;
|
||||
},
|
||||
@@ -115,15 +258,18 @@ const JSTimers = {
|
||||
* current JavaScript execution loop.
|
||||
*/
|
||||
setImmediate: function(func: Function, ...args?: any) {
|
||||
const id = _allocateCallback(() => func.apply(undefined, args), 'setImmediate');
|
||||
JSTimersExecution.immediates.push(id);
|
||||
const id = _allocateCallback(
|
||||
() => func.apply(undefined, args),
|
||||
'setImmediate',
|
||||
);
|
||||
immediates.push(id);
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {function} func Callback to be invoked every frame.
|
||||
*/
|
||||
requestAnimationFrame: function(func : Function) {
|
||||
requestAnimationFrame: function(func: Function) {
|
||||
const id = _allocateCallback(func, 'requestAnimationFrame');
|
||||
Timing.createTimer(id, 1, Date.now(), /* recurring */ false);
|
||||
return id;
|
||||
@@ -134,58 +280,58 @@ const JSTimers = {
|
||||
* with time remaining in frame.
|
||||
* @param {?object} options
|
||||
*/
|
||||
requestIdleCallback: function(func : Function, options : ?Object) {
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
requestIdleCallback: function(func: Function, options: ?Object) {
|
||||
if (requestIdleCallbacks.length === 0) {
|
||||
Timing.setSendIdleEvents(true);
|
||||
}
|
||||
|
||||
const timeout = options && options.timeout;
|
||||
const id = _allocateCallback(
|
||||
timeout != null ?
|
||||
deadline => {
|
||||
const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts.get(id);
|
||||
if (timeoutId) {
|
||||
JSTimers.clearTimeout(timeoutId);
|
||||
JSTimersExecution.requestIdleCallbackTimeouts.delete(id);
|
||||
timeout != null
|
||||
? deadline => {
|
||||
const timeoutId = requestIdleCallbackTimeouts[id];
|
||||
if (timeoutId) {
|
||||
JSTimers.clearTimeout(timeoutId);
|
||||
requestIdleCallbackTimeouts[id];
|
||||
}
|
||||
return func(deadline);
|
||||
}
|
||||
return func(deadline);
|
||||
} :
|
||||
func,
|
||||
'requestIdleCallback'
|
||||
: func,
|
||||
'requestIdleCallback',
|
||||
);
|
||||
JSTimersExecution.requestIdleCallbacks.push(id);
|
||||
requestIdleCallbacks.push(id);
|
||||
|
||||
if (timeout != null) {
|
||||
const timeoutId = JSTimers.setTimeout(() => {
|
||||
const index = JSTimersExecution.requestIdleCallbacks.indexOf(id);
|
||||
const index = requestIdleCallbacks.indexOf(id);
|
||||
if (index > -1) {
|
||||
JSTimersExecution.requestIdleCallbacks.splice(index, 1);
|
||||
JSTimersExecution.callTimer(id, performanceNow(), true);
|
||||
requestIdleCallbacks.splice(index, 1);
|
||||
_callTimer(id, performanceNow(), true);
|
||||
}
|
||||
JSTimersExecution.requestIdleCallbackTimeouts.delete(id);
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
delete requestIdleCallbackTimeouts[id];
|
||||
if (requestIdleCallbacks.length === 0) {
|
||||
Timing.setSendIdleEvents(false);
|
||||
}
|
||||
}, timeout);
|
||||
JSTimersExecution.requestIdleCallbackTimeouts.set(id, timeoutId);
|
||||
requestIdleCallbackTimeouts[id] = timeoutId;
|
||||
}
|
||||
return id;
|
||||
},
|
||||
|
||||
cancelIdleCallback: function(timerID: number) {
|
||||
_freeCallback(timerID);
|
||||
const index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID);
|
||||
const index = requestIdleCallbacks.indexOf(timerID);
|
||||
if (index !== -1) {
|
||||
JSTimersExecution.requestIdleCallbacks.splice(index, 1);
|
||||
requestIdleCallbacks.splice(index, 1);
|
||||
}
|
||||
|
||||
const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts.get(timerID);
|
||||
const timeoutId = requestIdleCallbackTimeouts[timerID];
|
||||
if (timeoutId) {
|
||||
JSTimers.clearTimeout(timeoutId);
|
||||
JSTimersExecution.requestIdleCallbackTimeouts.delete(timerID);
|
||||
delete requestIdleCallbackTimeouts[timerID];
|
||||
}
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
if (requestIdleCallbacks.length === 0) {
|
||||
Timing.setSendIdleEvents(false);
|
||||
}
|
||||
},
|
||||
@@ -200,15 +346,136 @@ const JSTimers = {
|
||||
|
||||
clearImmediate: function(timerID: number) {
|
||||
_freeCallback(timerID);
|
||||
const index = JSTimersExecution.immediates.indexOf(timerID);
|
||||
const index = immediates.indexOf(timerID);
|
||||
if (index !== -1) {
|
||||
JSTimersExecution.immediates.splice(index, 1);
|
||||
immediates.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
cancelAnimationFrame: function(timerID: number) {
|
||||
_freeCallback(timerID);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called from the native side. We are passed an array of timerIDs,
|
||||
* and
|
||||
*/
|
||||
callTimers: function(timersToCall: Array<number>) {
|
||||
invariant(
|
||||
timersToCall.length !== 0,
|
||||
'Cannot call `callTimers` with an empty list of IDs.',
|
||||
);
|
||||
|
||||
// $FlowFixMe: optionals do not allow assignment from null
|
||||
errors = null;
|
||||
for (let i = 0; i < timersToCall.length; i++) {
|
||||
_callTimer(timersToCall[i], 0);
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
const errorCount = errors.length;
|
||||
if (errorCount > 1) {
|
||||
// Throw all the other errors in a setTimeout, which will throw each
|
||||
// error one at a time
|
||||
for (let ii = 1; ii < errorCount; ii++) {
|
||||
JSTimers.setTimeout(
|
||||
(error => {
|
||||
throw error;
|
||||
}).bind(null, errors[ii]),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw errors[0];
|
||||
}
|
||||
},
|
||||
|
||||
callIdleCallbacks: function(frameTime: number) {
|
||||
if (
|
||||
FRAME_DURATION - (performanceNow() - frameTime) <
|
||||
IDLE_CALLBACK_FRAME_DEADLINE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// $FlowFixMe: optionals do not allow assignment from null
|
||||
errors = null;
|
||||
if (requestIdleCallbacks.length > 0) {
|
||||
const passIdleCallbacks = requestIdleCallbacks.slice();
|
||||
requestIdleCallbacks = [];
|
||||
|
||||
for (let i = 0; i < passIdleCallbacks.length; ++i) {
|
||||
_callTimer(passIdleCallbacks[i], frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (requestIdleCallbacks.length === 0) {
|
||||
Timing.setSendIdleEvents(false);
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
errors.forEach(error =>
|
||||
JSTimers.setTimeout(() => {
|
||||
throw error;
|
||||
}, 0),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a single pass over the enqueued immediates. Returns whether
|
||||
* more immediates are queued up (can be used as a condition a while loop).
|
||||
*/
|
||||
callImmediatesPass() {
|
||||
if (__DEV__) {
|
||||
Systrace.beginEvent('callImmediatesPass()');
|
||||
}
|
||||
|
||||
// The main reason to extract a single pass is so that we can track
|
||||
// in the system trace
|
||||
if (immediates.length > 0) {
|
||||
const passImmediates = immediates.slice();
|
||||
immediates = [];
|
||||
|
||||
// Use for loop rather than forEach as per @vjeux's advice
|
||||
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
|
||||
for (let i = 0; i < passImmediates.length; ++i) {
|
||||
_callTimer(passImmediates[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
return immediates.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called after we execute any command we receive from native but
|
||||
* before we hand control back to native.
|
||||
*/
|
||||
callImmediates() {
|
||||
errors = null;
|
||||
while (JSTimers.callImmediatesPass()) {}
|
||||
if (errors) {
|
||||
errors.forEach(error =>
|
||||
JSTimers.setTimeout(() => {
|
||||
throw error;
|
||||
}, 0),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from native (in development) when environment times are out-of-sync.
|
||||
*/
|
||||
emitTimeDriftWarning(warningMessage: string) {
|
||||
if (hasEmittedTimeDriftWarning) {
|
||||
return;
|
||||
}
|
||||
hasEmittedTimeDriftWarning = true;
|
||||
console.warn(warningMessage);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = JSTimers;
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule JSTimersExecution
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Systrace = require('Systrace');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
// These timing contants should be kept in sync with the ones in native ios and
|
||||
// android `RCTTiming` module.
|
||||
const FRAME_DURATION = 1000 / 60;
|
||||
const IDLE_CALLBACK_FRAME_DEADLINE = 1;
|
||||
|
||||
let hasEmittedTimeDriftWarning = false;
|
||||
|
||||
export type JSTimerType =
|
||||
'setTimeout' |
|
||||
'setInterval' |
|
||||
'requestAnimationFrame' |
|
||||
'setImmediate' |
|
||||
'requestIdleCallback';
|
||||
|
||||
/**
|
||||
* JS implementation of timer functions. Must be completely driven by an
|
||||
* external clock signal, all that's stored here is timerID, timer type, and
|
||||
* callback.
|
||||
*/
|
||||
const JSTimersExecution = {
|
||||
GUID: 1,
|
||||
|
||||
// Parallel arrays
|
||||
callbacks: ([] : Array<?Function>),
|
||||
types: ([] : Array<?JSTimerType>),
|
||||
timerIDs: ([] : Array<?number>),
|
||||
immediates: [],
|
||||
requestIdleCallbacks: [],
|
||||
requestIdleCallbackTimeouts: (new Map() : Map<number, number>),
|
||||
identifiers: ([] : Array<null | {methodName: string}>),
|
||||
|
||||
errors: (null : ?Array<Error>),
|
||||
|
||||
/**
|
||||
* Calls the callback associated with the ID. Also unregister that callback
|
||||
* if it was a one time timer (setTimeout), and not unregister it if it was
|
||||
* recurring (setInterval).
|
||||
*/
|
||||
callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
|
||||
warning(
|
||||
timerID <= JSTimersExecution.GUID,
|
||||
'Tried to call timer with ID %s but no such timer exists.',
|
||||
timerID
|
||||
);
|
||||
|
||||
// timerIndex of -1 means that no timer with that ID exists. There are
|
||||
// two situations when this happens, when a garbage timer ID was given
|
||||
// and when a previously existing timer was deleted before this callback
|
||||
// fired. In both cases we want to ignore the timer id, but in the former
|
||||
// case we warn as well.
|
||||
const timerIndex = JSTimersExecution.timerIDs.indexOf(timerID);
|
||||
if (timerIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = JSTimersExecution.types[timerIndex];
|
||||
const callback = JSTimersExecution.callbacks[timerIndex];
|
||||
if (!callback || !type) {
|
||||
console.error('No callback found for timerID ' + timerID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const identifier = JSTimersExecution.identifiers[timerIndex] || {};
|
||||
Systrace.beginEvent('Systrace.callTimer: ' + identifier.methodName);
|
||||
}
|
||||
|
||||
// Clear the metadata
|
||||
if (type === 'setTimeout' || type === 'setImmediate' ||
|
||||
type === 'requestAnimationFrame' || type === 'requestIdleCallback') {
|
||||
JSTimersExecution._clearIndex(timerIndex);
|
||||
}
|
||||
|
||||
try {
|
||||
if (type === 'setTimeout' || type === 'setInterval' ||
|
||||
type === 'setImmediate') {
|
||||
callback();
|
||||
} else if (type === 'requestAnimationFrame') {
|
||||
callback(performanceNow());
|
||||
} else if (type === 'requestIdleCallback') {
|
||||
callback({
|
||||
timeRemaining: function() {
|
||||
// TODO: Optimisation: allow running for longer than one frame if
|
||||
// there are no pending JS calls on the bridge from native. This
|
||||
// would require a way to check the bridge queue synchronously.
|
||||
return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime));
|
||||
},
|
||||
didTimeout: !!didTimeout,
|
||||
});
|
||||
} else {
|
||||
console.error('Tried to call a callback with invalid type: ' + type);
|
||||
}
|
||||
} catch (e) {
|
||||
// Don't rethrow so that we can run all timers.
|
||||
if (!JSTimersExecution.errors) {
|
||||
JSTimersExecution.errors = [e];
|
||||
} else {
|
||||
JSTimersExecution.errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called from the native side. We are passed an array of timerIDs,
|
||||
* and
|
||||
*/
|
||||
callTimers(timerIDs: [number]) {
|
||||
invariant(
|
||||
timerIDs.length !== 0,
|
||||
'Cannot call `callTimers` with an empty list of IDs.'
|
||||
);
|
||||
|
||||
JSTimersExecution.errors = null;
|
||||
for (let i = 0; i < timerIDs.length; i++) {
|
||||
JSTimersExecution.callTimer(timerIDs[i], 0);
|
||||
}
|
||||
|
||||
const errors = JSTimersExecution.errors;
|
||||
if (errors) {
|
||||
const errorCount = errors.length;
|
||||
if (errorCount > 1) {
|
||||
// Throw all the other errors in a setTimeout, which will throw each
|
||||
// error one at a time
|
||||
for (let ii = 1; ii < errorCount; ii++) {
|
||||
require('JSTimers').setTimeout(
|
||||
((error) => { throw error; }).bind(null, errors[ii]),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
throw errors[0];
|
||||
}
|
||||
},
|
||||
|
||||
callIdleCallbacks: function(frameTime: number) {
|
||||
if (FRAME_DURATION - (performanceNow() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSTimersExecution.errors = null;
|
||||
if (JSTimersExecution.requestIdleCallbacks.length > 0) {
|
||||
const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice();
|
||||
JSTimersExecution.requestIdleCallbacks = [];
|
||||
|
||||
for (let i = 0; i < passIdleCallbacks.length; ++i) {
|
||||
JSTimersExecution.callTimer(passIdleCallbacks[i], frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
const { Timing } = require('NativeModules');
|
||||
Timing.setSendIdleEvents(false);
|
||||
}
|
||||
|
||||
if (JSTimersExecution.errors) {
|
||||
JSTimersExecution.errors.forEach((error) =>
|
||||
require('JSTimers').setTimeout(() => { throw error; }, 0)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a single pass over the enqueued immediates. Returns whether
|
||||
* more immediates are queued up (can be used as a condition a while loop).
|
||||
*/
|
||||
callImmediatesPass() {
|
||||
if (__DEV__) {
|
||||
Systrace.beginEvent('JSTimersExecution.callImmediatesPass()');
|
||||
}
|
||||
|
||||
// The main reason to extract a single pass is so that we can track
|
||||
// in the system trace
|
||||
if (JSTimersExecution.immediates.length > 0) {
|
||||
const passImmediates = JSTimersExecution.immediates.slice();
|
||||
JSTimersExecution.immediates = [];
|
||||
|
||||
// Use for loop rather than forEach as per @vjeux's advice
|
||||
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
|
||||
for (let i = 0; i < passImmediates.length; ++i) {
|
||||
JSTimersExecution.callTimer(passImmediates[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
return JSTimersExecution.immediates.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called after we execute any command we receive from native but
|
||||
* before we hand control back to native.
|
||||
*/
|
||||
callImmediates() {
|
||||
JSTimersExecution.errors = null;
|
||||
while (JSTimersExecution.callImmediatesPass()) {}
|
||||
if (JSTimersExecution.errors) {
|
||||
JSTimersExecution.errors.forEach((error) =>
|
||||
require('JSTimers').setTimeout(() => { throw error; }, 0)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from native (in development) when environment times are out-of-sync.
|
||||
*/
|
||||
emitTimeDriftWarning(warningMessage: string) {
|
||||
if (hasEmittedTimeDriftWarning) {
|
||||
return;
|
||||
}
|
||||
hasEmittedTimeDriftWarning = true;
|
||||
console.warn(warningMessage);
|
||||
},
|
||||
|
||||
_clearIndex(i: number) {
|
||||
JSTimersExecution.timerIDs[i] = null;
|
||||
JSTimersExecution.callbacks[i] = null;
|
||||
JSTimersExecution.types[i] = null;
|
||||
JSTimersExecution.identifiers[i] = null;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = JSTimersExecution;
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* Utility class to provide the component owner hierarchy to native code for
|
||||
* debugging purposes.
|
||||
*
|
||||
* @providesModule RCTDebugComponentOwnership
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
|
||||
var RCTDebugComponentOwnership = {
|
||||
/**
|
||||
* Asynchronously returns the owner hierarchy as an array of strings. Request id is
|
||||
* passed along to the native module so that the native module can identify the
|
||||
* particular call instance.
|
||||
*
|
||||
* Example returned owner hierarchy: ['RootView', 'Dialog', 'TitleView', 'Text']
|
||||
*/
|
||||
getOwnerHierarchy: function(requestID: number, tag: number) {
|
||||
// Consider cleaning up these unused modules in a separate diff.
|
||||
throw new Error(
|
||||
'This seems to be unused. Will disable until it is needed again.'
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTDebugComponentOwnership',
|
||||
RCTDebugComponentOwnership
|
||||
);
|
||||
|
||||
module.exports = RCTDebugComponentOwnership;
|
||||
@@ -12,12 +12,11 @@
|
||||
'use strict';
|
||||
|
||||
const Image = require('Image');
|
||||
const React = require('React');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('React');
|
||||
const Text = require('Text');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
const View = require('View');
|
||||
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
|
||||
import type {ImageSource} from 'ImageSource';
|
||||
@@ -31,12 +30,12 @@ class SwipeableQuickActionButton extends React.Component {
|
||||
props: {
|
||||
accessibilityLabel?: string,
|
||||
imageSource: ImageSource | number,
|
||||
imageStyle?: ?View.propTypes.style,
|
||||
imageStyle?: ?ViewPropTypes.style,
|
||||
onPress?: Function,
|
||||
style?: ?View.propTypes.style,
|
||||
style?: ?ViewPropTypes.style,
|
||||
testID?: string,
|
||||
text?: ?(string | Object | Array<string | Object>),
|
||||
textStyle?: ?View.propTypes.style,
|
||||
textStyle?: ?ViewPropTypes.style,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
|
||||
@@ -72,6 +72,8 @@ const SwipeableRow = createReactClass({
|
||||
propTypes: {
|
||||
children: PropTypes.any,
|
||||
isOpen: PropTypes.bool,
|
||||
preventSwipeLeft: PropTypes.bool,
|
||||
preventSwipeRight: PropTypes.bool,
|
||||
maxSwipeDistance: PropTypes.number.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
@@ -108,6 +110,8 @@ const SwipeableRow = createReactClass({
|
||||
getDefaultProps(): Object {
|
||||
return {
|
||||
isOpen: false,
|
||||
preventSwipeLeft: false,
|
||||
preventSwipeRight: false,
|
||||
maxSwipeDistance: 0,
|
||||
onOpen: emptyFunction,
|
||||
onClose: emptyFunction,
|
||||
@@ -192,6 +196,11 @@ const SwipeableRow = createReactClass({
|
||||
);
|
||||
},
|
||||
|
||||
close(): void {
|
||||
this.props.onClose();
|
||||
this._animateToClosedPosition();
|
||||
},
|
||||
|
||||
_onSwipeableViewLayout(event: Object): void {
|
||||
this.setState({
|
||||
isSwipeableViewRendered: true,
|
||||
@@ -270,6 +279,7 @@ const SwipeableRow = createReactClass({
|
||||
{
|
||||
duration,
|
||||
toValue,
|
||||
useNativeDriver: true,
|
||||
},
|
||||
).start(() => {
|
||||
this._previousLeft = toValue;
|
||||
@@ -329,6 +339,12 @@ const SwipeableRow = createReactClass({
|
||||
|
||||
// Ignore swipes due to user's finger moving slightly when tapping
|
||||
_isValidSwipe(gestureState: Object): boolean {
|
||||
if (this.props.preventSwipeLeft && gestureState.dx < 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.props.preventSwipeRight && gestureState.dx > 0) {
|
||||
return false;
|
||||
}
|
||||
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
|
||||
},
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ const infoLog = require('infoLog');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const nullthrows = require('fbjs/lib/nullthrows');
|
||||
|
||||
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
/**
|
||||
@@ -613,7 +615,7 @@ type CellProps = {
|
||||
};
|
||||
class CellRenderer extends React.Component {
|
||||
props: CellProps;
|
||||
_containerRef: View;
|
||||
_containerRef: NativeMethodsMixinType;
|
||||
_offscreenRenderDone = false;
|
||||
_timeout = 0;
|
||||
_lastLayout: ?Object = null;
|
||||
|
||||
@@ -175,7 +175,7 @@ var Geolocation = {
|
||||
for (var ii = 0; ii < subscriptions.length; ii++) {
|
||||
var sub = subscriptions[ii];
|
||||
if (sub) {
|
||||
warning('Called stopObserving with existing subscriptions.');
|
||||
warning(false, 'Called stopObserving with existing subscriptions.');
|
||||
sub[0].remove();
|
||||
// array element refinements not yet enabled in Flow
|
||||
var sub1 = sub[1]; sub1 && sub1.remove();
|
||||
|
||||
@@ -35,8 +35,10 @@ const ImageViewManager = NativeModules.ImageViewManager;
|
||||
* including network images, static resources, temporary local images, and
|
||||
* images from local disk, such as the camera roll.
|
||||
*
|
||||
* This example shows both fetching and displaying an image from local
|
||||
* storage as well as one from network.
|
||||
* This example shows fetching and displaying an image from local storage
|
||||
* as well as one from network and even from data provided in the `'data:'` uri scheme.
|
||||
*
|
||||
* > Note that for network and data images, you will need to manually specify the dimensions of your image!
|
||||
*
|
||||
* ```ReactNativeWebPlayer
|
||||
* import React, { Component } from 'react';
|
||||
@@ -53,6 +55,10 @@ const ImageViewManager = NativeModules.ImageViewManager;
|
||||
* style={{width: 50, height: 50}}
|
||||
* source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
|
||||
* />
|
||||
* <Image
|
||||
* style={{width: 66, height: 58}}
|
||||
* source={{uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='}}
|
||||
* />
|
||||
* </View>
|
||||
* );
|
||||
* }
|
||||
@@ -104,17 +110,17 @@ const ImageViewManager = NativeModules.ImageViewManager;
|
||||
* ```
|
||||
* dependencies {
|
||||
* // If your app supports Android versions before Ice Cream Sandwich (API level 14)
|
||||
* compile 'com.facebook.fresco:animated-base-support:1.0.1'
|
||||
* compile 'com.facebook.fresco:animated-base-support:1.3.0'
|
||||
*
|
||||
* // For animated GIF support
|
||||
* compile 'com.facebook.fresco:animated-gif:1.0.1'
|
||||
* compile 'com.facebook.fresco:animated-gif:1.3.0'
|
||||
*
|
||||
* // For WebP support, including animated WebP
|
||||
* compile 'com.facebook.fresco:animated-webp:1.0.1'
|
||||
* compile 'com.facebook.fresco:webpsupport:1.0.1'
|
||||
* compile 'com.facebook.fresco:animated-webp:1.3.0'
|
||||
* compile 'com.facebook.fresco:webpsupport:1.3.0'
|
||||
*
|
||||
* // For WebP support, without animations
|
||||
* compile 'com.facebook.fresco:webpsupport:1.0.1'
|
||||
* compile 'com.facebook.fresco:webpsupport:1.3.0'
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
|
||||
const Image = require('Image');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
|
||||
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
|
||||
|
||||
/**
|
||||
* Very simple drop-in replacement for <Image> which supports nesting views.
|
||||
*
|
||||
@@ -41,20 +46,40 @@ const View = require('View');
|
||||
* ```
|
||||
*/
|
||||
class ImageBackground extends React.Component {
|
||||
setNativeProps(props: Object) {
|
||||
// Work-around flow
|
||||
const viewRef = this._viewRef;
|
||||
if (viewRef) {
|
||||
ensureComponentIsNative(viewRef);
|
||||
viewRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
_viewRef: ?NativeMethodsMixinType = null;
|
||||
|
||||
_captureRef = ref => {
|
||||
this._viewRef = ref;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, style, imageStyle, imageRef, ...props} = this.props;
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<View style={style} ref={this._captureRef}>
|
||||
<Image
|
||||
{...props}
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
// Temporary Workaround:
|
||||
// Current (imperfect yet) implementation of <Image> overwrites width and height styles
|
||||
// (which is not quite correct), and these styles conflict with explicitly set styles
|
||||
// of <ImageBackground> and with our internal layout model here.
|
||||
// So, we have to proxy/reapply these styles explicitly for actual <Image> component.
|
||||
// This workaround should be removed after implementing proper support of
|
||||
// intrinsic content size of the <Image>.
|
||||
width: style.width,
|
||||
height: style.height,
|
||||
},
|
||||
imageStyle,
|
||||
]}
|
||||
|
||||
+615
-591
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
// This is a stub for flow to make it understand require('./icon.png')
|
||||
// See packager/src/Bundler/index.js
|
||||
// See metro-bundler/src/Bundler/index.js
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.unmock('AssetRegistry')
|
||||
.unmock('AssetSourceResolver')
|
||||
.unmock('../resolveAssetSource')
|
||||
.unmock('../../../local-cli/bundle/assetPathUtils');
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
var Platform = require('Platform');
|
||||
var NativeModules = require('NativeModules');
|
||||
|
||||
@@ -35,10 +35,10 @@ class BoxInspector extends React.Component {
|
||||
<BoxContainer title="padding" box={padding}>
|
||||
<View>
|
||||
<Text style={styles.innerText}>
|
||||
({frame.left}, {frame.top})
|
||||
({(frame.left || 0).toFixed(1)}, {(frame.top || 0).toFixed(1)})
|
||||
</Text>
|
||||
<Text style={styles.innerText}>
|
||||
{frame.width} × {frame.height}
|
||||
{(frame.width || 0).toFixed(1)} × {(frame.height || 0).toFixed(1)}
|
||||
</Text>
|
||||
</View>
|
||||
</BoxContainer>
|
||||
@@ -110,4 +110,3 @@ var styles = StyleSheet.create({
|
||||
});
|
||||
|
||||
module.exports = BoxInspector;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ const UIManager = require('UIManager');
|
||||
const View = require('View');
|
||||
|
||||
const emptyObject = require('fbjs/lib/emptyObject');
|
||||
const invariant = require('invariant');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
export type ReactRenderer = {
|
||||
getInspectorDataForViewTag: (viewTag: number) => Object,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.disableAutomock()
|
||||
.mock('ErrorUtils')
|
||||
.mock('BatchedBridge');
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.disableAutomock()
|
||||
.mock('ErrorUtils')
|
||||
.mock('BatchedBridge');
|
||||
|
||||
@@ -164,6 +163,7 @@ describe('promise tasks', () => {
|
||||
}
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.useFakeTimers();
|
||||
InteractionManager = require('InteractionManager');
|
||||
BatchedBridge = require('BatchedBridge');
|
||||
sequenceId = 0;
|
||||
@@ -256,7 +256,9 @@ describe('promise tasks', () => {
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
const bigAsyncTest = () => {
|
||||
const bigAsyncTest = (resolve) => {
|
||||
jest.useRealTimers();
|
||||
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
@@ -282,27 +284,25 @@ describe('promise tasks', () => {
|
||||
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
|
||||
InteractionManager.runAfterInteractions(task6);
|
||||
|
||||
jest.runAllTimers();
|
||||
// runAllTimers doesn't actually run all timers with nested timer functions
|
||||
// inside Promises, so we have to call it extra times.
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers();
|
||||
setTimeout(() => {
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
expectToBeCalledOnce(task5);
|
||||
expectToBeCalledOnce(task6);
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
expectToBeCalledOnce(task5);
|
||||
expectToBeCalledOnce(task6);
|
||||
resolve();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
it('resolves async tasks recusively before other queued tasks', () => {
|
||||
bigAsyncTest();
|
||||
return new Promise(bigAsyncTest);
|
||||
});
|
||||
|
||||
it('should also work with a deadline', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
|
||||
bigAsyncTest();
|
||||
return new Promise(bigAsyncTest);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('InteractionMixin');
|
||||
jest.enableAutomock().unmock('InteractionMixin');
|
||||
|
||||
describe('InteractionMixin', () => {
|
||||
var InteractionManager;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
jest.unmock('TaskQueue');
|
||||
const Promise = require('promise');
|
||||
|
||||
function expectToBeCalledOnce(fn) {
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule FillRateHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console-disallow */
|
||||
@@ -56,17 +57,15 @@ class FillRateHelper {
|
||||
_mostlyBlankStartTime = (null: ?number);
|
||||
_samplesStartTime = (null: ?number);
|
||||
|
||||
static addListener(
|
||||
callback: (FillRateInfo) => void
|
||||
): {remove: () => void} {
|
||||
static addListener(callback: FillRateInfo => void): {remove: () => void} {
|
||||
warning(
|
||||
_sampleRate !== null,
|
||||
'Call `FillRateHelper.setSampleRate` before `addListener`.'
|
||||
'Call `FillRateHelper.setSampleRate` before `addListener`.',
|
||||
);
|
||||
_listeners.push(callback);
|
||||
return {
|
||||
remove: () => {
|
||||
_listeners = _listeners.filter((listener) => callback !== listener);
|
||||
_listeners = _listeners.filter(listener => callback !== listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -98,7 +97,8 @@ class FillRateHelper {
|
||||
}
|
||||
const start = this._samplesStartTime; // const for flow
|
||||
if (start == null) {
|
||||
DEBUG && console.debug('FillRateHelper: bail on deactivate with no start time');
|
||||
DEBUG &&
|
||||
console.debug('FillRateHelper: bail on deactivate with no start time');
|
||||
return;
|
||||
}
|
||||
if (this._info.sample_count < _minSampleCount) {
|
||||
@@ -115,10 +115,13 @@ class FillRateHelper {
|
||||
const derived = {
|
||||
avg_blankness: this._info.pixels_blank / this._info.pixels_sampled,
|
||||
avg_speed: this._info.pixels_scrolled / (total_time_spent / 1000),
|
||||
avg_speed_when_any_blank: this._info.any_blank_speed_sum / this._info.any_blank_count,
|
||||
any_blank_per_min: this._info.any_blank_count / (total_time_spent / 1000 / 60),
|
||||
avg_speed_when_any_blank:
|
||||
this._info.any_blank_speed_sum / this._info.any_blank_count,
|
||||
any_blank_per_min:
|
||||
this._info.any_blank_count / (total_time_spent / 1000 / 60),
|
||||
any_blank_time_frac: this._info.any_blank_ms / total_time_spent,
|
||||
mostly_blank_per_min: this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
|
||||
mostly_blank_per_min:
|
||||
this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
|
||||
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent,
|
||||
};
|
||||
for (const key in derived) {
|
||||
@@ -126,7 +129,7 @@ class FillRateHelper {
|
||||
}
|
||||
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info});
|
||||
}
|
||||
_listeners.forEach((listener) => listener(info));
|
||||
_listeners.forEach(listener => listener(info));
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
@@ -147,7 +150,11 @@ class FillRateHelper {
|
||||
visibleLength: number,
|
||||
},
|
||||
): number {
|
||||
if (!this._enabled || props.getItemCount(props.data) === 0 || this._samplesStartTime == null) {
|
||||
if (
|
||||
!this._enabled ||
|
||||
props.getItemCount(props.data) === 0 ||
|
||||
this._samplesStartTime == null
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
const {dOffset, offset, velocity, visibleLength} = scrollMetrics;
|
||||
@@ -180,7 +187,10 @@ class FillRateHelper {
|
||||
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header
|
||||
// as blank.
|
||||
if (firstFrame && first > 0) {
|
||||
blankTop = Math.min(visibleLength, Math.max(0, firstFrame.offset - offset));
|
||||
blankTop = Math.min(
|
||||
visibleLength,
|
||||
Math.max(0, firstFrame.offset - offset),
|
||||
);
|
||||
}
|
||||
let blankBottom = 0;
|
||||
let last = state.last;
|
||||
@@ -193,7 +203,10 @@ class FillRateHelper {
|
||||
// footer as blank.
|
||||
if (lastFrame && last < props.getItemCount(props.data) - 1) {
|
||||
const bottomEdge = lastFrame.offset + lastFrame.length;
|
||||
blankBottom = Math.min(visibleLength, Math.max(0, offset + visibleLength - bottomEdge));
|
||||
blankBottom = Math.min(
|
||||
visibleLength,
|
||||
Math.max(0, offset + visibleLength - bottomEdge),
|
||||
);
|
||||
}
|
||||
const pixels_blank = Math.round(blankTop + blankBottom);
|
||||
const blankness = pixels_blank / visibleLength;
|
||||
|
||||
+80
-23
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule FlatList
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -37,7 +38,7 @@ type RequiredProps<ItemT> = {
|
||||
* onShowUnderlay={separators.highlight}
|
||||
* onHideUnderlay={separators.unhighlight}>
|
||||
* <View style={{backgroundColor: 'white'}}>
|
||||
* <Text>{item.title}}</Text>
|
||||
* <Text>{item.title}</Text>
|
||||
* </View>
|
||||
* </TouchableHighlight>
|
||||
* )}
|
||||
@@ -109,8 +110,10 @@ type OptionalProps<ItemT> = {
|
||||
* Remember to include separator length (height or width) in your offset calculation if you
|
||||
* specify `ItemSeparatorComponent`.
|
||||
*/
|
||||
getItemLayout?: (data: ?Array<ItemT>, index: number) =>
|
||||
{length: number, offset: number, index: number},
|
||||
getItemLayout?: (
|
||||
data: ?Array<ItemT>,
|
||||
index: number,
|
||||
) => {length: number, offset: number, index: number},
|
||||
/**
|
||||
* If true, renders items next to each other horizontally instead of stacked vertically.
|
||||
*/
|
||||
@@ -128,6 +131,10 @@ type OptionalProps<ItemT> = {
|
||||
* `getItemLayout` to be implemented.
|
||||
*/
|
||||
initialScrollIndex?: ?number,
|
||||
/**
|
||||
* Reverses the direction of scroll. Uses scale transforms of -1.
|
||||
*/
|
||||
inverted?: ?boolean,
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index. Key is used for caching
|
||||
* and as the react key to track item re-ordering. The default extractor checks `item.key`, then
|
||||
@@ -163,6 +170,11 @@ type OptionalProps<ItemT> = {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
}) => void,
|
||||
/**
|
||||
* Set this when offset is needed for the loading indicator to show correctly.
|
||||
* @platform android
|
||||
*/
|
||||
progressViewOffset?: number,
|
||||
legacyImplementation?: ?boolean,
|
||||
/**
|
||||
* Set this true while waiting for new data from a refresh.
|
||||
@@ -179,7 +191,9 @@ type OptionalProps<ItemT> = {
|
||||
*/
|
||||
viewabilityConfig?: ViewabilityConfig,
|
||||
};
|
||||
type Props<ItemT> = RequiredProps<ItemT> & OptionalProps<ItemT> & VirtualizedListProps;
|
||||
type Props<ItemT> = RequiredProps<ItemT> &
|
||||
OptionalProps<ItemT> &
|
||||
VirtualizedListProps;
|
||||
|
||||
const defaultProps = {
|
||||
...VirtualizedList.defaultProps,
|
||||
@@ -273,7 +287,7 @@ type DefaultProps = typeof defaultProps;
|
||||
* }
|
||||
*
|
||||
* This is a convenience wrapper around [`<VirtualizedList>`](docs/virtualizedlist.html),
|
||||
* and thus inherits it's props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* here, along with the following caveats:
|
||||
*
|
||||
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
|
||||
@@ -290,7 +304,11 @@ type DefaultProps = typeof defaultProps;
|
||||
* Alternatively, you can provide a custom `keyExtractor` prop.
|
||||
*
|
||||
*/
|
||||
class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, void> {
|
||||
class FlatList<ItemT> extends React.PureComponent<
|
||||
DefaultProps,
|
||||
Props<ItemT>,
|
||||
void,
|
||||
> {
|
||||
static defaultProps: DefaultProps = defaultProps;
|
||||
props: Props<ItemT>;
|
||||
/**
|
||||
@@ -309,7 +327,10 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
* `getItemLayout` prop.
|
||||
*/
|
||||
scrollToIndex(params: {
|
||||
animated?: ?boolean, index: number, viewOffset?: number, viewPosition?: number,
|
||||
animated?: ?boolean,
|
||||
index: number,
|
||||
viewOffset?: number,
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
this._listRef.scrollToIndex(params);
|
||||
}
|
||||
@@ -320,13 +341,17 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
* Note: cannot scroll to locations outside the render window without specifying the
|
||||
* `getItemLayout` prop.
|
||||
*/
|
||||
scrollToItem(params: {animated?: ?boolean, item: ItemT, viewPosition?: number}) {
|
||||
scrollToItem(params: {
|
||||
animated?: ?boolean,
|
||||
item: ItemT,
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
this._listRef.scrollToItem(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific content pixel offset in the list.
|
||||
*
|
||||
*
|
||||
* Check out [scrollToOffset](docs/virtualizedlist.html#scrolltooffset) of VirtualizedList
|
||||
*/
|
||||
scrollToOffset(params: {animated?: ?boolean, offset: number}) {
|
||||
@@ -366,6 +391,12 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
}
|
||||
}
|
||||
|
||||
setNativeProps(props: Object) {
|
||||
if (this._listRef) {
|
||||
this._listRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._checkProps(this.props);
|
||||
}
|
||||
@@ -374,7 +405,7 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
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.'
|
||||
'changing the number of columns to force a fresh render of the component.',
|
||||
);
|
||||
this._checkProps(nextProps);
|
||||
}
|
||||
@@ -382,7 +413,9 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
_hasWarnedLegacy = false;
|
||||
_listRef: VirtualizedList;
|
||||
|
||||
_captureRef = (ref) => { this._listRef = ref; };
|
||||
_captureRef = ref => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
|
||||
_checkProps(props: Props<ItemT>) {
|
||||
const {
|
||||
@@ -393,20 +426,29 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
numColumns,
|
||||
columnWrapperStyle,
|
||||
} = props;
|
||||
invariant(!getItem && !getItemCount, 'FlatList does not support custom data formats.');
|
||||
invariant(
|
||||
!getItem && !getItemCount,
|
||||
'FlatList does not support custom data formats.',
|
||||
);
|
||||
if (numColumns > 1) {
|
||||
invariant(!horizontal, 'numColumns does not support horizontal.');
|
||||
} else {
|
||||
invariant(!columnWrapperStyle, 'columnWrapperStyle not supported for single column lists');
|
||||
invariant(
|
||||
!columnWrapperStyle,
|
||||
'columnWrapperStyle not supported for single column lists',
|
||||
);
|
||||
}
|
||||
if (legacyImplementation) {
|
||||
invariant(numColumns === 1, 'Legacy list does not support multiple columns.');
|
||||
invariant(
|
||||
numColumns === 1,
|
||||
'Legacy list does not support multiple columns.',
|
||||
);
|
||||
// Warning: may not have full feature parity and is meant more for debugging and performance
|
||||
// comparison.
|
||||
if (!this._hasWarnedLegacy) {
|
||||
console.warn(
|
||||
'FlatList: Using legacyImplementation - some features not supported and performance ' +
|
||||
'may suffer'
|
||||
'may suffer',
|
||||
);
|
||||
this._hasWarnedLegacy = true;
|
||||
}
|
||||
@@ -437,10 +479,12 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
invariant(
|
||||
Array.isArray(items),
|
||||
'FlatList: Encountered internal consistency error, expected each item to consist of an ' +
|
||||
'array with 1-%s columns; instead, received a single item.',
|
||||
'array with 1-%s columns; instead, received a single item.',
|
||||
numColumns,
|
||||
);
|
||||
return items.map((it, kk) => keyExtractor(it, index * numColumns + kk)).join(':');
|
||||
return items
|
||||
.map((it, kk) => keyExtractor(it, index * numColumns + kk))
|
||||
.join(':');
|
||||
} else {
|
||||
return keyExtractor(items, index);
|
||||
}
|
||||
@@ -455,7 +499,7 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
});
|
||||
}
|
||||
|
||||
_onViewableItemsChanged = (info) => {
|
||||
_onViewableItemsChanged = info => {
|
||||
const {numColumns, onViewableItemsChanged} = this.props;
|
||||
if (!onViewableItemsChanged) {
|
||||
return;
|
||||
@@ -463,8 +507,10 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
if (numColumns > 1) {
|
||||
const changed = [];
|
||||
const viewableItems = [];
|
||||
info.viewableItems.forEach((v) => this._pushMultiColumnViewable(viewableItems, v));
|
||||
info.changed.forEach((v) => this._pushMultiColumnViewable(changed, v));
|
||||
info.viewableItems.forEach(v =>
|
||||
this._pushMultiColumnViewable(viewableItems, v),
|
||||
);
|
||||
info.changed.forEach(v => this._pushMultiColumnViewable(changed, v));
|
||||
onViewableItemsChanged({viewableItems, changed});
|
||||
} else {
|
||||
onViewableItemsChanged(info);
|
||||
@@ -475,7 +521,10 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
const {renderItem, numColumns, columnWrapperStyle} = this.props;
|
||||
if (numColumns > 1) {
|
||||
const {item, index} = info;
|
||||
invariant(Array.isArray(item), 'Expected array of items with numColumns > 1');
|
||||
invariant(
|
||||
Array.isArray(item),
|
||||
'Expected array of items with numColumns > 1',
|
||||
);
|
||||
return (
|
||||
<View style={[{flexDirection: 'row'}, columnWrapperStyle]}>
|
||||
{item.map((it, kk) => {
|
||||
@@ -495,7 +544,13 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
|
||||
render() {
|
||||
if (this.props.legacyImplementation) {
|
||||
return <MetroListView {...this.props} items={this.props.data} ref={this._captureRef} />;
|
||||
return (
|
||||
<MetroListView
|
||||
{...this.props}
|
||||
items={this.props.data}
|
||||
ref={this._captureRef}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VirtualizedList
|
||||
@@ -505,7 +560,9 @@ class FlatList<ItemT> extends React.PureComponent<DefaultProps, Props<ItemT>, vo
|
||||
getItemCount={this._getItemCount}
|
||||
keyExtractor={this._keyExtractor}
|
||||
ref={this._captureRef}
|
||||
onViewableItemsChanged={this.props.onViewableItemsChanged && this._onViewableItemsChanged}
|
||||
onViewableItemsChanged={
|
||||
this.props.onViewableItemsChanged && this._onViewableItemsChanged
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ListView
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -34,8 +35,13 @@ var DEFAULT_SCROLL_RENDER_AHEAD = 1000;
|
||||
var DEFAULT_END_REACHED_THRESHOLD = 1000;
|
||||
var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
|
||||
|
||||
|
||||
/**
|
||||
* DEPRECATED - use one of the new list components, such as [`FlatList`](docs/flatlist.html)
|
||||
* or [`SectionList`](docs/sectionlist.html) for bounded memory use, fewer bugs,
|
||||
* better performance, an easier to use API, and more features. Check out this
|
||||
* [blog post](https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html)
|
||||
* for more details.
|
||||
*
|
||||
* ListView - A core component designed for efficient display of vertically
|
||||
* scrolling lists of changing data. The minimal API is to create a
|
||||
* [`ListView.DataSource`](docs/listviewdatasource.html), populate it with a simple
|
||||
@@ -235,7 +241,9 @@ var ListView = createReactClass({
|
||||
getMetrics: function() {
|
||||
return {
|
||||
contentLength: this.scrollProperties.contentLength,
|
||||
totalRows: (this.props.enableEmptySections ? this.props.dataSource.getRowAndSectionCount() : this.props.dataSource.getRowCount()),
|
||||
totalRows: this.props.enableEmptySections
|
||||
? this.props.dataSource.getRowAndSectionCount()
|
||||
: this.props.dataSource.getRowCount(),
|
||||
renderedRows: this.state.curRenderedRowsCount,
|
||||
visibleRows: Object.keys(this._visibleRows).length,
|
||||
};
|
||||
@@ -281,14 +289,14 @@ var ListView = createReactClass({
|
||||
*
|
||||
* See `ScrollView#scrollToEnd`.
|
||||
*/
|
||||
scrollToEnd: function(options?: ?{ animated?: ?boolean }) {
|
||||
scrollToEnd: function(options?: ?{animated?: ?boolean}) {
|
||||
if (this._scrollComponent) {
|
||||
if (this._scrollComponent.scrollToEnd) {
|
||||
this._scrollComponent.scrollToEnd(options);
|
||||
} else {
|
||||
console.warn(
|
||||
'The scroll component used by the ListView does not support ' +
|
||||
'scrollToEnd. Check the renderScrollComponent prop of your ListView.'
|
||||
'scrollToEnd. Check the renderScrollComponent prop of your ListView.',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -330,7 +338,7 @@ var ListView = createReactClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
curRenderedRowsCount: this.props.initialListSize,
|
||||
highlightedRow: ({} : Object),
|
||||
highlightedRow: ({}: Object),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -343,7 +351,7 @@ var ListView = createReactClass({
|
||||
this.scrollProperties = {
|
||||
visibleLength: null,
|
||||
contentLength: null,
|
||||
offset: 0
|
||||
offset: 0,
|
||||
};
|
||||
this._childFrames = [];
|
||||
this._visibleRows = {};
|
||||
@@ -360,20 +368,24 @@ var ListView = createReactClass({
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps: Object) {
|
||||
if (this.props.dataSource !== nextProps.dataSource ||
|
||||
this.props.initialListSize !== nextProps.initialListSize) {
|
||||
this.setState((state, props) => {
|
||||
this._prevRenderedRowsCount = 0;
|
||||
return {
|
||||
curRenderedRowsCount: Math.min(
|
||||
Math.max(
|
||||
state.curRenderedRowsCount,
|
||||
props.initialListSize
|
||||
if (
|
||||
this.props.dataSource !== nextProps.dataSource ||
|
||||
this.props.initialListSize !== nextProps.initialListSize
|
||||
) {
|
||||
this.setState(
|
||||
(state, props) => {
|
||||
this._prevRenderedRowsCount = 0;
|
||||
return {
|
||||
curRenderedRowsCount: Math.min(
|
||||
Math.max(state.curRenderedRowsCount, props.initialListSize),
|
||||
props.enableEmptySections
|
||||
? props.dataSource.getRowAndSectionCount()
|
||||
: props.dataSource.getRowCount(),
|
||||
),
|
||||
props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount()
|
||||
),
|
||||
};
|
||||
}, () => this._renderMoreRowsIfNeeded());
|
||||
};
|
||||
},
|
||||
() => this._renderMoreRowsIfNeeded(),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -407,26 +419,32 @@ var ListView = createReactClass({
|
||||
if (rowIDs.length === 0) {
|
||||
if (this.props.enableEmptySections === undefined) {
|
||||
var warning = require('fbjs/lib/warning');
|
||||
warning(false, 'In next release empty section headers will be rendered.'
|
||||
+ ' In this release you can use \'enableEmptySections\' flag to render empty section headers.');
|
||||
warning(
|
||||
false,
|
||||
'In next release empty section headers will be rendered.' +
|
||||
" In this release you can use 'enableEmptySections' flag to render empty section headers.",
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
invariant(
|
||||
this.props.enableEmptySections,
|
||||
'In next release \'enableEmptySections\' flag will be deprecated, empty section headers will always be rendered.'
|
||||
+ ' If empty section headers are not desirable their indices should be excluded from sectionIDs object.'
|
||||
+ ' In this release \'enableEmptySections\' may only have value \'true\' to allow empty section headers rendering.');
|
||||
"In next release 'enableEmptySections' flag will be deprecated, empty section headers will always be rendered." +
|
||||
' If empty section headers are not desirable their indices should be excluded from sectionIDs object.' +
|
||||
" In this release 'enableEmptySections' may only have value 'true' to allow empty section headers rendering.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (renderSectionHeader) {
|
||||
const element = renderSectionHeader(
|
||||
dataSource.getSectionHeaderData(sectionIdx),
|
||||
sectionID
|
||||
sectionID,
|
||||
);
|
||||
if (element) {
|
||||
bodyComponents.push(React.cloneElement(element, {key: 's_' + sectionID}));
|
||||
bodyComponents.push(
|
||||
React.cloneElement(element, {key: 's_' + sectionID}),
|
||||
);
|
||||
if (this.props.stickySectionHeadersEnabled) {
|
||||
stickySectionHeaderIndices.push(totalIndex);
|
||||
}
|
||||
@@ -437,9 +455,10 @@ var ListView = createReactClass({
|
||||
for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {
|
||||
var rowID = rowIDs[rowIdx];
|
||||
var comboID = sectionID + '_' + rowID;
|
||||
var shouldUpdateRow = rowCount >= this._prevRenderedRowsCount &&
|
||||
var shouldUpdateRow =
|
||||
rowCount >= this._prevRenderedRowsCount &&
|
||||
dataSource.rowShouldUpdate(sectionIdx, rowIdx);
|
||||
var row =
|
||||
var row = (
|
||||
<StaticRenderer
|
||||
key={'r_' + comboID}
|
||||
shouldUpdate={!!shouldUpdateRow}
|
||||
@@ -448,29 +467,31 @@ var ListView = createReactClass({
|
||||
dataSource.getRowData(sectionIdx, rowIdx),
|
||||
sectionID,
|
||||
rowID,
|
||||
this._onRowHighlighted
|
||||
this._onRowHighlighted,
|
||||
)}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
bodyComponents.push(row);
|
||||
totalIndex++;
|
||||
|
||||
if (this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) {
|
||||
if (
|
||||
this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)
|
||||
) {
|
||||
var adjacentRowHighlighted =
|
||||
this.state.highlightedRow.sectionID === sectionID && (
|
||||
this.state.highlightedRow.rowID === rowID ||
|
||||
this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]
|
||||
);
|
||||
this.state.highlightedRow.sectionID === sectionID &&
|
||||
(this.state.highlightedRow.rowID === rowID ||
|
||||
this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]);
|
||||
var separator = this.props.renderSeparator(
|
||||
sectionID,
|
||||
rowID,
|
||||
adjacentRowHighlighted
|
||||
adjacentRowHighlighted,
|
||||
);
|
||||
if (separator) {
|
||||
bodyComponents.push(
|
||||
<View key={'s_' + comboID}>
|
||||
{separator}
|
||||
</View>
|
||||
</View>,
|
||||
);
|
||||
totalIndex++;
|
||||
}
|
||||
@@ -484,10 +505,7 @@ var ListView = createReactClass({
|
||||
}
|
||||
}
|
||||
|
||||
var {
|
||||
renderScrollComponent,
|
||||
...props
|
||||
} = this.props;
|
||||
var {renderScrollComponent, ...props} = this.props;
|
||||
if (!props.scrollEventThrottle) {
|
||||
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
|
||||
}
|
||||
@@ -496,7 +514,9 @@ var ListView = createReactClass({
|
||||
}
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
stickyHeaderIndices: this.props.stickyHeaderIndices.concat(stickySectionHeaderIndices),
|
||||
stickyHeaderIndices: this.props.stickyHeaderIndices.concat(
|
||||
stickySectionHeaderIndices,
|
||||
),
|
||||
|
||||
// Do not pass these events downstream to ScrollView since they will be
|
||||
// registered in ListView's own ScrollResponder.Mixin
|
||||
@@ -506,11 +526,19 @@ var ListView = createReactClass({
|
||||
onKeyboardDidHide: undefined,
|
||||
});
|
||||
|
||||
return cloneReferencedElement(renderScrollComponent(props), {
|
||||
ref: this._setScrollComponentRef,
|
||||
onContentSizeChange: this._onContentSizeChange,
|
||||
onLayout: this._onLayout,
|
||||
}, header, bodyComponents, footer);
|
||||
return cloneReferencedElement(
|
||||
renderScrollComponent(props),
|
||||
{
|
||||
ref: this._setScrollComponentRef,
|
||||
onContentSizeChange: this._onContentSizeChange,
|
||||
onLayout: this._onLayout,
|
||||
DEPRECATED_sendUpdatedChildFrames:
|
||||
typeof props.onChangeVisibleRows !== undefined,
|
||||
},
|
||||
header,
|
||||
bodyComponents,
|
||||
footer,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -525,7 +553,8 @@ var ListView = createReactClass({
|
||||
|
||||
// RCTScrollViewManager.calculateChildFrames is not available on
|
||||
// every platform
|
||||
RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
|
||||
RCTScrollViewManager &&
|
||||
RCTScrollViewManager.calculateChildFrames &&
|
||||
RCTScrollViewManager.calculateChildFrames(
|
||||
ReactNative.findNodeHandle(scrollComponent),
|
||||
this._updateVisibleRows,
|
||||
@@ -543,7 +572,8 @@ var ListView = createReactClass({
|
||||
this._updateVisibleRows();
|
||||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
|
||||
this.props.onContentSizeChange &&
|
||||
this.props.onContentSizeChange(width, height);
|
||||
},
|
||||
|
||||
_onLayout: function(event: Object) {
|
||||
@@ -558,10 +588,16 @@ var ListView = createReactClass({
|
||||
},
|
||||
|
||||
_maybeCallOnEndReached: function(event?: Object) {
|
||||
if (this.props.onEndReached &&
|
||||
this.scrollProperties.contentLength !== this._sentEndForContentLength &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold &&
|
||||
this.state.curRenderedRowsCount === (this.props.enableEmptySections ? this.props.dataSource.getRowAndSectionCount() : this.props.dataSource.getRowCount())) {
|
||||
if (
|
||||
this.props.onEndReached &&
|
||||
this.scrollProperties.contentLength !== this._sentEndForContentLength &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) <
|
||||
this.props.onEndReachedThreshold &&
|
||||
this.state.curRenderedRowsCount ===
|
||||
(this.props.enableEmptySections
|
||||
? this.props.dataSource.getRowAndSectionCount()
|
||||
: this.props.dataSource.getRowCount())
|
||||
) {
|
||||
this._sentEndForContentLength = this.scrollProperties.contentLength;
|
||||
this.props.onEndReached(event);
|
||||
return true;
|
||||
@@ -570,9 +606,14 @@ var ListView = createReactClass({
|
||||
},
|
||||
|
||||
_renderMoreRowsIfNeeded: function() {
|
||||
if (this.scrollProperties.contentLength === null ||
|
||||
if (
|
||||
this.scrollProperties.contentLength === null ||
|
||||
this.scrollProperties.visibleLength === null ||
|
||||
this.state.curRenderedRowsCount === (this.props.enableEmptySections ? this.props.dataSource.getRowAndSectionCount() : this.props.dataSource.getRowCount())) {
|
||||
this.state.curRenderedRowsCount ===
|
||||
(this.props.enableEmptySections
|
||||
? this.props.dataSource.getRowAndSectionCount()
|
||||
: this.props.dataSource.getRowCount())
|
||||
) {
|
||||
this._maybeCallOnEndReached();
|
||||
return;
|
||||
}
|
||||
@@ -584,23 +625,32 @@ var ListView = createReactClass({
|
||||
},
|
||||
|
||||
_pageInNewRows: function() {
|
||||
this.setState((state, props) => {
|
||||
var rowsToRender = Math.min(
|
||||
state.curRenderedRowsCount + props.pageSize,
|
||||
(props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount())
|
||||
);
|
||||
this._prevRenderedRowsCount = state.curRenderedRowsCount;
|
||||
return {
|
||||
curRenderedRowsCount: rowsToRender
|
||||
};
|
||||
}, () => {
|
||||
this._measureAndUpdateScrollProps();
|
||||
this._prevRenderedRowsCount = this.state.curRenderedRowsCount;
|
||||
});
|
||||
this.setState(
|
||||
(state, props) => {
|
||||
var rowsToRender = Math.min(
|
||||
state.curRenderedRowsCount + props.pageSize,
|
||||
props.enableEmptySections
|
||||
? props.dataSource.getRowAndSectionCount()
|
||||
: props.dataSource.getRowCount(),
|
||||
);
|
||||
this._prevRenderedRowsCount = state.curRenderedRowsCount;
|
||||
return {
|
||||
curRenderedRowsCount: rowsToRender,
|
||||
};
|
||||
},
|
||||
() => {
|
||||
this._measureAndUpdateScrollProps();
|
||||
this._prevRenderedRowsCount = this.state.curRenderedRowsCount;
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
_getDistanceFromEnd: function(scrollProperties: Object) {
|
||||
return scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset;
|
||||
return (
|
||||
scrollProperties.contentLength -
|
||||
scrollProperties.visibleLength -
|
||||
scrollProperties.offset
|
||||
);
|
||||
},
|
||||
|
||||
_updateVisibleRows: function(updatedFrames?: Array<Object>) {
|
||||
@@ -608,7 +658,7 @@ var ListView = createReactClass({
|
||||
return; // No need to compute visible rows if there is no callback
|
||||
}
|
||||
if (updatedFrames) {
|
||||
updatedFrames.forEach((newFrame) => {
|
||||
updatedFrames.forEach(newFrame => {
|
||||
this._childFrames[newFrame.index] = merge(newFrame);
|
||||
});
|
||||
}
|
||||
@@ -639,8 +689,10 @@ var ListView = createReactClass({
|
||||
var rowID = rowIDs[rowIdx];
|
||||
var frame = this._childFrames[totalIndex];
|
||||
totalIndex++;
|
||||
if (this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)){
|
||||
if (
|
||||
this.props.renderSeparator &&
|
||||
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)
|
||||
) {
|
||||
totalIndex++;
|
||||
}
|
||||
if (!frame) {
|
||||
@@ -649,7 +701,7 @@ var ListView = createReactClass({
|
||||
var rowVisible = visibleSection[rowID];
|
||||
var min = isVertical ? frame.y : frame.x;
|
||||
var max = min + (isVertical ? frame.height : frame.width);
|
||||
if ((!min && !max) || (min === max)) {
|
||||
if ((!min && !max) || min === max) {
|
||||
break;
|
||||
}
|
||||
if (min > visibleMax || max < visibleMin) {
|
||||
@@ -676,27 +728,28 @@ var ListView = createReactClass({
|
||||
delete this._visibleRows[sectionID];
|
||||
}
|
||||
}
|
||||
visibilityChanged && this.props.onChangeVisibleRows(this._visibleRows, changedRows);
|
||||
visibilityChanged &&
|
||||
this.props.onChangeVisibleRows(this._visibleRows, changedRows);
|
||||
},
|
||||
|
||||
_onScroll: function(e: Object) {
|
||||
var isVertical = !this.props.horizontal;
|
||||
this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.contentLength = e.nativeEvent.contentSize[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.offset = e.nativeEvent.contentOffset[
|
||||
isVertical ? 'y' : 'x'
|
||||
];
|
||||
this.scrollProperties.visibleLength =
|
||||
e.nativeEvent.layoutMeasurement[isVertical ? 'height' : 'width'];
|
||||
this.scrollProperties.contentLength =
|
||||
e.nativeEvent.contentSize[isVertical ? 'height' : 'width'];
|
||||
this.scrollProperties.offset =
|
||||
e.nativeEvent.contentOffset[isVertical ? 'y' : 'x'];
|
||||
this._updateVisibleRows(e.nativeEvent.updatedChildFrames);
|
||||
if (!this._maybeCallOnEndReached(e)) {
|
||||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
|
||||
if (this.props.onEndReached &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold) {
|
||||
if (
|
||||
this.props.onEndReached &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) >
|
||||
this.props.onEndReachedThreshold
|
||||
) {
|
||||
// Scrolled out of the end zone, so it should be able to trigger again.
|
||||
this._sentEndForContentLength = null;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule ListViewDataSource
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -18,26 +19,26 @@ var warning = require('fbjs/lib/warning');
|
||||
function defaultGetRowData(
|
||||
dataBlob: any,
|
||||
sectionID: number | string,
|
||||
rowID: number | string
|
||||
rowID: number | string,
|
||||
): any {
|
||||
return dataBlob[sectionID][rowID];
|
||||
}
|
||||
|
||||
function defaultGetSectionHeaderData(
|
||||
dataBlob: any,
|
||||
sectionID: number | string
|
||||
sectionID: number | string,
|
||||
): any {
|
||||
return dataBlob[sectionID];
|
||||
}
|
||||
|
||||
type differType = (data1: any, data2: any) => bool;
|
||||
type differType = (data1: any, data2: any) => boolean;
|
||||
|
||||
type ParamType = {
|
||||
rowHasChanged: differType,
|
||||
getRowData?: ?typeof defaultGetRowData,
|
||||
sectionHeaderHasChanged?: ?differType,
|
||||
getSectionHeaderData?: ?typeof defaultGetSectionHeaderData,
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides efficient data processing and access to the
|
||||
@@ -74,7 +75,6 @@ type ParamType = {
|
||||
*/
|
||||
|
||||
class ListViewDataSource {
|
||||
|
||||
/**
|
||||
* You can provide custom extraction and `hasChanged` functions for section
|
||||
* headers and rows. If absent, data will be extracted with the
|
||||
@@ -103,7 +103,7 @@ class ListViewDataSource {
|
||||
constructor(params: ParamType) {
|
||||
invariant(
|
||||
params && typeof params.rowHasChanged === 'function',
|
||||
'Must provide a rowHasChanged function.'
|
||||
'Must provide a rowHasChanged function.',
|
||||
);
|
||||
this._rowHasChanged = params.rowHasChanged;
|
||||
this._getRowData = params.getRowData || defaultGetRowData;
|
||||
@@ -138,10 +138,10 @@ class ListViewDataSource {
|
||||
* handle merging of old and new data separately and then pass that into
|
||||
* this function as the `dataBlob`.
|
||||
*/
|
||||
cloneWithRows(
|
||||
dataBlob: $ReadOnlyArray<any> | {+[key: string]: any},
|
||||
rowIdentities: ?$ReadOnlyArray<string>
|
||||
): ListViewDataSource {
|
||||
cloneWithRows(
|
||||
dataBlob: $ReadOnlyArray<any> | {+[key: string]: any},
|
||||
rowIdentities: ?$ReadOnlyArray<string>,
|
||||
): ListViewDataSource {
|
||||
var rowIds = rowIdentities ? [[...rowIdentities]] : null;
|
||||
if (!this._sectionHeaderHasChanged) {
|
||||
this._sectionHeaderHasChanged = () => false;
|
||||
@@ -161,17 +161,19 @@ class ListViewDataSource {
|
||||
* Note: this returns a new object!
|
||||
*/
|
||||
cloneWithRowsAndSections(
|
||||
dataBlob: any,
|
||||
sectionIdentities: ?Array<string>,
|
||||
rowIdentities: ?Array<Array<string>>
|
||||
dataBlob: any,
|
||||
sectionIdentities: ?Array<string>,
|
||||
rowIdentities: ?Array<Array<string>>,
|
||||
): ListViewDataSource {
|
||||
invariant(
|
||||
typeof this._sectionHeaderHasChanged === 'function',
|
||||
'Must provide a sectionHeaderHasChanged function with section data.'
|
||||
'Must provide a sectionHeaderHasChanged function with section data.',
|
||||
);
|
||||
invariant(
|
||||
!sectionIdentities || !rowIdentities || sectionIdentities.length === rowIdentities.length,
|
||||
'row and section ids lengths must be the same'
|
||||
!sectionIdentities ||
|
||||
!rowIdentities ||
|
||||
sectionIdentities.length === rowIdentities.length,
|
||||
'row and section ids lengths must be the same',
|
||||
);
|
||||
|
||||
var newSource = new ListViewDataSource({
|
||||
@@ -190,7 +192,7 @@ class ListViewDataSource {
|
||||
newSource.rowIdentities = rowIdentities;
|
||||
} else {
|
||||
newSource.rowIdentities = [];
|
||||
newSource.sectionIdentities.forEach((sectionID) => {
|
||||
newSource.sectionIdentities.forEach(sectionID => {
|
||||
newSource.rowIdentities.push(Object.keys(dataBlob[sectionID]));
|
||||
});
|
||||
}
|
||||
@@ -199,7 +201,7 @@ class ListViewDataSource {
|
||||
newSource._calculateDirtyArrays(
|
||||
this._dataBlob,
|
||||
this.sectionIdentities,
|
||||
this.rowIdentities
|
||||
this.rowIdentities,
|
||||
);
|
||||
|
||||
return newSource;
|
||||
@@ -210,16 +212,18 @@ class ListViewDataSource {
|
||||
}
|
||||
|
||||
getRowAndSectionCount(): number {
|
||||
return (this._cachedRowCount + this.sectionIdentities.length);
|
||||
return this._cachedRowCount + this.sectionIdentities.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the row is dirtied and needs to be rerendered
|
||||
*/
|
||||
rowShouldUpdate(sectionIndex: number, rowIndex: number): bool {
|
||||
rowShouldUpdate(sectionIndex: number, rowIndex: number): boolean {
|
||||
var needsUpdate = this._dirtyRows[sectionIndex][rowIndex];
|
||||
warning(needsUpdate !== undefined,
|
||||
'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex);
|
||||
warning(
|
||||
needsUpdate !== undefined,
|
||||
'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex,
|
||||
);
|
||||
return needsUpdate;
|
||||
}
|
||||
|
||||
@@ -231,7 +235,7 @@ class ListViewDataSource {
|
||||
var rowID = this.rowIdentities[sectionIndex][rowIndex];
|
||||
warning(
|
||||
sectionID !== undefined && rowID !== undefined,
|
||||
'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex
|
||||
'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex,
|
||||
);
|
||||
return this._getRowData(this._dataBlob, sectionID, rowID);
|
||||
}
|
||||
@@ -282,10 +286,12 @@ class ListViewDataSource {
|
||||
/**
|
||||
* Returns if the section header is dirtied and needs to be rerendered
|
||||
*/
|
||||
sectionHeaderShouldUpdate(sectionIndex: number): bool {
|
||||
sectionHeaderShouldUpdate(sectionIndex: number): boolean {
|
||||
var needsUpdate = this._dirtySections[sectionIndex];
|
||||
warning(needsUpdate !== undefined,
|
||||
'missing dirtyBit for section: ' + sectionIndex);
|
||||
warning(
|
||||
needsUpdate !== undefined,
|
||||
'missing dirtyBit for section: ' + sectionIndex,
|
||||
);
|
||||
return needsUpdate;
|
||||
}
|
||||
|
||||
@@ -297,8 +303,10 @@ class ListViewDataSource {
|
||||
return null;
|
||||
}
|
||||
var sectionID = this.sectionIdentities[sectionIndex];
|
||||
warning(sectionID !== undefined,
|
||||
'renderSection called on invalid section: ' + sectionIndex);
|
||||
warning(
|
||||
sectionID !== undefined,
|
||||
'renderSection called on invalid section: ' + sectionIndex,
|
||||
);
|
||||
return this._getSectionHeaderData(this._dataBlob, sectionID);
|
||||
}
|
||||
|
||||
@@ -312,8 +320,8 @@ class ListViewDataSource {
|
||||
_sectionHeaderHasChanged: ?differType;
|
||||
|
||||
_dataBlob: any;
|
||||
_dirtyRows: Array<Array<bool>>;
|
||||
_dirtySections: Array<bool>;
|
||||
_dirtyRows: Array<Array<boolean>>;
|
||||
_dirtySections: Array<boolean>;
|
||||
_cachedRowCount: number;
|
||||
|
||||
// These two 'protected' variables are accessed by ListView to iterate over
|
||||
@@ -324,7 +332,7 @@ class ListViewDataSource {
|
||||
_calculateDirtyArrays(
|
||||
prevDataBlob: any,
|
||||
prevSectionIDs: Array<string>,
|
||||
prevRowIDs: Array<Array<string>>
|
||||
prevRowIDs: Array<Array<string>>,
|
||||
): void {
|
||||
// construct a hashmap of the existing (old) id arrays
|
||||
var prevSectionsHash = keyedDictionaryFromArray(prevSectionIDs);
|
||||
@@ -333,7 +341,7 @@ class ListViewDataSource {
|
||||
var sectionID = prevSectionIDs[ii];
|
||||
warning(
|
||||
!prevRowsHash[sectionID],
|
||||
'SectionID appears more than once: ' + sectionID
|
||||
'SectionID appears more than once: ' + sectionID,
|
||||
);
|
||||
prevRowsHash[sectionID] = keyedDictionaryFromArray(prevRowIDs[ii]);
|
||||
}
|
||||
@@ -351,13 +359,17 @@ class ListViewDataSource {
|
||||
if (!dirty && sectionHeaderHasChanged) {
|
||||
dirty = sectionHeaderHasChanged(
|
||||
this._getSectionHeaderData(prevDataBlob, sectionID),
|
||||
this._getSectionHeaderData(this._dataBlob, sectionID)
|
||||
this._getSectionHeaderData(this._dataBlob, sectionID),
|
||||
);
|
||||
}
|
||||
this._dirtySections.push(!!dirty);
|
||||
|
||||
this._dirtyRows[sIndex] = [];
|
||||
for (var rIndex = 0; rIndex < this.rowIdentities[sIndex].length; rIndex++) {
|
||||
for (
|
||||
var rIndex = 0;
|
||||
rIndex < this.rowIdentities[sIndex].length;
|
||||
rIndex++
|
||||
) {
|
||||
var rowID = this.rowIdentities[sIndex][rIndex];
|
||||
// dirty if the section is new, row is new or _rowHasChanged is true
|
||||
dirty =
|
||||
@@ -365,7 +377,7 @@ class ListViewDataSource {
|
||||
!prevRowsHash[sectionID][rowID] ||
|
||||
this._rowHasChanged(
|
||||
this._getRowData(prevDataBlob, sectionID, rowID),
|
||||
this._getRowData(this._dataBlob, sectionID, rowID)
|
||||
this._getRowData(this._dataBlob, sectionID, rowID),
|
||||
);
|
||||
this._dirtyRows[sIndex].push(!!dirty);
|
||||
}
|
||||
@@ -395,5 +407,4 @@ function keyedDictionaryFromArray(arr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
module.exports = ListViewDataSource;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -18,8 +19,8 @@ const StaticRenderer = require('StaticRenderer');
|
||||
class ListViewMock extends React.Component {
|
||||
static latestRef: ?ListViewMock;
|
||||
static defaultProps = {
|
||||
renderScrollComponent: (props) => <ScrollView {...props} />,
|
||||
}
|
||||
renderScrollComponent: props => <ScrollView {...props} />,
|
||||
};
|
||||
componentDidMount() {
|
||||
ListViewMock.latestRef = this;
|
||||
}
|
||||
@@ -41,9 +42,9 @@ class ListViewMock extends React.Component {
|
||||
null,
|
||||
dataSource.getRowData(sectionIdx, rowIdx),
|
||||
sectionID,
|
||||
rowID
|
||||
rowID,
|
||||
)}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule MetroListView
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -62,10 +63,18 @@ class MetroListView extends React.Component {
|
||||
scrollToEnd(params?: ?{animated?: ?boolean}) {
|
||||
throw new Error('scrollToEnd not supported in legacy ListView.');
|
||||
}
|
||||
scrollToIndex(params: {animated?: ?boolean, index: number, viewPosition?: number}) {
|
||||
scrollToIndex(params: {
|
||||
animated?: ?boolean,
|
||||
index: number,
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
throw new Error('scrollToIndex not supported in legacy ListView.');
|
||||
}
|
||||
scrollToItem(params: {animated?: ?boolean, item: Item, viewPosition?: number}) {
|
||||
scrollToItem(params: {
|
||||
animated?: ?boolean,
|
||||
item: Item,
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
throw new Error('scrollToItem not supported in legacy ListView.');
|
||||
}
|
||||
scrollToLocation(params: {
|
||||
@@ -80,12 +89,17 @@ class MetroListView extends React.Component {
|
||||
scrollToOffset(params: {animated?: ?boolean, offset: number}) {
|
||||
const {animated, offset} = params;
|
||||
this._listRef.scrollTo(
|
||||
this.props.horizontal ? {x: offset, animated} : {y: offset, animated}
|
||||
this.props.horizontal ? {x: offset, animated} : {y: offset, animated},
|
||||
);
|
||||
}
|
||||
getListRef() {
|
||||
return this._listRef;
|
||||
}
|
||||
setNativeProps(props: Object) {
|
||||
if (this._listRef) {
|
||||
this._listRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
static defaultProps: DefaultProps = {
|
||||
keyExtractor: (item, index) => item.key || String(index),
|
||||
renderScrollComponent: (props: Props) => {
|
||||
@@ -106,19 +120,17 @@ class MetroListView extends React.Component {
|
||||
}
|
||||
},
|
||||
};
|
||||
state = this._computeState(
|
||||
this.props,
|
||||
{
|
||||
ds: new ListView.DataSource({
|
||||
rowHasChanged: (itemA, itemB) => true,
|
||||
sectionHeaderHasChanged: () => true,
|
||||
getSectionHeaderData: (dataBlob, sectionID) => this.state.sectionHeaderData[sectionID],
|
||||
}),
|
||||
sectionHeaderData: {},
|
||||
},
|
||||
);
|
||||
state = this._computeState(this.props, {
|
||||
ds: new ListView.DataSource({
|
||||
rowHasChanged: (itemA, itemB) => true,
|
||||
sectionHeaderHasChanged: () => true,
|
||||
getSectionHeaderData: (dataBlob, sectionID) =>
|
||||
this.state.sectionHeaderData[sectionID],
|
||||
}),
|
||||
sectionHeaderData: {},
|
||||
});
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
this.setState((state) => this._computeState(newProps, state));
|
||||
this.setState(state => this._computeState(newProps, state));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
@@ -134,7 +146,9 @@ class MetroListView extends React.Component {
|
||||
);
|
||||
}
|
||||
_listRef: ListView;
|
||||
_captureRef = (ref) => { this._listRef = ref; };
|
||||
_captureRef = ref => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
_computeState(props: Props, state) {
|
||||
const sectionHeaderData = {};
|
||||
if (props.sections) {
|
||||
@@ -163,10 +177,14 @@ class MetroListView extends React.Component {
|
||||
};
|
||||
_renderSectionHeader = (section, sectionID) => {
|
||||
const {renderSectionHeader} = this.props;
|
||||
invariant(renderSectionHeader, 'Must provide renderSectionHeader with sections prop');
|
||||
invariant(
|
||||
renderSectionHeader,
|
||||
'Must provide renderSectionHeader with sections prop',
|
||||
);
|
||||
return renderSectionHeader({section});
|
||||
}
|
||||
_renderSeparator = (sID, rID) => <this.props.SeparatorComponent key={sID + rID} />;
|
||||
};
|
||||
_renderSeparator = (sID, rID) =>
|
||||
<this.props.SeparatorComponent key={sID + rID} />;
|
||||
}
|
||||
|
||||
module.exports = MetroListView;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule SectionList
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -121,12 +122,16 @@ type OptionalProps<SectionT: SectionBase<any>> = {
|
||||
* to improve perceived performance of scroll-to-top actions.
|
||||
*/
|
||||
initialNumToRender: number,
|
||||
/**
|
||||
* Reverses the direction of scroll. Uses scale transforms of -1.
|
||||
*/
|
||||
inverted?: ?boolean,
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index. Key is used for caching
|
||||
* and as the react key to track item re-ordering. The default extractor checks item.key, then
|
||||
* falls back to using the index, like react does.
|
||||
* falls back to using the index, like react does. Note that this sets keys for each item, but
|
||||
* each overall section still needs its own key.
|
||||
*/
|
||||
|
||||
keyExtractor: (item: Item, index: number) => string,
|
||||
/**
|
||||
* Called once when the scroll position gets within `onEndReachedThreshold` of the rendered
|
||||
@@ -181,9 +186,9 @@ type OptionalProps<SectionT: SectionBase<any>> = {
|
||||
legacyImplementation?: ?boolean,
|
||||
};
|
||||
|
||||
type Props<SectionT> = RequiredProps<SectionT>
|
||||
& OptionalProps<SectionT>
|
||||
& VirtualizedSectionListProps<SectionT>;
|
||||
type Props<SectionT> = RequiredProps<SectionT> &
|
||||
OptionalProps<SectionT> &
|
||||
VirtualizedSectionListProps<SectionT>;
|
||||
|
||||
const defaultProps = {
|
||||
...VirtualizedSectionList.defaultProps,
|
||||
@@ -230,7 +235,7 @@ type DefaultProps = typeof defaultProps;
|
||||
* />
|
||||
*
|
||||
* This is a convenience wrapper around [`<VirtualizedList>`](docs/virtualizedlist.html),
|
||||
* and thus inherits it's props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* here, along with the following caveats:
|
||||
*
|
||||
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
|
||||
@@ -247,9 +252,11 @@ type DefaultProps = typeof defaultProps;
|
||||
* Alternatively, you can provide a custom `keyExtractor` prop.
|
||||
*
|
||||
*/
|
||||
class SectionList<SectionT: SectionBase<any>>
|
||||
extends React.PureComponent<DefaultProps, Props<SectionT>, void>
|
||||
{
|
||||
class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
|
||||
DefaultProps,
|
||||
Props<SectionT>,
|
||||
void,
|
||||
> {
|
||||
props: Props<SectionT>;
|
||||
static defaultProps: DefaultProps = defaultProps;
|
||||
|
||||
@@ -311,12 +318,16 @@ class SectionList<SectionT: SectionBase<any>>
|
||||
}
|
||||
|
||||
render() {
|
||||
const List = this.props.legacyImplementation ? MetroListView : VirtualizedSectionList;
|
||||
const List = this.props.legacyImplementation
|
||||
? MetroListView
|
||||
: VirtualizedSectionList;
|
||||
return <List {...this.props} ref={this._captureRef} />;
|
||||
}
|
||||
|
||||
_wrapperListRef: MetroListView | VirtualizedSectionList<any>;
|
||||
_captureRef = (ref) => { this._wrapperListRef = ref; };
|
||||
_captureRef = ref => {
|
||||
this._wrapperListRef = ref;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = SectionList;
|
||||
|
||||
@@ -8,12 +8,19 @@
|
||||
*
|
||||
* @providesModule ViewabilityHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
export type ViewToken = {item: any, key: string, index: ?number, isViewable: boolean, section?: any};
|
||||
export type ViewToken = {
|
||||
item: any,
|
||||
key: string,
|
||||
index: ?number,
|
||||
isViewable: boolean,
|
||||
section?: any,
|
||||
};
|
||||
|
||||
export type ViewabilityConfig = {|
|
||||
/**
|
||||
@@ -64,7 +71,9 @@ class ViewabilityHelper {
|
||||
_viewableIndices: Array<number> = [];
|
||||
_viewableItems: Map<string, ViewToken> = new Map();
|
||||
|
||||
constructor(config: ViewabilityConfig = {viewAreaCoveragePercentThreshold: 0}) {
|
||||
constructor(
|
||||
config: ViewabilityConfig = {viewAreaCoveragePercentThreshold: 0},
|
||||
) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
@@ -85,14 +94,19 @@ class ViewabilityHelper {
|
||||
getFrameMetrics: (index: number) => ?{length: number, offset: number},
|
||||
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
|
||||
): Array<number> {
|
||||
const {itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold} = this._config;
|
||||
const {
|
||||
itemVisiblePercentThreshold,
|
||||
viewAreaCoveragePercentThreshold,
|
||||
} = this._config;
|
||||
const viewAreaMode = viewAreaCoveragePercentThreshold != null;
|
||||
const viewablePercentThreshold = viewAreaMode ?
|
||||
viewAreaCoveragePercentThreshold :
|
||||
itemVisiblePercentThreshold;
|
||||
const viewablePercentThreshold = viewAreaMode
|
||||
? viewAreaCoveragePercentThreshold
|
||||
: itemVisiblePercentThreshold;
|
||||
invariant(
|
||||
viewablePercentThreshold != null &&
|
||||
(itemVisiblePercentThreshold != null) !== (viewAreaCoveragePercentThreshold != null),
|
||||
itemVisiblePercentThreshold !=
|
||||
null !==
|
||||
(viewAreaCoveragePercentThreshold != null),
|
||||
'Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold',
|
||||
);
|
||||
const viewableIndices = [];
|
||||
@@ -103,7 +117,7 @@ class ViewabilityHelper {
|
||||
const {first, last} = renderRange || {first: 0, last: itemCount - 1};
|
||||
invariant(
|
||||
last < itemCount,
|
||||
'Invalid render range ' + JSON.stringify({renderRange, itemCount})
|
||||
'Invalid render range ' + JSON.stringify({renderRange, itemCount}),
|
||||
);
|
||||
for (let idx = first; idx <= last; idx++) {
|
||||
const metrics = getFrameMetrics(idx);
|
||||
@@ -112,16 +126,18 @@ class ViewabilityHelper {
|
||||
}
|
||||
const top = metrics.offset - scrollOffset;
|
||||
const bottom = top + metrics.length;
|
||||
if ((top < viewportHeight) && (bottom > 0)) {
|
||||
if (top < viewportHeight && bottom > 0) {
|
||||
firstVisible = idx;
|
||||
if (_isViewable(
|
||||
viewAreaMode,
|
||||
viewablePercentThreshold,
|
||||
top,
|
||||
bottom,
|
||||
viewportHeight,
|
||||
metrics.length,
|
||||
)) {
|
||||
if (
|
||||
_isViewable(
|
||||
viewAreaMode,
|
||||
viewablePercentThreshold,
|
||||
top,
|
||||
bottom,
|
||||
viewportHeight,
|
||||
metrics.length,
|
||||
)
|
||||
) {
|
||||
viewableIndices.push(idx);
|
||||
}
|
||||
} else if (firstVisible >= 0) {
|
||||
@@ -141,7 +157,10 @@ class ViewabilityHelper {
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (index: number) => ?{length: number, offset: number},
|
||||
createViewToken: (index: number, isViewable: boolean) => ViewToken,
|
||||
onViewableItemsChanged: ({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void,
|
||||
onViewableItemsChanged: ({
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
}) => void,
|
||||
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
|
||||
): void {
|
||||
const updateTime = Date.now();
|
||||
@@ -149,7 +168,9 @@ class ViewabilityHelper {
|
||||
// Only count updates after the first item is rendered and has a frame.
|
||||
this._lastUpdateTime = updateTime;
|
||||
}
|
||||
const updateElapsed = this._lastUpdateTime ? updateTime - this._lastUpdateTime : 0;
|
||||
const updateElapsed = this._lastUpdateTime
|
||||
? updateTime - this._lastUpdateTime
|
||||
: 0;
|
||||
if (this._config.waitForInteraction && !this._hasInteracted) {
|
||||
return;
|
||||
}
|
||||
@@ -163,25 +184,35 @@ class ViewabilityHelper {
|
||||
renderRange,
|
||||
);
|
||||
}
|
||||
if (this._viewableIndices.length === viewableIndices.length &&
|
||||
this._viewableIndices.every((v, ii) => v === viewableIndices[ii])) {
|
||||
if (
|
||||
this._viewableIndices.length === viewableIndices.length &&
|
||||
this._viewableIndices.every((v, ii) => v === viewableIndices[ii])
|
||||
) {
|
||||
// We might get a lot of scroll events where visibility doesn't change and we don't want to do
|
||||
// extra work in those cases.
|
||||
return;
|
||||
}
|
||||
this._viewableIndices = viewableIndices;
|
||||
this._lastUpdateTime = updateTime;
|
||||
if (this._config.minimumViewTime && updateElapsed < this._config.minimumViewTime) {
|
||||
const handle = setTimeout(
|
||||
() => {
|
||||
this._timers.delete(handle);
|
||||
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
|
||||
},
|
||||
this._config.minimumViewTime,
|
||||
);
|
||||
if (
|
||||
this._config.minimumViewTime &&
|
||||
updateElapsed < this._config.minimumViewTime
|
||||
) {
|
||||
const handle = setTimeout(() => {
|
||||
this._timers.delete(handle);
|
||||
this._onUpdateSync(
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
);
|
||||
}, this._config.minimumViewTime);
|
||||
this._timers.add(handle);
|
||||
} else {
|
||||
this._onUpdateSync(viewableIndices, onViewableItemsChanged, createViewToken);
|
||||
this._onUpdateSync(
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,17 +223,21 @@ class ViewabilityHelper {
|
||||
this._hasInteracted = true;
|
||||
}
|
||||
|
||||
_onUpdateSync(viewableIndicesToCheck, onViewableItemsChanged, createViewToken) {
|
||||
_onUpdateSync(
|
||||
viewableIndicesToCheck,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
) {
|
||||
// Filter out indices that have gone out of view since this call was scheduled.
|
||||
viewableIndicesToCheck = viewableIndicesToCheck.filter(
|
||||
(ii) => this._viewableIndices.includes(ii)
|
||||
viewableIndicesToCheck = viewableIndicesToCheck.filter(ii =>
|
||||
this._viewableIndices.includes(ii),
|
||||
);
|
||||
const prevItems = this._viewableItems;
|
||||
const nextItems = new Map(
|
||||
viewableIndicesToCheck.map(ii => {
|
||||
const viewable = createViewToken(ii, true);
|
||||
return [viewable.key, viewable];
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const changed = [];
|
||||
@@ -218,12 +253,14 @@ class ViewabilityHelper {
|
||||
}
|
||||
if (changed.length > 0) {
|
||||
this._viewableItems = nextItems;
|
||||
onViewableItemsChanged({viewableItems: Array.from(nextItems.values()), changed});
|
||||
onViewableItemsChanged({
|
||||
viewableItems: Array.from(nextItems.values()),
|
||||
changed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _isViewable(
|
||||
viewAreaMode: boolean,
|
||||
viewablePercentThreshold: number,
|
||||
@@ -231,12 +268,13 @@ function _isViewable(
|
||||
bottom: number,
|
||||
viewportHeight: number,
|
||||
itemLength: number,
|
||||
): bool {
|
||||
): boolean {
|
||||
if (_isEntirelyVisible(top, bottom, viewportHeight)) {
|
||||
return true;
|
||||
} else {
|
||||
const pixels = _getPixelsVisible(top, bottom, viewportHeight);
|
||||
const percent = 100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
|
||||
const percent =
|
||||
100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
|
||||
return percent >= viewablePercentThreshold;
|
||||
}
|
||||
}
|
||||
@@ -244,7 +282,7 @@ function _isViewable(
|
||||
function _getPixelsVisible(
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: number
|
||||
viewportHeight: number,
|
||||
): number {
|
||||
const visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
|
||||
return Math.max(0, visibleHeight);
|
||||
@@ -253,8 +291,8 @@ function _getPixelsVisible(
|
||||
function _isEntirelyVisible(
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: number
|
||||
): bool {
|
||||
viewportHeight: number,
|
||||
): boolean {
|
||||
return top >= 0 && bottom <= viewportHeight && bottom > top;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule VirtualizeUtils
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -33,7 +34,8 @@ function elementsThatOverlapOffsets(
|
||||
if (kk === offsets.length - 1) {
|
||||
invariant(
|
||||
out.length === offsets.length,
|
||||
'bad offsets input, should be in increasing order ' + JSON.stringify(offsets)
|
||||
'bad offsets input, should be in increasing order ' +
|
||||
JSON.stringify(offsets),
|
||||
);
|
||||
return out;
|
||||
}
|
||||
@@ -53,11 +55,15 @@ function newRangeCount(
|
||||
prev: {first: number, last: number},
|
||||
next: {first: number, last: number},
|
||||
): number {
|
||||
return (next.last - next.first + 1) -
|
||||
return (
|
||||
next.last -
|
||||
next.first +
|
||||
1 -
|
||||
Math.max(
|
||||
0,
|
||||
1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first)
|
||||
);
|
||||
1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +81,12 @@ function computeWindowedRenderLimits(
|
||||
},
|
||||
prev: {first: number, last: number},
|
||||
getFrameMetricsApprox: (index: number) => {length: number, offset: number},
|
||||
scrollMetrics: {dt: number, offset: number, velocity: number, visibleLength: number},
|
||||
scrollMetrics: {
|
||||
dt: number,
|
||||
offset: number,
|
||||
velocity: number,
|
||||
visibleLength: number,
|
||||
},
|
||||
): {first: number, last: number} {
|
||||
const {data, getItemCount, maxToRenderPerBatch, windowSize} = props;
|
||||
const itemCount = getItemCount(data);
|
||||
@@ -94,9 +105,13 @@ function computeWindowedRenderLimits(
|
||||
// Considering velocity seems to introduce more churn than it's worth.
|
||||
const leadFactor = 0.5; // Math.max(0, Math.min(1, velocity / 25 + 0.5));
|
||||
|
||||
const fillPreference = velocity > 1 ? 'after' : (velocity < -1 ? 'before' : 'none');
|
||||
const fillPreference =
|
||||
velocity > 1 ? 'after' : velocity < -1 ? 'before' : 'none';
|
||||
|
||||
const overscanBegin = Math.max(0, visibleBegin - (1 - leadFactor) * overscanLength);
|
||||
const overscanBegin = Math.max(
|
||||
0,
|
||||
visibleBegin - (1 - leadFactor) * overscanLength,
|
||||
);
|
||||
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
|
||||
|
||||
// Find the indices that correspond to the items at the render boundaries we're targetting.
|
||||
@@ -107,8 +122,11 @@ function computeWindowedRenderLimits(
|
||||
);
|
||||
overscanFirst = overscanFirst == null ? 0 : overscanFirst;
|
||||
first = first == null ? Math.max(0, overscanFirst) : first;
|
||||
overscanLast = overscanLast == null ? (itemCount - 1) : overscanLast;
|
||||
last = last == null ? Math.min(overscanLast, first + maxToRenderPerBatch - 1) : last;
|
||||
overscanLast = overscanLast == null ? itemCount - 1 : overscanLast;
|
||||
last =
|
||||
last == null
|
||||
? Math.min(overscanLast, first + maxToRenderPerBatch - 1)
|
||||
: last;
|
||||
const visible = {first, last};
|
||||
|
||||
// We want to limit the number of new cells we're rendering per batch so that we can fill the
|
||||
@@ -124,9 +142,11 @@ function computeWindowedRenderLimits(
|
||||
}
|
||||
const maxNewCells = newCellCount >= maxToRenderPerBatch;
|
||||
const firstWillAddMore = first <= prev.first || first > prev.last;
|
||||
const firstShouldIncrement = first > overscanFirst && (!maxNewCells || !firstWillAddMore);
|
||||
const firstShouldIncrement =
|
||||
first > overscanFirst && (!maxNewCells || !firstWillAddMore);
|
||||
const lastWillAddMore = last >= prev.last || last < prev.first;
|
||||
const lastShouldIncrement = last < overscanLast && (!maxNewCells || !lastWillAddMore);
|
||||
const lastShouldIncrement =
|
||||
last < overscanLast && (!maxNewCells || !lastWillAddMore);
|
||||
if (maxNewCells && !firstShouldIncrement && !lastShouldIncrement) {
|
||||
// We only want to stop if we've hit maxNewCells AND we cannot increment first or last
|
||||
// without rendering new items. This let's us preserve as many already rendered items as
|
||||
@@ -134,29 +154,47 @@ function computeWindowedRenderLimits(
|
||||
// possible.
|
||||
break;
|
||||
}
|
||||
if (firstShouldIncrement &&
|
||||
!(fillPreference === 'after' && lastShouldIncrement && lastWillAddMore)) {
|
||||
if (
|
||||
firstShouldIncrement &&
|
||||
!(fillPreference === 'after' && lastShouldIncrement && lastWillAddMore)
|
||||
) {
|
||||
if (firstWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
first--;
|
||||
}
|
||||
if (lastShouldIncrement &&
|
||||
!(fillPreference === 'before' && firstShouldIncrement && firstWillAddMore)) {
|
||||
if (
|
||||
lastShouldIncrement &&
|
||||
!(fillPreference === 'before' && firstShouldIncrement && firstWillAddMore)
|
||||
) {
|
||||
if (lastWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
last++;
|
||||
}
|
||||
}
|
||||
if (!(
|
||||
last >= first &&
|
||||
first >= 0 && last < itemCount &&
|
||||
first >= overscanFirst && last <= overscanLast &&
|
||||
first <= visible.first && last >= visible.last
|
||||
)) {
|
||||
throw new Error('Bad window calculation ' +
|
||||
JSON.stringify({first, last, itemCount, overscanFirst, overscanLast, visible}));
|
||||
if (
|
||||
!(
|
||||
last >= first &&
|
||||
first >= 0 &&
|
||||
last < itemCount &&
|
||||
first >= overscanFirst &&
|
||||
last <= overscanLast &&
|
||||
first <= visible.first &&
|
||||
last >= visible.last
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'Bad window calculation ' +
|
||||
JSON.stringify({
|
||||
first,
|
||||
last,
|
||||
itemCount,
|
||||
overscanFirst,
|
||||
overscanLast,
|
||||
visible,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return {first, last};
|
||||
}
|
||||
|
||||
+457
-164
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
*
|
||||
* @providesModule VirtualizedSectionList
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -108,19 +109,23 @@ type OptionalProps<SectionT: SectionBase> = {
|
||||
* Called when the viewability of rows changes, as defined by the
|
||||
* `viewabilityConfig` prop.
|
||||
*/
|
||||
onViewableItemsChanged?: ?({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void,
|
||||
onViewableItemsChanged?: ?({
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
}) => void,
|
||||
/**
|
||||
* Set this true while waiting for new data from a refresh.
|
||||
*/
|
||||
refreshing?: ?boolean,
|
||||
};
|
||||
|
||||
export type Props<SectionT> =
|
||||
RequiredProps<SectionT> &
|
||||
export type Props<SectionT> = RequiredProps<SectionT> &
|
||||
OptionalProps<SectionT> &
|
||||
VirtualizedListProps;
|
||||
|
||||
type DefaultProps = (typeof VirtualizedList.defaultProps) & {data: $ReadOnlyArray<Item>};
|
||||
type DefaultProps = typeof VirtualizedList.defaultProps & {
|
||||
data: $ReadOnlyArray<Item>,
|
||||
};
|
||||
type State = {childProps: VirtualizedListProps};
|
||||
|
||||
/**
|
||||
@@ -128,9 +133,11 @@ type State = {childProps: VirtualizedListProps};
|
||||
* hood. The only operation that might not scale well is concatting the data arrays of all the
|
||||
* sections when new props are received, which should be plenty fast for up to ~10,000 items.
|
||||
*/
|
||||
class VirtualizedSectionList<SectionT: SectionBase>
|
||||
extends React.PureComponent<DefaultProps, Props<SectionT>, State>
|
||||
{
|
||||
class VirtualizedSectionList<SectionT: SectionBase> extends React.PureComponent<
|
||||
DefaultProps,
|
||||
Props<SectionT>,
|
||||
State,
|
||||
> {
|
||||
props: Props<SectionT>;
|
||||
|
||||
state: State;
|
||||
@@ -141,7 +148,10 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
};
|
||||
|
||||
scrollToLocation(params: {
|
||||
animated?: ?boolean, itemIndex: number, sectionIndex: number, viewPosition?: number
|
||||
animated?: ?boolean,
|
||||
itemIndex: number,
|
||||
sectionIndex: number,
|
||||
viewPosition?: number,
|
||||
}) {
|
||||
let index = params.itemIndex + 1;
|
||||
for (let ii = 0; ii < params.sectionIndex; ii++) {
|
||||
@@ -229,16 +239,22 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
};
|
||||
};
|
||||
|
||||
_onViewableItemsChanged = (
|
||||
{viewableItems, changed}: {viewableItems: Array<ViewToken>, changed: Array<ViewToken>}
|
||||
) => {
|
||||
_onViewableItemsChanged = ({
|
||||
viewableItems,
|
||||
changed,
|
||||
}: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
}) => {
|
||||
if (this.props.onViewableItemsChanged) {
|
||||
this.props.onViewableItemsChanged({
|
||||
viewableItems: viewableItems.map(this._convertViewable, this).filter(Boolean),
|
||||
viewableItems: viewableItems
|
||||
.map(this._convertViewable, this)
|
||||
.filter(Boolean),
|
||||
changed: changed.map(this._convertViewable, this).filter(Boolean),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_renderItem = ({item, index}: {item: Item, index: number}) => {
|
||||
const info = this._subExtractor(index);
|
||||
@@ -262,9 +278,8 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
return (
|
||||
<ItemWithSeparator
|
||||
SeparatorComponent={SeparatorComponent}
|
||||
LeadingSeparatorComponent={infoIndex === 0
|
||||
? this.props.SectionSeparatorComponent
|
||||
: undefined
|
||||
LeadingSeparatorComponent={
|
||||
infoIndex === 0 ? this.props.SectionSeparatorComponent : undefined
|
||||
}
|
||||
cellKey={info.key}
|
||||
index={infoIndex}
|
||||
@@ -273,7 +288,9 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
leadingSection={info.leadingSection}
|
||||
onUpdateSeparator={this._onUpdateSeparator}
|
||||
prevCellKey={(this._subExtractor(index - 1) || {}).key}
|
||||
ref={(ref) => {this._cellRefs[info.key] = ref;}}
|
||||
ref={ref => {
|
||||
this._cellRefs[info.key] = ref;
|
||||
}}
|
||||
renderItem={renderItem}
|
||||
section={info.section}
|
||||
trailingItem={info.trailingItem}
|
||||
@@ -310,13 +327,10 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
_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
|
||||
);
|
||||
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: {
|
||||
@@ -327,9 +341,12 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
getItemCount: () => itemCount,
|
||||
getItem,
|
||||
keyExtractor: this._keyExtractor,
|
||||
onViewableItemsChanged:
|
||||
props.onViewableItemsChanged ? this._onViewableItemsChanged : undefined,
|
||||
stickyHeaderIndices: props.stickySectionHeadersEnabled ? stickyHeaderIndices : undefined,
|
||||
onViewableItemsChanged: props.onViewableItemsChanged
|
||||
? this._onViewableItemsChanged
|
||||
: undefined,
|
||||
stickyHeaderIndices: props.stickySectionHeadersEnabled
|
||||
? stickyHeaderIndices
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -344,12 +361,16 @@ class VirtualizedSectionList<SectionT: SectionBase>
|
||||
}
|
||||
|
||||
render() {
|
||||
return <VirtualizedList {...this.state.childProps} ref={this._captureRef} />;
|
||||
return (
|
||||
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
|
||||
);
|
||||
}
|
||||
|
||||
_cellRefs = {};
|
||||
_listRef: VirtualizedList;
|
||||
_captureRef = (ref) => { this._listRef = ref; };
|
||||
_captureRef = ref => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
}
|
||||
|
||||
class ItemWithSeparator extends React.Component {
|
||||
@@ -390,40 +411,62 @@ class ItemWithSeparator extends React.Component {
|
||||
|
||||
_separators = {
|
||||
highlight: () => {
|
||||
['leading', 'trailing'].forEach(s => this._separators.updateProps(s, {highlighted: true}));
|
||||
['leading', 'trailing'].forEach(s =>
|
||||
this._separators.updateProps(s, {highlighted: true}),
|
||||
);
|
||||
},
|
||||
unhighlight: () => {
|
||||
['leading', 'trailing'].forEach(s => this._separators.updateProps(s, {highlighted: false}));
|
||||
['leading', 'trailing'].forEach(s =>
|
||||
this._separators.updateProps(s, {highlighted: false}),
|
||||
);
|
||||
},
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => {
|
||||
const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props;
|
||||
if (select === 'leading' && LeadingSeparatorComponent) {
|
||||
this.setState(state => ({
|
||||
leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps}
|
||||
leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps},
|
||||
}));
|
||||
} else {
|
||||
this.props.onUpdateSeparator((select === 'leading' && prevCellKey) || cellKey, newProps);
|
||||
this.props.onUpdateSeparator(
|
||||
(select === 'leading' && prevCellKey) || cellKey,
|
||||
newProps,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
updateSeparatorProps(newProps: Object) {
|
||||
this.setState(state => ({separatorProps: {...state.separatorProps, ...newProps}}));
|
||||
this.setState(state => ({
|
||||
separatorProps: {...state.separatorProps, ...newProps},
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const {LeadingSeparatorComponent, SeparatorComponent, item, index, section} = this.props;
|
||||
const {
|
||||
LeadingSeparatorComponent,
|
||||
SeparatorComponent,
|
||||
item,
|
||||
index,
|
||||
section,
|
||||
} = this.props;
|
||||
const element = this.props.renderItem({
|
||||
item,
|
||||
index,
|
||||
section,
|
||||
separators: this._separators,
|
||||
});
|
||||
const leadingSeparator = LeadingSeparatorComponent &&
|
||||
const leadingSeparator =
|
||||
LeadingSeparatorComponent &&
|
||||
<LeadingSeparatorComponent {...this.state.leadingSeparatorProps} />;
|
||||
const separator = SeparatorComponent && <SeparatorComponent {...this.state.separatorProps} />;
|
||||
return (leadingSeparator || separator)
|
||||
? <View>{leadingSeparator}{element}{separator}</View>
|
||||
const separator =
|
||||
SeparatorComponent &&
|
||||
<SeparatorComponent {...this.state.separatorProps} />;
|
||||
return leadingSeparator || separator
|
||||
? <View>
|
||||
{leadingSeparator}
|
||||
{element}
|
||||
{separator}
|
||||
</View>
|
||||
: element;
|
||||
}
|
||||
}
|
||||
@@ -443,7 +486,7 @@ function getItem(sections: ?$ReadOnlyArray<Item>, index: number): ?Item {
|
||||
// If we are in the bounds of the list's data then return the item.
|
||||
return sections[ii].data[itemIdx];
|
||||
} else {
|
||||
itemIdx -= (sections[ii].data.length + 2); // Add two for the header and footer
|
||||
itemIdx -= sections[ii].data.length + 2; // Add two for the header and footer
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -20,60 +21,82 @@ function renderMyListItem(info: {item: {title: string}, index: number}) {
|
||||
|
||||
module.exports = {
|
||||
testEverythingIsFine() {
|
||||
const data = [{
|
||||
title: 'Title Text',
|
||||
key: 1,
|
||||
}];
|
||||
const data = [
|
||||
{
|
||||
title: 'Title Text',
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testBadDataWithTypicalItem() {
|
||||
// $FlowExpectedError - bad title type 6, should be string
|
||||
const data = [{
|
||||
title: 6,
|
||||
key: 1,
|
||||
}];
|
||||
const data = [
|
||||
{
|
||||
// $FlowExpectedError - bad title type 6, should be string
|
||||
title: 6,
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testMissingFieldWithTypicalItem() {
|
||||
const data = [{
|
||||
key: 1,
|
||||
}];
|
||||
const data = [
|
||||
{
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
// $FlowExpectedError - missing title
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testGoodDataWithBadCustomRenderItemFunction() {
|
||||
const data = [{
|
||||
widget: 6,
|
||||
key: 1,
|
||||
}];
|
||||
const data = [
|
||||
{
|
||||
widget: 6,
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<FlatList
|
||||
renderItem={(info) =>
|
||||
// $FlowExpectedError - bad widgetCount type 6, should be Object
|
||||
<span>{info.item.widget.missingProp}</span>
|
||||
}
|
||||
renderItem={info =>
|
||||
<span>
|
||||
{
|
||||
// $FlowExpectedError - bad widgetCount type 6, should be Object
|
||||
info.item.widget.missingProp
|
||||
}
|
||||
</span>}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
testBadRenderItemFunction() {
|
||||
const data = [{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
}];
|
||||
const data = [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return [
|
||||
// $FlowExpectedError - title should be inside `item`
|
||||
<FlatList renderItem={(info: {title: string}) => <span /> } data={data} />,
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
<FlatList renderItem={(info: {item: any, index: string}) => <span /> } data={data} />,
|
||||
// $FlowExpectedError - bad title type number, should be string
|
||||
<FlatList renderItem={(info: {item: {title: number}}) => <span /> } data={data} />,
|
||||
<FlatList renderItem={(info: {title: string}) => <span />} data={data} />,
|
||||
<FlatList
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
renderItem={(info: {item: any, index: string}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
<FlatList
|
||||
// $FlowExpectedError - bad title type number, should be string
|
||||
renderItem={(info: {item: {title: number}}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
// EverythingIsFine
|
||||
<FlatList renderItem={(info: {item: {title: string}}) => <span /> } data={data} />,
|
||||
<FlatList
|
||||
renderItem={(info: {item: {title: string}}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -18,40 +19,66 @@ function renderMyListItem(info: {item: {title: string}, index: number}) {
|
||||
return <span />;
|
||||
}
|
||||
|
||||
const renderMyHeader = ({section}: {section: {fooNumber: number} & Object}) => <span />;
|
||||
const renderMyHeader = ({section}: {section: {fooNumber: number} & Object}) =>
|
||||
<span />;
|
||||
|
||||
module.exports = {
|
||||
testGoodDataWithGoodItem() {
|
||||
const sections = [{
|
||||
key: 'a', data: [{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
}],
|
||||
}];
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return <SectionList renderItem={renderMyListItem} sections={sections} />;
|
||||
},
|
||||
|
||||
testBadRenderItemFunction() {
|
||||
const sections = [{
|
||||
key: 'a', data: [{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
}],
|
||||
}];
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return [
|
||||
// $FlowExpectedError - title should be inside `item`
|
||||
<SectionList renderItem={(info: {title: string}) => <span /> } sections={sections} />,
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
<SectionList renderItem={(info: {index: string}) => <span /> } sections={sections} />,
|
||||
<SectionList
|
||||
renderItem={(info: {title: string}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
<SectionList
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
renderItem={(info: {index: string}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
// EverythingIsFine
|
||||
<SectionList renderItem={(info: {item: {title: string}}) => <span /> } sections={sections} />,
|
||||
<SectionList
|
||||
renderItem={(info: {item: {title: string}}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
|
||||
testBadInheritedDefaultProp(): React.Element<*> {
|
||||
const sections = [];
|
||||
// $FlowExpectedError - bad windowSize type "big"
|
||||
return <SectionList renderItem={renderMyListItem} sections={sections} windowSize="big" />;
|
||||
return (
|
||||
<SectionList
|
||||
renderItem={renderMyListItem}
|
||||
sections={sections}
|
||||
// $FlowExpectedError - bad windowSize type "big"
|
||||
windowSize="big"
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
testMissingData(): React.Element<*> {
|
||||
@@ -60,24 +87,35 @@ module.exports = {
|
||||
},
|
||||
|
||||
testBadSectionsShape(): React.Element<*> {
|
||||
const sections = [{
|
||||
key: 'a', items: [{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
}],
|
||||
}];
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
items: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// $FlowExpectedError - section missing `data` field
|
||||
return <SectionList renderItem={renderMyListItem} sections={sections} />;
|
||||
},
|
||||
|
||||
testBadSectionsMetadata(): React.Element<*> {
|
||||
// $FlowExpectedError - section has bad meta data `fooNumber` field of type string
|
||||
const sections = [{
|
||||
key: 'a', fooNumber: 'string', data: [{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
}],
|
||||
}];
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
// $FlowExpectedError - section has bad meta data `fooNumber` field of type string
|
||||
fooNumber: 'string',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<SectionList
|
||||
renderSectionHeader={renderMyHeader}
|
||||
|
||||
@@ -6,16 +6,22 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('FillRateHelper');
|
||||
|
||||
const FillRateHelper = require('FillRateHelper');
|
||||
|
||||
let rowFramesGlobal;
|
||||
const dataGlobal =
|
||||
[{key: 'header'}, {key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}, {key: 'footer'}];
|
||||
const dataGlobal = [
|
||||
{key: 'header'},
|
||||
{key: 'a'},
|
||||
{key: 'b'},
|
||||
{key: 'c'},
|
||||
{key: 'd'},
|
||||
{key: 'footer'},
|
||||
];
|
||||
function getFrameMetrics(index: number) {
|
||||
const frame = rowFramesGlobal[dataGlobal[index].key];
|
||||
return {length: frame.height, offset: frame.y, inLayout: frame.inLayout};
|
||||
@@ -26,7 +32,7 @@ function computeResult({helper, props, state, scroll}): number {
|
||||
return helper.computeBlankness(
|
||||
{
|
||||
data: dataGlobal,
|
||||
getItemCount: (data2) => data2.length,
|
||||
getItemCount: data2 => data2.length,
|
||||
initialNumToRender: 10,
|
||||
...(props || {}),
|
||||
},
|
||||
@@ -93,8 +99,8 @@ describe('computeBlankness', function() {
|
||||
|
||||
it('can handle multiple listeners and unsubscribe', function() {
|
||||
const listeners = [jest.fn(), jest.fn(), jest.fn()];
|
||||
const subscriptions = listeners.map(
|
||||
(listener) => FillRateHelper.addListener(listener)
|
||||
const subscriptions = listeners.map(listener =>
|
||||
FillRateHelper.addListener(listener),
|
||||
);
|
||||
subscriptions[1].remove();
|
||||
const helper = new FillRateHelper(getFrameMetrics);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
@@ -22,16 +22,13 @@ describe('FlatList', () => {
|
||||
<FlatList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
it('renders empty list', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<FlatList
|
||||
data={[]}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
<FlatList data={[]} renderItem={({item}) => <item value={item.key} />} />,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -40,7 +37,7 @@ describe('FlatList', () => {
|
||||
<FlatList
|
||||
data={undefined}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -58,7 +55,7 @@ describe('FlatList', () => {
|
||||
refreshing={false}
|
||||
onRefresh={jest.fn()}
|
||||
renderItem={({item}) => <item value={item.id} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('SectionList', () => {
|
||||
<SectionList
|
||||
sections={[]}
|
||||
renderItem={({item}) => <item v={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -32,7 +32,7 @@ describe('SectionList', () => {
|
||||
sections={[{key: 's1', data: [{key: 'i1'}, {key: 'i2'}]}]}
|
||||
renderItem={({item}) => <item v={item.key} />}
|
||||
renderSectionHeader={() => null}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -40,17 +40,20 @@ describe('SectionList', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<SectionList
|
||||
initialNumToRender={Infinity}
|
||||
ItemSeparatorComponent={(props) => <defaultItemSeparator v={propStr(props)} />}
|
||||
ListEmptyComponent={(props) => <empty v={propStr(props)} />}
|
||||
ListFooterComponent={(props) => <footer v={propStr(props)} />}
|
||||
ListHeaderComponent={(props) => <header v={propStr(props)} />}
|
||||
SectionSeparatorComponent={(props) => <sectionSeparator v={propStr(props)} />}
|
||||
ItemSeparatorComponent={props =>
|
||||
<defaultItemSeparator v={propStr(props)} />}
|
||||
ListEmptyComponent={props => <empty v={propStr(props)} />}
|
||||
ListFooterComponent={props => <footer v={propStr(props)} />}
|
||||
ListHeaderComponent={props => <header v={propStr(props)} />}
|
||||
SectionSeparatorComponent={props =>
|
||||
<sectionSeparator v={propStr(props)} />}
|
||||
sections={[
|
||||
{
|
||||
renderItem: (props) => <itemForSection1 v={propStr(props)} />,
|
||||
renderItem: props => <itemForSection1 v={propStr(props)} />,
|
||||
key: 's1',
|
||||
keyExtractor: (item, index) => item.id,
|
||||
ItemSeparatorComponent: (props) => <itemSeparatorForSection1 v={propStr(props)} />,
|
||||
ItemSeparatorComponent: props =>
|
||||
<itemSeparatorForSection1 v={propStr(props)} />,
|
||||
data: [{id: 'i1s1'}, {id: 'i2s1'}],
|
||||
},
|
||||
{
|
||||
@@ -64,10 +67,10 @@ describe('SectionList', () => {
|
||||
]}
|
||||
refreshing={false}
|
||||
onRefresh={jest.fn()}
|
||||
renderItem={(props) => <defaultItem v={propStr(props)} />}
|
||||
renderSectionHeader={(props) => <sectionHeader v={propStr(props)} />}
|
||||
renderSectionFooter={(props) => <sectionFooter v={propStr(props)} />}
|
||||
/>
|
||||
renderItem={props => <defaultItem v={propStr(props)} />}
|
||||
renderSectionHeader={props => <sectionHeader v={propStr(props)} />}
|
||||
renderSectionFooter={props => <sectionFooter v={propStr(props)} />}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -76,9 +79,9 @@ describe('SectionList', () => {
|
||||
<SectionList
|
||||
sections={[{key: 's1', data: []}]}
|
||||
renderItem={({item}) => <item v={item.key} />}
|
||||
renderSectionHeader={(props) => <sectionHeader v={propStr(props)} />}
|
||||
renderSectionFooter={(props) => <sectionFooter v={propStr(props)} />}
|
||||
/>
|
||||
renderSectionHeader={props => <sectionHeader v={propStr(props)} />}
|
||||
renderSectionFooter={props => <sectionFooter v={propStr(props)} />}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -87,16 +90,18 @@ describe('SectionList', () => {
|
||||
<SectionList
|
||||
sections={[{key: 's1', data: []}]}
|
||||
renderItem={({item}) => <item v={item.key} />}
|
||||
renderSectionFooter={(props) => <sectionFooter v={propStr(props)} />}
|
||||
/>
|
||||
renderSectionFooter={props => <sectionFooter v={propStr(props)} />}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
function propStr(props) {
|
||||
return Object.keys(props).map(k => {
|
||||
const propObj = props[k] || {};
|
||||
return `${k}:${propObj.key || propObj.id || props[k]}`;
|
||||
}).join(',');
|
||||
return Object.keys(props)
|
||||
.map(k => {
|
||||
const propObj = props[k] || {};
|
||||
return `${k}:${propObj.key || propObj.id || props[k]}`;
|
||||
})
|
||||
.join(',');
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('ViewabilityHelper');
|
||||
|
||||
const ViewabilityHelper = require('ViewabilityHelper');
|
||||
|
||||
let rowFrames;
|
||||
@@ -25,7 +25,9 @@ function createViewToken(index: number, isViewable: boolean) {
|
||||
|
||||
describe('computeViewableItems', function() {
|
||||
it('returns all 4 entirely visible rows as viewable', function() {
|
||||
const helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 50});
|
||||
const helper = new ViewabilityHelper({
|
||||
viewAreaCoveragePercentThreshold: 50,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 50},
|
||||
@@ -33,327 +35,337 @@ describe('computeViewableItems', function() {
|
||||
d: {y: 150, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
expect(helper.computeViewableItems(data.length, 0, 200, getFrameMetrics))
|
||||
.toEqual([0, 1, 2, 3]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 200, getFrameMetrics),
|
||||
).toEqual([0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
it(
|
||||
'returns top 2 rows as viewable (1. entirely visible and 2. majority)',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 50});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
expect(helper.computeViewableItems(data.length, 0, 200, getFrameMetrics))
|
||||
.toEqual([0, 1]);
|
||||
it('returns top 2 rows as viewable (1. entirely visible and 2. majority)', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
viewAreaCoveragePercentThreshold: 50,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 200, getFrameMetrics),
|
||||
).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
it(
|
||||
'returns only 2nd row as viewable (majority)',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 50});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
expect(helper.computeViewableItems(data.length, 25, 200, getFrameMetrics))
|
||||
.toEqual([1]);
|
||||
it('returns only 2nd row as viewable (majority)', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
viewAreaCoveragePercentThreshold: 50,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 25, 200, getFrameMetrics),
|
||||
).toEqual([1]);
|
||||
});
|
||||
|
||||
it(
|
||||
'handles empty input',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 50});
|
||||
rowFrames = {};
|
||||
data = [];
|
||||
expect(helper.computeViewableItems(data.length, 0, 200, getFrameMetrics))
|
||||
.toEqual([]);
|
||||
it('handles empty input', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
viewAreaCoveragePercentThreshold: 50,
|
||||
});
|
||||
rowFrames = {};
|
||||
data = [];
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 200, getFrameMetrics),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it(
|
||||
'handles different view area coverage percent thresholds',
|
||||
function() {
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 500},
|
||||
d: {y: 700, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
it('handles different view area coverage percent thresholds', function() {
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 500},
|
||||
d: {y: 700, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
|
||||
let helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 0});
|
||||
expect(helper.computeViewableItems(data.length, 0, 50, getFrameMetrics))
|
||||
.toEqual([0]);
|
||||
expect(helper.computeViewableItems(data.length, 1, 50, getFrameMetrics))
|
||||
.toEqual([0, 1]);
|
||||
expect(helper.computeViewableItems(data.length, 199, 50, getFrameMetrics))
|
||||
.toEqual([1, 2]);
|
||||
expect(helper.computeViewableItems(data.length, 250, 50, getFrameMetrics))
|
||||
.toEqual([2]);
|
||||
let helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 0});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 50, getFrameMetrics),
|
||||
).toEqual([0]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 1, 50, getFrameMetrics),
|
||||
).toEqual([0, 1]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 199, 50, getFrameMetrics),
|
||||
).toEqual([1, 2]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 250, 50, getFrameMetrics),
|
||||
).toEqual([2]);
|
||||
|
||||
helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 100});
|
||||
expect(helper.computeViewableItems(data.length, 0, 200, getFrameMetrics))
|
||||
.toEqual([0, 1]);
|
||||
expect(helper.computeViewableItems(data.length, 1, 200, getFrameMetrics))
|
||||
.toEqual([1]);
|
||||
expect(helper.computeViewableItems(data.length, 400, 200, getFrameMetrics))
|
||||
.toEqual([2]);
|
||||
expect(helper.computeViewableItems(data.length, 600, 200, getFrameMetrics))
|
||||
.toEqual([3]);
|
||||
helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 100});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 200, getFrameMetrics),
|
||||
).toEqual([0, 1]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 1, 200, getFrameMetrics),
|
||||
).toEqual([1]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 400, 200, getFrameMetrics),
|
||||
).toEqual([2]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 600, 200, getFrameMetrics),
|
||||
).toEqual([3]);
|
||||
|
||||
helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 10});
|
||||
expect(helper.computeViewableItems(data.length, 30, 200, getFrameMetrics))
|
||||
.toEqual([0, 1, 2]);
|
||||
expect(helper.computeViewableItems(data.length, 31, 200, getFrameMetrics))
|
||||
.toEqual([1, 2]);
|
||||
helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 10});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 30, 200, getFrameMetrics),
|
||||
).toEqual([0, 1, 2]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 31, 200, getFrameMetrics),
|
||||
).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it(
|
||||
'handles different item visible percent thresholds',
|
||||
function() {
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
let helper = new ViewabilityHelper({itemVisiblePercentThreshold: 0});
|
||||
expect(helper.computeViewableItems(data.length, 0, 50, getFrameMetrics))
|
||||
.toEqual([0]);
|
||||
expect(helper.computeViewableItems(data.length, 1, 50, getFrameMetrics))
|
||||
.toEqual([0, 1]);
|
||||
it('handles different item visible percent thresholds', function() {
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
b: {y: 50, height: 150},
|
||||
c: {y: 200, height: 50},
|
||||
d: {y: 250, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}];
|
||||
let helper = new ViewabilityHelper({itemVisiblePercentThreshold: 0});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 50, getFrameMetrics),
|
||||
).toEqual([0]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 1, 50, getFrameMetrics),
|
||||
).toEqual([0, 1]);
|
||||
|
||||
helper = new ViewabilityHelper({itemVisiblePercentThreshold: 100});
|
||||
expect(helper.computeViewableItems(data.length, 0, 250, getFrameMetrics))
|
||||
.toEqual([0, 1, 2]);
|
||||
expect(helper.computeViewableItems(data.length, 1, 250, getFrameMetrics))
|
||||
.toEqual([1, 2]);
|
||||
helper = new ViewabilityHelper({itemVisiblePercentThreshold: 100});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 0, 250, getFrameMetrics),
|
||||
).toEqual([0, 1, 2]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 1, 250, getFrameMetrics),
|
||||
).toEqual([1, 2]);
|
||||
|
||||
helper = new ViewabilityHelper({itemVisiblePercentThreshold: 10});
|
||||
expect(helper.computeViewableItems(data.length, 184, 20, getFrameMetrics))
|
||||
.toEqual([1]);
|
||||
expect(helper.computeViewableItems(data.length, 185, 20, getFrameMetrics))
|
||||
.toEqual([1, 2]);
|
||||
expect(helper.computeViewableItems(data.length, 186, 20, getFrameMetrics))
|
||||
.toEqual([2]);
|
||||
helper = new ViewabilityHelper({itemVisiblePercentThreshold: 10});
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 184, 20, getFrameMetrics),
|
||||
).toEqual([1]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 185, 20, getFrameMetrics),
|
||||
).toEqual([1, 2]);
|
||||
expect(
|
||||
helper.computeViewableItems(data.length, 186, 20, getFrameMetrics),
|
||||
).toEqual([2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onUpdate', function() {
|
||||
it(
|
||||
'returns 1 visible row as viewable then scrolls away',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper();
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1); // nothing changed!
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
100,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(2);
|
||||
expect(onViewableItemsChanged.mock.calls[1][0]).toEqual({
|
||||
changed: [{isViewable: false, key: 'a'}],
|
||||
viewableItems: [],
|
||||
});
|
||||
},
|
||||
);
|
||||
it('returns 1 visible row as viewable then scrolls away', function() {
|
||||
const helper = new ViewabilityHelper();
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 50},
|
||||
};
|
||||
data = [{key: 'a'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1); // nothing changed!
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
100,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(2);
|
||||
expect(onViewableItemsChanged.mock.calls[1][0]).toEqual({
|
||||
changed: [{isViewable: false, key: 'a'}],
|
||||
viewableItems: [],
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'returns 1st visible row then 1st and 2nd then just 2nd',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper();
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
100,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(2);
|
||||
// Both visible with 100px overlap each
|
||||
expect(onViewableItemsChanged.mock.calls[1][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'b'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}, {isViewable: true, key: 'b'}],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
200,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(3);
|
||||
expect(onViewableItemsChanged.mock.calls[2][0]).toEqual({
|
||||
changed: [{isViewable: false, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'b'}],
|
||||
});
|
||||
},
|
||||
);
|
||||
it('returns 1st visible row then 1st and 2nd then just 2nd', function() {
|
||||
const helper = new ViewabilityHelper();
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
100,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(2);
|
||||
// Both visible with 100px overlap each
|
||||
expect(onViewableItemsChanged.mock.calls[1][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'b'}],
|
||||
viewableItems: [
|
||||
{isViewable: true, key: 'a'},
|
||||
{isViewable: true, key: 'b'},
|
||||
],
|
||||
});
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
200,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(3);
|
||||
expect(onViewableItemsChanged.mock.calls[2][0]).toEqual({
|
||||
changed: [{isViewable: false, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'b'}],
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'minimumViewTime delays callback',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({minimumViewTime: 350, viewAreaCoveragePercentThreshold: 0});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged).not.toBeCalled();
|
||||
it('minimumViewTime delays callback', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
minimumViewTime: 350,
|
||||
viewAreaCoveragePercentThreshold: 0,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
}
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'minimumViewTime skips briefly visible items',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({minimumViewTime: 350, viewAreaCoveragePercentThreshold: 0});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 250},
|
||||
b: {y: 250, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
300, // scroll past item 'a'
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
it('minimumViewTime skips briefly visible items', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
minimumViewTime: 350,
|
||||
viewAreaCoveragePercentThreshold: 0,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 250},
|
||||
b: {y: 250, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
300, // scroll past item 'a'
|
||||
200,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'b'}],
|
||||
viewableItems: [{isViewable: true, key: 'b'}],
|
||||
});
|
||||
}
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'b'}],
|
||||
viewableItems: [{isViewable: true, key: 'b'}],
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'waitForInteraction blocks callback until interaction',
|
||||
function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
waitForInteraction: true,
|
||||
viewAreaCoveragePercentThreshold: 0,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
100,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged).not.toBeCalled();
|
||||
it('waitForInteraction blocks callback until interaction', function() {
|
||||
const helper = new ViewabilityHelper({
|
||||
waitForInteraction: true,
|
||||
viewAreaCoveragePercentThreshold: 0,
|
||||
});
|
||||
rowFrames = {
|
||||
a: {y: 0, height: 200},
|
||||
b: {y: 200, height: 200},
|
||||
};
|
||||
data = [{key: 'a'}, {key: 'b'}];
|
||||
const onViewableItemsChanged = jest.fn();
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
0,
|
||||
100,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged).not.toBeCalled();
|
||||
|
||||
helper.recordInteraction();
|
||||
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
20,
|
||||
100,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
}
|
||||
);
|
||||
helper.recordInteraction();
|
||||
|
||||
helper.onUpdate(
|
||||
data.length,
|
||||
20,
|
||||
100,
|
||||
getFrameMetrics,
|
||||
createViewToken,
|
||||
onViewableItemsChanged,
|
||||
);
|
||||
expect(onViewableItemsChanged.mock.calls.length).toBe(1);
|
||||
expect(onViewableItemsChanged.mock.calls[0][0]).toEqual({
|
||||
changed: [{isViewable: true, key: 'a'}],
|
||||
viewableItems: [{isViewable: true, key: 'a'}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.unmock('VirtualizeUtils');
|
||||
|
||||
const { elementsThatOverlapOffsets, newRangeCount } = require('VirtualizeUtils');
|
||||
const {elementsThatOverlapOffsets, newRangeCount} = require('VirtualizeUtils');
|
||||
|
||||
describe('newRangeCount', function() {
|
||||
it('handles subset', function() {
|
||||
@@ -46,10 +46,15 @@ describe('elementsThatOverlapOffsets', function() {
|
||||
function getFrameMetrics(index: number) {
|
||||
return {
|
||||
length: 100,
|
||||
offset: (100 * index),
|
||||
offset: 100 * index,
|
||||
};
|
||||
}
|
||||
expect(elementsThatOverlapOffsets(offsets, 100, getFrameMetrics)).toEqual([0, 2, 3, 4]);
|
||||
expect(elementsThatOverlapOffsets(offsets, 100, getFrameMetrics)).toEqual([
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
]);
|
||||
});
|
||||
it('handles variable length', function() {
|
||||
const offsets = [150, 250, 900];
|
||||
@@ -60,7 +65,9 @@ describe('elementsThatOverlapOffsets', function() {
|
||||
{offset: 850, length: 100},
|
||||
{offset: 950, length: 150},
|
||||
];
|
||||
expect(elementsThatOverlapOffsets(offsets, frames.length, (ii) => frames[ii])).toEqual([1,1,3]);
|
||||
expect(
|
||||
elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii]),
|
||||
).toEqual([1, 1, 3]);
|
||||
});
|
||||
it('handles out of bounds', function() {
|
||||
const offsets = [150, 900];
|
||||
@@ -69,6 +76,8 @@ describe('elementsThatOverlapOffsets', function() {
|
||||
{offset: 50, length: 150},
|
||||
{offset: 250, length: 100},
|
||||
];
|
||||
expect(elementsThatOverlapOffsets(offsets, frames.length, (ii) => frames[ii])).toEqual([1]);
|
||||
expect(
|
||||
elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii]),
|
||||
).toEqual([1]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,26 +6,25 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
const VirtualizedList = require('VirtualizedList');
|
||||
|
||||
describe('VirtualizedList', () => {
|
||||
|
||||
it('renders simple list', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
/>
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -36,8 +35,8 @@ describe('VirtualizedList', () => {
|
||||
data={[]}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
/>
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -48,8 +47,8 @@ describe('VirtualizedList', () => {
|
||||
data={undefined}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => 0}
|
||||
/>
|
||||
getItemCount={data => 0}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -62,9 +61,9 @@ describe('VirtualizedList', () => {
|
||||
ListFooterComponent={() => <footer />}
|
||||
ListHeaderComponent={() => <header />}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
getItemCount={data => data.length}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -75,9 +74,9 @@ describe('VirtualizedList', () => {
|
||||
data={[{key: 'hello'}]}
|
||||
ListEmptyComponent={() => <empty />}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
getItemCount={data => data.length}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -91,13 +90,14 @@ describe('VirtualizedList', () => {
|
||||
ListHeaderComponent={() => <header />}
|
||||
data={new Array(5).fill().map((_, ii) => ({id: String(ii)}))}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
keyExtractor={(item, index) => item.id}
|
||||
getItemCount={data => data.length}
|
||||
getItemLayout={({index}) => ({length: 50, offset: index * 50})}
|
||||
refreshing={false}
|
||||
inverted={true}
|
||||
keyExtractor={(item, index) => item.id}
|
||||
onRefresh={jest.fn()}
|
||||
refreshing={false}
|
||||
renderItem={({item}) => <item value={item.id} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -109,7 +109,7 @@ describe('VirtualizedList', () => {
|
||||
getItem={(data, index) => data.get('id_' + index)}
|
||||
getItemCount={(data: Map) => data.size}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
@@ -118,15 +118,15 @@ describe('VirtualizedList', () => {
|
||||
const infos = [];
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
ItemSeparatorComponent={(props) => <separator {...props} />}
|
||||
ItemSeparatorComponent={props => <separator {...props} />}
|
||||
data={[{key: 'i0'}, {key: 'i1'}, {key: 'i2'}]}
|
||||
renderItem={(info) => {
|
||||
renderItem={info => {
|
||||
infos.push(info);
|
||||
return <item title={info.item.key} />;
|
||||
}}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={(data) => data.length}
|
||||
/>
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
infos[1].separators.highlight();
|
||||
@@ -135,4 +135,28 @@ describe('VirtualizedList', () => {
|
||||
expect(component).toMatchSnapshot();
|
||||
infos[1].separators.unhighlight();
|
||||
});
|
||||
|
||||
it('handles nested lists', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
data={[{key: 'outer0'}, {key: 'outer1'}]}
|
||||
renderItem={outerInfo =>
|
||||
<VirtualizedList
|
||||
data={[
|
||||
{key: outerInfo.item.key + ':inner0'},
|
||||
{key: outerInfo.item.key + ':inner1'},
|
||||
]}
|
||||
horizontal={outerInfo.item.key === 'outer1'}
|
||||
renderItem={innerInfo => {
|
||||
return <item title={innerInfo.item.key} />;
|
||||
}}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
/>}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,12 +46,12 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
refreshControl={
|
||||
<RefreshControlMock
|
||||
onRefresh={[Function]}
|
||||
progressViewOffset={undefined}
|
||||
refreshing={false}
|
||||
/>
|
||||
}
|
||||
refreshing={false}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -61,11 +61,13 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<header />
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
@@ -88,6 +90,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
@@ -110,6 +113,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
@@ -128,6 +132,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<footer />
|
||||
</View>
|
||||
@@ -155,7 +160,6 @@ exports[`FlatList renders empty list 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -185,7 +189,6 @@ exports[`FlatList renders null list 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -227,7 +230,6 @@ exports[`FlatList renders simple list 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -236,6 +238,7 @@ exports[`FlatList renders simple list 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
@@ -243,6 +246,7 @@ exports[`FlatList renders simple list 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i2"
|
||||
@@ -250,6 +254,7 @@ exports[`FlatList renders simple list 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
|
||||
@@ -34,7 +34,6 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
sections={
|
||||
@@ -64,9 +63,11 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
/>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
v="i1"
|
||||
@@ -74,6 +75,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
v="i2"
|
||||
@@ -81,6 +83,7 @@ exports[`SectionList rendering empty section headers is fine 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
/>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
@@ -113,7 +116,6 @@ exports[`SectionList renders a footer when there is no data 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
renderSectionFooter={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
@@ -137,6 +139,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionHeader
|
||||
v="section:s1"
|
||||
@@ -144,6 +147,7 @@ exports[`SectionList renders a footer when there is no data 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionFooter
|
||||
v="section:s1"
|
||||
@@ -180,7 +184,6 @@ exports[`SectionList renders a footer when there is no data and no header 1`] =
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
renderSectionFooter={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
sections={
|
||||
@@ -203,9 +206,11 @@ exports[`SectionList renders a footer when there is no data and no header 1`] =
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
/>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionFooter
|
||||
v="section:s1"
|
||||
@@ -281,12 +286,12 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
refreshControl={
|
||||
<RefreshControlMock
|
||||
onRefresh={[Function]}
|
||||
progressViewOffset={undefined}
|
||||
refreshing={false}
|
||||
/>
|
||||
}
|
||||
refreshing={false}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
renderSectionFooter={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
@@ -345,6 +350,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<header
|
||||
v=""
|
||||
@@ -352,6 +358,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionHeader
|
||||
v="section:s1"
|
||||
@@ -359,6 +366,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<sectionSeparator
|
||||
@@ -374,6 +382,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<itemForSection1
|
||||
@@ -386,6 +395,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionFooter
|
||||
v="section:s1"
|
||||
@@ -393,6 +403,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionHeader
|
||||
v="section:s2"
|
||||
@@ -400,6 +411,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<sectionSeparator
|
||||
@@ -415,6 +427,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<defaultItem
|
||||
@@ -427,6 +440,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionFooter
|
||||
v="section:s2"
|
||||
@@ -434,6 +448,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionHeader
|
||||
v="section:s3"
|
||||
@@ -441,6 +456,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<sectionSeparator
|
||||
@@ -456,6 +472,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<defaultItem
|
||||
@@ -468,6 +485,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<sectionFooter
|
||||
v="section:s3"
|
||||
@@ -475,6 +493,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<footer
|
||||
v=""
|
||||
@@ -504,7 +523,6 @@ exports[`SectionList renders empty list 1`] = `
|
||||
onScrollEndDrag={[Function]}
|
||||
onViewableItemsChanged={undefined}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
sections={Array []}
|
||||
stickyHeaderIndices={Array []}
|
||||
|
||||
@@ -1,5 +1,150 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VirtualizedList handles nested lists 1`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "outer0",
|
||||
},
|
||||
Object {
|
||||
"key": "outer1",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "outer0:inner0",
|
||||
},
|
||||
Object {
|
||||
"key": "outer0:inner1",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="outer0:inner0"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="outer0:inner1"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "outer1:inner0",
|
||||
},
|
||||
Object {
|
||||
"key": "outer1:inner1",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={true}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="outer1:inner0"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="outer1:inner1"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
<RCTScrollView
|
||||
ItemSeparatorComponent={[Function]}
|
||||
@@ -31,7 +176,6 @@ exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -40,6 +184,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i0"
|
||||
@@ -55,6 +200,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i1"
|
||||
@@ -70,6 +216,7 @@ exports[`VirtualizedList handles separators correctly 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i2"
|
||||
@@ -110,7 +257,6 @@ exports[`VirtualizedList handles separators correctly 2`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -119,6 +265,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i0"
|
||||
@@ -134,6 +281,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i1"
|
||||
@@ -149,6 +297,7 @@ exports[`VirtualizedList handles separators correctly 2`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i2"
|
||||
@@ -189,7 +338,6 @@ exports[`VirtualizedList handles separators correctly 3`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -198,6 +346,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i0"
|
||||
@@ -213,6 +362,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i1"
|
||||
@@ -229,6 +379,7 @@ exports[`VirtualizedList handles separators correctly 3`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
title="i2"
|
||||
@@ -269,6 +420,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
getItemLayout={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
inverted={true}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
@@ -282,14 +434,26 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
refreshControl={
|
||||
<RefreshControlMock
|
||||
onRefresh={[Function]}
|
||||
progressViewOffset={undefined}
|
||||
refreshing={false}
|
||||
/>
|
||||
}
|
||||
refreshing={false}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
@@ -297,11 +461,29 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<header />
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="0"
|
||||
@@ -310,6 +492,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="1"
|
||||
@@ -318,6 +509,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="2"
|
||||
@@ -326,6 +526,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="3"
|
||||
@@ -334,6 +543,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={undefined}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="4"
|
||||
@@ -341,6 +559,15 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleY": -1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<footer />
|
||||
</View>
|
||||
@@ -366,7 +593,6 @@ exports[`VirtualizedList renders empty list 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -397,7 +623,6 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -406,16 +631,19 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<header />
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<empty />
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<footer />
|
||||
</View>
|
||||
@@ -448,7 +676,6 @@ exports[`VirtualizedList renders list with empty component 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -457,6 +684,7 @@ exports[`VirtualizedList renders list with empty component 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="hello"
|
||||
@@ -484,7 +712,6 @@ exports[`VirtualizedList renders null list 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -524,7 +751,6 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -533,6 +759,7 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
@@ -540,6 +767,7 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i2"
|
||||
@@ -547,6 +775,7 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
@@ -580,7 +809,6 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
renderScrollComponent={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
@@ -589,6 +817,7 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="item_0"
|
||||
|
||||
@@ -78,6 +78,7 @@ const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
class Modal extends React.Component {
|
||||
static propTypes = {
|
||||
/**
|
||||
@@ -90,6 +91,19 @@ class Modal extends React.Component {
|
||||
* Default is set to `none`.
|
||||
*/
|
||||
animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
|
||||
/**
|
||||
* The `presentationStyle` prop controls how the modal appears (generally on larger devices such as iPad or plus-sized iPhones).
|
||||
* See https://developer.apple.com/reference/uikit/uimodalpresentationstyle for details.
|
||||
* @platform ios
|
||||
*
|
||||
* - `fullScreen` covers the screen completely
|
||||
* - `pageSheet` covers portrait-width view centered (only on larger devices)
|
||||
* - `formSheet` covers narrow-width view centered (only on larger devices)
|
||||
* - `overFullScreen` covers the screen completely, but allows transparency
|
||||
*
|
||||
* Default is set to `overFullScreen` or `fullScreen` depending on `transparent` property.
|
||||
*/
|
||||
presentationStyle: PropTypes.oneOf(['fullScreen', 'pageSheet', 'formSheet', 'overFullScreen']),
|
||||
/**
|
||||
* The `transparent` prop determines whether your modal will fill the entire view. Setting this to `true` will render the modal over a transparent background.
|
||||
*/
|
||||
@@ -119,6 +133,7 @@ class Modal extends React.Component {
|
||||
/**
|
||||
* The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
|
||||
* On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field.
|
||||
* When using `presentationStyle` of `pageSheet` or `formSheet`, this property will be ignored by iOS.
|
||||
* @platform ios
|
||||
*/
|
||||
supportedOrientations: PropTypes.arrayOf(PropTypes.oneOf(['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'])),
|
||||
@@ -139,6 +154,21 @@ class Modal extends React.Component {
|
||||
rootTag: PropTypes.number,
|
||||
};
|
||||
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
Modal._confirmProps(props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Object) {
|
||||
Modal._confirmProps(nextProps);
|
||||
}
|
||||
|
||||
static _confirmProps(props: Object) {
|
||||
if (props.presentationStyle && props.presentationStyle !== 'overFullScreen' && props.transparent) {
|
||||
console.warn(`Modal with '${props.presentationStyle}' presentation style and 'transparent' value is not supported.`);
|
||||
}
|
||||
}
|
||||
|
||||
render(): ?React.Element<any> {
|
||||
if (this.props.visible === false) {
|
||||
return null;
|
||||
@@ -157,6 +187,14 @@ class Modal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let presentationStyle = this.props.presentationStyle;
|
||||
if (!presentationStyle) {
|
||||
presentationStyle = 'fullScreen';
|
||||
if (this.props.transparent) {
|
||||
presentationStyle = 'overFullScreen';
|
||||
}
|
||||
}
|
||||
|
||||
const innerChildren = __DEV__ ?
|
||||
( <AppContainer rootTag={this.context.rootTag}>
|
||||
{this.props.children}
|
||||
@@ -166,6 +204,7 @@ class Modal extends React.Component {
|
||||
return (
|
||||
<RCTModalHostView
|
||||
animationType={animationType}
|
||||
presentationStyle={presentationStyle}
|
||||
transparent={this.props.transparent}
|
||||
hardwareAccelerated={this.props.hardwareAccelerated}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
|
||||
@@ -95,7 +95,7 @@ class RCTNetworking extends NativeEventEmitter {
|
||||
if (__DEV__ && !RCTNetworkingNative) {
|
||||
class MissingNativeRCTNetworkingShim extends MissingNativeEventEmitterShim {
|
||||
constructor() {
|
||||
super('RCTAppState', 'AppState');
|
||||
super('RCTNetworking', 'Networking');
|
||||
}
|
||||
|
||||
sendRequest(...args: Array<any>) {
|
||||
|
||||
@@ -63,7 +63,7 @@ class RCTNetworking extends NativeEventEmitter {
|
||||
if (__DEV__ && !RCTNetworkingNative) {
|
||||
class MissingNativeRCTNetworkingShim extends MissingNativeEventEmitterShim {
|
||||
constructor() {
|
||||
super('RCTAppState', 'AppState');
|
||||
super('RCTNetworking', 'Networking');
|
||||
}
|
||||
|
||||
sendRequest(...args: Array<any>) {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
jest.unmock('FormData');
|
||||
|
||||
const FormData = require('FormData');
|
||||
|
||||
describe('FormData', function() {
|
||||
|
||||
@@ -20,7 +20,6 @@ function setRequestId(id){
|
||||
}
|
||||
|
||||
jest
|
||||
.disableAutomock()
|
||||
.dontMock('event-target-shim')
|
||||
.setMock('NativeModules', {
|
||||
Networking: {
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule fetch
|
||||
* @nolint
|
||||
*
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'whatwg-fetch';
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule RCTRenderingPerf
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var performanceNow = require('fbjs/lib/performanceNow');
|
||||
|
||||
type perfModule = {
|
||||
start: () => void,
|
||||
stop: () => void,
|
||||
}
|
||||
|
||||
var perfModules = [];
|
||||
var enabled = false;
|
||||
var lastRenderStartTime = 0;
|
||||
var totalRenderDuration = 0;
|
||||
|
||||
var RCTRenderingPerfDevtool = {
|
||||
onBeginLifeCycleTimer(debugID, timerType) {
|
||||
if (timerType === 'render') {
|
||||
lastRenderStartTime = performanceNow();
|
||||
}
|
||||
},
|
||||
onEndLifeCycleTimer(debugID, timerType) {
|
||||
if (timerType === 'render') {
|
||||
var lastRenderDuration = performanceNow() - lastRenderStartTime;
|
||||
totalRenderDuration += lastRenderDuration;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var RCTRenderingPerf = {
|
||||
// Once perf is enabled, it stays enabled
|
||||
toggle: function() {
|
||||
console.log('Render perfomance measurements enabled');
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactPerf.start();
|
||||
ReactDebugTool.addHook(RCTRenderingPerfDevtool);
|
||||
perfModules.forEach((module) => module.start());
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactPerf.stop();
|
||||
ReactPerf.printInclusive();
|
||||
ReactPerf.printWasted();
|
||||
ReactDebugTool.removeHook(RCTRenderingPerfDevtool);
|
||||
|
||||
console.log(`Total time spent in render(): ${totalRenderDuration.toFixed(2)} ms`);
|
||||
lastRenderStartTime = 0;
|
||||
totalRenderDuration = 0;
|
||||
|
||||
perfModules.forEach((module) => module.stop());
|
||||
},
|
||||
|
||||
register: function(module: perfModule) {
|
||||
invariant(
|
||||
typeof module.start === 'function',
|
||||
'Perf module should have start() function'
|
||||
);
|
||||
invariant(
|
||||
typeof module.stop === 'function',
|
||||
'Perf module should have stop() function'
|
||||
);
|
||||
perfModules.push(module);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RCTRenderingPerf;
|
||||
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
type RelayProfiler = {
|
||||
attachProfileHandler(
|
||||
name: string,
|
||||
@@ -29,52 +31,89 @@ const TRACE_TAG_JSC_CALLS = 1 << 27;
|
||||
|
||||
let _enabled = false;
|
||||
let _asyncCookie = 0;
|
||||
const _markStack = [];
|
||||
let _markStackIndex = -1;
|
||||
|
||||
const ReactSystraceDevtool = __DEV__ ? {
|
||||
onBeforeMountComponent(debugID) {
|
||||
const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook;
|
||||
const displayName = ReactComponentTreeHook.getDisplayName(debugID);
|
||||
Systrace.beginEvent(`ReactReconciler.mountComponent(${displayName})`);
|
||||
// Implements a subset of User Timing API necessary for React measurements.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
|
||||
const REACT_MARKER = '\u269B';
|
||||
const userTimingPolyfill = {
|
||||
mark(markName: string) {
|
||||
if (__DEV__) {
|
||||
if (_enabled) {
|
||||
_markStackIndex++;
|
||||
_markStack[_markStackIndex] = markName;
|
||||
let systraceLabel = markName;
|
||||
// Since perf measurements are a shared namespace in User Timing API,
|
||||
// we prefix all React results with a React emoji.
|
||||
if (markName[0] === REACT_MARKER) {
|
||||
// This is coming from React.
|
||||
// Removing component IDs keeps trace colors stable.
|
||||
const indexOfId = markName.lastIndexOf(' (#');
|
||||
const cutoffIndex = indexOfId !== -1 ? indexOfId : markName.length;
|
||||
// Also cut off the emoji because it breaks Systrace
|
||||
systraceLabel = markName.slice(2, cutoffIndex);
|
||||
}
|
||||
Systrace.beginEvent(systraceLabel);
|
||||
}
|
||||
}
|
||||
},
|
||||
onMountComponent(debugID) {
|
||||
Systrace.endEvent();
|
||||
measure(measureName: string, startMark: ?string, endMark: ?string) {
|
||||
if (__DEV__) {
|
||||
if (_enabled) {
|
||||
invariant(
|
||||
typeof measureName === 'string' &&
|
||||
typeof startMark === 'string' &&
|
||||
typeof endMark === 'undefined',
|
||||
'Only performance.measure(string, string) overload is supported.'
|
||||
);
|
||||
const topMark = _markStack[_markStackIndex];
|
||||
invariant(
|
||||
startMark === topMark,
|
||||
'There was a mismatching performance.measure() call. ' +
|
||||
'Expected "%s" but got "%s."',
|
||||
topMark,
|
||||
startMark,
|
||||
);
|
||||
_markStackIndex--;
|
||||
// We can't use more descriptive measureName because Systrace doesn't
|
||||
// let us edit labels post factum.
|
||||
Systrace.endEvent();
|
||||
}
|
||||
}
|
||||
},
|
||||
onBeforeUpdateComponent(debugID) {
|
||||
const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook;
|
||||
const displayName = ReactComponentTreeHook.getDisplayName(debugID);
|
||||
Systrace.beginEvent(`ReactReconciler.updateComponent(${displayName})`);
|
||||
clearMarks(markName: string) {
|
||||
if (__DEV__) {
|
||||
if (_enabled) {
|
||||
if (_markStackIndex === -1) {
|
||||
return;
|
||||
}
|
||||
if (markName === _markStack[_markStackIndex]) {
|
||||
// React uses this for "cancelling" started measurements.
|
||||
// Systrace doesn't support deleting measurements, so we just stop them.
|
||||
userTimingPolyfill.measure(markName, markName);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdateComponent(debugID) {
|
||||
Systrace.endEvent();
|
||||
clearMeasures() {
|
||||
// React calls this to avoid memory leaks in browsers, but we don't keep
|
||||
// measurements anyway.
|
||||
},
|
||||
onBeforeUnmountComponent(debugID) {
|
||||
const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook;
|
||||
const displayName = ReactComponentTreeHook.getDisplayName(debugID);
|
||||
Systrace.beginEvent(`ReactReconciler.unmountComponent(${displayName})`);
|
||||
},
|
||||
onUnmountComponent(debugID) {
|
||||
Systrace.endEvent();
|
||||
},
|
||||
onBeginLifeCycleTimer(debugID, timerType) {
|
||||
const ReactComponentTreeHook = require('ReactGlobalSharedState').ReactComponentTreeHook;
|
||||
const displayName = ReactComponentTreeHook.getDisplayName(debugID);
|
||||
Systrace.beginEvent(`${displayName}.${timerType}()`);
|
||||
},
|
||||
onEndLifeCycleTimer(debugID, timerType) {
|
||||
Systrace.endEvent();
|
||||
},
|
||||
} : null;
|
||||
};
|
||||
|
||||
const Systrace = {
|
||||
getUserTimingPolyfill() {
|
||||
return userTimingPolyfill;
|
||||
},
|
||||
|
||||
setEnabled(enabled: boolean) {
|
||||
if (_enabled !== enabled) {
|
||||
if (__DEV__) {
|
||||
if (enabled) {
|
||||
global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS);
|
||||
require('ReactDebugTool').addHook(ReactSystraceDevtool);
|
||||
} else {
|
||||
global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS);
|
||||
require('ReactDebugTool').removeHook(ReactSystraceDevtool);
|
||||
}
|
||||
}
|
||||
_enabled = enabled;
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
const Promise = require('fbjs/lib/Promise.native');
|
||||
|
||||
const prettyFormat = require('pretty-format');
|
||||
|
||||
if (__DEV__) {
|
||||
require('promise/setimmediate/rejection-tracking').enable({
|
||||
allRejections: true,
|
||||
@@ -27,7 +25,7 @@ if (__DEV__) {
|
||||
message = Error.prototype.toString.call(error);
|
||||
stack = error.stack;
|
||||
} else {
|
||||
message = prettyFormat(error);
|
||||
message = require('pretty-format')(error);
|
||||
}
|
||||
|
||||
const warning =
|
||||
|
||||
@@ -24,6 +24,8 @@ const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
|
||||
const NOTIF_REGISTRATION_ERROR_EVENT = 'remoteNotificationRegistrationError';
|
||||
const DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived';
|
||||
|
||||
export type ContentAvailable = 1 | null | void;
|
||||
|
||||
export type FetchResult = {
|
||||
NewData: string,
|
||||
NoData: string,
|
||||
@@ -122,10 +124,11 @@ class PushNotificationIOS {
|
||||
_alert: string | Object;
|
||||
_sound: string;
|
||||
_category: string;
|
||||
_contentAvailable: ContentAvailable;
|
||||
_badgeCount: number;
|
||||
_notificationId: string;
|
||||
_isRemote: boolean;
|
||||
_remoteNotificationCompleteCalllbackCalled: boolean;
|
||||
_remoteNotificationCompleteCallbackCalled: boolean;
|
||||
|
||||
static FetchResult: FetchResult = {
|
||||
NewData: 'UIBackgroundFetchResultNewData',
|
||||
@@ -403,7 +406,7 @@ class PushNotificationIOS {
|
||||
*/
|
||||
constructor(nativeNotif: Object) {
|
||||
this._data = {};
|
||||
this._remoteNotificationCompleteCalllbackCalled = false;
|
||||
this._remoteNotificationCompleteCallbackCalled = false;
|
||||
this._isRemote = nativeNotif.remote;
|
||||
if (this._isRemote) {
|
||||
this._notificationId = nativeNotif.notificationId;
|
||||
@@ -419,6 +422,7 @@ class PushNotificationIOS {
|
||||
this._sound = notifVal.sound;
|
||||
this._badgeCount = notifVal.badge;
|
||||
this._category = notifVal.category;
|
||||
this._contentAvailable = notifVal['content-available'];
|
||||
} else {
|
||||
this._data[notifKey] = notifVal;
|
||||
}
|
||||
@@ -447,10 +451,10 @@ class PushNotificationIOS {
|
||||
* be throttled, to read more about it see the above documentation link.
|
||||
*/
|
||||
finish(fetchResult: FetchResult) {
|
||||
if (!this._isRemote || !this._notificationId || this._remoteNotificationCompleteCalllbackCalled) {
|
||||
if (!this._isRemote || !this._notificationId || this._remoteNotificationCompleteCallbackCalled) {
|
||||
return;
|
||||
}
|
||||
this._remoteNotificationCompleteCalllbackCalled = true;
|
||||
this._remoteNotificationCompleteCallbackCalled = true;
|
||||
|
||||
RCTPushNotificationManager.onFinishRemoteNotification(this._notificationId, fetchResult);
|
||||
}
|
||||
@@ -484,6 +488,13 @@ class PushNotificationIOS {
|
||||
return this._alert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content-available number from the `aps` object
|
||||
*/
|
||||
getContentAvailable(): ContentAvailable {
|
||||
return this._contentAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the badge count number from the `aps` object
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AppContainer
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
@@ -23,10 +24,11 @@ const View = require('View');
|
||||
type Context = {
|
||||
rootTag: number,
|
||||
};
|
||||
type Props = {
|
||||
type Props = {|
|
||||
children?: React.Children,
|
||||
rootTag: number,
|
||||
};
|
||||
WrapperComponent?: ?ReactClass<*>,
|
||||
|};
|
||||
type State = {
|
||||
inspector: ?React.Element<*>,
|
||||
mainKey: number,
|
||||
@@ -62,12 +64,13 @@ class AppContainer extends React.Component {
|
||||
? null
|
||||
: <Inspector
|
||||
inspectedViewTag={ReactNative.findNodeHandle(this._mainRef)}
|
||||
onRequestRerenderApp={(updateInspectedViewTag) => {
|
||||
onRequestRerenderApp={updateInspectedViewTag => {
|
||||
this.setState(
|
||||
(s) => ({mainKey: s.mainKey + 1}),
|
||||
() => updateInspectedViewTag(
|
||||
ReactNative.findNodeHandle(this._mainRef)
|
||||
)
|
||||
s => ({mainKey: s.mainKey + 1}),
|
||||
() =>
|
||||
updateInspectedViewTag(
|
||||
ReactNative.findNodeHandle(this._mainRef),
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>;
|
||||
@@ -93,15 +96,30 @@ class AppContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let innerView = (
|
||||
<View
|
||||
collapsable={!this.state.inspector}
|
||||
key={this.state.mainKey}
|
||||
pointerEvents="box-none"
|
||||
style={styles.appContainer}
|
||||
ref={ref => {
|
||||
this._mainRef = ref;
|
||||
}}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
|
||||
const Wrapper = this.props.WrapperComponent;
|
||||
if (Wrapper) {
|
||||
innerView = (
|
||||
<Wrapper>
|
||||
{innerView}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.appContainer} pointerEvents="box-none">
|
||||
<View
|
||||
collapsable={!this.state.inspector}
|
||||
key={this.state.mainKey}
|
||||
pointerEvents="box-none"
|
||||
style={styles.appContainer} ref={(ref) => {this._mainRef = ref;}}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
{innerView}
|
||||
{yellowBox}
|
||||
{this.state.inspector}
|
||||
</View>
|
||||
|
||||
@@ -43,6 +43,7 @@ export type Registry = {
|
||||
sections: Array<string>,
|
||||
runnables: Runnables,
|
||||
};
|
||||
export type WrapperComponentProvider = any => ReactClass<*>;
|
||||
|
||||
const runnables: Runnables = {};
|
||||
let runCount = 1;
|
||||
@@ -51,6 +52,8 @@ const tasks: Map<string, TaskProvider> = new Map();
|
||||
let componentProviderInstrumentationHook: ComponentProviderInstrumentationHook =
|
||||
(component: ComponentProvider) => component();
|
||||
|
||||
let wrapperComponentProvider: ?WrapperComponentProvider;
|
||||
|
||||
/**
|
||||
* <div class="banner-crna-ejected">
|
||||
* <h3>Project with Native Code Required</h3>
|
||||
@@ -78,6 +81,10 @@ let componentProviderInstrumentationHook: ComponentProviderInstrumentationHook =
|
||||
* `require`d.
|
||||
*/
|
||||
const AppRegistry = {
|
||||
setWrapperComponentProvider(provider: WrapperComponentProvider) {
|
||||
wrapperComponentProvider = provider;
|
||||
},
|
||||
|
||||
registerConfig(config: Array<AppConfig>): void {
|
||||
config.forEach((appConfig) => {
|
||||
if (appConfig.run) {
|
||||
@@ -109,7 +116,8 @@ const AppRegistry = {
|
||||
renderApplication(
|
||||
componentProviderInstrumentationHook(componentProvider),
|
||||
appParameters.initialProps,
|
||||
appParameters.rootTag
|
||||
appParameters.rootTag,
|
||||
wrapperComponentProvider && wrapperComponentProvider(appParameters),
|
||||
)
|
||||
};
|
||||
if (section) {
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule renderApplication
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var AppContainer = require('AppContainer');
|
||||
var React = require('React');
|
||||
var ReactNative = require('ReactNative');
|
||||
const AppContainer = require('AppContainer');
|
||||
const React = require('React');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
// require BackHandler so it sets the default handler that exits the app if no listeners respond
|
||||
require('BackHandler');
|
||||
@@ -24,20 +25,16 @@ require('BackHandler');
|
||||
function renderApplication<Props: Object>(
|
||||
RootComponent: ReactClass<Props>,
|
||||
initialProps: Props,
|
||||
rootTag: any
|
||||
rootTag: any,
|
||||
WrapperComponent?: ?ReactClass<*>,
|
||||
) {
|
||||
invariant(
|
||||
rootTag,
|
||||
'Expect to have a valid rootTag, instead got ', rootTag
|
||||
);
|
||||
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
|
||||
|
||||
ReactNative.render(
|
||||
<AppContainer rootTag={rootTag}>
|
||||
<RootComponent
|
||||
{...initialProps}
|
||||
rootTag={rootTag}
|
||||
/>
|
||||
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
|
||||
<RootComponent {...initialProps} rootTag={rootTag} />
|
||||
</AppContainer>,
|
||||
rootTag
|
||||
rootTag,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,13 +70,19 @@ function requireNativeComponent(
|
||||
viewConfig.propTypes = null;
|
||||
}
|
||||
|
||||
// The ViewConfig doesn't contain any props inherited from the view manager's
|
||||
// superclass, so we manually merge in the RCTView ones. Other inheritance
|
||||
// patterns are currenty not supported.
|
||||
const nativeProps = {
|
||||
...UIManager.RCTView.NativeProps,
|
||||
...viewConfig.NativeProps,
|
||||
};
|
||||
let baseModuleName = viewConfig.baseModuleName;
|
||||
let nativeProps = { ...viewConfig.NativeProps };
|
||||
while (baseModuleName) {
|
||||
const baseModule = UIManager[baseModuleName];
|
||||
if (!baseModule) {
|
||||
warning(false, 'Base module "%s" does not exist', baseModuleName);
|
||||
baseModuleName = null;
|
||||
} else {
|
||||
nativeProps = { ...nativeProps, ...baseModule.NativeProps };
|
||||
baseModuleName = baseModule.baseModuleName;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in nativeProps) {
|
||||
let useAttribute = false;
|
||||
const attribute = {};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user