diff --git a/.buckconfig b/.buckconfig index 19c2d6302ea98f..a4c6d696ba66b7 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,12 +1,13 @@ [android] - target = Google Inc.:Google APIs:23 + target = android-26 [download] max_number_of_retries = 3 [maven_repositories] central = https://repo1.maven.org/maven2 + google = https://maven.google.com [alias] rntester = //RNTester/android/app:app diff --git a/.circleci/config.yml b/.circleci/config.yml index 100bc1276ffd33..336b4dbf200818 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,59 +2,77 @@ aliases: # Cache Management - &restore-yarn-cache keys: - - v1-yarn-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} - # Fallback in case checksum fails - - v1-yarn-{{ arch }}-{{ .Branch }}- - # Fallback in case this is a first-time run on a fork - - v1-yarn-{{ arch }}-master- + - v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }} + - v1-yarn-cache-{{ arch }} - &save-yarn-cache paths: - - node_modules - ~/.cache/yarn - key: v1-yarn-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }} + key: v1-yarn-cache-{{ arch }}-{{ checksum "package.json" }} + + - &restore-node-modules + keys: + - v2-node-modules-{{ arch }}-{{ checksum "package.json" }} + - &save-node-modules + paths: + - node_modules + key: v2-node-modules-{{ arch }}-{{ checksum "package.json" }} - &restore-cache-analysis keys: - - v1-analysis-dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - # Fallback in case checksum fails - - v1-analysis-dependencies-{{ arch }}-{{ .Branch }}- - # Fallback in case this is a first-time run on a fork - - v1-analysis-dependencies-{{ arch }}-master- + - v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - &save-cache-analysis paths: - bots/node_modules - node_modules - key: v1-analysis-dependencies-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} + key: v1-analysis-dependencies-{{ arch }}-{{ checksum "package.json" }}{{ checksum "bots/package.json" }} - &restore-cache-android-packages keys: - - v1-android-sdkmanager-packages-{{ arch }}-{{ .Branch }}-api-26-alpha-{{ checksum "scripts/circle-ci-android-setup.sh" }} - # Fallback in case checksum fails - - v1-android-sdkmanager-packages-{{ arch }}-{{ .Branch }}-api-26-alpha- - # Fallback in case this is a first-time run on a fork - - v1-android-sdkmanager-packages-{{ arch }}-master-api-26-alpha- + - v1-android-sdkmanager-packages-api-26-alpha-{{ checksum "scripts/.tests.env" }} - &save-cache-android-packages paths: - /opt/android/sdk - key: v1-android-sdkmanager-packages-{{ arch }}-{{ .Branch }}-api-26-alpha-{{ checksum "scripts/circle-ci-android-setup.sh" }} + key: v1-android-sdkmanager-packages-api-26-alpha-{{ checksum "scripts/.tests.env" }} - - &restore-cache-ndk + - &restore-cache-gradle keys: - - v2-android-ndk-{{ arch }}-r10e-32-64-{{ checksum "scripts/circle-ci-android-setup.sh" }} + - v1-gradle-{{ arch }}-{{ .Branch }}-{{ checksum "build.gradle" }}-{{ checksum "ReactAndroid/build.gradle" }} # Fallback in case checksum fails - - v2-android-ndk-{{ arch }}-r10e-32-64- + - v1-gradle-{{ arch }}-{{ .Branch }}-{{ checksum "build.gradle" }}- + - v1-gradle-{{ arch }}-{{ .Branch }}- + # Fallback in case this is a first-time run on a fork + - v1-gradle-{{ arch }}-master- + - &save-cache-gradle + paths: + - ~/.gradle + key: v1-gradle-{{ arch }}-{{ .Branch }}-{{ checksum "build.gradle" }}-{{ checksum "ReactAndroid/build.gradle" }} + + - &restore-cache-apt + keys: + - v1-apt-{{ .Branch }}-{{ checksum "scripts/circleci/apt-get-android-deps.sh" }} + # Fallback in case this is a first-time run on a fork + - v1-apt-master-{{ checksum "scripts/circleci/apt-get-android-deps.sh" }} + - &save-cache-apt + paths: + - ~/vendor/apt + key: v1-apt-{{ .Branch }}-{{ checksum "scripts/circleci/apt-get-android-deps.sh" }} + + - &restore-cache-ndk + keys: + - v3-android-ndk-{{ arch }}-r10e-{{ checksum "scripts/android-setup.sh" }} - &save-cache-ndk paths: - /opt/ndk - key: v2-android-ndk-{{ arch }}-r10e-32-64-{{ checksum "scripts/circle-ci-android-setup.sh" }} + key: v3-android-ndk-{{ arch }}-r10e-{{ checksum "scripts/android-setup.sh" }} - &restore-cache-buck keys: - - v2-buck-{{ arch }}-v2018.02.16.01 + - v3-buck-{{ arch }}-v2018.06.25.01 - &save-cache-buck paths: - ~/buck - key: v2-buck-{{ arch }}-v2018.02.16.01 + - ~/okbuck + key: v3-buck-{{ arch }}-v2018.06.25.01 - &restore-cache-watchman keys: @@ -64,6 +82,17 @@ aliases: - ~/watchman key: v1-watchman-{{ arch }}-v4.9.0 + - &restore-cache-gradle-downloads + keys: + - v1-gradle-{{ arch }}-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }} + - v1-gradle-{{ arch }}- + - &save-cache-gradle-downloads + paths: + - ~/.gradle + - ReactAndroid/build/downloads + - ReactAndroid/build/third-party-ndk + key: v1-gradle-{{ arch }}-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }} + # Branch Filtering - &filter-only-master-stable branches: @@ -90,14 +119,20 @@ aliases: # Dependency Management - &install-ndk name: Install Android NDK - command: source scripts/circle-ci-android-setup.sh && getAndroidNDK + command: source scripts/android-setup.sh && getAndroidNDK - &yarn - | - yarn install --non-interactive --cache-folder ~/.cache/yarn + name: Run Yarn + command: | + # Skip yarn install on metro bump commits as the package is not yet + # available on npm + if [[ $(echo "$GIT_COMMIT_DESC" | grep -c "Bump metro@") -eq 0 ]]; then + yarn install --non-interactive --cache-folder ~/.cache/yarn + fi - &install-yarn - | + name: Install Yarn + command: | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn @@ -107,15 +142,24 @@ aliases: npm install --no-package-lock --no-spin --no-progress - &install-buck - | + name: Install BUCK + command: | if [[ ! -e ~/buck ]]; then - git clone https://github.com/facebook/buck.git ~/buck --branch v2018.02.16.01 --depth=1 + git clone https://github.com/facebook/buck.git ~/buck --branch v2018.06.25.01 --depth=1 fi cd ~/buck && ant buck --version + # Install related tooling + if [[ ! -e ~/okbuck ]]; then + git clone https://github.com/uber/okbuck.git ~/okbuck --depth=1 + fi + mkdir -p ~/react-native/tooling/junit + cp -R ~/okbuck/tooling/junit/* ~/react-native/tooling/junit/. + - &install-node - | + name: Install Node + command: | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get install -y nodejs @@ -132,18 +176,16 @@ aliases: - &configure-android-path name: Configure Environment Variables command: | - echo 'export PATH=${ANDROID_NDK}:~/react-native/gradle-2.9/bin:~/buck/bin:$PATH' >> $BASH_ENV + echo 'export PATH=${ANDROID_NDK}:~/buck/bin:$PATH' >> $BASH_ENV source $BASH_ENV - &install-android-packages name: Install Android SDK Packages - command: source scripts/circle-ci-android-setup.sh && getAndroidSDK + command: source scripts/android-setup.sh && getAndroidPackages - &install-android-build-dependencies name: Install Android Build Dependencies - command: | - sudo apt-get update -y - sudo apt-get install ant autoconf automake g++ gcc libqt5widgets5 lib32z1 lib32stdc++6 make maven python-dev python3-dev qml-module-qtquick-controls qtdeclarative5-dev file -y + command: ./scripts/circleci/apt-get-android-deps.sh - &validate-android-sdk name: Validate Android SDK Install @@ -157,16 +199,21 @@ aliases: - &run-js-tests name: JavaScript Test Suite command: yarn test-ci - + + # eslint sometimes runs into trouble generating the reports - &run-lint-checks name: Lint code - command: yarn lint --format junit -o ~/react-native/reports/junit/js-lint-results.xml - when: always + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/react-native/reports/junit/eslint/results.xml + fi - &run-flow-checks name: Check for errors in code using Flow - command: yarn flow check - when: always + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + yarn flow check + fi - &run-sanity-checks name: Sanity checks @@ -174,7 +221,11 @@ aliases: ./scripts/circleci/check_license.sh ./scripts/circleci/check_cache.sh when: always - + + - &gradle-download-deps + name: Download C++ Dependencies + command: ./scripts/circleci/gradle_download_deps.sh + - &build-android-app name: Build Android App command: | @@ -183,16 +234,16 @@ aliases: - &create-avd name: Create Android Virtual Device - command: source scripts/circle-ci-android-setup.sh && createAVD - + command: source scripts/android-setup.sh && createAVD + - &launch-avd name: Launch Android Virtual Device in Background - command: source scripts/circle-ci-android-setup.sh && launchAVD + command: source scripts/android-setup.sh && launchAVD background: true - &wait-for-avd name: Wait for Android Virtual Device - command: source scripts/circle-ci-android-setup.sh && waitForAVD + command: source scripts/android-setup.sh && waitForAVD - &build-js-bundle name: Build JavaScript Bundle @@ -205,57 +256,110 @@ aliases: - &run-android-unit-tests name: Run Unit Tests - command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS + command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ~/react-native/reports/buck/all-results-raw.xml - &run-android-instrumentation-tests name: Run Instrumentation Tests command: | - if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then + if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then echo "JavaScript bundle missing, cannot run instrumentation tests. Verify build-js-bundle step completed successfully."; exit 1; fi - source scripts/circle-ci-android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS - + source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS + - &collect-android-test-results name: Collect Test Results command: | - find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/react-native/reports/junit/ \; - find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/react-native/reports/junit/ \; - find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/react-native/reports/junit/ \; + find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/react-native/reports/build/ \; + find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/react-native/reports/outputs/ \; + find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/react-native/reports/buck/ \; + ./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/all-results-junit.xml when: always - &setup-artifacts name: Initial Setup - command: | + command: | + mkdir -p ~/react-native/reports/buck/ + mkdir -p ~/react-native/reports/build/ mkdir -p ~/react-native/reports/junit/ - + mkdir -p ~/react-native/reports/outputs/ + + - &boot-simulator-iphone + name: Boot iPhone Simulator + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + xcrun simctl boot "iPhone 5s" || true + fi + + - &boot-simulator-appletv + name: Boot Apple TV Simulator + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + xcrun simctl boot "Apple TV" || true + fi + - &run-objc-ios-tests name: iOS Test Suite - command: ./scripts/objc-test-ios.sh test + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/objc-test-ios.sh test + fi - &run-objc-tvos-tests name: tvOS Test Suite - command: ./scripts/objc-test-tvos.sh test + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/objc-test-tvos.sh test + fi + + - &run-podspec-tests + name: Test CocoaPods + command: | + if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/process-podspecs.sh + fi + + - &run-e2e-tests + name: End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --android --ios --tvos --js --retries 3; - &run-objc-ios-e2e-tests name: iOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + node ./scripts/run-ci-e2e-tests.js --ios --retries 3; + fi - &run-objc-tvos-e2e-tests name: tvOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; + fi + + - &run-android-e2e-tests + name: Android End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --android --retries 3; + + - &run-js-e2e-tests + name: JavaScript End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --js --retries 3; defaults: &defaults working_directory: ~/react-native + environment: + - GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1 js_defaults: &js_defaults <<: *defaults docker: - image: circleci/node:8 + environment: + - PATH: "/opt/yarn/yarn-v1.5.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" android_defaults: &android_defaults <<: *defaults docker: - - image: circleci/android:api-26-alpha + - image: circleci/android:api-26-node8-alpha resource_class: "large" environment: - TERM: "dumb" @@ -268,11 +372,10 @@ android_defaults: &android_defaults macos_defaults: &macos_defaults <<: *defaults macos: - xcode: "9.2.0" + xcode: "9.4.0" version: 2 jobs: - # Set up a Node environment for downstream jobs checkout_code: <<: *js_defaults @@ -319,84 +422,67 @@ jobs: - store_test_results: path: ~/react-native/reports/junit - # Runs JavaScript tests on Node 6 - test_javascript_node6_compatibility: - <<: *defaults - docker: - - image: circleci/node:6 - steps: - - checkout - - run: *setup-artifacts - - - restore-cache: *restore-yarn-cache - - run: *yarn - - save-cache: *save-yarn-cache - - - run: *run-js-tests - - - store_test_results: - path: ~/react-native/reports/junit - - # Runs unit tests on iOS devices - test_ios: + # Runs unit tests on iOS and Apple TV devices + test_objc: <<: *macos_defaults + parallelism: 3 steps: - attach_workspace: at: ~/react-native - - run: xcrun instruments -w "iPhone 5s (11.1)" || true + - run: *boot-simulator-iphone + - run: *boot-simulator-appletv - run: brew install watchman + - run: *run-objc-ios-tests + - run: *run-objc-tvos-tests + - run: *run-podspec-tests - store_test_results: path: ~/react-native/reports/junit - # Runs unit tests on tvOS devices - test_tvos: + # Runs end to end tests + test_end_to_end: <<: *macos_defaults + parallelism: 2 steps: - attach_workspace: at: ~/react-native - - run: xcrun instruments -w "Apple TV 1080p (11.1)" || true - - run: brew install watchman - - run: *run-objc-tvos-tests + - run: *boot-simulator-iphone + - run: *boot-simulator-appletv - - store_test_results: - path: ~/react-native/reports/junit + - run: + name: Configure Environment Variables + command: | + echo 'export PATH=/usr/local/opt/node@8/bin:$PATH' >> $BASH_ENV + source $BASH_ENV - # Runs end to end tests - test_ios_e2e: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native + - run: + name: Install Node 8 + command: | + brew install node@8 + brew link node@8 + node -v - - run: xcrun instruments -w "iPhone 5s (11.1)" || true - run: *run-objc-ios-e2e-tests + # Disabled for now + # - run: *run-objc-tvos-e2e-tests - store_test_results: path: ~/react-native/reports/junit - # Checks podspec - test_podspec: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native - - - run: ./scripts/process-podspecs.sh - # Publishes new version onto npm publish_npm_package: <<: *android_defaults steps: # Checkout code so that we can work with `git` in publish.js - checkout - + # Configure Android SDK and related dependencies - run: *configure-android-path - run: *install-android-build-dependencies + - restore-cache: *restore-cache-android-packages - run: *install-android-packages - save-cache: *save-cache-android-packages @@ -417,14 +503,15 @@ jobs: - run: buck fetch ReactAndroid/src/main/java/com/facebook/react/shell - run: buck fetch ReactAndroid/src/test/... - run: buck fetch ReactAndroid/src/androidTest/... - - run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders - - run: *install-node - - run: *install-yarn + - restore-cache: *restore-cache-gradle-downloads + - run: *gradle-download-deps + - save-cache: *save-cache-gradle-downloads + - restore-cache: *restore-yarn-cache - run: *yarn - save-cache: *save-yarn-cache - + - run: name: Publish React Native Package command: | @@ -435,7 +522,7 @@ jobs: echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" > ~/.netrc node ./scripts/publish-npm.js else - echo "Skipping deploy." + echo "Skipping deploy." fi # Set up an Android environment for downstream jobs @@ -447,14 +534,17 @@ jobs: # Configure Android SDK and related dependencies - run: *configure-android-path + - restore-cache: *restore-cache-apt - run: *install-android-build-dependencies + - save-cache: *save-cache-apt + - restore-cache: *restore-cache-android-packages - run: *install-android-packages - save-cache: *save-cache-android-packages - run: *validate-android-sdk - # Starting emulator in advance as it takes some time to boot. + # Starting emulator in advance as it takes some time to boot. - run: *create-avd - run: *launch-avd @@ -478,14 +568,16 @@ jobs: - run: buck fetch ReactAndroid/src/main/java/com/facebook/react/shell - run: buck fetch ReactAndroid/src/test/... - run: buck fetch ReactAndroid/src/androidTest/... - - run: ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders + + - restore-cache: *restore-cache-gradle-downloads + - run: *gradle-download-deps + - save-cache: *save-cache-gradle-downloads # Build and compile - run: *build-android-app - run: *compile-native-libs # Build JavaScript Bundle for instrumentation tests - - run: *install-node - run: *build-js-bundle # Wait for AVD to finish booting before running tests @@ -494,13 +586,22 @@ jobs: # Test Suite - run: *run-android-unit-tests - run: *run-android-instrumentation-tests - - # post (always runs) + + # Build Android RNTester + - run: + name: Build Android RNTester + command: | + ./gradlew RNTester:android:app:assembleRelease -Pjobs=$BUILD_THREADS + + # Run Android end-to-end tests + # Disabled + # - run: *run-android-e2e-tests + + # Collect Results - run: *collect-android-test-results - store_test_results: path: ~/react-native/reports/junit - # Analyze pull request and raise any lint/flow issues. # Issues will be posted to the PR itself via GitHub bots. # This workflow should only fail if the bots fail to run. @@ -520,18 +621,18 @@ jobs: cd bots yarn install --non-interactive --cache-folder ~/.cache/yarn else - echo "Skipping dependency installation." + echo "Skipping dependency installation." fi - save-cache: *save-cache-analysis - run: name: Analyze Pull Request command: | - # DANGER_GITHUB_API_TOKEN=Facebook-Open-Source-Bot public_repo access token + # DANGER_GITHUB_API_TOKEN=React-Linter public_repo access token if [ -n "$CIRCLE_PR_NUMBER" ]; then - cd bots && DANGER_GITHUB_API_TOKEN="b186c9a82bab3b08ec80""c0818117619eec6f281a" yarn danger + cd bots && DANGER_GITHUB_API_TOKEN="80aa64c50f38a267e9ba""575d41d528f9c234edb8" yarn danger else - echo "Skipping pull request analysis." + echo "Skipping pull request analysis." fi when: always - run: @@ -539,9 +640,9 @@ jobs: command: | # GITHUB_TOKEN=eslint-bot public_repo access token if [ -n "$CIRCLE_PR_NUMBER" ]; then - cat <(echo eslint; yarn --silent lint --format=json; echo flow; yarn --silent flow check --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" CI_USER=$CIRCLE_PROJECT_USERNAME CI_REPO=$CIRCLE_PROJECT_REPONAME PULL_REQUEST_NUMBER=$CIRCLE_PR_NUMBER node bots/code-analysis-bot.js + GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" CI_USER=$CIRCLE_PROJECT_USERNAME CI_REPO=$CIRCLE_PROJECT_REPONAME PULL_REQUEST_NUMBER=$CIRCLE_PR_NUMBER scripts/circleci/analyze_code.sh else - echo "Skipping code analysis." + echo "Skipping code analysis." fi when: always @@ -551,7 +652,6 @@ workflows: tests: jobs: - # Checkout repo and run Yarn - checkout_code: filters: *filter-ignore-gh-pages @@ -568,10 +668,6 @@ workflows: requires: - checkout_code - # Test JavaScript using Node 6, the minimum supported version - - test_javascript_node6_compatibility: - filters: *filter-ignore-gh-pages - # Test Android - test_android: filters: *filter-ignore-gh-pages @@ -579,60 +675,40 @@ workflows: - checkout_code # Test iOS & tvOS - - test_ios: - filters: *filter-ignore-gh-pages - requires: - - checkout_code - - test_tvos: + - test_objc: filters: *filter-ignore-gh-pages requires: - checkout_code # End-to-end tests - - test_ios_e2e: + - test_end_to_end: filters: *filter-ignore-gh-pages requires: - checkout_code + # Only runs on vX.X.X tags if all tests are green + - publish_npm_package: + filters: + branches: + ignore: /.*/ + tags: + only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ + requires: + - test_javascript + - test_objc + - test_android + - test_end_to_end + - analyze + # Only runs on PRs analyze: jobs: # Checkout repo and run Yarn - checkout_code: filters: *filter-ignore-master-stable - + # Run code checks - - analyze_pr: + - analyze_pr: filters: *filter-ignore-master-stable - requires: - - checkout_code - - # Only runs on NN-stable branches - deploy: - jobs: - # If we are on a stable branch, wait for approval to deploy to npm - - approve_publish_npm_package: - filters: *filter-only-stable - type: approval - - - publish_npm_package: requires: - - approve_publish_npm_package - - # These tests are flaky or are yet to be fixed. They are placed on their own - # workflow to avoid marking benign PRs as broken. - # To run them, uncomment the entire block and open a PR (do not merge). - # Once a test is fixed, move the test definition to the 'tests' workflow. - # disabled_tests: - # jobs: - # # Checkout repo and run Yarn (pre-req, should succeed) - # - checkout_code: - # filters: *filter-ignore-gh-pages - - # # The following were DISABLED because they have not run since - # # the migration from Travis, and they have broken since then, - # # CocoaPods - # - test_podspec: - # filters: *filter-ignore-gh-pages - # requires: - # - checkout_code + - checkout_code diff --git a/.eslintrc b/.eslintrc index 9887d1c6d8dbe0..e05c065a0bb432 100644 --- a/.eslintrc +++ b/.eslintrc @@ -57,18 +57,7 @@ "rules": { // General - - // This must be disallowed in this repo because the minimum supported - // version of node is 4 which doesn't support trailing commas. - // Once the minimum supported version is 8 or greater this can be changed - "comma-dangle": [2, { // disallow trailing commas in object literals - "arrays": "ignore", - "objects": "ignore", - "imports": "ignore", - "exports": "ignore", - "functions": "never" - }], - + "comma-dangle": [1, "always-multiline"], // allow or disallow trailing commas "no-cond-assign": 1, // disallow assignment in conditional expressions "no-console": 0, // disallow use of console (off by default in the node environment) "no-const-assign": 2, // disallow assignment to const-declared variables @@ -154,7 +143,7 @@ "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block "no-undefined": 0, // disallow use of undefined variable (off by default) "no-undef-init": 1, // disallow use of undefined when initializing variables - "no-unused-vars": [1, {"vars": "all", "args": "none"}], // disallow declaration of variables that are not used in the code + "no-unused-vars": [1, {"vars": "all", "args": "none", ignoreRestSiblings: true}], // disallow declaration of variables that are not used in the code "no-use-before-define": 0, // disallow use of variables before they are defined // Node.js @@ -267,27 +256,11 @@ }, "overrides": [ - { - "files": [ - "Libraries/**/*.js", - "RNTester/**/*.js", - "jest/**/*.js", - ], - "rules": { - // These folders currently run through babel and don't need to be - // compatible with node 4 - "comma-dangle": 0, - }, - }, { "files": [ "local-cli/**/*.js", ], "rules": { - // This folder currently runs through babel and doesn't need to be - // compatible with node 4 - "comma-dangle": 0, - "lint/extra-arrow-initializer": 0, "no-alert": 0, "no-console-disallow": 0, diff --git a/.flowconfig b/.flowconfig index 361d475f24042d..7ba7b7ccf41d93 100644 --- a/.flowconfig +++ b/.flowconfig @@ -25,6 +25,11 @@ ; Ignore metro .*/node_modules/metro/.* +; These should not be required directly +; require from fbjs/lib instead: require('fbjs/lib/invariant') +.*/node_modules/invariant/.* +.*/node_modules/warning/.* + [include] [libs] @@ -36,6 +41,23 @@ flow-github/ emoji=true module.system=haste +module.system.haste.use_name_reducers=true +# keep the following in sync with server/haste/hasteImpl.js +# get basename +module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' +# strip .js or .js.flow suffix +module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' +# strip .ios suffix +module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' +module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' +module.system.haste.paths.blacklist=.*/__tests__/.* +module.system.haste.paths.blacklist=.*/__mocks__/.* +module.system.haste.paths.blacklist=/Libraries/Animated/src/polyfills/.* +module.system.haste.paths.whitelist=/Libraries/.* +module.system.haste.paths.whitelist=/RNTester/.* +module.system.haste.paths.whitelist=/IntegrationTests/.* +module.system.haste.paths.blacklist=/Libraries/Animated/src/polyfills/.* munge_underscores=true @@ -51,5 +73,28 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]* suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError +[lints] +all=warn + +# There is an ESLint rule for this +unclear-type=off + +sketchy-null=off +sketchy-null-number=warn +sketchy-null-mixed=warn + +# This is noisy for now. We *do* still want to warn on importing types +# from untyped files, which is covered by untyped-type-import +untyped-import=off + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + [version] -^0.67.0 +^0.76.0 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index bbe1d31e0b8f46..d31f3a36ccac03 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,29 +1,3 @@ - +GitHub Issues in the `facebook/react-native` repository are used exclusively for tracking bugs in React Native. -(Describe your issue in detail.) - -### Environment - -(Run `react-native info` in your terminal and paste its contents here.) - -### Expected Behavior - -(Write what you thought would happen.) - -### Actual Behavior - -(Write what happened. Include screenshots if needed.) - -### Steps to Reproduce - -(Link to Snack, or steps to reproduce.) - - +Please take a look at the issue templates at https://github.com/facebook/react-native/issues/new/choose before submitting a new issue. Following one of the issue templates will ensure maintainers can route your request efficiently. Thanks! diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000000..85c66cde5f6281 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,18 @@ +--- +name: 🐛 Bug Report +about: Report a reproducible bug or regression in the core React Native library. +--- + + + - [ ] Review the documentation: https://facebook.github.io/react-native + - [ ] Search for existing issues: https://github.com/facebook/react-native/issues + - [ ] Use the latest React Native release: https://github.com/facebook/react-native/releases + +## Environment +Run `react-native info` in your terminal and paste its contents here. + +## Description +Describe your issue in detail. Include screenshots if needed. If this is a regression, let us know. + +## Reproducible Demo +Let us know how to reproduce the issue. Include a code sample, share a project, or share an app that reproduces the issue using https://snack.expo.io/. Please follow the guidelines for providing a MCVE: https://stackoverflow.com/help/mcve diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md new file mode 100644 index 00000000000000..a576b0de5c7539 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/discussion.md @@ -0,0 +1,19 @@ +--- +name: 🗣 Start a Discussion +about: Use https://discuss.reactjs.org/ to propose changes or discuss feature requests. +--- + +Please use https://discuss.reactjs.org/ to propose changes or discuss feature requests. + +If you feel strongly about starting a discussion as a GitHub Issue instead of using the discussion forum, you may follow this template. + +We kindly ask that issues of this type are kept to a minimum to ensure bug reports and regressions are given the priority they require. + +Maintainers may flag an issue for Stack Overflow or kindly ask you to move the discussion to https://discuss.reactjs.org at their own discretion. + +--- + +# For Discussion + + + diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 00000000000000..1a4751ee5a9998 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,11 @@ +--- +name: 📖 Documentation Issue +about: Report issues with the docs at https://github.com/facebook/react-native-website/issues. + +--- + +--------------^ Click "Preview" for a nicer view! + +GitHub Issues in the `facebook/react-native` repository are used exclusively for tracking bugs in React Native. + +If you would like to report an issue in the React Native documentation, or anything related to the [React Native website](http://facebook.github.io/react-native), please visit https://github.com/facebook/react-native-website/issues. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000000000..cc203120f24559 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,21 @@ +--- +name: 💬 Question +about: If you need help with your React Native app, the right place to go depends on the type of help that you need. + +--- + +--------------^ Click "Preview" for a nicer view! + +GitHub Issues in the `facebook/react-native` repository are used exclusively for tracking bugs in React Native. GitHub may not be the ideal place to ask questions about using React Native, but we have compiled a list of resources that should help. + +### Get help with your React Native app or ask code-level questions + +Many members of the community use Stack Overflow to ask questions. Read through the [existing questions](http://stackoverflow.com/questions/tagged/react-native?sort=frequent) tagged with **react-native** or [ask your own](http://stackoverflow.com/questions/ask?tags=react-native)! + +### Talk about best practices or propose changes to React Native + +For longer-form conversations about React Native, we’ve set up a [discussion forum at **discuss.reactjs.org**](https://discuss.reactjs.org/t/welcome-react-native-community-group/10239). This forum is a great place for discussion about best practices and application architecture as well as the future of React Native. + +### Chat with React and React Native community members + +If you need an answer right away, check out the [Reactiflux Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) community. There are usually a number of React Native experts there who can help out or point you to somewhere you might want to look. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d8a1469f329099..2ae837de23e7e3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,43 +1,30 @@ - - -## Motivation +If this PR fixes an issue, type "Fixes #issueNumber" to automatically close the issue when the PR is merged. -(Write your motivation here.) +Test Plan: +---------- +Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos! -## Test Plan +Release Notes: +-------------- +Help reviewers and the release process by writing your own release notes. See below for an example. -(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos!) +[CATEGORY] [TYPE] [LOCATION] - Message -## Related PRs - -(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/facebook/react-native-website, and link to your PR here.) - -## Release Notes 0 interactions @@ -203,8 +204,10 @@ function _processUpdate() { if (nextInteractionCount === 0) { while (_taskQueue.hasTasksToProcess()) { _taskQueue.processNext(); - if (_deadline > 0 && - BatchedBridge.getEventLoopRunningTime() >= _deadline) { + if ( + _deadline > 0 && + BatchedBridge.getEventLoopRunningTime() >= _deadline + ) { // Hit deadline before processing all tasks, so process more later. _scheduleUpdate(); break; diff --git a/Libraries/Interaction/InteractionMixin.js b/Libraries/Interaction/InteractionMixin.js index 9e81a4fddaec1e..78b81f295cdc33 100644 --- a/Libraries/Interaction/InteractionMixin.js +++ b/Libraries/Interaction/InteractionMixin.js @@ -4,23 +4,24 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule InteractionMixin + * @format * @flow */ + 'use strict'; -var InteractionManager = require('InteractionManager'); +const InteractionManager = require('InteractionManager'); /** * This mixin provides safe versions of InteractionManager start/end methods * that ensures `clearInteractionHandle` is always called * once per start, even if the component is unmounted. */ -var InteractionMixin = { +const InteractionMixin = { componentWillUnmount: function() { while (this._interactionMixinHandles.length) { InteractionManager.clearInteractionHandle( - this._interactionMixinHandles.pop() + this._interactionMixinHandles.pop(), ); } }, @@ -28,7 +29,7 @@ var InteractionMixin = { _interactionMixinHandles: ([]: Array), createInteractionHandle: function() { - var handle = InteractionManager.createInteractionHandle(); + const handle = InteractionManager.createInteractionHandle(); this._interactionMixinHandles.push(handle); return handle; }, @@ -36,7 +37,7 @@ var InteractionMixin = { clearInteractionHandle: function(clearHandle: number) { InteractionManager.clearInteractionHandle(clearHandle); this._interactionMixinHandles = this._interactionMixinHandles.filter( - handle => handle !== clearHandle + handle => handle !== clearHandle, ); }, diff --git a/Libraries/Interaction/InteractionStallDebugger.js b/Libraries/Interaction/InteractionStallDebugger.js index 8d2bcc82e5f9ba..899eee0ec1143a 100644 --- a/Libraries/Interaction/InteractionStallDebugger.js +++ b/Libraries/Interaction/InteractionStallDebugger.js @@ -4,22 +4,19 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule InteractionStallDebugger + * @format * @flow */ + 'use strict'; const BridgeSpyStallHandler = require('BridgeSpyStallHandler'); const JSEventLoopWatchdog = require('JSEventLoopWatchdog'); -const ReactPerfStallHandler = require('ReactPerfStallHandler'); const InteractionStallDebugger = { - install: function(options: {thresholdMS: number}) { + install(options: {thresholdMS: number}): void { JSEventLoopWatchdog.install(options); BridgeSpyStallHandler.register(); - if (__DEV__) { - ReactPerfStallHandler.register(); - } }, }; diff --git a/Libraries/Interaction/JSEventLoopWatchdog.js b/Libraries/Interaction/JSEventLoopWatchdog.js index 2d1866a0c77f2f..5d5328325c1872 100644 --- a/Libraries/Interaction/JSEventLoopWatchdog.js +++ b/Libraries/Interaction/JSEventLoopWatchdog.js @@ -4,9 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule JSEventLoopWatchdog + * @format * @flow */ + 'use strict'; const infoLog = require('infoLog'); @@ -61,14 +62,15 @@ const JSEventLoopWatchdog = { stallCount++; totalStallTime += stallTime; longestStall = Math.max(longestStall, stallTime); - let msg = `JSEventLoopWatchdog: JS thread busy for ${busyTime}ms. ` + + let msg = + `JSEventLoopWatchdog: JS thread busy for ${busyTime}ms. ` + `${totalStallTime}ms in ${stallCount} stalls so far. `; - handlers.forEach((handler) => { + handlers.forEach(handler => { msg += handler.onStall({lastInterval, busyTime}) || ''; }); infoLog(msg); } - handlers.forEach((handler) => { + handlers.forEach(handler => { handler.onIterate && handler.onIterate(); }); lastInterval = now; diff --git a/Libraries/Interaction/PanResponder.js b/Libraries/Interaction/PanResponder.js index db7d85c87e2714..fb974c9931e069 100644 --- a/Libraries/Interaction/PanResponder.js +++ b/Libraries/Interaction/PanResponder.js @@ -4,18 +4,22 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule PanResponder + * @format */ 'use strict'; const InteractionManager = require('./InteractionManager'); -const TouchHistoryMath = require('TouchHistoryMath'); +const TouchHistoryMath = require('./TouchHistoryMath'); -const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; -const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; -const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; -const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; +const currentCentroidXOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; +const currentCentroidYOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; +const previousCentroidXOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; +const previousCentroidYOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; const currentCentroidX = TouchHistoryMath.currentCentroidX; const currentCentroidY = TouchHistoryMath.currentCentroidY; @@ -118,7 +122,6 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY; */ const PanResponder = { - /** * * A graphical explanation of the touch data flow: @@ -182,7 +185,7 @@ const PanResponder = { * - vx/vy: Velocity. */ - _initializeGestureState: function (gestureState) { + _initializeGestureState: function(gestureState) { gestureState.moveX = 0; gestureState.moveY = 0; gestureState.x0 = 0; @@ -220,20 +223,33 @@ const PanResponder = { * typical responder callback pattern (without using `PanResponder`), but * avoids more dispatches than necessary. */ - _updateGestureStateOnMove: function (gestureState, touchHistory) { + _updateGestureStateOnMove: function(gestureState, touchHistory) { gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); - gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); + gestureState.moveX = currentCentroidXOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo, + ); + gestureState.moveY = currentCentroidYOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo, + ); const movedAfter = gestureState._accountsForMovesUpTo; - const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + const prevX = previousCentroidXOfTouchesChangedAfter( + touchHistory, + movedAfter, + ); const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + const prevY = previousCentroidYOfTouchesChangedAfter( + touchHistory, + movedAfter, + ); const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); const nextDX = gestureState.dx + (x - prevX); const nextDY = gestureState.dy + (y - prevY); // TODO: This must be filtered intelligently. - const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; + const dt = + touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; gestureState.vx = (nextDX - gestureState.dx) / dt; gestureState.vy = (nextDY - gestureState.dy) / dt; @@ -274,7 +290,7 @@ const PanResponder = { * accordingly. (numberActiveTouches) may not be totally accurate unless you * are the responder. */ - create: function (config) { + create: function(config) { const interactionState = { handle: (null: ?number), }; @@ -284,43 +300,46 @@ const PanResponder = { }; PanResponder._initializeGestureState(gestureState); const panHandlers = { - onStartShouldSetResponder: function (e) { - return config.onStartShouldSetPanResponder === undefined ? - false : - config.onStartShouldSetPanResponder(e, gestureState); + onStartShouldSetResponder: function(e) { + return config.onStartShouldSetPanResponder === undefined + ? false + : config.onStartShouldSetPanResponder(e, gestureState); }, - onMoveShouldSetResponder: function (e) { - return config.onMoveShouldSetPanResponder === undefined ? - false : - config.onMoveShouldSetPanResponder(e, gestureState); + onMoveShouldSetResponder: function(e) { + return config.onMoveShouldSetPanResponder === undefined + ? false + : config.onMoveShouldSetPanResponder(e, gestureState); }, - onStartShouldSetResponderCapture: function (e) { + onStartShouldSetResponderCapture: function(e) { // TODO: Actually, we should reinitialize the state any time // touches.length increases from 0 active to > 0 active. if (e.nativeEvent.touches.length === 1) { PanResponder._initializeGestureState(gestureState); } gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; - return config.onStartShouldSetPanResponderCapture !== undefined ? - config.onStartShouldSetPanResponderCapture(e, gestureState) : - false; + return config.onStartShouldSetPanResponderCapture !== undefined + ? config.onStartShouldSetPanResponderCapture(e, gestureState) + : false; }, - onMoveShouldSetResponderCapture: function (e) { + onMoveShouldSetResponderCapture: function(e) { const touchHistory = e.touchHistory; // Responder system incorrectly dispatches should* to current responder // Filter out any touch moves past the first one - we would have // already processed multi-touch geometry during the first event. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + if ( + gestureState._accountsForMovesUpTo === + touchHistory.mostRecentTimeStamp + ) { return false; } PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - return config.onMoveShouldSetPanResponderCapture ? - config.onMoveShouldSetPanResponderCapture(e, gestureState) : - false; + return config.onMoveShouldSetPanResponderCapture + ? config.onMoveShouldSetPanResponderCapture(e, gestureState) + : false; }, - onResponderGrant: function (e) { + onResponderGrant: function(e) { if (!interactionState.handle) { interactionState.handle = InteractionManager.createInteractionHandle(); } @@ -332,21 +351,31 @@ const PanResponder = { config.onPanResponderGrant(e, gestureState); } // TODO: t7467124 investigate if this can be removed - return config.onShouldBlockNativeResponder === undefined ? - true : - config.onShouldBlockNativeResponder(); + return config.onShouldBlockNativeResponder === undefined + ? true + : config.onShouldBlockNativeResponder(); }, - onResponderReject: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState); + onResponderReject: function(e) { + clearInteractionHandle( + interactionState, + config.onPanResponderReject, + e, + gestureState, + ); }, - onResponderRelease: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState); + onResponderRelease: function(e) { + clearInteractionHandle( + interactionState, + config.onPanResponderRelease, + e, + gestureState, + ); PanResponder._initializeGestureState(gestureState); }, - onResponderStart: function (e) { + onResponderStart: function(e) { const touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; if (config.onPanResponderStart) { @@ -354,11 +383,14 @@ const PanResponder = { } }, - onResponderMove: function (e) { + onResponderMove: function(e) { const touchHistory = e.touchHistory; // Guard against the dispatch of two touch moves when there are two // simultaneously changed touches. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + if ( + gestureState._accountsForMovesUpTo === + touchHistory.mostRecentTimeStamp + ) { return; } // Filter out any touch moves past the first one - we would have @@ -369,22 +401,32 @@ const PanResponder = { } }, - onResponderEnd: function (e) { + onResponderEnd: function(e) { const touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState); + clearInteractionHandle( + interactionState, + config.onPanResponderEnd, + e, + gestureState, + ); }, - onResponderTerminate: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState); + onResponderTerminate: function(e) { + clearInteractionHandle( + interactionState, + config.onPanResponderTerminate, + e, + gestureState, + ); PanResponder._initializeGestureState(gestureState); }, - onResponderTerminationRequest: function (e) { - return config.onPanResponderTerminationRequest === undefined ? - true : - config.onPanResponderTerminationRequest(e, gestureState); - } + onResponderTerminationRequest: function(e) { + return config.onPanResponderTerminationRequest === undefined + ? true + : config.onPanResponderTerminationRequest(e, gestureState); + }, }; return { panHandlers, @@ -392,14 +434,14 @@ const PanResponder = { return interactionState.handle; }, }; - } + }, }; function clearInteractionHandle( interactionState: {handle: ?number}, callback: Function, event: Object, - gestureState: Object + gestureState: Object, ) { if (interactionState.handle) { InteractionManager.clearInteractionHandle(interactionState.handle); diff --git a/Libraries/Interaction/ReactPerfStallHandler.js b/Libraries/Interaction/ReactPerfStallHandler.js deleted file mode 100644 index 394bc7533ca1c7..00000000000000 --- a/Libraries/Interaction/ReactPerfStallHandler.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @providesModule ReactPerfStallHandler - * @flow - */ -'use strict'; - -const JSEventLoopWatchdog = require('JSEventLoopWatchdog'); -const ReactPerf = require('ReactPerf'); - -const ReactPerfStallHandler = { - register: function() { - ReactPerf.start(); - JSEventLoopWatchdog.addHandler({ - onStall: () => { - ReactPerf.stop(); - ReactPerf.printInclusive(); - ReactPerf.printWasted(); - ReactPerf.start(); - }, - onIterate: () => { - ReactPerf.stop(); - ReactPerf.start(); - }, - }); - }, -}; - -module.exports = ReactPerfStallHandler; diff --git a/Libraries/Interaction/TaskQueue.js b/Libraries/Interaction/TaskQueue.js index 0ee5e4500af9f0..90f49a8e84d330 100644 --- a/Libraries/Interaction/TaskQueue.js +++ b/Libraries/Interaction/TaskQueue.js @@ -4,9 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule TaskQueue + * @format * @flow */ + 'use strict'; const infoLog = require('infoLog'); @@ -63,17 +64,17 @@ class TaskQueue { } enqueueTasks(tasks: Array): void { - tasks.forEach((task) => this.enqueue(task)); + tasks.forEach(task => this.enqueue(task)); } cancelTasks(tasksToCancel: Array): void { // search through all tasks and remove them. this._queueStack = this._queueStack - .map((queue) => ({ + .map(queue => ({ ...queue, - tasks: queue.tasks.filter((task) => tasksToCancel.indexOf(task) === -1), + tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1), })) - .filter((queue, idx) => (queue.tasks.length > 0 || idx === 0)); + .filter((queue, idx) => queue.tasks.length > 0 || idx === 0); } /** @@ -86,7 +87,7 @@ class TaskQueue { * `onMoreTasks` will be called after each `PromiseTask` resolves if there are * tasks ready to run at that point. */ - hasTasksToProcess(): bool { + hasTasksToProcess(): boolean { return this._getCurrentQueue().length > 0; } @@ -108,30 +109,36 @@ class TaskQueue { invariant( typeof task === 'function', 'Expected Function, SimpleTask, or PromiseTask, but got:\n' + - JSON.stringify(task, null, 2) + JSON.stringify(task, null, 2), ); DEBUG && infoLog('run anonymous task'); task(); } } catch (e) { - e.message = 'TaskQueue: Error with task ' + (task.name || '') + ': ' + - e.message; + e.message = + 'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message; throw e; } } } - _queueStack: Array<{tasks: Array, popable: bool}>; + _queueStack: Array<{tasks: Array, popable: boolean}>; _onMoreTasks: () => void; _getCurrentQueue(): Array { const stackIdx = this._queueStack.length - 1; const queue = this._queueStack[stackIdx]; - if (queue.popable && - queue.tasks.length === 0 && - this._queueStack.length > 1) { + if ( + queue.popable && + queue.tasks.length === 0 && + this._queueStack.length > 1 + ) { this._queueStack.pop(); - DEBUG && infoLog('popped queue: ', {stackIdx, queueStackSize: this._queueStack.length}); + DEBUG && + infoLog('popped queue: ', { + stackIdx, + queueStackSize: this._queueStack.length, + }); return this._getCurrentQueue(); } else { return queue.tasks; @@ -147,22 +154,25 @@ class TaskQueue { const stackIdx = this._queueStack.length - 1; DEBUG && infoLog('push new queue: ', {stackIdx}); DEBUG && infoLog('exec gen task ' + task.name); - task.gen() + task + .gen() .then(() => { - DEBUG && infoLog( - 'onThen for gen task ' + task.name, - {stackIdx, queueStackSize: this._queueStack.length}, - ); + DEBUG && + infoLog('onThen for gen task ' + task.name, { + stackIdx, + queueStackSize: this._queueStack.length, + }); this._queueStack[stackIdx].popable = true; this.hasTasksToProcess() && this._onMoreTasks(); }) - .catch((ex) => { - ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`; + .catch(ex => { + ex.message = `TaskQueue: Error resolving Promise in task ${ + task.name + }: ${ex.message}`; throw ex; }) .done(); } } - module.exports = TaskQueue; diff --git a/Libraries/Interaction/TouchHistoryMath.js b/Libraries/Interaction/TouchHistoryMath.js new file mode 100644 index 00000000000000..8d6d8253390f0c --- /dev/null +++ b/Libraries/Interaction/TouchHistoryMath.js @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const TouchHistoryMath = { + /** + * This code is optimized and not intended to look beautiful. This allows + * computing of touch centroids that have moved after `touchesChangedAfter` + * timeStamp. You can compute the current centroid involving all touches + * moves after `touchesChangedAfter`, or you can compute the previous + * centroid of all touches that were moved after `touchesChangedAfter`. + * + * @param {TouchHistoryMath} touchHistory Standard Responder touch track + * data. + * @param {number} touchesChangedAfter timeStamp after which moved touches + * are considered "actively moving" - not just "active". + * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension. + * @param {boolean} ofCurrent Compute current centroid for actively moving + * touches vs. previous centroid of now actively moving touches. + * @return {number} value of centroid in specified dimension. + */ + centroidDimension: function( + touchHistory, + touchesChangedAfter, + isXAxis, + ofCurrent, + ) { + const touchBank = touchHistory.touchBank; + let total = 0; + let count = 0; + + const oneTouchData = + touchHistory.numberActiveTouches === 1 + ? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] + : null; + + if (oneTouchData !== null) { + if ( + oneTouchData.touchActive && + oneTouchData.currentTimeStamp > touchesChangedAfter + ) { + total += + ofCurrent && isXAxis + ? oneTouchData.currentPageX + : ofCurrent && !isXAxis + ? oneTouchData.currentPageY + : !ofCurrent && isXAxis + ? oneTouchData.previousPageX + : oneTouchData.previousPageY; + count = 1; + } + } else { + for (let i = 0; i < touchBank.length; i++) { + const touchTrack = touchBank[i]; + if ( + touchTrack !== null && + touchTrack !== undefined && + touchTrack.touchActive && + touchTrack.currentTimeStamp >= touchesChangedAfter + ) { + let toAdd; // Yuck, program temporarily in invalid state. + if (ofCurrent && isXAxis) { + toAdd = touchTrack.currentPageX; + } else if (ofCurrent && !isXAxis) { + toAdd = touchTrack.currentPageY; + } else if (!ofCurrent && isXAxis) { + toAdd = touchTrack.previousPageX; + } else { + toAdd = touchTrack.previousPageY; + } + total += toAdd; + count++; + } + } + } + return count > 0 ? total / count : TouchHistoryMath.noCentroid; + }, + + currentCentroidXOfTouchesChangedAfter: function( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + true, // ofCurrent + ); + }, + + currentCentroidYOfTouchesChangedAfter: function( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + true, // ofCurrent + ); + }, + + previousCentroidXOfTouchesChangedAfter: function( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + false, // ofCurrent + ); + }, + + previousCentroidYOfTouchesChangedAfter: function( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + false, // ofCurrent + ); + }, + + currentCentroidX: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + true, // isXAxis + true, // ofCurrent + ); + }, + + currentCentroidY: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + false, // isXAxis + true, // ofCurrent + ); + }, + + noCentroid: -1, +}; + +module.exports = TouchHistoryMath; diff --git a/Libraries/Interaction/__tests__/Batchinator-test.js b/Libraries/Interaction/__tests__/Batchinator-test.js index a0afacd4e0d0cd..4cd4668fb27e51 100644 --- a/Libraries/Interaction/__tests__/Batchinator-test.js +++ b/Libraries/Interaction/__tests__/Batchinator-test.js @@ -4,14 +4,13 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @emails oncall+react_native */ 'use strict'; -jest - .mock('ErrorUtils') - .mock('BatchedBridge'); +jest.mock('ErrorUtils').mock('BatchedBridge'); function expectToBeCalledOnce(fn) { expect(fn.mock.calls.length).toBe(1); diff --git a/Libraries/Interaction/__tests__/InteractionManager-test.js b/Libraries/Interaction/__tests__/InteractionManager-test.js index 0a74b7cc766485..edbdb50fb52762 100644 --- a/Libraries/Interaction/__tests__/InteractionManager-test.js +++ b/Libraries/Interaction/__tests__/InteractionManager-test.js @@ -4,14 +4,13 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @emails oncall+react_native */ 'use strict'; -jest - .mock('ErrorUtils') - .mock('BatchedBridge'); +jest.mock('ErrorUtils').mock('BatchedBridge'); function expectToBeCalledOnce(fn) { expect(fn.mock.calls.length).toBe(1); @@ -31,11 +30,11 @@ describe('InteractionManager', () => { InteractionManager.addListener( InteractionManager.Events.interactionStart, - interactionStart + interactionStart, ); InteractionManager.addListener( InteractionManager.Events.interactionComplete, - interactionComplete + interactionComplete, ); }); @@ -168,7 +167,6 @@ describe('promise tasks', () => { sequenceId = 0; }); - it('should run a basic promise task', () => { const task1 = jest.fn(() => { expect(++sequenceId).toBe(1); @@ -183,8 +181,10 @@ describe('promise tasks', () => { const task1 = jest.fn(() => { expect(++sequenceId).toBe(1); return new Promise(resolve => { - InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'}) - .then(resolve); + InteractionManager.runAfterInteractions({ + gen: task2, + name: 'gen2', + }).then(resolve); }); }); const task2 = jest.fn(() => { @@ -255,7 +255,7 @@ describe('promise tasks', () => { expectToBeCalledOnce(task2); }); - const bigAsyncTest = (resolve) => { + const bigAsyncTest = resolve => { jest.useRealTimers(); const task1 = createSequenceTask(1); @@ -264,8 +264,10 @@ describe('promise tasks', () => { return new Promise(resolve => { InteractionManager.runAfterInteractions(task3); setTimeout(() => { - InteractionManager.runAfterInteractions({gen: task4, name: 'gen4'}) - .then(resolve); + InteractionManager.runAfterInteractions({ + gen: task4, + name: 'gen4', + }).then(resolve); }, 1); }); }); diff --git a/Libraries/Interaction/__tests__/InteractionMixin-test.js b/Libraries/Interaction/__tests__/InteractionMixin-test.js index 08f33d759f5016..77c75c1e9ca576 100644 --- a/Libraries/Interaction/__tests__/InteractionMixin-test.js +++ b/Libraries/Interaction/__tests__/InteractionMixin-test.js @@ -4,16 +4,18 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @emails oncall+react_native */ + 'use strict'; jest.enableAutomock().unmock('InteractionMixin'); describe('InteractionMixin', () => { - var InteractionManager; - var InteractionMixin; - var component; + let InteractionManager; + let InteractionMixin; + let component; beforeEach(() => { jest.resetModules(); @@ -29,19 +31,19 @@ describe('InteractionMixin', () => { }); it('should end interactions', () => { - var handle = {}; + const handle = {}; component.clearInteractionHandle(handle); expect(InteractionManager.clearInteractionHandle).toBeCalledWith(handle); }); it('should schedule tasks', () => { - var task = jest.fn(); + const task = jest.fn(); component.runAfterInteractions(task); expect(InteractionManager.runAfterInteractions).toBeCalledWith(task); }); it('should end unfinished interactions in componentWillUnmount', () => { - var handle = component.createInteractionHandle(); + const handle = component.createInteractionHandle(); component.componentWillUnmount(); expect(InteractionManager.clearInteractionHandle).toBeCalledWith(handle); }); diff --git a/Libraries/Interaction/__tests__/TaskQueue-test.js b/Libraries/Interaction/__tests__/TaskQueue-test.js index 407e8fa435cd0b..ce00415fefd7df 100644 --- a/Libraries/Interaction/__tests__/TaskQueue-test.js +++ b/Libraries/Interaction/__tests__/TaskQueue-test.js @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @emails oncall+react_native */ @@ -40,7 +41,6 @@ describe('TaskQueue', () => { sequenceId = 0; }); - it('should run a basic task', () => { const task1 = createSequenceTask(1); taskQueue.enqueue({run: task1, name: 'run1'}); diff --git a/Libraries/JSInspector/InspectorAgent.js b/Libraries/JSInspector/InspectorAgent.js index a5b6cca0da3da3..2afe86fbe4e080 100644 --- a/Libraries/JSInspector/InspectorAgent.js +++ b/Libraries/JSInspector/InspectorAgent.js @@ -4,9 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule InspectorAgent + * @format * @flow */ + 'use strict'; export type EventSender = (name: string, params: Object) => void; diff --git a/Libraries/JSInspector/JSInspector.js b/Libraries/JSInspector/JSInspector.js index 9a6b13ffb7c690..21ccf7fbc1510c 100644 --- a/Libraries/JSInspector/JSInspector.js +++ b/Libraries/JSInspector/JSInspector.js @@ -4,19 +4,20 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule JSInspector + * @format * @flow */ + 'use strict'; import type EventSender from 'InspectorAgent'; interface Agent { - constructor(eventSender: EventSender): void + constructor(eventSender: EventSender): void; } // Flow doesn't support static declarations in interface -type AgentClass = Class & { DOMAIN: string }; +type AgentClass = Class & {DOMAIN: string}; declare function __registerInspectorAgent(type: AgentClass): void; declare function __inspectorTimestamp(): number; diff --git a/Libraries/JSInspector/NetworkAgent.js b/Libraries/JSInspector/NetworkAgent.js index 56f6f2a37fdccf..2b25be3da54c0e 100644 --- a/Libraries/JSInspector/NetworkAgent.js +++ b/Libraries/JSInspector/NetworkAgent.js @@ -4,9 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule NetworkAgent + * @format * @flow */ + 'use strict'; const InspectorAgent = require('InspectorAgent'); @@ -28,34 +29,34 @@ type Headers = Object; type ResourceTiming = null; type ResourceType = - 'Document' | - 'Stylesheet' | - 'Image' | - 'Media' | - 'Font' | - 'Script' | - 'TextTrack' | - 'XHR' | - 'Fetch' | - 'EventSource' | - 'WebSocket' | - 'Manifest' | - 'Other'; + | 'Document' + | 'Stylesheet' + | 'Image' + | 'Media' + | 'Font' + | 'Script' + | 'TextTrack' + | 'XHR' + | 'Fetch' + | 'EventSource' + | 'WebSocket' + | 'Manifest' + | 'Other'; type SecurityState = - 'unknown' | - 'neutral' | - 'insecure' | - 'warning' | - 'secure' | - 'info'; + | 'unknown' + | 'neutral' + | 'insecure' + | 'warning' + | 'secure' + | 'info'; type BlockedReason = - 'csp' | - 'mixed-content' | - 'origin' | - 'inspector' | - 'subresource-filter' | - 'other'; + | 'csp' + | 'mixed-content' + | 'origin' + | 'inspector' + | 'subresource-filter' + | 'other'; type StackTrace = null; @@ -63,8 +64,8 @@ type Initiator = { type: 'script' | 'other', stackTrace?: StackTrace, url?: string, - lineNumber?: number -} + lineNumber?: number, +}; type ResourcePriority = 'VeryLow' | 'Low' | 'Medium' | 'High' | 'VeryHigh'; @@ -152,11 +153,7 @@ class Interceptor { return this._requests.get(requestId); } - requestSent( - id: number, - url: string, - method: string, - headers: Object) { + requestSent(id: number, url: string, method: string, headers: Object) { const requestId = String(id); this._requests.set(requestId, ''); @@ -184,11 +181,7 @@ class Interceptor { this._agent.sendEvent('requestWillBeSent', event); } - responseReceived( - id: number, - url: string, - status: number, - headers: Object) { + responseReceived(id: number, url: string, status: number, headers: Object) { const requestId = String(id); const response: Response = { url, @@ -215,9 +208,7 @@ class Interceptor { this._agent.sendEvent('responseReceived', event); } - dataReceived( - id: number, - data: string) { + dataReceived(id: number, data: string) { const requestId = String(id); const existingData = this._requests.get(requestId) || ''; this._requests.set(requestId, existingData.concat(data)); @@ -230,9 +221,7 @@ class Interceptor { this._agent.sendEvent('dataReceived', event); } - loadingFinished( - id: number, - encodedDataLength: number) { + loadingFinished(id: number, encodedDataLength: number) { const event: LoadingFinishedEvent = { requestId: String(id), timestamp: JSInspector.getTimestamp(), @@ -241,9 +230,7 @@ class Interceptor { this._agent.sendEvent('loadingFinished', event); } - loadingFailed( - id: number, - error: string) { + loadingFailed(id: number, error: string) { const event: LoadingFailedEvent = { requestId: String(id), timestamp: JSInspector.getTimestamp(), @@ -261,7 +248,7 @@ class Interceptor { type EnableArgs = { maxResourceBufferSize?: number, - maxTotalBufferSize?: number + maxTotalBufferSize?: number, }; class NetworkAgent extends InspectorAgent { @@ -270,7 +257,7 @@ class NetworkAgent extends InspectorAgent { _sendEvent: EventSender; _interceptor: ?Interceptor; - enable({ maxResourceBufferSize, maxTotalBufferSize }: EnableArgs) { + enable({maxResourceBufferSize, maxTotalBufferSize}: EnableArgs) { this._interceptor = new Interceptor(this); XMLHttpRequest.setInterceptor(this._interceptor); } @@ -280,8 +267,11 @@ class NetworkAgent extends InspectorAgent { this._interceptor = null; } - getResponseBody({requestId}: {requestId: RequestId}) - : {body: ?string, base64Encoded: boolean} { + getResponseBody({ + requestId, + }: { + requestId: RequestId, + }): {body: ?string, base64Encoded: boolean} { return {body: this.interceptor().getData(requestId), base64Encoded: false}; } @@ -291,7 +281,6 @@ class NetworkAgent extends InspectorAgent { } else { throw Error('_interceptor can not be null'); } - } } diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js index fe4a2cc2b84328..e739ec8595158d 100644 --- a/Libraries/LayoutAnimation/LayoutAnimation.js +++ b/Libraries/LayoutAnimation/LayoutAnimation.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule LayoutAnimation * @flow * @format */ @@ -32,6 +31,8 @@ const Types = keyMirror(TypesEnum); const PropertiesEnum = { opacity: true, + scaleX: true, + scaleY: true, scaleXY: true, }; const Properties = keyMirror(PropertiesEnum); diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js index a621e647adccf8..fec44f634fab8c 100644 --- a/Libraries/Linking/Linking.js +++ b/Libraries/Linking/Linking.js @@ -4,9 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule Linking + * @format * @flow */ + 'use strict'; const NativeEventEmitter = require('NativeEventEmitter'); @@ -15,8 +16,10 @@ const Platform = require('Platform'); const invariant = require('fbjs/lib/invariant'); -const LinkingManager = Platform.OS === 'android' ? - NativeModules.IntentAndroid : NativeModules.LinkingManager; +const LinkingManager = + Platform.OS === 'android' + ? NativeModules.IntentAndroid + : NativeModules.LinkingManager; /** * `Linking` gives you a general interface to interact with both incoming @@ -25,7 +28,6 @@ const LinkingManager = Platform.OS === 'android' ? * See https://facebook.github.io/react-native/docs/linking.html */ class Linking extends NativeEventEmitter { - constructor() { super(LinkingManager); } @@ -45,7 +47,7 @@ class Linking extends NativeEventEmitter { * * See https://facebook.github.io/react-native/docs/linking.html#removeeventlistener */ - removeEventListener(type: string, handler: Function ) { + removeEventListener(type: string, handler: Function) { this.removeListener(type, handler); } @@ -82,12 +84,9 @@ class Linking extends NativeEventEmitter { _validateURL(url: string) { invariant( typeof url === 'string', - 'Invalid URL: should be a string. Was: ' + url - ); - invariant( - url, - 'Invalid URL: cannot be empty' + 'Invalid URL: should be a string. Was: ' + url, ); + invariant(url, 'Invalid URL: cannot be empty'); } } diff --git a/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj b/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj index b7ab8f88d800ee..34003d2a601639 100644 --- a/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj +++ b/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 58B511D31A9E6C8500147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 2D2A28461D9B043800D4039D = { @@ -171,20 +171,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -198,7 +209,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -218,20 +229,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_SHADOW = YES; @@ -239,7 +260,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SKIP_INSTALL = YES; diff --git a/Libraries/LinkingIOS/RCTLinkingManager.h b/Libraries/LinkingIOS/RCTLinkingManager.h index 55187727ac95d2..16d7e8f5986a36 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.h +++ b/Libraries/LinkingIOS/RCTLinkingManager.h @@ -22,6 +22,6 @@ + (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity - restorationHandler:(void (^)(NSArray *))restorationHandler; + restorationHandler:(void (^)(NSArray * __nullable))restorationHandler; @end diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 50982fa976d669..4aa6df0882e3d8 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -67,7 +67,7 @@ + (BOOL)application:(UIApplication *)application + (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity - restorationHandler:(void (^)(NSArray *))restorationHandler + restorationHandler:(void (^)(NSArray * __nullable))restorationHandler { if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { NSDictionary *payload = @{@"url": userActivity.webpageURL.absoluteString}; diff --git a/Libraries/Lists/FillRateHelper.js b/Libraries/Lists/FillRateHelper.js index f060897356b73a..82b0612cdcf8b6 100644 --- a/Libraries/Lists/FillRateHelper.js +++ b/Libraries/Lists/FillRateHelper.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule FillRateHelper * @flow * @format */ diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index 13b87ed995b756..adebd267f7f64d 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule FlatList * @flow * @format */ @@ -15,10 +14,11 @@ const React = require('React'); const View = require('View'); const VirtualizedList = require('VirtualizedList'); const ListView = require('ListView'); +const StyleSheet = require('StyleSheet'); const invariant = require('fbjs/lib/invariant'); -import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; +import type {DangerouslyImpreciseStyleProp, ViewStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -88,11 +88,19 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * Optional custom style for multi-item rows generated when numColumns > 1. */ @@ -347,6 +355,7 @@ class FlatList extends React.PureComponent, void> { viewPosition?: number, }) { if (this._listRef) { + // $FlowFixMe Found when typing ListView this._listRef.scrollToIndex(params); } } @@ -363,6 +372,7 @@ class FlatList extends React.PureComponent, void> { viewPosition?: number, }) { if (this._listRef) { + // $FlowFixMe Found when typing ListView this._listRef.scrollToItem(params); } } @@ -374,6 +384,7 @@ class FlatList extends React.PureComponent, void> { */ scrollToOffset(params: {animated?: ?boolean, offset: number}) { if (this._listRef) { + // $FlowFixMe Found when typing ListView this._listRef.scrollToOffset(params); } } @@ -385,6 +396,7 @@ class FlatList extends React.PureComponent, void> { */ recordInteraction() { if (this._listRef) { + // $FlowFixMe Found when typing ListView this._listRef.recordInteraction(); } } @@ -396,6 +408,7 @@ class FlatList extends React.PureComponent, void> { */ flashScrollIndicators() { if (this._listRef) { + // $FlowFixMe Found when typing ListView this._listRef.flashScrollIndicators(); } } @@ -405,51 +418,27 @@ class FlatList extends React.PureComponent, void> { */ getScrollResponder() { if (this._listRef) { + // $FlowFixMe Found when typing ListView return this._listRef.getScrollResponder(); } } getScrollableNode() { if (this._listRef) { + // $FlowFixMe Found when typing ListView return this._listRef.getScrollableNode(); } } - setNativeProps(props: Object) { + setNativeProps(props: {[string]: mixed}) { if (this._listRef) { this._listRef.setNativeProps(props); } } - UNSAFE_componentWillMount() { - this._checkProps(this.props); - } - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - invariant( - nextProps.numColumns === this.props.numColumns, - 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + - 'changing the number of columns to force a fresh render of the component.', - ); - invariant( - nextProps.onViewableItemsChanged === this.props.onViewableItemsChanged, - 'Changing onViewableItemsChanged on the fly is not supported', - ); - invariant( - nextProps.viewabilityConfig === this.props.viewabilityConfig, - 'Changing viewabilityConfig on the fly is not supported', - ); - invariant( - nextProps.viewabilityConfigCallbackPairs === - this.props.viewabilityConfigCallbackPairs, - 'Changing viewabilityConfigCallbackPairs on the fly is not supported', - ); - - this._checkProps(nextProps); - } - - constructor(props: Props<*>) { + constructor(props: Props) { super(props); + this._checkProps(this.props); if (this.props.viewabilityConfigCallbackPairs) { this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map( pair => ({ @@ -472,8 +461,31 @@ class FlatList extends React.PureComponent, void> { } } + componentDidUpdate(prevProps: Props) { + invariant( + prevProps.numColumns === this.props.numColumns, + 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + + 'changing the number of columns to force a fresh render of the component.', + ); + invariant( + prevProps.onViewableItemsChanged === this.props.onViewableItemsChanged, + 'Changing onViewableItemsChanged on the fly is not supported', + ); + invariant( + prevProps.viewabilityConfig === this.props.viewabilityConfig, + 'Changing viewabilityConfig on the fly is not supported', + ); + invariant( + prevProps.viewabilityConfigCallbackPairs === + this.props.viewabilityConfigCallbackPairs, + 'Changing viewabilityConfigCallbackPairs on the fly is not supported', + ); + + this._checkProps(this.props); + } + _hasWarnedLegacy = false; - _listRef: null | VirtualizedList | ListView; + _listRef: null | VirtualizedList | ListView | MetroListView; _virtualizedListPairs: Array = []; _captureRef = ref => { @@ -531,7 +543,9 @@ class FlatList extends React.PureComponent, void> { const ret = []; for (let kk = 0; kk < numColumns; kk++) { const item = data[index * numColumns + kk]; - item && ret.push(item); + if (item != null) { + ret.push(item); + } } return ret; } else { @@ -608,7 +622,11 @@ class FlatList extends React.PureComponent, void> { 'Expected array of items with numColumns > 1', ); return ( - + {item.map((it, kk) => { const element = renderItem({ item: it, @@ -655,4 +673,8 @@ class FlatList extends React.PureComponent, void> { } } +const styles = StyleSheet.create({ + row: {flexDirection: 'row'}, +}); + module.exports = FlatList; diff --git a/Libraries/Lists/ListView/InternalListViewType.js b/Libraries/Lists/ListView/InternalListViewType.js new file mode 100644 index 00000000000000..1df93fd565d997 --- /dev/null +++ b/Libraries/Lists/ListView/InternalListViewType.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +const React = require('React'); +const ListViewDataSource = require('ListViewDataSource'); + +// This class is purely a facsimile of ListView so that we can +// properly type it with Flow before migrating ListView off of +// createReactClass. If there are things missing here that are in +// ListView, that is unintentional. +class InternalListViewType extends React.Component { + static DataSource = ListViewDataSource; + setNativeProps(props: Object) {} + flashScrollIndicators() {} + getScrollResponder(): any {} + getScrollableNode(): any {} + // $FlowFixMe + getMetrics(): Object {} + scrollTo(...args: Array) {} + scrollToEnd(options?: ?{animated?: ?boolean}) {} +} + +module.exports = InternalListViewType; diff --git a/Libraries/Lists/ListView/ListView.js b/Libraries/Lists/ListView/ListView.js index 9aefd2ea9222b3..bed5dbda2e2f93 100644 --- a/Libraries/Lists/ListView/ListView.js +++ b/Libraries/Lists/ListView/ListView.js @@ -4,40 +4,158 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule ListView * @flow * @format */ 'use strict'; -var ListViewDataSource = require('ListViewDataSource'); -var Platform = require('Platform'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var RCTScrollViewManager = require('NativeModules').ScrollViewManager; -var ScrollView = require('ScrollView'); -var ScrollResponder = require('ScrollResponder'); -var StaticRenderer = require('StaticRenderer'); -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ -var TimerMixin = require('react-timer-mixin'); -var View = require('View'); - -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ -var cloneReferencedElement = require('react-clone-referenced-element'); -var createReactClass = require('create-react-class'); -var isEmpty = require('isEmpty'); -var merge = require('merge'); - -var DEFAULT_PAGE_SIZE = 1; -var DEFAULT_INITIAL_ROWS = 10; -var DEFAULT_SCROLL_RENDER_AHEAD = 1000; -var DEFAULT_END_REACHED_THRESHOLD = 1000; -var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; +const InternalListViewType = require('InternalListViewType'); +const ListViewDataSource = require('ListViewDataSource'); +const Platform = require('Platform'); +const React = require('React'); +const ReactNative = require('ReactNative'); +const RCTScrollViewManager = require('NativeModules').ScrollViewManager; +const ScrollView = require('ScrollView'); +const ScrollResponder = require('ScrollResponder'); +const StaticRenderer = require('StaticRenderer'); +const TimerMixin = require('react-timer-mixin'); +const View = require('View'); +const cloneReferencedElement = require('react-clone-referenced-element'); +const createReactClass = require('create-react-class'); +const isEmpty = require('isEmpty'); +const merge = require('merge'); + +import type {Props as ScrollViewProps} from 'ScrollView'; + +const DEFAULT_PAGE_SIZE = 1; +const DEFAULT_INITIAL_ROWS = 10; +const DEFAULT_SCROLL_RENDER_AHEAD = 1000; +const DEFAULT_END_REACHED_THRESHOLD = 1000; +const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; + +type Props = $ReadOnly<{| + ...ScrollViewProps, + + /** + * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use + */ + dataSource: ListViewDataSource, + /** + * (sectionID, rowID, adjacentRowHighlighted) => renderable + * + * If provided, a renderable component to be rendered as the separator + * below each row but not the last row if there is a section header below. + * Take a sectionID and rowID of the row above and whether its adjacent row + * is highlighted. + */ + renderSeparator?: ?Function, + /** + * (rowData, sectionID, rowID, highlightRow) => renderable + * + * Takes a data entry from the data source and its ids and should return + * a renderable component to be rendered as the row. By default the data + * is exactly what was put into the data source, but it's also possible to + * provide custom extractors. ListView can be notified when a row is + * being highlighted by calling `highlightRow(sectionID, rowID)`. This + * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you + * to control the separators above and below the highlighted row. The highlighted + * state of a row can be reset by calling highlightRow(null). + */ + renderRow: Function, + /** + * How many rows to render on initial component mount. Use this to make + * it so that the first screen worth of data appears at one time instead of + * over the course of multiple frames. + */ + initialListSize?: ?number, + /** + * Called when all rows have been rendered and the list has been scrolled + * to within onEndReachedThreshold of the bottom. The native scroll + * event is provided. + */ + onEndReached?: ?Function, + /** + * Threshold in pixels (virtual, not physical) for calling onEndReached. + */ + onEndReachedThreshold?: ?number, + /** + * Number of rows to render per event loop. Note: if your 'rows' are actually + * cells, i.e. they don't span the full width of your view (as in the + * ListViewGridLayoutExample), you should set the pageSize to be a multiple + * of the number of cells per row, otherwise you're likely to see gaps at + * the edge of the ListView as new pages are loaded. + */ + pageSize?: ?number, + /** + * () => renderable + * + * The header and footer are always rendered (if these props are provided) + * on every render pass. If they are expensive to re-render, wrap them + * in StaticContainer or other mechanism as appropriate. Footer is always + * at the bottom of the list, and header at the top, on every render pass. + * In a horizontal ListView, the header is rendered on the left and the + * footer on the right. + */ + renderFooter?: ?Function, + renderHeader?: ?Function, + /** + * (sectionData, sectionID) => renderable + * + * If provided, a header is rendered for this section. + */ + renderSectionHeader?: ?Function, + /** + * (props) => renderable + * + * A function that returns the scrollable component in which the list rows + * are rendered. Defaults to returning a ScrollView with the given props. + */ + renderScrollComponent?: ?Function, + /** + * How early to start rendering rows before they come on screen, in + * pixels. + */ + scrollRenderAheadDistance?: ?number, + /** + * (visibleRows, changedRows) => void + * + * Called when the set of visible rows changes. `visibleRows` maps + * { sectionID: { rowID: true }} for all the visible rows, and + * `changedRows` maps { sectionID: { rowID: true | false }} for the rows + * that have changed their visibility, with true indicating visible, and + * false indicating the view has moved out of view. + */ + onChangeVisibleRows?: ?Function, + /** + * A performance optimization for improving scroll perf of + * large lists, used in conjunction with overflow: 'hidden' on the row + * containers. This is enabled by default. + */ + removeClippedSubviews?: ?boolean, + /** + * Makes the sections headers sticky. The sticky behavior means that it + * will scroll with the content at the top of the section until it reaches + * the top of the screen, at which point it will stick to the top until it + * is pushed off the screen by the next section header. This property is + * not supported in conjunction with `horizontal={true}`. Only enabled by + * default on iOS because of typical platform standards. + */ + stickySectionHeadersEnabled?: ?boolean, + /** + * An array of child indices determining which children get docked to the + * top of the screen when scrolling. For example, passing + * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the + * top of the scroll view. This property is not supported in conjunction + * with `horizontal={true}`. + */ + stickyHeaderIndices?: ?$ReadOnlyArray, + /** + * Flag indicating whether empty section headers should be rendered. In the future release + * empty section headers will be rendered by default, and the flag will be deprecated. + * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. + */ + enableEmptySections?: ?boolean, +|}>; /** * DEPRECATED - use one of the new list components, such as [`FlatList`](docs/flatlist.html) @@ -96,11 +214,11 @@ var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; * rendering rows. */ -var ListView = createReactClass({ +const ListView = createReactClass({ displayName: 'ListView', _childFrames: ([]: Array), _sentEndForContentLength: (null: ?number), - _scrollComponent: (null: any), + _scrollComponent: (null: ?React.ElementRef), _prevRenderedRowsCount: 0, _visibleRows: ({}: Object), scrollProperties: ({}: Object), @@ -111,136 +229,6 @@ var ListView = createReactClass({ DataSource: ListViewDataSource, }, - /** - * You must provide a renderRow function. If you omit any of the other render - * functions, ListView will simply skip rendering them. - * - * - renderRow(rowData, sectionID, rowID, highlightRow); - * - renderSectionHeader(sectionData, sectionID); - */ - propTypes: { - ...ScrollView.propTypes, - /** - * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use - */ - dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, - /** - * (sectionID, rowID, adjacentRowHighlighted) => renderable - * - * If provided, a renderable component to be rendered as the separator - * below each row but not the last row if there is a section header below. - * Take a sectionID and rowID of the row above and whether its adjacent row - * is highlighted. - */ - renderSeparator: PropTypes.func, - /** - * (rowData, sectionID, rowID, highlightRow) => renderable - * - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. ListView can be notified when a row is - * being highlighted by calling `highlightRow(sectionID, rowID)`. This - * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you - * to control the separators above and below the highlighted row. The highlighted - * state of a row can be reset by calling highlightRow(null). - */ - renderRow: PropTypes.func.isRequired, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data appears at one time instead of - * over the course of multiple frames. - */ - initialListSize: PropTypes.number.isRequired, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels (virtual, not physical) for calling onEndReached. - */ - onEndReachedThreshold: PropTypes.number.isRequired, - /** - * Number of rows to render per event loop. Note: if your 'rows' are actually - * cells, i.e. they don't span the full width of your view (as in the - * ListViewGridLayoutExample), you should set the pageSize to be a multiple - * of the number of cells per row, otherwise you're likely to see gaps at - * the edge of the ListView as new pages are loaded. - */ - pageSize: PropTypes.number.isRequired, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - * In a horizontal ListView, the header is rendered on the left and the - * footer on the right. - */ - renderFooter: PropTypes.func, - renderHeader: PropTypes.func, - /** - * (sectionData, sectionID) => renderable - * - * If provided, a header is rendered for this section. - */ - renderSectionHeader: PropTypes.func, - /** - * (props) => renderable - * - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent: PropTypes.func.isRequired, - /** - * How early to start rendering rows before they come on screen, in - * pixels. - */ - scrollRenderAheadDistance: PropTypes.number.isRequired, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows: PropTypes.func, - /** - * A performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. This is enabled by default. - */ - removeClippedSubviews: PropTypes.bool, - /** - * Makes the sections headers sticky. The sticky behavior means that it - * will scroll with the content at the top of the section until it reaches - * the top of the screen, at which point it will stick to the top until it - * is pushed off the screen by the next section header. This property is - * not supported in conjunction with `horizontal={true}`. Only enabled by - * default on iOS because of typical platform standards. - */ - stickySectionHeadersEnabled: PropTypes.bool, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number).isRequired, - /** - * Flag indicating whether empty section headers should be rendered. In the future release - * empty section headers will be rendered by default, and the flag will be deprecated. - * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. - */ - enableEmptySections: PropTypes.bool, - }, - /** * Exports some data, e.g. for perf investigations or analytics. */ @@ -279,7 +267,7 @@ var ListView = createReactClass({ * * See `ScrollView#scrollTo`. */ - scrollTo: function(...args: Array) { + scrollTo: function(...args: any) { if (this._scrollComponent && this._scrollComponent.scrollTo) { this._scrollComponent.scrollTo(...args); } @@ -295,7 +283,7 @@ 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); @@ -349,7 +337,7 @@ var ListView = createReactClass({ }, getInnerViewNode: function() { - return this._scrollComponent.getInnerViewNode(); + return this._scrollComponent && this._scrollComponent.getInnerViewNode(); }, UNSAFE_componentWillMount: function() { @@ -406,28 +394,25 @@ var ListView = createReactClass({ }, render: function() { - var bodyComponents = []; + const bodyComponents = []; - var dataSource = this.props.dataSource; - var allRowIDs = dataSource.rowIdentities; - var rowCount = 0; - var stickySectionHeaderIndices = []; + const dataSource = this.props.dataSource; + const allRowIDs = dataSource.rowIdentities; + let rowCount = 0; + const stickySectionHeaderIndices = []; const {renderSectionHeader} = this.props; - var header = this.props.renderHeader && this.props.renderHeader(); - var footer = this.props.renderFooter && this.props.renderFooter(); - var totalIndex = header ? 1 : 0; + const header = this.props.renderHeader && this.props.renderHeader(); + const footer = this.props.renderFooter && this.props.renderFooter(); + let totalIndex = header ? 1 : 0; - for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - var sectionID = dataSource.sectionIdentities[sectionIdx]; - var rowIDs = allRowIDs[sectionIdx]; + for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { + const sectionID = dataSource.sectionIdentities[sectionIdx]; + const rowIDs = allRowIDs[sectionIdx]; if (rowIDs.length === 0) { if (this.props.enableEmptySections === undefined) { - /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses - * an error found when Flow v0.54 was deployed. To see the error - * delete this comment and run Flow. */ - var warning = require('fbjs/lib/warning'); + const warning = require('fbjs/lib/warning'); warning( false, 'In next release empty section headers will be rendered.' + @@ -435,7 +420,7 @@ var ListView = createReactClass({ ); continue; } else { - var invariant = require('fbjs/lib/invariant'); + const invariant = require('fbjs/lib/invariant'); invariant( this.props.enableEmptySections, "In next release 'enableEmptySections' flag will be deprecated, empty section headers will always be rendered." + @@ -461,13 +446,13 @@ var ListView = createReactClass({ } } - for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - var rowID = rowIDs[rowIdx]; - var comboID = sectionID + '_' + rowID; - var shouldUpdateRow = + for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { + const rowID = rowIDs[rowIdx]; + const comboID = sectionID + '_' + rowID; + const shouldUpdateRow = rowCount >= this._prevRenderedRowsCount && dataSource.rowShouldUpdate(sectionIdx, rowIdx); - var row = ( + const row = ( { - var rowsToRender = Math.min( + const rowsToRender = Math.min( state.curRenderedRowsCount + props.pageSize, props.enableEmptySections ? props.dataSource.getRowAndSectionCount() @@ -667,32 +652,32 @@ var ListView = createReactClass({ this._childFrames[newFrame.index] = merge(newFrame); }); } - var isVertical = !this.props.horizontal; - var dataSource = this.props.dataSource; - var visibleMin = this.scrollProperties.offset; - var visibleMax = visibleMin + this.scrollProperties.visibleLength; - var allRowIDs = dataSource.rowIdentities; - - var header = this.props.renderHeader && this.props.renderHeader(); - var totalIndex = header ? 1 : 0; - var visibilityChanged = false; - var changedRows = {}; - for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - var rowIDs = allRowIDs[sectionIdx]; + const isVertical = !this.props.horizontal; + const dataSource = this.props.dataSource; + const visibleMin = this.scrollProperties.offset; + const visibleMax = visibleMin + this.scrollProperties.visibleLength; + const allRowIDs = dataSource.rowIdentities; + + const header = this.props.renderHeader && this.props.renderHeader(); + let totalIndex = header ? 1 : 0; + let visibilityChanged = false; + const changedRows = {}; + for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { + const rowIDs = allRowIDs[sectionIdx]; if (rowIDs.length === 0) { continue; } - var sectionID = dataSource.sectionIdentities[sectionIdx]; + const sectionID = dataSource.sectionIdentities[sectionIdx]; if (this.props.renderSectionHeader) { totalIndex++; } - var visibleSection = this._visibleRows[sectionID]; + let visibleSection = this._visibleRows[sectionID]; if (!visibleSection) { visibleSection = {}; } - for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - var rowID = rowIDs[rowIdx]; - var frame = this._childFrames[totalIndex]; + for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { + const rowID = rowIDs[rowIdx]; + const frame = this._childFrames[totalIndex]; totalIndex++; if ( this.props.renderSeparator && @@ -703,9 +688,9 @@ var ListView = createReactClass({ if (!frame) { break; } - var rowVisible = visibleSection[rowID]; - var min = isVertical ? frame.y : frame.x; - var max = min + (isVertical ? frame.height : frame.width); + const rowVisible = visibleSection[rowID]; + const min = isVertical ? frame.y : frame.x; + const max = min + (isVertical ? frame.height : frame.width); if ((!min && !max) || min === max) { break; } @@ -738,7 +723,7 @@ var ListView = createReactClass({ }, _onScroll: function(e: Object) { - var isVertical = !this.props.horizontal; + const isVertical = !this.props.horizontal; this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[isVertical ? 'height' : 'width']; this.scrollProperties.contentLength = @@ -763,4 +748,4 @@ var ListView = createReactClass({ }, }); -module.exports = ListView; +module.exports = ((ListView: any): Class>); diff --git a/Libraries/Lists/ListView/ListViewDataSource.js b/Libraries/Lists/ListView/ListViewDataSource.js index ec63c94d0f46dd..b046a58bfd2af1 100644 --- a/Libraries/Lists/ListView/ListViewDataSource.js +++ b/Libraries/Lists/ListView/ListViewDataSource.js @@ -4,18 +4,17 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule ListViewDataSource * @flow * @format */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var isEmpty = require('isEmpty'); +const invariant = require('fbjs/lib/invariant'); +const isEmpty = require('isEmpty'); /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error * found when Flow v0.54 was deployed. To see the error delete this comment and * run Flow. */ -var warning = require('fbjs/lib/warning'); +const warning = require('fbjs/lib/warning'); function defaultGetRowData( dataBlob: any, @@ -143,7 +142,7 @@ class ListViewDataSource { dataBlob: $ReadOnlyArray | {+[key: string]: any}, rowIdentities: ?$ReadOnlyArray, ): ListViewDataSource { - var rowIds = rowIdentities ? [[...rowIdentities]] : null; + const rowIds = rowIdentities ? [[...rowIdentities]] : null; if (!this._sectionHeaderHasChanged) { this._sectionHeaderHasChanged = () => false; } @@ -185,7 +184,7 @@ class ListViewDataSource { 'row and section ids lengths must be the same', ); - var newSource = new ListViewDataSource({ + const newSource = new ListViewDataSource({ getRowData: this._getRowData, getSectionHeaderData: this._getSectionHeaderData, rowHasChanged: this._rowHasChanged, @@ -238,7 +237,7 @@ class ListViewDataSource { * Returns if the row is dirtied and needs to be rerendered */ rowShouldUpdate(sectionIndex: number, rowIndex: number): boolean { - var needsUpdate = this._dirtyRows[sectionIndex][rowIndex]; + const needsUpdate = this._dirtyRows[sectionIndex][rowIndex]; warning( needsUpdate !== undefined, 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex, @@ -250,8 +249,8 @@ class ListViewDataSource { * Gets the data required to render the row. */ getRowData(sectionIndex: number, rowIndex: number): any { - var sectionID = this.sectionIdentities[sectionIndex]; - var rowID = this.rowIdentities[sectionIndex][rowIndex]; + const sectionID = this.sectionIdentities[sectionIndex]; + const rowID = this.rowIdentities[sectionIndex][rowIndex]; warning( sectionID !== undefined && rowID !== undefined, 'rendering invalid section, row: ' + sectionIndex + ', ' + rowIndex, @@ -264,8 +263,8 @@ class ListViewDataSource { * or null of out of range indexes. */ getRowIDForFlatIndex(index: number): ?string { - var accessIndex = index; - for (var ii = 0; ii < this.sectionIdentities.length; ii++) { + let accessIndex = index; + for (let ii = 0; ii < this.sectionIdentities.length; ii++) { if (accessIndex >= this.rowIdentities[ii].length) { accessIndex -= this.rowIdentities[ii].length; } else { @@ -280,8 +279,8 @@ class ListViewDataSource { * or null for out of range indexes. */ getSectionIDForFlatIndex(index: number): ?string { - var accessIndex = index; - for (var ii = 0; ii < this.sectionIdentities.length; ii++) { + let accessIndex = index; + for (let ii = 0; ii < this.sectionIdentities.length; ii++) { if (accessIndex >= this.rowIdentities[ii].length) { accessIndex -= this.rowIdentities[ii].length; } else { @@ -295,8 +294,8 @@ class ListViewDataSource { * Returns an array containing the number of rows in each section */ getSectionLengths(): Array { - var results = []; - for (var ii = 0; ii < this.sectionIdentities.length; ii++) { + const results = []; + for (let ii = 0; ii < this.sectionIdentities.length; ii++) { results.push(this.rowIdentities[ii].length); } return results; @@ -306,7 +305,7 @@ class ListViewDataSource { * Returns if the section header is dirtied and needs to be rerendered */ sectionHeaderShouldUpdate(sectionIndex: number): boolean { - var needsUpdate = this._dirtySections[sectionIndex]; + const needsUpdate = this._dirtySections[sectionIndex]; warning( needsUpdate !== undefined, 'missing dirtyBit for section: ' + sectionIndex, @@ -321,7 +320,7 @@ class ListViewDataSource { if (!this._getSectionHeaderData) { return null; } - var sectionID = this.sectionIdentities[sectionIndex]; + const sectionID = this.sectionIdentities[sectionIndex]; warning( sectionID !== undefined, 'renderSection called on invalid section: ' + sectionIndex, @@ -354,9 +353,9 @@ class ListViewDataSource { prevRowIDs: Array>, ): void { // construct a hashmap of the existing (old) id arrays - var prevSectionsHash = keyedDictionaryFromArray(prevSectionIDs); - var prevRowsHash = {}; - for (var ii = 0; ii < prevRowIDs.length; ii++) { + const prevSectionsHash = keyedDictionaryFromArray(prevSectionIDs); + const prevRowsHash = {}; + for (let ii = 0; ii < prevRowIDs.length; ii++) { var sectionID = prevSectionIDs[ii]; warning( !prevRowsHash[sectionID], @@ -369,12 +368,12 @@ class ListViewDataSource { this._dirtySections = []; this._dirtyRows = []; - var dirty; - for (var sIndex = 0; sIndex < this.sectionIdentities.length; sIndex++) { + let dirty; + for (let sIndex = 0; sIndex < this.sectionIdentities.length; sIndex++) { var sectionID = this.sectionIdentities[sIndex]; // dirty if the sectionHeader is new or _sectionHasChanged is true dirty = !prevSectionsHash[sectionID]; - var sectionHeaderHasChanged = this._sectionHeaderHasChanged; + const sectionHeaderHasChanged = this._sectionHeaderHasChanged; if (!dirty && sectionHeaderHasChanged) { dirty = sectionHeaderHasChanged( this._getSectionHeaderData(prevDataBlob, sectionID), @@ -385,11 +384,11 @@ class ListViewDataSource { this._dirtyRows[sIndex] = []; for ( - var rIndex = 0; + let rIndex = 0; rIndex < this.rowIdentities[sIndex].length; rIndex++ ) { - var rowID = this.rowIdentities[sIndex][rIndex]; + const rowID = this.rowIdentities[sIndex][rIndex]; // dirty if the section is new, row is new or _rowHasChanged is true dirty = !prevSectionsHash[sectionID] || @@ -405,9 +404,9 @@ class ListViewDataSource { } function countRows(allRowIDs) { - var totalRows = 0; - for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - var rowIDs = allRowIDs[sectionIdx]; + let totalRows = 0; + for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { + const rowIDs = allRowIDs[sectionIdx]; totalRows += rowIDs.length; } return totalRows; @@ -417,9 +416,9 @@ function keyedDictionaryFromArray(arr) { if (isEmpty(arr)) { return {}; } - var result = {}; - for (var ii = 0; ii < arr.length; ii++) { - var key = arr[ii]; + const result = {}; + for (let ii = 0; ii < arr.length; ii++) { + const key = arr[ii]; warning(!result[key], 'Value appears more than once in array: ' + key); result[key] = true; } diff --git a/Libraries/Lists/MetroListView.js b/Libraries/Lists/MetroListView.js index 5f22eb264ee85b..1d59dba8d16582 100644 --- a/Libraries/Lists/MetroListView.js +++ b/Libraries/Lists/MetroListView.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule MetroListView * @flow * @format */ @@ -86,6 +85,7 @@ class MetroListView extends React.Component { } scrollToOffset(params: {animated?: ?boolean, offset: number}) { const {animated, offset} = params; + // $FlowFixMe Invalid prop usage this._listRef.scrollTo( this.props.horizontal ? {x: offset, animated} : {y: offset, animated}, ); @@ -103,6 +103,7 @@ class MetroListView extends React.Component { renderScrollComponent: (props: Props) => { if (props.onRefresh) { return ( + // $FlowFixMe Invalid prop usage { /> ); } else { + // $FlowFixMe Invalid prop usage return ; } }, @@ -136,6 +138,7 @@ class MetroListView extends React.Component { } render() { return ( + // $FlowFixMe Found when typing ListView { /> ); } - _listRef: ListView; + _listRef: ?ListView; _captureRef = ref => { this._listRef = ref; }; @@ -168,6 +171,7 @@ class MetroListView extends React.Component { } else { invariant(!props.sections, 'Cannot have both sections and items props.'); return { + // $FlowFixMe Found when typing ListView ds: state.ds.cloneWithRows(props.items), sectionHeaderData, }; diff --git a/Libraries/Lists/SectionList.js b/Libraries/Lists/SectionList.js index 2404d26f54c72a..9d7d2eb8195d54 100644 --- a/Libraries/Lists/SectionList.js +++ b/Libraries/Lists/SectionList.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule SectionList * @flow * @format */ @@ -285,6 +284,7 @@ class SectionList> extends React.PureComponent< */ recordInteraction() { const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + // $FlowFixMe Found when typing ListView listRef && listRef.recordInteraction(); } diff --git a/Libraries/Lists/ViewabilityHelper.js b/Libraries/Lists/ViewabilityHelper.js index 0c0a8356019075..415e77895360da 100644 --- a/Libraries/Lists/ViewabilityHelper.js +++ b/Libraries/Lists/ViewabilityHelper.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule ViewabilityHelper * @flow * @format */ diff --git a/Libraries/Lists/VirtualizeUtils.js b/Libraries/Lists/VirtualizeUtils.js index 663b36b8cc6e76..b47ceda1fece63 100644 --- a/Libraries/Lists/VirtualizeUtils.js +++ b/Libraries/Lists/VirtualizeUtils.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule VirtualizeUtils * @flow * @format */ diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 58fe055a373cde..2efbba657a850a 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule VirtualizedList * @flow * @format */ @@ -32,7 +31,7 @@ const warning = require('fbjs/lib/warning'); const {computeWindowedRenderLimits} = require('VirtualizeUtils'); -import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; +import type {DangerouslyImpreciseStyleProp, ViewStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -125,11 +124,19 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * A unique identifier for this list. If there are multiple VirtualizedLists at the same level of * nesting within another VirtualizedList, this key is necessary for virtualization to @@ -173,6 +180,12 @@ type OptionalProps = { * @platform android */ progressViewOffset?: number, + /** + * A custom refresh control element. When set, it overrides the default + * component built internally. The onRefresh and refreshing + * props are also ignored. Only works for vertical VirtualizedList. + */ + refreshControl?: ?React.Element, /** * Set this true while waiting for new data from a refresh. */ @@ -212,6 +225,7 @@ type OptionalProps = { export type Props = RequiredProps & OptionalProps; let _usedIndexForKey = false; +let _keylessItemComponentName: string = ''; type Frame = { offset: number, @@ -425,6 +439,9 @@ class VirtualizedList extends React.PureComponent { return item.key; } _usedIndexForKey = true; + if (item.type && item.type.displayName) { + _keylessItemComponentName = item.type.displayName; + } return String(index); }, maxToRenderPerBatch: 10, @@ -747,12 +764,16 @@ class VirtualizedList extends React.PureComponent { - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} + + { + // $FlowFixMe - Typing ReactNativeComponent revealed errors + element + } , ); @@ -760,6 +781,7 @@ class VirtualizedList extends React.PureComponent { const itemCount = this.props.getItemCount(data); if (itemCount > 0) { _usedIndexForKey = false; + _keylessItemComponentName = ''; const spacerKey = !horizontal ? 'height' : 'width'; const lastInitialIndex = this.props.initialScrollIndex ? -1 @@ -783,8 +805,7 @@ class VirtualizedList extends React.PureComponent { if (stickyIndicesFromProps.has(ii + stickyOffset)) { const initBlock = this._getFrameMetricsApprox(lastInitialIndex); const stickyBlock = this._getFrameMetricsApprox(ii); - const leadSpace = - stickyBlock.offset - (initBlock.offset + initBlock.length); + const leadSpace = stickyBlock.offset - initBlock.offset; cells.push( , ); @@ -829,6 +850,7 @@ class VirtualizedList extends React.PureComponent { console.warn( 'VirtualizedList: missing keys for items, make sure to specify a key property on each ' + 'item or provide a custom keyExtractor.', + _keylessItemComponentName, ); this._hasWarned.keys = true; } @@ -850,23 +872,25 @@ class VirtualizedList extends React.PureComponent { ); } } else if (ListEmptyComponent) { - const element = React.isValidElement(ListEmptyComponent) ? ( + const element: React.Element = ((React.isValidElement( + ListEmptyComponent, + ) ? ( ListEmptyComponent ) : ( // $FlowFixMe - ); + )): any); cells.push( - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} - , + React.cloneElement(element, { + key: '$empty', + onLayout: event => { + this._onLayoutEmpty(event); + if (element.props.onLayout) { + element.props.onLayout(event); + } + }, + style: [element.props.style, inversionStyle], + }), ); } if (ListFooterComponent) { @@ -880,12 +904,16 @@ class VirtualizedList extends React.PureComponent { - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} + + { + // $FlowFixMe - Typing ReactNativeComponent revealed errors + element + } , ); @@ -899,10 +927,16 @@ class VirtualizedList extends React.PureComponent { onScrollEndDrag: this._onScrollEndDrag, onMomentumScrollEnd: this._onMomentumScrollEnd, scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support - invertStickyHeaders: this.props.inverted, + invertStickyHeaders: + this.props.invertStickyHeaders !== undefined + ? this.props.invertStickyHeaders + : this.props.inverted, stickyHeaderIndices, }; if (inversionStyle) { + /* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.70 was deployed. To see the error delete + * this comment and run Flow. */ scrollProps.style = [inversionStyle, this.props.style]; } @@ -913,6 +947,7 @@ class VirtualizedList extends React.PureComponent { (this.props.renderScrollComponent || this._defaultRenderScrollComponent)( scrollProps, ), + // $FlowFixMe Invalid prop usage { ref: this._captureScrollRef, }, @@ -994,9 +1029,11 @@ class VirtualizedList extends React.PureComponent { } _defaultRenderScrollComponent = props => { + const onRefresh = props.onRefresh; if (this._isNestedWithSameOrientation()) { + // $FlowFixMe - Typing ReactNativeComponent revealed errors return ; - } else if (props.onRefresh) { + } else if (onRefresh) { invariant( typeof props.refreshing === 'boolean', '`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' + @@ -1004,21 +1041,24 @@ class VirtualizedList extends React.PureComponent { '`', ); return ( + // $FlowFixMe Invalid prop usage =0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for - * React. To see the error delete this comment and run Flow. */ - + props.refreshControl == null ? ( + + ) : ( + props.refreshControl + ) } /> ); } else { + // $FlowFixMe Invalid prop usage return ; } }; @@ -1051,6 +1091,17 @@ class VirtualizedList extends React.PureComponent { } else { this._frames[cellKey].inLayout = true; } + + const childListKeys = this._cellKeysToChildListKeys.get(cellKey); + if (childListKeys) { + for (let childKey of childListKeys) { + const childList = this._nestedChildLists.get(childKey); + childList && + childList.ref && + childList.ref.measureLayoutRelativeToContainingList(); + } + } + this._computeBlankness(); } @@ -1061,7 +1112,7 @@ class VirtualizedList extends React.PureComponent { } }; - _measureLayoutRelativeToContainingList(): void { + measureLayoutRelativeToContainingList(): void { UIManager.measureLayout( ReactNative.findNodeHandle(this), ReactNative.findNodeHandle( @@ -1090,7 +1141,7 @@ class VirtualizedList extends React.PureComponent { if (this._isNestedWithSameOrientation()) { // Need to adjust our scroll metrics to be relative to our containing // VirtualizedList before we can make claims about list item viewability - this._measureLayoutRelativeToContainingList(); + this.measureLayoutRelativeToContainingList(); } else { this._scrollMetrics.visibleLength = this._selectLength( e.nativeEvent.layout, @@ -1120,6 +1171,9 @@ class VirtualizedList extends React.PureComponent { const itemCount = this.props.getItemCount(this.props.data); for (let ii = 0; ii < itemCount; ii++) { const frame = this._getFrameMetricsApprox(ii); + /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.68 was deployed. To see the error delete this + * comment and run Flow. */ if (frame.inLayout) { framesInLayout.push(frame); } @@ -1328,18 +1382,26 @@ class VirtualizedList extends React.PureComponent { const {offset, visibleLength, velocity} = this._scrollMetrics; const itemCount = this.props.getItemCount(this.props.data); let hiPri = false; - if (first > 0 || last < itemCount - 1) { + const scrollingThreshold = + /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.63 was deployed. To see the error delete + * this comment and run Flow. */ + (this.props.onEndReachedThreshold * visibleLength) / 2; + // Mark as high priority if we're close to the start of the first item + // But only if there are items before the first rendered item + if (first > 0) { const distTop = offset - this._getFrameMetricsApprox(first).offset; + hiPri = + hiPri || distTop < 0 || (velocity < -2 && distTop < scrollingThreshold); + } + // Mark as high priority if we're close to the end of the last item + // But only if there are items after the last rendered item + if (last < itemCount - 1) { const distBottom = this._getFrameMetricsApprox(last).offset - (offset + visibleLength); - const scrollingThreshold = - /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.63 was deployed. To see the error delete - * this comment and run Flow. */ - this.props.onEndReachedThreshold * visibleLength / 2; hiPri = - Math.min(distTop, distBottom) < 0 || - (velocity < -2 && distTop < scrollingThreshold) || + hiPri || + distBottom < 0 || (velocity > 2 && distBottom < scrollingThreshold); } // Only trigger high-priority updates if we've actually rendered cells, @@ -1644,6 +1706,9 @@ class CellRenderer extends React.Component< separators: this._separators, }); const onLayout = + /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.68 was deployed. To see the error delete this + * comment and run Flow. */ getItemLayout && !parentProps.debug && !fillRateHelper.enabled() ? undefined : this.props.onLayout; @@ -1656,7 +1721,9 @@ class CellRenderer extends React.Component< ? horizontal ? [{flexDirection: 'row-reverse'}, inversionStyle] : [{flexDirection: 'column-reverse'}, inversionStyle] - : horizontal ? [{flexDirection: 'row'}, inversionStyle] : inversionStyle; + : horizontal + ? [{flexDirection: 'row'}, inversionStyle] + : inversionStyle; if (!CellRendererComponent) { return ( diff --git a/Libraries/Lists/VirtualizedSectionList.js b/Libraries/Lists/VirtualizedSectionList.js index 2bb196814abf13..616d976bbe12ef 100644 --- a/Libraries/Lists/VirtualizedSectionList.js +++ b/Libraries/Lists/VirtualizedSectionList.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule VirtualizedSectionList * @flow * @format */ @@ -38,12 +37,12 @@ type SectionBase = { updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, }, }) => ?React.Element, - ItemSeparatorComponent?: ?React.ComponentType<*>, + ItemSeparatorComponent?: ?React.ComponentType, keyExtractor?: (item: SectionItem, index: ?number) => string, // TODO: support more optional/override props - // FooterComponent?: ?ReactClass<*>, - // HeaderComponent?: ?ReactClass<*>, + // FooterComponent?: ?ReactClass, + // HeaderComponent?: ?ReactClass, // onViewableItemsChanged?: ({viewableItems: Array, changed: Array}) => void, }; @@ -55,11 +54,11 @@ type OptionalProps = { /** * Rendered after the last item in the last section. */ - ListFooterComponent?: ?(React.ComponentType<*> | React.Element), + ListFooterComponent?: ?(React.ComponentType | React.Element), /** * Rendered at the very beginning of the list. */ - ListHeaderComponent?: ?(React.ComponentType<*> | React.Element), + ListHeaderComponent?: ?(React.ComponentType | React.Element), /** * Default renderer for every item in every section. */ @@ -85,11 +84,11 @@ type OptionalProps = { * Rendered at the bottom of every Section, except the very last one, in place of the normal * ItemSeparatorComponent. */ - SectionSeparatorComponent?: ?React.ComponentType<*>, + SectionSeparatorComponent?: ?React.ComponentType, /** * Rendered at the bottom of every Item except the very last one in the last section. */ - ItemSeparatorComponent?: ?React.ComponentType<*>, + ItemSeparatorComponent?: ?React.ComponentType, /** * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes * the state of items when they scroll out of the render window, so make sure all relavent data is @@ -102,7 +101,7 @@ type OptionalProps = { * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make * sure to also set the `refreshing` prop correctly. */ - onRefresh?: ?Function, + onRefresh?: ?() => void, /** * Called when the viewability of rows changes, as defined by the * `viewabilityConfig` prop. @@ -135,10 +134,6 @@ class VirtualizedSectionList extends React.PureComponent< Props, State, > { - props: Props; - - state: State; - static defaultProps: DefaultProps = { ...VirtualizedList.defaultProps, data: [], @@ -165,6 +160,48 @@ class VirtualizedSectionList extends React.PureComponent< return this._listRef; } + constructor(props: Props, context: Object) { + super(props, context); + this.state = this._computeState(props); + } + + UNSAFE_componentWillReceiveProps(nextProps: Props) { + this.setState(this._computeState(nextProps)); + } + + _computeState(props: Props): State { + const offset = props.ListHeaderComponent ? 1 : 0; + const stickyHeaderIndices = []; + const itemCount = props.sections.reduce((v, section) => { + stickyHeaderIndices.push(v + offset); + return v + section.data.length + 2; // Add two for the section header and footer. + }, 0); + + return { + childProps: { + ...props, + renderItem: this._renderItem, + ItemSeparatorComponent: undefined, // Rendered with renderItem + data: props.sections, + getItemCount: () => itemCount, + getItem, + keyExtractor: this._keyExtractor, + onViewableItemsChanged: props.onViewableItemsChanged + ? this._onViewableItemsChanged + : undefined, + stickyHeaderIndices: props.stickySectionHeadersEnabled + ? stickyHeaderIndices + : undefined, + }, + }; + } + + render() { + return ( + + ); + } + _keyExtractor = (item: Item, index: number) => { const info = this._subExtractor(index); return (info && info.key) || String(index); @@ -308,7 +345,7 @@ class VirtualizedSectionList extends React.PureComponent< _getSeparatorComponent( index: number, info?: ?Object, - ): ?React.ComponentType<*> { + ): ?React.ComponentType { info = info || this._subExtractor(index); if (!info) { return null; @@ -327,48 +364,6 @@ class VirtualizedSectionList extends React.PureComponent< return null; } - _computeState(props: Props): State { - const offset = props.ListHeaderComponent ? 1 : 0; - const stickyHeaderIndices = []; - const itemCount = props.sections.reduce((v, section) => { - stickyHeaderIndices.push(v + offset); - return v + section.data.length + 2; // Add two for the section header and footer. - }, 0); - - return { - childProps: { - ...props, - renderItem: this._renderItem, - ItemSeparatorComponent: undefined, // Rendered with renderItem - data: props.sections, - getItemCount: () => itemCount, - getItem, - keyExtractor: this._keyExtractor, - onViewableItemsChanged: props.onViewableItemsChanged - ? this._onViewableItemsChanged - : undefined, - stickyHeaderIndices: props.stickySectionHeadersEnabled - ? stickyHeaderIndices - : undefined, - }, - }; - } - - constructor(props: Props, context: Object) { - super(props, context); - this.state = this._computeState(props); - } - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - this.setState(this._computeState(nextProps)); - } - - render() { - return ( - - ); - } - _cellRefs = {}; _listRef: VirtualizedList; _captureRef = ref => { @@ -379,25 +374,40 @@ class VirtualizedSectionList extends React.PureComponent< }; } -type ItemWithSeparatorProps = { - LeadingSeparatorComponent: ?React.ComponentType<*>, - SeparatorComponent: ?React.ComponentType<*>, +type ItemWithSeparatorCommonProps = $ReadOnly<{| + leadingItem: ?Item, + leadingSection: ?Object, + section: Object, + trailingItem: ?Item, + trailingSection: ?Object, +|}>; + +type ItemWithSeparatorProps = $ReadOnly<{| + ...ItemWithSeparatorCommonProps, + LeadingSeparatorComponent: ?React.ComponentType, + SeparatorComponent: ?React.ComponentType, cellKey: string, index: number, item: Item, onUpdateSeparator: (cellKey: string, newProps: Object) => void, prevCellKey?: ?string, renderItem: Function, - section: Object, - leadingItem: ?Item, - leadingSection: ?Object, - trailingItem: ?Item, - trailingSection: ?Object, +|}>; + +type ItemWithSeparatorState = { + separatorProps: $ReadOnly<{| + highlighted: false, + ...ItemWithSeparatorCommonProps, + |}>, + leadingSeparatorProps: $ReadOnly<{| + highlighted: false, + ...ItemWithSeparatorCommonProps, + |}>, }; class ItemWithSeparator extends React.Component< ItemWithSeparatorProps, - $FlowFixMeState, + ItemWithSeparatorState, > { state = { separatorProps: { @@ -431,7 +441,7 @@ class ItemWithSeparator extends React.Component< }, updateProps: (select: 'leading' | 'trailing', newProps: Object) => { const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props; - if (select === 'leading' && LeadingSeparatorComponent) { + if (select === 'leading' && LeadingSeparatorComponent != null) { this.setState(state => ({ leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps}, })); @@ -444,10 +454,13 @@ class ItemWithSeparator extends React.Component< }, }; - UNSAFE_componentWillReceiveProps(props: ItemWithSeparatorProps) { - this.setState(state => ({ + static getDerivedStateFromProps( + props: ItemWithSeparatorProps, + prevState: ItemWithSeparatorState, + ): ?ItemWithSeparatorState { + return { separatorProps: { - ...this.state.separatorProps, + ...prevState.separatorProps, leadingItem: props.item, leadingSection: props.leadingSection, section: props.section, @@ -455,14 +468,14 @@ class ItemWithSeparator extends React.Component< trailingSection: props.trailingSection, }, leadingSeparatorProps: { - ...this.state.leadingSeparatorProps, + ...prevState.leadingSeparatorProps, leadingItem: props.leadingItem, leadingSection: props.leadingSection, section: props.section, trailingItem: props.item, trailingSection: props.trailingSection, }, - })); + }; } updateSeparatorProps(newProps: Object) { diff --git a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap index 837ef99b1f12b9..7f523be29ac848 100644 --- a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap +++ b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap @@ -31,7 +31,6 @@ exports[`FlatList renders all the bells and whistles 1`] = ` getItemLayout={[Function]} horizontal={false} initialNumToRender={10} - invertStickyHeaders={undefined} keyExtractor={[Function]} maxToRenderPerBatch={10} numColumns={2} @@ -46,7 +45,6 @@ exports[`FlatList renders all the bells and whistles 1`] = ` refreshControl={ } @@ -62,22 +60,17 @@ exports[`FlatList renders all the bells and whistles 1`] = `