diff --git a/.buckconfig b/.buckconfig index 19c2d6302ea98f..272ffbbe1221a5 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,12 +1,17 @@ [android] - target = Google Inc.:Google APIs:23 + target = android-31 + +[kotlin] + compile_against_abis = True + kotlin_version = 1.6.10 [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 + rntester = //packages/rn-tester/android/app:app diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 00000000000000..848943bb5274b1 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/.circleci/DockerTests.md b/.circleci/DockerTests.md new file mode 100644 index 00000000000000..c65e72640e860b --- /dev/null +++ b/.circleci/DockerTests.md @@ -0,0 +1,81 @@ +# Docker Test Environment + +This is a high-level overview of the test configuration using Docker. +It explains how to run the tests locally. + +## Docker Installation + +It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles. +See for more information on how to install. + +## Convenience Scripts + +We have added a number of default run scripts to the `package.json` file to simplify building and running your tests. + +### Configuring Docker Images + +The following two scripts need to be run first before you can move on to testing: + +- `yarn run docker-setup-android`: Pulls down the React Native Community Android image that serves as a base image when building the actual test image. + +- `yarn run docker-build-android`: Builds a test image with the latest dependencies and React Native library code, including a compiled Android test app. + +### Running Tests + +Once the test image has been built, it can be used to run our Android tests. + +- `yarn run test-android-run-unit` runs the unit tests, as defined in `scripts/run-android-docker-unit-tests.sh`. +- `yarn run test-android-run-e2e` runs the end to end tests, as defined in `scripts/run-ci-e2e-tests.sh`. +- `yarn run test-android-run-instrumentation` runs the instrumentation tests, as defined in `scripts/run-android-docker-instrumentation-tests.sh`. + +#### Instrumentation Tests + +The instrumentation test script accepts the following flags in order to customize the execution of the tests: + +`--filter` - A regex that filters which instrumentation tests will be run. (Defaults to .*) + +`--package` - Name of the java package containing the instrumentation tests (Defaults to com.facebook.react.tests) + +`--path` - Path to the directory containing the instrumentation tests. (Defaults to ./ReactAndroid/src/androidTest/java/com/facebook/react/tests) + +`--retries` - Number of times to retry a failed test before declaring a failure (Defaults to 2) + +For example, if locally you only wanted to run the InitialPropsTestCase, you could do the following: +`yarn run test-android-run-instrumentation -- --filter="InitialPropsTestCase"` + +## Detailed Android Setup + +There are two Dockerfiles for use with the Android codebase. +The base image used to build `reactnativecommunity/react-native-android` is located in the https://github.com/react-native-community/docker-android GitHub repository. +It contains all the necessary prerequisites required to run the React Android tests. +It is separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite a beastly image since it contains all the Android dependencies for running Android and the emulators (~9GB). + +The good news is you should rarely have to build or pull down the base image! +All iterative code updates happen as part of the `Dockerfile.android` image build. + +Lets break it down... + +First, you'll need to pull the base image. +You can use `docker pull` to grab the latest version of the `reactnativecommunity/react-native-android` base image. +This is what you get when you run `yarn run docker-setup-android`. + +This will take quite some time depending on your connection and you need to ensure you have ~10GB of free disk space. + +Once you have downloaded the base image, the test image can be built using `docker build -t reactnativeci/android -f ./.circleci/Dockerfiles/Dockerfile.android .`. This is what `yarn run docker-build-android` does. Note that the `-t` flag is how you tell Docker what to name this image locally. You can then use `docker run -t reactnativeci/android` to run this image. + +Now that you've built the test image, you can run unit tests using what you've learned so far: + +```bash +docker run --cap-add=SYS_ADMIN -it reactnativeci/android bash .circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh +``` + +> Note: `--cap-add=SYS_ADMIN` flag is required for the `.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh` and `.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh` in order to allow the remounting of `/dev/shm` as writeable so the `buck` build system may write temporary output to that location. + +Every time you make any modifications to the codebase, including changes to the test scripts inside `.circleci/Dockerfiles/scripts`, you should re-run the `docker build ...` command in order for your updates to be included in your local docker test image. + +For rapid iteration, it's useful to keep in mind that Docker can pass along arbitrary commands to an image. +For example, you can alternatively use Gradle in this manner: + +```bash +docker run --cap-add=SYS_ADMIN -it reactnativeci/android ./gradlew RNTester:android:app:assembleRelease +``` diff --git a/.circleci/Dockerfiles/Dockerfile.android b/.circleci/Dockerfiles/Dockerfile.android new file mode 100644 index 00000000000000..8d0e28803a5fc8 --- /dev/null +++ b/.circleci/Dockerfiles/Dockerfile.android @@ -0,0 +1,56 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# +# This image builds upon the React Native Community Android image: +# https://github.com/react-native-community/docker-android +# +# The base image is expected to remain relatively stable, and only +# needs to be updated when major dependencies such as the Android +# SDK or NDK are updated. +# +# In this Android Test image, we download the latest dependencies +# and build a Android application that can be used to run the +# tests specified in the scripts/ directory. +# +FROM reactnativecommunity/react-native-android:5.4 + +LABEL Description="React Native Android Test Image" +LABEL maintainer="Héctor Ramos " + +# set default environment variables +ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\"" +ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8" +ENV KOTLIN_HOME="third-party/kotlin" + +ADD .buckconfig /app/.buckconfig +ADD .buckjavaargs /app/.buckjavaargs +ADD BUCK /app/BUCK +ADD Libraries /app/Libraries +ADD ReactAndroid /app/ReactAndroid +ADD ReactCommon /app/ReactCommon +ADD React /app/React +ADD keystores /app/keystores +ADD packages/react-native-codegen /app/packages/react-native-codegen +ADD tools /app/tools +add scripts /app/scripts + +WORKDIR /app + +RUN scripts/download-kotlin-compiler-with-buck.sh + +RUN buck fetch ReactAndroid/src/test/java/com/facebook/react/modules +RUN buck fetch ReactAndroid/src/main/java/com/facebook/react +RUN buck fetch ReactAndroid/src/main/java/com/facebook/react/shell +RUN buck fetch ReactAndroid/src/test/... +RUN buck fetch ReactAndroid/src/androidTest/... + +RUN buck build ReactAndroid/src/main/java/com/facebook/react +RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell + +ADD . /app + +RUN yarn + +RUN ./gradlew :ReactAndroid:assembleDebug diff --git a/ContainerShip/scripts/run-android-ci-instrumentation-tests.js b/.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js similarity index 91% rename from ContainerShip/scripts/run-android-ci-instrumentation-tests.js rename to .circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js index 706159c4902bc4..f34e8d30eb4647 100644 --- a/ContainerShip/scripts/run-android-ci-instrumentation-tests.js +++ b/.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -28,7 +28,7 @@ const path = require('path'); const colors = { GREEN: '\x1b[32m', RED: '\x1b[31m', - RESET: '\x1b[0m' + RESET: '\x1b[0m', }; const test_opts = { @@ -38,10 +38,10 @@ const test_opts = { PATH: argv.path || './ReactAndroid/src/androidTest/java/com/facebook/react/tests', RETRIES: parseInt(argv.retries || 2, 10), - TEST_TIMEOUT: parseInt(argv['test-timeout'] || 1000 * 60 * 10), + TEST_TIMEOUT: parseInt(argv['test-timeout'] || 1000 * 60 * 10, 10), OFFSET: argv.offset, - COUNT: argv.count + COUNT: argv.count, }; let max_test_class_length = Number.NEGATIVE_INFINITY; @@ -68,7 +68,6 @@ testClasses = testClasses.map((clazz) => { // only process subset of the tests at corresponding offset and count if args provided if (test_opts.COUNT != null && test_opts.OFFSET != null) { - const testCount = testClasses.length; const start = test_opts.COUNT * test_opts.OFFSET; const end = start + test_opts.COUNT; @@ -81,14 +80,14 @@ if (test_opts.COUNT != null && test_opts.OFFSET != null) { } } -return async.mapSeries(testClasses, (clazz, callback) => { +async.mapSeries(testClasses, (clazz, callback) => { if (clazz.length > max_test_class_length) { max_test_class_length = clazz.length; } return async.retry(test_opts.RETRIES, (retryCb) => { - const test_process = child_process.spawn('./ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], { - stdio: 'inherit' + const test_process = child_process.spawn('./.circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], { + stdio: 'inherit', }); const timeout = setTimeout(() => { @@ -112,7 +111,7 @@ return async.mapSeries(testClasses, (clazz, callback) => { }, (err) => { return callback(null, { name: clazz, - status: err ? 'failure' : 'success' + status: err ? 'failure' : 'success', }); }); }, (err, results) => { diff --git a/.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh b/.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh new file mode 100644 index 00000000000000..64f0ee465642ab --- /dev/null +++ b/.circleci/Dockerfiles/scripts/run-android-docker-instrumentation-tests.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# for buck gen +mount -o remount,exec /dev/shm + +AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1) + +# create virtual device +echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a + +# emulator setup +emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim & +bootanim="" +until [[ "$bootanim" =~ "stopped" ]]; do + sleep 5 + bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1) + echo "boot animation status=$bootanim" +done + +set -x + +# solve issue with max user watches limit +echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches +watchman shutdown-server + +# integration tests +# build JS bundle for instrumentation tests +node cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js + +# build test APK +# shellcheck disable=SC1091 +source ./scripts/android-setup.sh && NO_BUCKD=1 retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1 + +# run installed apk with tests +node ./.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js "$*" +exit $? diff --git a/.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh b/.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh new file mode 100644 index 00000000000000..93a38f46318fef --- /dev/null +++ b/.circleci/Dockerfiles/scripts/run-android-docker-unit-tests.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# set default environment variables +UNIT_TESTS_BUILD_THREADS="${UNIT_TESTS_BUILD_THREADS:-1}" + +# for buck gen +mount -o remount,exec /dev/shm + +set -x + +# run unit tests +buck test ReactAndroid/src/test/... --config build.threads="$UNIT_TESTS_BUILD_THREADS" diff --git a/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh b/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh new file mode 100755 index 00000000000000..07a8779f60c46f --- /dev/null +++ b/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh @@ -0,0 +1,251 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +set -ex + +# set default environment variables +ROOT=$(pwd) +SCRIPTS=$(pwd)/scripts + +RUN_ANDROID=0 +RUN_CLI_INSTALL=1 +RUN_IOS=0 +RUN_JS=0 + +RETRY_COUNT=${RETRY_COUNT:-2} +AVD_UUID=$(< /dev/urandom tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1) +ANDROID_NPM_DEPS="appium@1.5.1 mocha@2.4.5 wd@0.3.11 colors@1.0.3 pretty-data2@0.40.1" +CLI_PACKAGE="$ROOT/react-native-cli/react-native-cli-*.tgz" +PACKAGE="$ROOT/react-native-*.tgz" + +# solve issue with max user watches limit +echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches +watchman shutdown-server + +# retries command on failure +# $1 -- max attempts +# $2 -- command to run +function retry() { + local -r -i max_attempts="$1"; shift + local -r cmd="$*" + local -i attempt_num=1 + + until $cmd; do + if (( attempt_num == max_attempts )); then + echo "Execution of '$cmd' failed; no more attempts left" + return 1 + else + (( attempt_num++ )) + echo "Execution of '$cmd' failed; retrying for attempt number $attempt_num..." + fi + done +} + +# parse command line args & flags +while :; do + case "$1" in + --android) + RUN_ANDROID=1 + shift + ;; + + --ios) + RUN_IOS=1 + shift + ;; + + --js) + RUN_JS=1 + shift + ;; + + --skip-cli-install) + RUN_CLI_INSTALL=0 + shift + ;; + + *) + break + esac +done + +function e2e_suite() { + cd "$ROOT" + + if [ $RUN_ANDROID -eq 0 ] && [ $RUN_IOS -eq 0 ] && [ $RUN_JS -eq 0 ]; then + echo "No e2e tests specified!" + return 0 + fi + + # create temp dir + TEMP_DIR=$(mktemp -d /tmp/react-native-XXXXXXXX) + + # To make sure we actually installed the local version + # of react-native, we will create a temp file inside the template + # and check that it exists after `react-native init + IOS_MARKER="$(mktemp "$ROOT"/template/ios/HelloWorld/XXXXXXXX)" + ANDROID_MARKER="$(mktemp "$ROOT"/template/android/XXXXXXXX)" + + # install CLI + cd react-native-cli + npm pack + cd .. + + # can skip cli install for non sudo mode + if [ $RUN_CLI_INSTALL -ne 0 ]; then + if ! npm install -g "$CLI_PACKAGE" + then + echo "Could not install react-native-cli globally, please run in su mode" + echo "Or with --skip-cli-install to skip this step" + return 1 + fi + fi + + if [ $RUN_ANDROID -ne 0 ]; then + set +ex + + # create virtual device + if ! android list avd | grep "$AVD_UUID" > /dev/null; then + echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a + fi + + # newline at end of adb devices call and first line is headers + DEVICE_COUNT=$(adb devices | wc -l) + ((DEVICE_COUNT -= 2)) + + # will always kill an existing emulator if one exists for fresh setup + if [[ $DEVICE_COUNT -ge 1 ]]; then + adb emu kill + fi + + # emulator setup + emulator64-arm -avd "$AVD_UUID" -no-skin -no-audio -no-window -no-boot-anim & + + bootanim="" + # shellcheck disable=SC2076 + until [[ "$bootanim" =~ "stopped" ]]; do + sleep 5 + bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1) + echo "boot animation status=$bootanim" + done + + set -ex + + if ! ./gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError" + then + echo "Failed to compile Android binaries" + return 1 + fi + fi + + if ! npm pack + then + echo "Failed to pack react-native" + return 1 + fi + + cd "$TEMP_DIR" + + if ! retry "$RETRY_COUNT" react-native init EndToEndTest --version "$PACKAGE" --npm + then + echo "Failed to execute react-native init" + echo "Most common reason is npm registry connectivity, try again" + return 1 + fi + + cd EndToEndTest + + # android tests + if [ $RUN_ANDROID -ne 0 ]; then + echo "Running an Android e2e test" + echo "Installing e2e framework" + + if ! retry "$RETRY_COUNT" npm install --save-dev "$ANDROID_NPM_DEPS" --silent >> /dev/null + then + echo "Failed to install appium" + echo "Most common reason is npm registry connectivity, try again" + return 1 + fi + + cp "$SCRIPTS/android-e2e-test.js" android-e2e-test.js + + ( + cd android || exit + echo "Downloading Maven deps" + ./gradlew :app:copyDownloadableDepsToLibs + ) + + keytool -genkey -v -keystore android/keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + + node ./node_modules/.bin/appium >> /dev/null & + APPIUM_PID=$! + echo "Starting appium server $APPIUM_PID" + + echo "Building app" + buck build android/app + + # hack to get node unhung (kill buckd) + if ! kill -9 "$(pgrep java)" + then + echo "could not execute Buck build, is it installed and in PATH?" + return 1 + fi + + echo "Starting Metro" + npm start >> /dev/null & + SERVER_PID=$! + sleep 15 + + echo "Executing android e2e test" + if ! retry "$RETRY_COUNT" node node_modules/.bin/_mocha android-e2e-test.js + then + echo "Failed to run Android e2e tests" + echo "Most likely the code is broken" + return 1 + fi + + # kill packager process + if kill -0 "$SERVER_PID"; then + echo "Killing packager $SERVER_PID" + kill -9 "$SERVER_PID" + fi + + # kill appium process + if kill -0 "$APPIUM_PID"; then + echo "Killing appium $APPIUM_PID" + kill -9 "$APPIUM_PID" + fi + + fi + + # ios tests + if [ $RUN_IOS -ne 0 ]; then + echo "Running ios e2e tests not yet implemented for docker!" + fi + + # js tests + if [ $RUN_JS -ne 0 ]; then + # Check the packager produces a bundle (doesn't throw an error) + if ! react-native bundle --max-workers 1 --platform android --dev true --entry-file index.js --bundle-output android-bundle.js + then + echo "Could not build android bundle" + return 1 + fi + + if ! react-native bundle --max-workers 1 --platform ios --dev true --entry-file index.js --bundle-output ios-bundle.js + then + echo "Could not build iOS bundle" + return 1 + fi + + # directory cleanup + rm "$IOS_MARKER" + rm "$ANDROID_MARKER" + + return 0 +} + +retry "$RETRY_COUNT" e2e_suite diff --git a/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh b/.circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh similarity index 83% rename from ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh rename to .circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh index 5a9a976a97da4e..d095b972a42787 100755 --- a/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh +++ b/.circleci/Dockerfiles/scripts/run-instrumentation-tests-via-adb-shell.sh @@ -1,5 +1,10 @@ #!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# shellcheck disable=SC1117 # Python script to run instrumentation tests, copied from https://github.com/circleci/circle-dummy-android # Example: ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests com.facebook.react.tests.ReactPickerTestCase # @@ -9,7 +14,7 @@ export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH" adb logcat -c # run tests and check output -python - $1 $2 << END +python - "$1" "$2" << END import re import subprocess as sp @@ -24,7 +29,7 @@ test_class = None if len(sys.argv) > 2: test_class = sys.argv[2] - + def update(): # prevent CircleCI from killing the process for inactivity while not done: @@ -38,10 +43,10 @@ t.start() def run(): sp.Popen(['adb', 'wait-for-device']).communicate() if (test_class != None): - p = sp.Popen('adb shell am instrument -w -e class %s %s/android.support.test.runner.AndroidJUnitRunner' + p = sp.Popen('adb shell am instrument -w -e class %s %s/android.support.test.runner.AndroidJUnitRunner' % (test_class, test_app), shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE) else : - p = sp.Popen('adb shell am instrument -w %s/android.support.test.runner.AndroidJUnitRunner' + p = sp.Popen('adb shell am instrument -w %s/android.support.test.runner.AndroidJUnitRunner' % (test_app), shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE) return p.communicate() diff --git a/.circleci/config.yml b/.circleci/config.yml index 100bc1276ffd33..fee64be10324af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,638 +1,1392 @@ -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- - - &save-yarn-cache - paths: - - node_modules - - ~/.cache/yarn - key: v1-yarn-{{ arch }}-{{ .Branch }}-{{ 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- - - &save-cache-analysis - paths: - - bots/node_modules - - node_modules - key: v1-analysis-dependencies-{{ arch }}-{{ .Branch }}-{{ 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- - - &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" }} - - - &restore-cache-ndk - keys: - - v2-android-ndk-{{ arch }}-r10e-32-64-{{ checksum "scripts/circle-ci-android-setup.sh" }} - # Fallback in case checksum fails - - v2-android-ndk-{{ arch }}-r10e-32-64- - - &save-cache-ndk - paths: - - /opt/ndk - key: v2-android-ndk-{{ arch }}-r10e-32-64-{{ checksum "scripts/circle-ci-android-setup.sh" }} - - - &restore-cache-buck - keys: - - v2-buck-{{ arch }}-v2018.02.16.01 - - &save-cache-buck - paths: - - ~/buck - key: v2-buck-{{ arch }}-v2018.02.16.01 - - - &restore-cache-watchman - keys: - - v1-watchman-{{ arch }}-v4.9.0 - - &save-cache-watchman - paths: - - ~/watchman - key: v1-watchman-{{ arch }}-v4.9.0 - - # Branch Filtering - - &filter-only-master-stable +version: 2.1 + +# ------------------------- +# ORBS +# ------------------------- + +orbs: + win: circleci/windows@2.4.0 + +# ------------------------- +# REFERENCES +# ------------------------- +references: + defaults: &defaults + working_directory: ~/react-native + environment: + - GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1 + # The public github tokens are publicly visible by design + - PUBLIC_PULLBOT_GITHUB_TOKEN_A: &github_pullbot_token_a "a6edf8e8d40ce4e8b11a" + - PUBLIC_PULLBOT_GITHUB_TOKEN_B: &github_pullbot_token_b "150e1341f4dd9c944d2a" + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: &github_analysisbot_token_a "312d354b5c36f082cfe9" + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: &github_analysisbot_token_b "07973d757026bdd9f196" + # Homebrew currently breaks while updating: + # https://discuss.circleci.com/t/brew-install-fails-while-updating/32992 + - HOMEBREW_NO_AUTO_UPDATE: 1 + + hermes_workspace_root: &hermes_workspace_root + /tmp/hermes + attach_hermes_workspace: &attach_hermes_workspace + attach_workspace: + at: *hermes_workspace_root + + # ------------------------- + # Dependency Anchors + # ------------------------- + dependency_versions: + # The Xcode version used on CircleCI OSS tests must be kept in sync with + # the Xcode version used on Sandcastle OSS tests. See _XCODE_VERSION in + # tools/utd/migrated_nbtd_jobs/react_native_oss.td + xcode_version: &xcode_version "13.3.1" + nodelts_image: &nodelts_image "cimg/node:16.14" + nodeprevlts_image: &nodeprevlts_image "cimg/node:14.19" + + # ------------------------- + # Cache Key Anchors + # ------------------------- + # Anchors for the cache keys + + cache_keys: + buck_cache_key: &buck_cache_key v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}} + checkout_cache_key: &checkout_cache_key v1-checkout + gems_cache_key: &gems_cache_key v1-gems-{{ checksum "Gemfile.lock" }} + gradle_cache_key: &gradle_cache_key v1-gradle-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "ReactAndroid/gradle.properties" }} + hermes_cache_key: &hermes_cache_key v1-hermes-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/hermes/hermesversion" }} + hermes_sdk_cache_key: &hermes_sdk_cache_key v1-hermes-{{ .Environment.CIRCLE_JOB }}-{{ checksum "sdks/.hermes-cache-key-file" }} + hermes_windows_cache_key: &hermes_windows_cache_key v1-hermes-{{ .Environment.CIRCLE_JOB }}-{{ checksum "tmp/hermes/hermesversion" }} + pods_cache_key: &pods_cache_key v6-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "packages/rn-tester/Podfile.lock.bak" }}-{{ checksum "packages/rn-tester/Podfile" }} + windows_yarn_cache_key: &windows_yarn_cache_key v1-win-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }} + yarn_cache_key: &yarn_cache_key v5-yarn-cache-{{ .Environment.CIRCLE_JOB }} + + # ------------------------- + # Filters + # ------------------------- + # CircleCI filters are OR-ed, with all branches triggering by default and tags excluded by default + # CircleCI env-vars are only set with the branch OR tag that triggered the job, not both. + + # In this case, CIRCLE_BRANCH is unset, but CIRCLE_TAG is set. + only_release_tags: &only_release_tags + # Both of the following conditions must be included! + # Ignore any commit on any branch by default. branches: - only: - - /.*-stable/ - - master - - - &filter-only-stable - branches: - only: - - /.*-stable/ - - - &filter-ignore-gh-pages - branches: - ignore: gh-pages - - - &filter-ignore-master-stable - branches: - ignore: - - master - - /.*-stable/ - - gh-pages - - # Dependency Management - - &install-ndk - name: Install Android NDK - command: source scripts/circle-ci-android-setup.sh && getAndroidNDK - - - &yarn - | - yarn install --non-interactive --cache-folder ~/.cache/yarn - - - &install-yarn - | - 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 - - - &install-node-dependencies - | - npm install --no-package-lock --no-spin --no-progress - - - &install-buck - | - if [[ ! -e ~/buck ]]; then - git clone https://github.com/facebook/buck.git ~/buck --branch v2018.02.16.01 --depth=1 - fi - cd ~/buck && ant - buck --version - - - &install-node - | - curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - - sudo apt-get install -y nodejs - - - &create-ndk-directory - name: Create Android NDK Directory - command: | - if [[ ! -e /opt/ndk ]]; then - sudo mkdir /opt/ndk - fi - sudo chown ${USER:=$(/usr/bin/id -run)}:$USER /opt/ndk - - # CircleCI does not support interpolating env variables in the environment - # https://circleci.com/docs/2.0/env-vars/#interpolating-environment-variables-to-set-other-environment-variables - - &configure-android-path - name: Configure Environment Variables - command: | - echo 'export PATH=${ANDROID_NDK}:~/react-native/gradle-2.9/bin:~/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 - - - &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 - - - &validate-android-sdk - name: Validate Android SDK Install - command: ./scripts/validate-android-sdk.sh - - - &validate-android-test-env - name: Validate Android Test Environment - command: ./scripts/validate-android-test-env.sh - - # Test Definitions - - &run-js-tests - name: JavaScript Test Suite - command: yarn test-ci - - - &run-lint-checks - name: Lint code - command: yarn lint --format junit -o ~/react-native/reports/junit/js-lint-results.xml - when: always - - - &run-flow-checks - name: Check for errors in code using Flow - command: yarn flow check - when: always - - - &run-sanity-checks - name: Sanity checks - command: | - ./scripts/circleci/check_license.sh - ./scripts/circleci/check_cache.sh - when: always - - - &build-android-app - name: Build Android App - command: | - buck build ReactAndroid/src/main/java/com/facebook/react - buck build ReactAndroid/src/main/java/com/facebook/react/shell - - - &create-avd - name: Create Android Virtual Device - command: source scripts/circle-ci-android-setup.sh && createAVD - - - &launch-avd - name: Launch Android Virtual Device in Background - command: source scripts/circle-ci-android-setup.sh && launchAVD - background: true - - - &wait-for-avd - name: Wait for Android Virtual Device - command: source scripts/circle-ci-android-setup.sh && waitForAVD - - - &build-js-bundle - name: Build JavaScript Bundle - command: node local-cli/cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js - - - &compile-native-libs - name: Compile Native Libs for Unit and Integration Tests - command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS -Pcom.android.build.threadPoolSize=1 - no_output_timeout: 6m - - - &run-android-unit-tests - name: Run Unit Tests - command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS - - - &run-android-instrumentation-tests - name: Run Instrumentation Tests - command: | - 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 - - - &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/ \; - when: always - - - &setup-artifacts - name: Initial Setup - command: | - mkdir -p ~/react-native/reports/junit/ - - - &run-objc-ios-tests - name: iOS Test Suite - command: ./scripts/objc-test-ios.sh test - - - &run-objc-tvos-tests - name: tvOS Test Suite - command: ./scripts/objc-test-tvos.sh test - - - &run-objc-ios-e2e-tests - name: iOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; - - - &run-objc-tvos-e2e-tests - name: tvOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; - -defaults: &defaults - working_directory: ~/react-native - -js_defaults: &js_defaults - <<: *defaults - docker: - - image: circleci/node:8 - -android_defaults: &android_defaults - <<: *defaults - docker: - - image: circleci/android:api-26-alpha - resource_class: "large" - environment: - - TERM: "dumb" - - ADB_INSTALL_TIMEOUT: 10 - - _JAVA_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" - - GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"' - - ANDROID_NDK: '/opt/ndk/android-ndk-r10e' - - BUILD_THREADS: 2 - -macos_defaults: &macos_defaults - <<: *defaults - macos: - xcode: "9.2.0" - -version: 2 -jobs: - - # Set up a Node environment for downstream jobs - checkout_code: - <<: *js_defaults + ignore: /.*/ + # Only act on version tags. + tags: + only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ + +# ------------------------- +# EXECUTORS +# ------------------------- +executors: + nodelts: + <<: *defaults + docker: + # Note: Version set separately for Windows builds, see below. + - image: *nodelts_image + resource_class: "xlarge" + nodeprevlts: + <<: *defaults + docker: + - image: *nodeprevlts_image + resource_class: "xlarge" + reactnativeandroid: + <<: *defaults + docker: + - image: reactnativecommunity/react-native-android:5.4 + resource_class: "xlarge" + environment: + - TERM: "dumb" + - ADB_INSTALL_TIMEOUT: 10 + - GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"' + - BUILD_THREADS: 2 + # Repeated here, as the environment key in this executor will overwrite the one in defaults + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: *github_analysisbot_token_a + - PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: *github_analysisbot_token_b + - PUBLIC_PULLBOT_GITHUB_TOKEN_A: *github_pullbot_token_a + - PUBLIC_PULLBOT_GITHUB_TOKEN_B: *github_pullbot_token_b + reactnativeios: + <<: *defaults + macos: + xcode: *xcode_version + resource_class: macos.x86.medium.gen2 + +# ------------------------- +# COMMANDS +# ------------------------- +commands: + # Checkout with cache, on machines that are using Docker the cache is ignored + checkout_code_with_cache: + parameters: + checkout_base_cache_key: + default: *checkout_cache_key + type: string steps: + - restore_cache: + keys: + - << parameters.checkout_base_cache_key >>-{{ .Branch }}-{{ .Revision }} + - << parameters.checkout_base_cache_key >>-{{ .Branch }}- + - << parameters.checkout_base_cache_key >> - checkout - - run: *setup-artifacts + - save_cache: + key: << parameters.checkout_base_cache_key >>-{{ .Branch }}-{{ .Revision }} + paths: + - ".git" - - restore-cache: *restore-yarn-cache - - run: *yarn - - save-cache: *save-yarn-cache + setup_artifacts: + steps: + - run: + name: Initial Setup + command: mkdir -p ./reports/{buck,build,junit,outputs} - # Basic checks against the checkout, cache... - - run: *run-sanity-checks + setup_ruby: + steps: + - restore_cache: + key: *gems_cache_key + - run: + name: Bundle Install + command: | + source /usr/local/share/chruby/auto.sh + bundle check || bundle install --path vendor/bundle --clean + - save_cache: + key: *gems_cache_key + paths: + - vendor/bundle + + run_yarn: + parameters: + yarn_base_cache_key: + default: *yarn_cache_key + type: string - - persist_to_workspace: - root: . - paths: . + steps: + - restore_cache: + keys: + - << parameters.yarn_base_cache_key >>-{{ arch }}-{{ checksum "yarn.lock" }} + - << parameters.yarn_base_cache_key >>-{{ arch }} + - << parameters.yarn_base_cache_key >> + - run: + name: "Yarn: Install Dependencies" + 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 + - save_cache: + paths: + - ~/.cache/yarn + key: << parameters.yarn_base_cache_key >>-{{ arch }}-{{ checksum "yarn.lock" }} - # Runs JavaScript lint and flow checks. - # Currently will fail a PR if lint/flow raises issues. - analyze: - <<: *js_defaults + install_buck_tooling: steps: - - attach_workspace: - at: ~/react-native + - restore_cache: + keys: + - *buck_cache_key + - run: + name: Install BUCK + command: | + buck --version + # Install related tooling + if [[ ! -e ~/okbuck ]]; then + git clone https://github.com/uber/okbuck.git ~/okbuck --depth=1 + fi + - save_cache: + paths: + - ~/buck + - ~/okbuck + key: *buck_cache_key - - run: *run-lint-checks - - run: *run-flow-checks + install_github_bot_deps: + steps: + - run: + name: "Yarn: Install dependencies (GitHub bots)" + command: cd bots && yarn install --non-interactive --cache-folder ~/.cache/yarn + + brew_install: + parameters: + package: + description: Homebrew package to install + type: string + steps: + - run: + name: "Brew: Install << parameters.package >>" + command: brew install << parameters.package >> >/dev/null - - store_test_results: - path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/yarn.lock + with_rntester_pods_cache_span: + parameters: + steps: + type: steps + steps: + - run: + name: Setup CocoaPods cache + # Copy packages/rn-tester/Podfile.lock since it can be changed by pod install + command: cp packages/rn-tester/Podfile.lock packages/rn-tester/Podfile.lock.bak + - restore_cache: + keys: + # The committed lockfile is generated using USE_FRAMEWORKS=0 and USE_HERMES=1 so it could load an outdated cache if a change + # only affects the frameworks or hermes config. To help prevent this also cache based on the content of Podfile. + - *pods_cache_key + - steps: << parameters.steps >> + - save_cache: + paths: + - packages/rn-tester/Pods + key: *pods_cache_key + + download_gradle_dependencies: + steps: + - restore_cache: + keys: + - *gradle_cache_key + - run: + name: Download Dependencies Using Gradle + command: ./gradlew downloadAll + - save_cache: + paths: + - ~/.gradle + - ReactAndroid/build/downloads + - ReactAndroid/build/third-party-ndk + key: *gradle_cache_key + + download_buck_dependencies: + steps: + - run: + name: Download Dependencies Using Buck + command: ./scripts/circleci/buck_fetch.sh + + run_e2e: + parameters: + platform: + description: Target platform + type: enum + enum: ["android", "ios", "js"] + default: "js" + retries: + description: How many times the job should try to run these tests + type: integer + default: 3 + steps: + - run: + name: "Run Tests: << parameters.platform >> End-to-End Tests" + command: node ./scripts/run-ci-e2e-tests.js --<< parameters.platform >> --retries << parameters.retries >> + + report_bundle_size: + parameters: + platform: + description: Target platform + type: enum + enum: ["android", "ios"] + steps: + - install_github_bot_deps + - run: + name: Report size of RNTester.app (analysis-bot) + command: GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" scripts/circleci/report-bundle-size.sh << parameters.platform >> || true - # Runs JavaScript tests on Node 8 - test_javascript: - <<: *js_defaults + with_hermes_sdk_cache_span: + parameters: + steps: + type: steps steps: - - attach_workspace: - at: ~/react-native + - run: + name: Setup Hermes cache + command: | + HERMES_CACHE_KEY_FILE="sdks/.hermes-cache-key-file" + if [ ! -f "$HERMES_CACHE_KEY_FILE" ]; then + git ls-remote https://github.com/facebook/hermes main | cut -f 1 > $HERMES_CACHE_KEY_FILE + fi + - restore_cache: + keys: + - *hermes_sdk_cache_key + - steps: << parameters.steps >> + - save_cache: + key: *hermes_sdk_cache_key + paths: + - sdks/hermesc + - sdks/hermes + +# ------------------------- +# JOBS +# ------------------------- +jobs: + # ------------------------- + # JOBS: Analyze PR + # ------------------------- + # 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. + analyze_pr: + executor: reactnativeandroid + steps: + - checkout + - run_yarn - - run: *run-js-tests + - install_github_bot_deps - - store_test_results: - path: ~/react-native/reports/junit + # Note: The yarn gpg key needs to be refreshed to work around https://github.com/yarnpkg/yarn/issues/7866 + - run: + name: Install additional GitHub bot dependencies + # TEMP: Added workaround from https://github.com/nodesource/distributions/issues/1266#issuecomment-932583579 + command: | + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - + apt update && apt install -y shellcheck jq + apt-get -y install openssl ca-certificates + update-ca-certificates + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - + apt update && apt install -y shellcheck jq - # Runs JavaScript tests on Node 6 - test_javascript_node6_compatibility: - <<: *defaults - docker: - - image: circleci/node:6 + - run: + name: Run linters against modified files (analysis-bot) + command: GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" yarn lint-ci + when: always + + # ------------------------- + # JOBS: Analyze Code + # ------------------------- + analyze_code: + executor: reactnativeandroid steps: - checkout - - run: *setup-artifacts + - setup_artifacts + - run_yarn - - restore-cache: *restore-yarn-cache - - run: *yarn - - save-cache: *save-yarn-cache + - run: + name: Lint code + command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ./reports/junit/eslint/results.xml + when: always - - run: *run-js-tests + - run: + name: Lint Java + command: scripts/circleci/exec_swallow_error.sh yarn lint-java --check + when: always - - store_test_results: - path: ~/react-native/reports/junit + - run: + name: Check for errors in code using Flow (iOS) + command: yarn flow-check-ios + when: always - # Runs unit tests on iOS devices - test_ios: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native + - run: + name: Check for errors in code using Flow (Android) + command: yarn flow-check-android + when: always - - run: xcrun instruments -w "iPhone 5s (11.1)" || true - - run: brew install watchman - - run: *run-objc-ios-tests + - run: + name: Sanity checks + command: | + ./scripts/circleci/check_license.sh + ./scripts/circleci/validate_yarn_lockfile.sh + when: always - - store_test_results: - path: ~/react-native/reports/junit + - run: + name: Check formatting + command: yarn run format-check + when: always - # Runs unit tests on tvOS devices - test_tvos: - <<: *macos_defaults + - store_test_results: + path: ./reports/junit + + # ------------------------- + # JOBS: Test JavaScript + # ------------------------- + test_js: + parameters: + executor: + type: executor + default: nodelts + run_disabled_tests: + type: boolean + default: false + executor: << parameters.executor >> steps: - - attach_workspace: - at: ~/react-native + - checkout + - setup_artifacts + - run_yarn + - run: + name: Install rsync + command: sudo apt update && sudo apt install rsync - - run: xcrun instruments -w "Apple TV 1080p (11.1)" || true - - run: brew install watchman - - run: *run-objc-tvos-tests + # ------------------------- + # Run JavaScript tests + - run: + name: "Run Tests: JavaScript Tests" + command: node ./scripts/run-ci-javascript-tests.js --maxWorkers 2 + - run_e2e: + platform: js + + # Optionally, run disabled tests + - when: + condition: << parameters.run_disabled_tests >> + steps: + - run: echo "Failing tests may be moved here temporarily." + # ------------------------- - store_test_results: - path: ~/react-native/reports/junit + path: ./reports/junit - # Runs end to end tests - test_ios_e2e: - <<: *macos_defaults + # ------------------------- + # JOBS: iOS Unit Tests + # ------------------------- + test_ios: + executor: reactnativeios + parameters: + use_frameworks: + type: boolean + default: false + run_unit_tests: + description: Specifies whether unit tests should run. + type: boolean + default: false + run_disabled_tests: + description: Specifies whether disabled tests should run. Set this to true to debug failing tests. + type: boolean + default: false + environment: + - REPORTS_DIR: "./reports/junit" steps: - - attach_workspace: - at: ~/react-native + - checkout_code_with_cache + - setup_artifacts + - setup_ruby + - run: + name: Run Ruby Tests + command: | + cd scripts + sh run_ruby_tests.sh + - run_yarn + - run: | + cd packages/rn-tester + bundle check || bundle install + - run: + name: Boot iPhone Simulator + command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true - - run: xcrun instruments -w "iPhone 5s (11.1)" || true - - run: *run-objc-ios-e2e-tests + - run: + name: Configure Environment Variables + command: | + echo 'export PATH=/usr/local/opt/node@16/bin:$PATH' >> $BASH_ENV + source $BASH_ENV - - store_test_results: - path: ~/react-native/reports/junit + - run: + name: "Brew: Tap wix/brew" + command: brew tap wix/brew >/dev/null + - brew_install: + package: applesimutils watchman cmake - # Checks podspec - test_podspec: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native + - run: + name: Configure Watchman + command: echo "{}" > .watchmanconfig - - 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 - - # Install Android NDK - - run: *create-ndk-directory - - restore-cache: *restore-cache-ndk - - run: *install-ndk - - save-cache: *save-cache-ndk - - # Fetch dependencies using BUCK - - restore-cache: *restore-cache-buck - - run: *install-buck - - save-cache: *save-cache-buck - - - run: buck fetch ReactAndroid/src/test/java/com/facebook/react/modules - - run: buck fetch ReactAndroid/src/main/java/com/facebook/react - - 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-yarn-cache - - run: *yarn - - save-cache: *save-yarn-cache - - - run: - name: Publish React Native Package - command: | - if [ -z "$CIRCLE_PULL_REQUEST" ]; then - echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc - git config --global user.email "reactjs-bot@users.noreply.github.com" - git config --global user.name "npm Deployment Script" - echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" > ~/.netrc - node ./scripts/publish-npm.js - else - echo "Skipping deploy." - fi + - when: + condition: << parameters.use_frameworks >> + steps: + - run: + name: Set USE_FRAMEWORKS=1 + command: echo "export USE_FRAMEWORKS=1" >> $BASH_ENV + + - run: + name: Set USE_HERMES=1 + command: echo "export USE_HERMES=1" >> $BASH_ENV + + - run: + name: Set BUILD_HERMES_SOURCE=1 + command: echo "export BUILD_HERMES_SOURCE=1" >> $BASH_ENV + + - run: + name: Setup the CocoaPods environment + command: bundle exec pod setup + + - with_hermes_sdk_cache_span: + steps: + - with_rntester_pods_cache_span: + steps: + - run: + name: Generate RNTesterPods Workspace + command: cd packages/rn-tester && bundle exec pod install --verbose + + # ------------------------- + # Runs iOS unit tests + - when: + condition: << parameters.run_unit_tests >> + steps: + - run: + name: "Run Tests: iOS Unit and Integration Tests" + command: yarn test-ios + + # Optionally, run disabled tests + - when: + condition: << parameters.run_disabled_tests >> + steps: + - run: echo "Failing tests may be moved here temporarily." + - run: + name: "Run Tests: CocoaPods" + command: ./scripts/process-podspecs.sh + - run: + name: Free up port 8081 for iOS End-to-End Tests + command: | + # free up port 8081 for the packager before running tests + set +eo pipefail + lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill + set -eo pipefail + - run_e2e: + platform: ios + # ------------------------- + + # Collect Results + - report_bundle_size: + platform: ios + - store_test_results: + path: ./reports/junit - # Set up an Android environment for downstream jobs + # ------------------------- + # JOBS: Test Android + # ------------------------- test_android: - <<: *android_defaults + executor: reactnativeandroid + parameters: + run_disabled_tests: + type: boolean + default: false + environment: + KOTLIN_HOME=third-party/kotlin steps: - - attach_workspace: - at: ~/react-native + - checkout + - setup_artifacts + - run_yarn - # 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 + # Validate Android SDK installation and packages + - run: + name: Validate Android SDK Install + command: ./scripts/validate-android-sdk.sh - - run: *validate-android-sdk + # Starting emulator in advance as it takes some time to boot. + - run: + name: Create Android Virtual Device + command: source scripts/android-setup.sh && createAVD + - run: + name: Launch Android Virtual Device in Background + command: source scripts/android-setup.sh && launchAVD + background: true - # Starting emulator in advance as it takes some time to boot. - - run: *create-avd - - run: *launch-avd + # Install Buck + - install_buck_tooling - # Keep configuring Android dependencies while AVD boots up + # Validate Android test environment (including Buck) + - run: + name: Validate Android Test Environment + command: ./scripts/validate-android-test-env.sh - # Install Android NDK - - run: *create-ndk-directory - - restore-cache: *restore-cache-ndk - - run: *install-ndk - - save-cache: *save-cache-ndk + - download_buck_dependencies + - download_gradle_dependencies - # Fetch dependencies using BUCK - - restore-cache: *restore-cache-buck - - run: *install-buck - - save-cache: *save-cache-buck + # Build and compile + - run: + name: Build & Test React Native using Buck + command: | + buck build ReactAndroid/src/main/java/com/facebook/react + buck build ReactAndroid/src/main/java/com/facebook/react/shell - - run: *validate-android-test-env + - run: + name: Build & Test React Native using Gradle + command: ./gradlew buildAll - - run: buck fetch ReactAndroid/src/test/java/com/facebook/react/modules - - run: buck fetch ReactAndroid/src/main/java/com/facebook/react - - 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: + name: Compile Native Libs for Unit and Integration Tests + command: ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=$BUILD_THREADS + no_output_timeout: 30m - # Build and compile - - run: *build-android-app - - run: *compile-native-libs + - run: + name: Build RN Tester for Release using Gradle + command: ./gradlew packages:rn-tester:android:app:assembleRelease # Build JavaScript Bundle for instrumentation tests - - run: *install-node - - run: *build-js-bundle + - run: + name: Build JavaScript Bundle + command: node cli.js bundle --max-workers 2 --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js # Wait for AVD to finish booting before running tests - - run: *wait-for-avd - - # Test Suite - - run: *run-android-unit-tests - - run: *run-android-instrumentation-tests - - # post (always runs) - - run: *collect-android-test-results + - run: + name: Wait for Android Virtual Device + command: source scripts/android-setup.sh && waitForAVD + + # ------------------------- + # Run Android tests + - run: + name: Run Tests - Android Unit Tests with Buck + command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ./reports/buck/all-results-raw.xml + - run: + name: Build Tests - Android Instrumentation Tests with Buck + # Here, just build the instrumentation tests. There is a known issue with installing the APK to android-21+ emulator. + command: | + if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then + echo "JavaScript bundle missing, cannot run instrumentation tests. Verify Build JavaScript Bundle step completed successfully."; exit 1; + fi + source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck build ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS + + # Optionally, run disabled tests + - when: + condition: << parameters.run_disabled_tests >> + steps: + - run: echo "Failing tests may be moved here temporarily." + - run_e2e: + platform: android + # ------------------------- + + # Collect Results + - report_bundle_size: + platform: android + - run: + name: Collect Test Results + command: | + find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ./reports/build/ \; + find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ./reports/outputs/ \; + find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ./reports/buck/ \; + if [ -f ~/react-native/reports/buck/all-results-raw.xml ]; then + cd ~/okbuck + ./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/results.xml + fi + when: always - store_test_results: - path: ~/react-native/reports/junit + path: ./reports/junit + # ------------------------- + # JOBS: Test Android Template + # ------------------------- + test_android_template: + executor: reactnativeandroid + steps: + - checkout + - run_yarn + - attach_workspace: + at: . + + - run: + name: Create Android template project + command: | + REPO_ROOT=$(pwd) + PACKAGE=$(cat build/react-native-package-version) + PATH_TO_PACKAGE="$REPO_ROOT/build/$PACKAGE" + cd template + npm add $PATH_TO_PACKAGE + npm install + + - run: + name: Build the template application + command: cd template/android/ && ./gradlew assembleDebug + + # ------------------------- + # JOBS: Test iOS Template + # ------------------------- + test_ios_template: + executor: reactnativeios + environment: + - PROJECT_NAME: "iOSTemplateProject" + - HERMES_WS_DIR: *hermes_workspace_root - # 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. - analyze_pr: - <<: *js_defaults steps: + - checkout_code_with_cache + - run_yarn - attach_workspace: - at: ~/react-native + at: . + - *attach_hermes_workspace + - run: + name: Set USE_HERMES=1 + command: echo "export USE_HERMES=1" >> $BASH_ENV + - run: + name: Set HERMES_ENGINE_TARBALL_PATH + command: | + echo "export HERMES_ENGINE_TARBALL_PATH=$(ls -AU $HERMES_WS_DIR/hermes-runtime-darwin/hermes-runtime-darwin-*.tar.gz | head -1)" >> $BASH_ENV + - run: + name: Create iOS template project + command: | + REPO_ROOT=$(pwd) + PACKAGE=$(cat build/react-native-package-version) + PATH_TO_PACKAGE="$REPO_ROOT/build/$PACKAGE" + node ./scripts/set-rn-template-version.js "file:$PATH_TO_PACKAGE" + mkdir -p ~/tmp + cd ~/tmp + node "$REPO_ROOT/cli.js" init "$PROJECT_NAME" --template "$REPO_ROOT" --verbose + - run: + name: Build template project + command: | + xcodebuild build \ + -workspace ~/tmp/$PROJECT_NAME/ios/$PROJECT_NAME.xcworkspace \ + -scheme $PROJECT_NAME \ + -sdk iphonesimulator + + # ------------------------- + # JOBS: Test iOS RNTester + # ------------------------- + test_ios_rntester: + executor: reactnativeios + steps: + - checkout_code_with_cache + - run_yarn + + # The macOS machine can run out of storage if Hermes is enabled and built from source. + # Since this job does not use the iOS Simulator, deleting it provides a quick way to + # free up space. + - run: + name: Delete iOS Simulators + background: true + command: sudo rm -rf /Library/Developer/CoreSimulator/Profiles/Runtimes/ + + - run: + name: Set USE_HERMES=1 + command: echo "export USE_HERMES=1" >> $BASH_ENV + + - run: + name: Set BUILD_HERMES_SOURCE=1 + command: echo "export BUILD_HERMES_SOURCE=1" >> $BASH_ENV + + - brew_install: + package: cmake + + - with_hermes_sdk_cache_span: + steps: + - run: + name: Install CocoaPods dependencies + command: | + rm -rf packages/rn-tester/Pods + cd packages/rn-tester && bundle exec pod install - - restore-cache: *restore-cache-analysis - - run: *yarn - run: - name: Install Additional Dependencies + name: Build RNTester command: | - if [ -n "$CIRCLE_PR_NUMBER" ]; then - yarn add github@0.2.4 - cd bots - yarn install --non-interactive --cache-folder ~/.cache/yarn - else - echo "Skipping dependency installation." + xcodebuild build \ + -workspace packages/rn-tester/RNTesterPods.xcworkspace \ + -scheme RNTester \ + -sdk iphonesimulator + + # ------------------------- + # JOBS: Windows + # ------------------------- + test_windows: + executor: + name: win/default + parameters: + run_disabled_tests: + type: boolean + default: false + environment: + - ANDROID_HOME: "C:\\Android\\android-sdk" + - ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\20.1.5948944" + - ANDROID_BUILD_VERSION: 31 + - ANDROID_TOOLS_VERSION: 31.0.0 + - GRADLE_OPTS: -Dorg.gradle.daemon=false + steps: + - checkout_code_with_cache + + - run: + name: Install Node + # Note: Version set separately for non-Windows builds, see above. + command: | + nvm install 16 + nvm use 16 + + # Setup Dependencies + - run: + name: Install Yarn + command: choco install yarn + + - run: + name: Display Environment info + command: npx envinfo@latest + + - restore_cache: + keys: + - *windows_yarn_cache_key + - run: + name: "Yarn: Install Dependencies" + command: yarn install --frozen-lockfile --non-interactive + - save_cache: + key: *windows_yarn_cache_key + paths: + - C:\Users\circleci\AppData\Local\Yarn + + # Try to install the SDK up to 3 times, since network flakiness can cause install failures + # Using a timeout of 9 mins, as circle ci will timeout if there is no output for 10 mins + - run: + name: Install Android SDK Tools + command: choco install android-sdk --timeout 540; if (!$?) { choco install android-sdk --timeout 540 --force --forcedependencies}; if (!$?) { choco install android-sdk --force --forcedependencies} + + - run: + name: Setup Android SDKs + command: | + sdkmanager --licenses + sdkmanager "system-images;android-21;google_apis;armeabi-v7a" + sdkmanager "platforms;android-%ANDROID_BUILD_VERSION%" + sdkmanager "build-tools;%ANDROID_TOOLS_VERSION%" + sdkmanager "add-ons;addon-google_apis-google-23" + sdkmanager "extras;android;m2repository" + + # ------------------------- + # Run Tests + - run: + name: "Flow: Check Android" + command: yarn flow-check-android + - run: + name: "Flow: Check iOS" + command: yarn flow-check-ios + - run: + name: "Run Tests: JavaScript Tests" + command: yarn test + + # Optionally, run disabled tests + - when: + condition: << parameters.run_disabled_tests >> + steps: + - run: echo "Failing tests may be moved here temporarily." + - run: + name: Android Build + command: ./gradlew.bat packages:rn-tester:android:app:assembleRelease + + # ------------------------- + # JOBS: Coverage + # ------------------------- + # Collect JavaScript test coverage + js_coverage: + executor: nodelts + environment: + - CI_BRANCH: $CIRCLE_BRANCH + - CI_PULL_REQUEST: $CIRCLE_PULL_REQUEST + - CI_BUILD_NUMBER: $CIRCLE_BUILD_NUM + - CI_BUILD_URL: $CIRCLE_BUILD_URL + steps: + - checkout + - setup_artifacts + - run_yarn + - run: + name: Collect test coverage information + command: | + scripts/circleci/exec_swallow_error.sh yarn test --coverage --maxWorkers=2 + if [[ -e ./coverage/lcov.info ]]; then + cat ./coverage/lcov.info | scripts/circleci/exec_swallow_error.sh ./node_modules/.bin/coveralls fi - - save-cache: *save-cache-analysis + - store_artifacts: + path: ~/react-native/coverage/ + # ------------------------- + # JOBS: Build hermesc + # ------------------------- + prepare_hermes_workspace: + docker: + - image: debian:bullseye + environment: + - HERMES_WS_DIR: *hermes_workspace_root + - HERMES_VERSION_FILE: "sdks/.hermesversion" + steps: + - run: + name: Install dependencies + command: | + apt update + apt install -y wget git curl + curl -sL https://deb.nodesource.com/setup_16.x | bash - + apt install -y nodejs + npm install --global yarn + - checkout + - run_yarn - run: - name: Analyze Pull Request + name: Set up Hermes workspace and caching command: | - # DANGER_GITHUB_API_TOKEN=Facebook-Open-Source-Bot public_repo access token - if [ -n "$CIRCLE_PR_NUMBER" ]; then - cd bots && DANGER_GITHUB_API_TOKEN="b186c9a82bab3b08ec80""c0818117619eec6f281a" yarn danger + mkdir -p "/tmp/hermes" "/tmp/hermes/download" "/tmp/hermes/hermes" + + if [ -f "$HERMES_VERSION_FILE" ]; then + cat $HERMES_VERSION_FILE > /tmp/hermes/hermesversion else - echo "Skipping pull request analysis." + HERMES_TAG_SHA=$(git ls-remote https://github.com/facebook/hermes main | cut -f 1 | tr -d '[:space:]') + echo $HERMES_TAG_SHA > /tmp/hermes/hermesversion fi - when: always + - restore_cache: + key: *hermes_cache_key + - run: + name: Download Hermes tarball + command: | + node scripts/hermes/prepare-hermes-for-build + cp sdks/download/* $HERMES_WS_DIR/download/. + cp -r sdks/hermes/* $HERMES_WS_DIR/hermes/. + - save_cache: + key: *hermes_cache_key + paths: + - /tmp/hermes/download/ + - /tmp/hermes/hermes/ + - persist_to_workspace: + root: *hermes_workspace_root + paths: + - download + - hermes + - hermesversion + + build_hermesc_linux: + docker: + - image: debian:bullseye + resource_class: "xlarge" + working_directory: /root + steps: - run: - name: Analyze Code + name: Install dependencies 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 + apt update + apt install -y git openssh-client cmake build-essential \ + libreadline-dev libicu-dev zip python3 + - *attach_hermes_workspace + - restore_cache: + key: *hermes_cache_key + - run: + name: Set up workspace + command: | + mkdir -p /tmp/hermes/linux64-bin + - run: + name: Build HermesC for Linux + command: | + if [ -f /tmp/hermes/linux64-bin/hermesc ]; then + echo 'Skipping; Clean "/tmp/hermes/linux64-bin" to rebuild.' else - echo "Skipping code analysis." + cd /tmp/hermes + cmake -S hermes -B build -DHERMES_STATIC_LINK=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True -DCMAKE_CXX_FLAGS=-s -DCMAKE_C_FLAGS=-s \ + -DCMAKE_EXE_LINKER_FLAGS="-Wl,--whole-archive -lpthread -Wl,--no-whole-archive" + cmake --build build --target check-hermes -j 4 + cp /tmp/hermes/build/bin/hermesc /tmp/hermes/linux64-bin/. fi - when: always - -# Workflows enables us to run multiple jobs in parallel + - save_cache: + key: *hermes_cache_key + paths: + - /tmp/hermes/linux64-bin/ + - /tmp/hermes/hermes/destroot/ + - store_artifacts: + path: /tmp/hermes/linux64-bin/ + - persist_to_workspace: + root: /tmp/hermes/ + paths: + - linux64-bin + + build_hermes_macos: + executor: reactnativeios + environment: + - HERMES_WS_DIR: *hermes_workspace_root + steps: + - checkout_code_with_cache + - *attach_hermes_workspace + - restore_cache: + key: *hermes_cache_key + - run: + name: Set up workspace + command: | + mkdir -p /tmp/hermes/osx-bin + mkdir -p ~/react-native/sdks/hermes + cp -r $HERMES_WS_DIR/hermes/* ~/react-native/sdks/hermes/. + - run: + name: Install dependencies + command: | + brew install cmake + - run: + name: Build the Hermes iOS frameworks + command: | + cd ~/react-native/sdks/hermes + ./utils/build-ios-framework.sh + - run: + name: Build the Hermes Mac frameworks + command: | + cd ~/react-native/sdks/hermes + ./utils/build-mac-framework.sh + cp build_macosx/bin/hermesc /tmp/hermes/osx-bin/. + - run: + name: Package the Hermes Apple frameworks + command: | + cd ~/react-native/sdks/hermes + . ./utils/build-apple-framework.sh + + mkdir -p /tmp/cocoapods-package-root/destroot + mkdir -p /tmp/hermes/output + cp -R ./destroot /tmp/cocoapods-package-root + cp LICENSE /tmp/cocoapods-package-root + + tar -C /tmp/cocoapods-package-root/ -czvf /tmp/hermes/output/hermes-runtime-darwin-v$(get_release_version).tar.gz . + + mkdir -p /tmp/hermes/hermes-runtime-darwin + cp /tmp/hermes/output/hermes-runtime-darwin-v$(get_release_version).tar.gz /tmp/hermes/hermes-runtime-darwin/. + - save_cache: + key: *hermes_cache_key + paths: + - ~/react-native/hermes/build_host_hermesc + - ~/react-native/hermes/build_iphoneos + - ~/react-native/hermes/build_catalyst + - ~/react-native/hermes/build_iphonesimulator + - ~/react-native/hermes/build_macosx + - ~/react-native/hermes/destroot + - store_artifacts: + path: /tmp/hermes/hermes-runtime-darwin/ + - store_artifacts: + path: /tmp/hermes/osx-bin/ + - persist_to_workspace: + root: /tmp/hermes/ + paths: + - hermes-runtime-darwin + - osx-bin + + build_hermesc_windows: + executor: + name: win/default + shell: powershell.exe + environment: + - HERMES_WS_DIR: 'C:\tmp\hermes' + - ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip" + - MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin' + - CMAKE_DIR: 'C:\Program Files\CMake\bin' + steps: + - *attach_hermes_workspace + - restore_cache: + key: *hermes_windows_cache_key + - run: + name: Set up workspace + command: | + New-Item -ItemType Directory $Env:HERMES_WS_DIR + New-Item -ItemType Directory $Env:HERMES_WS_DIR\icu + New-Item -ItemType Directory $Env:HERMES_WS_DIR\deps + New-Item -ItemType Directory $Env:HERMES_WS_DIR\win64-bin + New-Item -ItemType SymbolicLink -Target tmp\hermes\hermes -Path $Env:HERMES_WS_DIR -Name hermes + - run: + name: Build HermesC for Windows + command: | + if (-not(Test-Path -Path $Env:HERMES_WS_DIR\win64-bin\hermesc.exe)) { + choco install --no-progress cmake --version 3.14.7 + if (-not $?) { throw "Failed to install CMake" } + choco install --no-progress python3 + if (-not $?) { throw "Failed to install Python" } + + cd $Env:HERMES_WS_DIR\icu + # If Invoke-WebRequest shows a progress bar, it will fail with + # Win32 internal error "Access is denied" 0x5 occurred [...] + $progressPreference = 'silentlyContinue' + Invoke-WebRequest -Uri "$Env:ICU_URL" -OutFile "icu.zip" + Expand-Archive -Path "icu.zip" -DestinationPath "." + + cd $Env:HERMES_WS_DIR + Copy-Item -Path "icu\bin64\icu*.dll" -Destination "deps" + # Include MSVC++ 2015 redistributables + Copy-Item -Path "c:\windows\system32\msvcp140.dll" -Destination "deps" + Copy-Item -Path "c:\windows\system32\vcruntime140.dll" -Destination "deps" + Copy-Item -Path "c:\windows\system32\vcruntime140_1.dll" -Destination "deps" + + $Env:PATH += ";$Env:CMAKE_DIR;$Env:MSBUILD_DIR" + $Env:ICU_ROOT = "$Env:HERMES_WS_DIR\icu" + + cmake -S hermes -B build_release -G 'Visual Studio 16 2019' -Ax64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True -DHERMES_ENABLE_WIN10_ICU_FALLBACK=OFF + if (-not $?) { throw "Failed to configure Hermes" } + cd build_release + cmake --build . --target hermesc --config Release + if (-not $?) { throw "Failed to build Hermes" } + + cd $Env:HERMES_WS_DIR + Copy-Item -Path "build_release\bin\Release\hermesc.exe" -Destination "win64-bin" + # Include Windows runtime dependencies + Copy-Item -Path "deps\*" -Destination "win64-bin" + } + else { + Write-Host "Skipping; Clean c:\tmp\hermes\win64-bin to rebuild." + } + - save_cache: + key: *hermes_windows_cache_key + paths: + - C:\tmp\hermes\win64-bin\ + - C:\tmp\hermes\hermes\icu\ + - C:\tmp\hermes\hermes\deps\ + - C:\tmp\hermes\hermes\build_release\ + - store_artifacts: + path: C:\tmp\hermes\win64-bin\ + - persist_to_workspace: + root: C:\tmp\hermes\ + paths: + - win64-bin + + # ------------------------- + # JOBS: Releases + # ------------------------- + prepare_package_for_release: + parameters: + version: + type: string + latest: + type: boolean + default: false + executor: reactnativeios + steps: + - checkout_code_with_cache + - run_yarn + - add_ssh_keys: + fingerprints: + - "1c:98:e0:3a:52:79:95:29:12:cd:b4:87:5b:41:e2:bb" + - run: + name: "Set new react-native version and commit changes" + command: | + node ./scripts/prepare-package-for-release.js -v << parameters.version >> -l << parameters.latest >> + + build_npm_package: + parameters: + publish_npm_args: + type: string + default: --dry-run + executor: reactnativeandroid + environment: + - HERMES_WS_DIR: *hermes_workspace_root + steps: + - run: + name: Add github.com to SSH known hosts + command: | + mkdir -p ~/.ssh + echo '|1|If6MU203eXTaaWL678YEfWkVMrw=|kqLeIAyTy8pzpj8x8Ae4Fr8Mtlc= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts + - checkout + - *attach_hermes_workspace + - run: + name: Copy HermesC binaries + command: | + mkdir -p ./sdks/hermesc ./sdks/hermesc/osx-bin ./sdks/hermesc/win64-bin ./sdks/hermesc/linux64-bin + cp -r $HERMES_WS_DIR/osx-bin/* ./sdks/hermesc/osx-bin/. + cp -r $HERMES_WS_DIR/win64-bin/* ./sdks/hermesc/win64-bin/. + cp -r $HERMES_WS_DIR/linux64-bin/* ./sdks/hermesc/linux64-bin/. + + - run_yarn + - install_buck_tooling + - download_buck_dependencies + - download_gradle_dependencies + + # START: Stables and nightlies + # This conditional step sets up the necessary credentials for publishing react-native to npm, + # and for interacting with GitHub as the react-native-bot account. Important: these steps + # should not be allowed to run on commits from pull requests. + - when: + condition: + or: + - equal: [ --release, << parameters.publish_npm_args >> ] + - equal: [ --nightly, << parameters.publish_npm_args >> ] + steps: + - run: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc + - run: | + git config --global user.email "react-native-bot@users.noreply.github.com" + git config --global user.name "npm Deployment Script" + echo "machine github.com login react-native-bot password $GITHUB_TOKEN" > ~/.netrc + # END: Stables and nightlies + + - run: node ./scripts/publish-npm.js << parameters.publish_npm_args >> + - run: + name: Zip Hermes Native Symbols + command: zip -r /tmp/hermes-native-symbols.zip ~/react-native/ReactAndroid/hermes-engine/build/intermediates/cmake/ + - store_artifacts: + path: /tmp/hermes-native-symbols.zip + + # START: Commitlies + # Provide a react-native package for this commit as a Circle CI release artifact. + - when: + condition: + equal: [ --dry-run, << parameters.publish_npm_args >> ] + steps: + - run: + name: Build release package as a job artifact + command: | + mkdir -p build + FILENAME=$(npm pack) + mv $FILENAME build/ + echo $FILENAME > build/react-native-package-version + - store_artifacts: + path: ~/react-native/build/ + destination: build + - persist_to_workspace: + root: . + paths: + - build/* + # END: Commitlies + + # START: Commits from pull requests + # When building commits from pull requests, leave a comment on the PR with a link to build artifacts + - when: + condition: + matches: { pattern: '^pull\/.*$', value: << pipeline.git.branch >> } + steps: + - install_github_bot_deps + - run: + name: Post link to PR build artifacts (pull-bot) + command: GITHUB_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" scripts/circleci/post-artifacts-link.sh || true + # END: Commits from pull requests + + # START: Stable releases + - when: + condition: + equal: [ --release, << parameters.publish_npm_args >> ] + steps: + - run: + name: Update rn-diff-purge to generate upgrade-support diff + command: | + curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \ + -H "Accept: application/vnd.github.v3+json" \ + -u "$PAT_USERNAME:$PAT_TOKEN" \ + -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${CIRCLE_TAG:1}\" }}" + - run: + name: Install dependencies + command: apt update && apt install -y jq jo + - run: + name: Create draft GitHub Release and upload Hermes binaries + command: | + ARTIFACTS=("$HERMES_WS_DIR/hermes-runtime-darwin/hermes-runtime-darwin-$CIRCLE_TAG.tar.gz") + ./scripts/circleci/create_github_release.sh $CIRCLE_TAG $CIRCLE_PROJECT_USERNAME $CIRCLE_PROJECT_REPONAME $GITHUB_TOKEN "${ARTIFACTS[@]}" + # END: Stable releases + + # ------------------------- + # JOBS: Nightly + # ------------------------- + nightly_job: + machine: + image: ubuntu-2004:202010-01 + steps: + - run: + name: Nightly + command: | + echo "Nightly build run" + + +# ------------------------- +# PIPELINE PARAMETERS +# ------------------------- +parameters: + run_package_release_workflow_only: + default: false + type: boolean + + release_latest: + default: false + type: boolean + + release_version: + default: "9999" + type: string + +# ------------------------- +# WORK FLOWS +# +# When creating a new workflow, make sure to include condition `unless: << pipeline.parameters.run_package_release_workflow_only >>` +# It's setup this way so we can trigger a release via a POST +# See limitations: https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2 +# ------------------------- workflows: version: 2 tests: + unless: << pipeline.parameters.run_package_release_workflow_only >> jobs: - - # Checkout repo and run Yarn - - checkout_code: - filters: *filter-ignore-gh-pages - - # Run lint, flow, and other checks - - analyze: - filters: *filter-ignore-gh-pages + - prepare_hermes_workspace + - build_hermesc_linux: requires: - - checkout_code - - # Test JavaScript - - test_javascript: - filters: *filter-ignore-gh-pages + - prepare_hermes_workspace + - build_hermes_macos: requires: - - checkout_code - - # Test JavaScript using Node 6, the minimum supported version - - test_javascript_node6_compatibility: - filters: *filter-ignore-gh-pages - - # Test Android + - prepare_hermes_workspace + - build_hermesc_windows: + requires: + - prepare_hermes_workspace + - build_npm_package: + # Build a release package on every untagged commit, but do not publish to npm. + publish_npm_args: --dry-run + requires: + - build_hermesc_linux + - build_hermes_macos + - build_hermesc_windows + - test_js: + run_disabled_tests: false - test_android: - filters: *filter-ignore-gh-pages + run_disabled_tests: false + - test_android_template: requires: - - checkout_code - - # Test iOS & tvOS + - build_npm_package + - test_ios_template: + requires: + - build_npm_package + - test_ios_rntester - test_ios: - filters: *filter-ignore-gh-pages + run_unit_tests: true + # DISABLED: USE_FRAMEWORKS=1 not supported by Flipper + # - test_ios: + # name: test_ios_frameworks + # use_frameworks: true + # run_unit_tests: true + # requires: + # - build_ios_frameworks + - test_js: + name: test_js_prev_lts + executor: nodeprevlts + - test_windows: + run_disabled_tests: false + + # This workflow should only be triggered by release script + package_release: + when: << pipeline.parameters.run_package_release_workflow_only >> + jobs: + # This job will trigger publish_release workflow + - prepare_package_for_release: + name: prepare_package_for_release + version: << pipeline.parameters.release_version >> + latest : << pipeline.parameters.release_latest >> + + publish_release: + unless: << pipeline.parameters.run_package_release_workflow_only >> + jobs: + - prepare_hermes_workspace: + filters: *only_release_tags + - build_hermesc_linux: + filters: *only_release_tags requires: - - checkout_code - - test_tvos: - filters: *filter-ignore-gh-pages + - prepare_hermes_workspace + - build_hermes_macos: + filters: *only_release_tags requires: - - checkout_code - - # End-to-end tests - - test_ios_e2e: - filters: *filter-ignore-gh-pages + - prepare_hermes_workspace + - build_hermesc_windows: + filters: *only_release_tags requires: - - checkout_code + - prepare_hermes_workspace + # This job will trigger when a version tag is pushed (by package_release) + - build_npm_package: + name: build_and_publish_npm_package + context: react-native-bot + publish_npm_args: --release + filters: *only_release_tags + requires: + - build_hermesc_linux + - build_hermes_macos + - build_hermesc_windows - # Only runs on PRs - analyze: + analysis: + unless: << pipeline.parameters.run_package_release_workflow_only >> + jobs: + # Run lints on every commit + - analyze_code + + # Run code checks on PRs + - analyze_pr + + # Gather coverage + - js_coverage + + nightly: + unless: << pipeline.parameters.run_package_release_workflow_only >> + triggers: + - schedule: + cron: "0 20 * * *" + filters: + branches: + only: + - main jobs: - # Checkout repo and run Yarn - - checkout_code: - filters: *filter-ignore-master-stable - - # Run code checks - - 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: + - nightly_job + + - prepare_hermes_workspace + - build_hermesc_linux: + requires: + - prepare_hermes_workspace + - build_hermes_macos: + requires: + - prepare_hermes_workspace + - build_hermesc_windows: + requires: + - prepare_hermes_workspace + - build_npm_package: + publish_npm_args: --nightly 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 + - build_hermesc_linux + - build_hermes_macos + - build_hermesc_windows diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000000..bc20b078f0eaf9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,91 @@ +--- +AccessModifierOffset: -1 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ] +IncludeCategories: + - Regex: '^<.*\.h(pp)?>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +--- +Language: ObjC +ColumnLimit: 120 +BreakBeforeBraces: WebKit +... diff --git a/.editorconfig b/.editorconfig index 4cde30709106fc..eeda81f4e0cb82 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# EditorConfig is awesome: http://EditorConfig.org +# EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true @@ -12,3 +12,9 @@ indent_size = 2 [*.gradle] indent_size = 4 + +[*.kts] +indent_size = 4 + +[BUCK] +indent_size = 4 diff --git a/.eslintignore b/.eslintignore index 698b5357d68697..f1beb735a483c6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,10 +1,10 @@ -# node_modules ignored by default - -**/staticBundle.js **/main.js -Libraries/vendor/**/* -Libraries/Renderer/* -pr-inactivity-bookmarklet.js -question-bookmarklet.js +**/staticBundle.js +bots/node_modules +docs/generatedComponentApiDocs.js flow/ -danger/ +Libraries/Renderer/* +Libraries/vendor/**/* +node_modules/ +packages/*/node_modules +packages/react-native-codegen/lib diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9887d1c6d8dbe0..00000000000000 --- a/.eslintrc +++ /dev/null @@ -1,300 +0,0 @@ -{ - "root": true, - - "parser": "babel-eslint", - - "env": { - "es6": true, - "jest": true, - }, - - "plugins": [ - "eslint-comments", - "flowtype", - "prettier", - "react", - "react-native", - "jest" - ], - - // Map from global var to bool specifying if it can be redefined - "globals": { - "__DEV__": true, - "__dirname": false, - "__fbBatchedBridgeConfig": false, - "alert": false, - "cancelAnimationFrame": false, - "cancelIdleCallback": false, - "clearImmediate": true, - "clearInterval": false, - "clearTimeout": false, - "console": false, - "document": false, - "escape": false, - "Event": false, - "EventTarget": false, - "exports": false, - "fetch": false, - "FormData": false, - "global": false, - "jest": false, - "Map": true, - "module": false, - "navigator": false, - "process": false, - "Promise": true, - "requestAnimationFrame": true, - "requestIdleCallback": true, - "require": false, - "Set": true, - "setImmediate": true, - "setInterval": false, - "setTimeout": false, - "window": false, - "XMLHttpRequest": false, - "pit": false - }, - - "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" - }], - - "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 - "no-constant-condition": 0, // disallow use of constant expressions in conditions - "no-control-regex": 1, // disallow control characters in regular expressions - "no-debugger": 1, // disallow use of debugger - "no-dupe-keys": 2, // disallow duplicate keys when creating object literals - "no-empty": 0, // disallow empty statements - "no-ex-assign": 1, // disallow assigning to the exception in a catch block - "no-extra-boolean-cast": 1, // disallow double-negation boolean casts in a boolean context - "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) - "no-extra-semi": 1, // disallow unnecessary semicolons - "no-func-assign": 1, // disallow overwriting functions written as function declarations - "no-inner-declarations": 0, // disallow function or variable declarations in nested blocks - "no-invalid-regexp": 1, // disallow invalid regular expression strings in the RegExp constructor - "no-negated-in-lhs": 1, // disallow negation of the left operand of an in expression - "no-obj-calls": 1, // disallow the use of object properties of the global object (Math and JSON) as functions - "no-regex-spaces": 1, // disallow multiple spaces in a regular expression literal - "no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default) - "no-sparse-arrays": 1, // disallow sparse arrays - "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement - "use-isnan": 1, // disallow comparisons with the value NaN - "valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default) - "valid-typeof": 1, // Ensure that the results of typeof are compared against a valid string - - // Best Practices - // These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns. - - "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default) - "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) - "consistent-return": 0, // require return statements to either always or never specify values - "curly": 1, // specify curly brace conventions for all control statements - "default-case": 0, // require default case in switch statements (off by default) - "dot-notation": 1, // encourages use of dot notation whenever possible - "eqeqeq": [1, "allow-null"], // require the use of === and !== - "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) - "no-alert": 1, // disallow the use of alert, confirm, and prompt - "no-caller": 1, // disallow use of arguments.caller or arguments.callee - "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) - "no-else-return": 0, // disallow else after a return in an if (off by default) - "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) - "no-eval": 2, // disallow use of eval() - "no-extend-native": 1, // disallow adding to native types - "no-extra-bind": 1, // disallow unnecessary function binding - "no-fallthrough": 1, // disallow fallthrough of case statements - "no-floating-decimal": 1, // disallow the use of leading or trailing decimal points in numeric literals (off by default) - "no-implied-eval": 1, // disallow use of eval()-like methods - "no-labels": 1, // disallow use of labeled statements - "no-iterator": 1, // disallow usage of __iterator__ property - "no-lone-blocks": 1, // disallow unnecessary nested blocks - "no-loop-func": 0, // disallow creation of functions within loops - "no-multi-str": 0, // disallow use of multiline strings - "no-native-reassign": 0, // disallow reassignments of native objects - "no-new": 1, // disallow use of new operator when not part of the assignment or comparison - "no-new-func": 2, // disallow use of new operator for Function object - "no-new-wrappers": 1, // disallows creating new instances of String,Number, and Boolean - "no-octal": 1, // disallow use of octal literals - "no-octal-escape": 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; - "no-proto": 1, // disallow usage of __proto__ property - "no-redeclare": 0, // disallow declaring the same variable more then once - "no-return-assign": 1, // disallow use of assignment in return statement - "no-script-url": 1, // disallow use of javascript: urls. - "no-self-compare": 1, // disallow comparisons where both sides are exactly the same (off by default) - "no-sequences": 1, // disallow use of comma operator - "no-unused-expressions": 0, // disallow usage of expressions in statement position - "no-void": 1, // disallow use of void operator (off by default) - "no-warning-comments": 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default) - "no-with": 1, // disallow use of the with statement - "radix": 1, // require use of the second argument for parseInt() (off by default) - "semi-spacing": 1, // require a space after a semi-colon - "vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default) - "wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default) - "yoda": 1, // require or disallow Yoda conditions - - // Variables - // These rules have to do with variable declarations. - - "no-catch-shadow": 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) - "no-delete-var": 1, // disallow deletion of variables - "no-label-var": 1, // disallow labels that share a name with a variable - "no-shadow": 1, // disallow declaration of variables already declared in the outer scope - "no-shadow-restricted-names": 1, // disallow shadowing of names such as arguments - "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-use-before-define": 0, // disallow use of variables before they are defined - - // Node.js - // These rules are specific to JavaScript running on Node.js. - - "handle-callback-err": 1, // enforces error handling in callbacks (off by default) (on by default in the node environment) - "no-mixed-requires": 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) - "no-new-require": 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment) - "no-path-concat": 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) - "no-process-exit": 0, // disallow process.exit() (on by default in the node environment) - "no-restricted-modules": 1, // restrict usage of specified node modules (off by default) - "no-sync": 0, // disallow use of synchronous methods (off by default) - - // ESLint Comments Plugin - // The following rules are made available via `eslint-plugin-eslint-comments` - "eslint-comments/no-aggregating-enable": 1, // disallows eslint-enable comments for multiple eslint-disable comments - "eslint-comments/no-unlimited-disable": 1, // disallows eslint-disable comments without rule names - "eslint-comments/no-unused-disable": 1, // disallow disables that don't cover any errors - "eslint-comments/no-unused-enable": 1, // // disallow enables that don't enable anything or enable rules that weren't disabled - - // Flow Plugin - // The following rules are made available via `eslint-plugin-flowtype` - "flowtype/define-flow-type": 1, - "flowtype/use-flow-type": 1, - - // Prettier Plugin - // https://github.com/prettier/eslint-plugin-prettier - "prettier/prettier": [2, "fb", "@format"], - - // Stylistic Issues - // These rules are purely matters of style and are quite subjective. - - "key-spacing": 0, - "keyword-spacing": 1, // enforce spacing before and after keywords - "jsx-quotes": [1, "prefer-double"], // enforces the usage of double quotes for all JSX attribute values which doesn’t contain a double quote - "comma-spacing": 0, - "no-multi-spaces": 0, - "brace-style": 0, // enforce one true brace style (off by default) - "camelcase": 0, // require camel case names - "consistent-this": 1, // enforces consistent naming when capturing the current execution context (off by default) - "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines - "func-names": 0, // require function expressions to have a name (off by default) - "func-style": 0, // enforces use of function declarations or expressions (off by default) - "new-cap": 0, // require a capital letter for constructors - "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments - "no-nested-ternary": 0, // disallow nested ternary expressions (off by default) - "no-array-constructor": 1, // disallow use of the Array constructor - "no-empty-character-class": 1, // disallow the use of empty character classes in regular expressions - "no-lonely-if": 0, // disallow if as the only statement in an else block (off by default) - "no-new-object": 1, // disallow use of the Object constructor - "no-spaced-func": 1, // disallow space between function identifier and application - "no-ternary": 0, // disallow the use of ternary operators (off by default) - "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines - "no-underscore-dangle": 0, // disallow dangling underscores in identifiers - "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation - "quotes": [1, "single", "avoid-escape"], // specify whether double or single quotes should be used - "quote-props": 0, // require quotes around object literal property names (off by default) - "semi": 1, // require or disallow use of semicolons instead of ASI - "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) - "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) - "space-infix-ops": 1, // require spaces around operators - "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) - "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) - "one-var": 0, // allow just one var statement per function (off by default) - "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) - - // Legacy - // The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same. - - "max-depth": 0, // specify the maximum depth that blocks can be nested (off by default) - "max-len": 0, // specify the maximum length of a line in your program (off by default) - "max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default) - "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) - "no-bitwise": 1, // disallow use of bitwise operators (off by default) - "no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default) - - // React Plugin - // The following rules are made available via `eslint-plugin-react`. - - "react/display-name": 0, - "react/jsx-boolean-value": 0, - "react/jsx-no-comment-textnodes": 1, - "react/jsx-no-duplicate-props": 2, - "react/jsx-no-undef": 2, - "react/jsx-sort-props": 0, - "react/jsx-uses-react": 1, - "react/jsx-uses-vars": 1, - "react/no-did-mount-set-state": 1, - "react/no-did-update-set-state": 1, - "react/no-multi-comp": 0, - "react/no-string-refs": 1, - "react/no-unknown-property": 0, - "react/prop-types": 0, - "react/react-in-jsx-scope": 1, - "react/self-closing-comp": 1, - "react/wrap-multilines": 0, - - // React-Native Plugin - // The following rules are made available via `eslint-plugin-react-native` - - "react-native/no-inline-styles": 1, - - // Jest Plugin - // The following rules are made available via `eslint-plugin-jest`. - "jest/no-disabled-tests": 1, - "jest/no-focused-tests": 1, - "jest/no-identical-title": 1, - "jest/valid-expect": 1, - }, - - "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, - }, - "env": { - "node": true, - }, - }, - ], -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000000000..3d0dad11dfcd21 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +module.exports = { + root: true, + + extends: ['./packages/eslint-config-react-native-community/index.js'], + + plugins: ['@react-native/eslint-plugin-specs'], + + overrides: [ + // overriding the JS config from eslint-config-react-native-community config to ensure + // that we use hermes-eslint for all js files + { + files: ['*.js'], + parser: 'hermes-eslint', + rules: { + // These rules are not required with hermes-eslint + 'ft-flow/define-flow-type': 0, + 'ft-flow/use-flow-type': 0, + 'flowtype/define-flow-type': 0, + 'flowtype/use-flow-type': 0, + // flow handles this check for us, so it's not required + 'no-undef': 0, + }, + }, + + { + files: ['Libraries/**/*.js'], + rules: { + '@react-native-community/no-haste-imports': 2, + '@react-native-community/error-subclass-name': 2, + '@react-native-community/platform-colors': 2, + '@react-native/specs/react-native-modules': 2, + }, + }, + { + files: ['flow-typed/**/*.js'], + rules: { + quotes: 0, + }, + }, + { + files: [ + '**/__fixtures__/**/*.js', + '**/__mocks__/**/*.js', + '**/__tests__/**/*.js', + 'jest/**/*.js', + 'packages/rn-tester/**/*.js', + ], + globals: { + // Expose some Jest globals for test helpers + afterAll: true, + afterEach: true, + beforeAll: true, + beforeEach: true, + expect: true, + jest: true, + }, + }, + { + files: ['**/__tests__/**/*-test.js'], + env: { + jasmine: true, + jest: true, + }, + }, + ], +}; diff --git a/.flowconfig b/.flowconfig index 361d475f24042d..c2d114b4f6769c 100644 --- a/.flowconfig +++ b/.flowconfig @@ -3,7 +3,7 @@ .*/*[.]android.js ; Ignore templates for 'react-native init' -.*/local-cli/templates/.* +/template/.* ; Ignore the Dangerfile /bots/dangerfile.js @@ -11,45 +11,67 @@ ; Ignore "BUCK" generated dirs /\.buckd/ -; Ignore unexpected extra "@providesModule" -.*/node_modules/.*/node_modules/fbjs/.* +; Flow doesn't support platforms +.*/Libraries/Utilities/LoadingView.js -; Ignore duplicate module providers -; For RN Apps installed via npm, "Libraries" folder is inside -; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js +.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$ -; Ignore polyfills -.*/Libraries/polyfills/.* - -; Ignore metro -.*/node_modules/metro/.* +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* [include] +[declarations] +.*/node_modules/.* + [libs] -Libraries/react-native/react-native-interface.js +interface.js flow/ -flow-github/ [options] emoji=true -module.system=haste +exact_by_default=true +exact_empty_objects=true + +format.bracket_spacing=false + +module.file_ext=.js +module.file_ext=.json +module.file_ext=.ios.js munge_underscores=true -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' +module.name_mapper='^react-native$' -> '/index.js' +module.name_mapper='^react-native/\(.*\)$' -> '/\1' +module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/Libraries/Image/RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError +suppress_type=$FlowFixMeEmpty + +experimental.env_mode=ssa + +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=error +unsafe-getters-setters=warn +unnecessary-invariant=warn + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import [version] -^0.67.0 +^0.182.0 diff --git a/.flowconfig.android b/.flowconfig.android new file mode 100644 index 00000000000000..dc9d12aba7e9a8 --- /dev/null +++ b/.flowconfig.android @@ -0,0 +1,77 @@ +[ignore] +; We fork some components by platform +.*/*[.]ios.js + +; Ignore templates for 'react-native init' +/template/.* + +; Ignore the Dangerfile +/bots/dangerfile.js + +; Ignore "BUCK" generated dirs +/\.buckd/ + +; Flow doesn't support platforms +.*/Libraries/Utilities/LoadingView.js + +.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$ + +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* + +[include] + +[declarations] +.*/node_modules/.* + +[libs] +interface.js +flow/ + +[options] +emoji=true + +exact_by_default=true +exact_empty_objects=true + +format.bracket_spacing=false + +module.file_ext=.js +module.file_ext=.json +module.file_ext=.android.js + +munge_underscores=true + +module.name_mapper='^react-native$' -> '/index.js' +module.name_mapper='^react-native/\(.*\)$' -> '/\1' +module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/Libraries/Image/RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState +suppress_type=$FlowFixMeEmpty + +experimental.env_mode=ssa + +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=error +unsafe-getters-setters=warn +unnecessary-invariant=warn + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + +[version] +^0.182.0 diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 8f9372e008985a..00000000000000 --- a/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# Force LF line endings for Bash scripts. On Windows the rest of the source -# files will typically have CR+LF endings (Git default on Windows), but Bash -# scripts need to have LF endings to work (under Cygwin), thus override to force -# that. -gradlew text eol=lf -*.sh text eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b9d084bf849cf8..38dbd76f893b2b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,40 @@ -Libraries/Animated/* @janicduplessis -Libraries/NativeAnimation/* @janicduplessis -Libraries/Image/* @shergin -Libraries/Text/* @shergin +# See https://help.github.com/en/articles/about-code-owners +# to learn more about code owners. +# Order is important; the last matching pattern takes the most +# precedence. You may specify either a GitHub username, or an +# email address if you prefer, as the code owner. + +# Any Markdown file anywhere in the repository +**/*.md @hramos @cpojer + +# GitHub Settings, Bots +/.github/ @hramos +/bots @hramos + +# Continuous Integration +/.circleci/ @hramos +/.circleci/Dockerfiles @gengjiawen +/.appveyor/ @gengjiawen + +# Internals React/Base/* @shergin React/Views/* @shergin React/Modules/* @shergin React/CxxBridge/* @mhorowitz + +# Components and APIs ReactAndroid/src/main/java/com/facebook/react/animated/* @janicduplessis -**/*.md @hramos -package.json @hramos -local-cli/core/* @grabbou @kureev -local-cli/link/* @grabbou @kureev -local-cli/unlink/* @grabbou @kureev +Libraries/Animated/* @janicduplessis +Libraries/NativeAnimation/* @janicduplessis +Libraries/Image/* @shergin +Libraries/Text/* @shergin + +# Modifications to package.json typically require +# additional effort from a Facebook employee to land +/package.json @hramos @cpojer + +# These should not be modified through a GitHub PR +LICENSE* @hramos @cpojer @yungsters + +# The eslint-config-react-native-community package requires manual publishing after merging +/packages/eslint-config-react-native-community/* @matt-oakes diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index bbe1d31e0b8f46..7b530841f371c3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,29 +1,4 @@ - +✋ To keep the backlog clean and actionable, issues will be +🚫 closed if they do not follow one of the issue templates: +👉 https://github.com/facebook/react-native/issues/new/choose -(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.) - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000000..9c245257bf1eb2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,48 @@ +name: 🐛 Bug Report +description: Report a reproducible bug or regression in React Native. +labels: ["Needs: Triage :mag:"] +body: + - type: markdown + attributes: + value: | + Please provide all the information requested. Issues that do not follow this format are likely to stall. + - type: textarea + id: description + attributes: + label: Description + description: Please provide a clear and concise description of what the bug is. Include screenshots if needed. Please test using the latest React Native release to make sure your issue has not already been fixed - https://reactnative.dev/docs/upgrading.html + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: What react-native version does this appear on? + placeholder: ex. 0.66.0 + validations: + required: true + - type: textarea + id: react-native-info + attributes: + label: Output of `npx react-native info` + description: Run `npx react-native info` in your terminal, copy and paste the results here. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: Provide a detailed list of steps that reproduce the issue. + validations: + required: true + - type: textarea + id: extra + attributes: + label: Snack, code example, screenshot, or link to a repository + description: | + Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem. + You may provide a screenshot of the application if you think it is relevant to your bug report. + Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve + Please note that a reproducer is mandatory. Issues without reproducer are more likely to stall and will be closed. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000000..9b140cf8107ee4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: 📃 Documentation Issue + url: https://github.com/facebook/react-native-website/issues + about: Please report documentation issues in the React Native website repository. + - name: 🤔 Questions and Help + url: https://reactnative.dev/help + about: Looking for help with your app? Please refer to the React Native community's support resources. + - name: 💫 New Architecture - Questions & Technical Deep dive insights + url: https://github.com/reactwg/react-native-new-architecture + about: Questions and doubts related to technical questions for the New Architecture should be directed to the Working Group. Instructions on how to join are available in the README. + - name: 🚀 Discussions and Proposals + url: https://github.com/react-native-community/discussions-and-proposals + about: Discuss the future of React Native in the React Native community's discussions and proposals repository. diff --git a/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml b/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml new file mode 100644 index 00000000000000..8f060dd42de927 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml @@ -0,0 +1,53 @@ +name: 💫 New Architecture - Bug Report +description: Report a reproducible bug or a build issue when using the New Architecture (Fabric & TurboModules) in React Native. +labels: ["Needs: Triage :mag:", "Type: New Architecture"] +body: + - type: markdown + attributes: + value: | + Please provide all the information requested. Issues that do not follow this format are going to be closed. + This issue report is reserved to bug & build issues for users on the New Architecture. If you're not using + the New Architecture, please don't open issues on this category. + - type: textarea + id: description + attributes: + label: Description + description: | + Please provide a clear and concise description of what the bug or issue is. Include screenshots if needed. + Please make sure you check the New Architecture documentation first, as your issue might + already be answered there - https://reactnative.dev/docs/next/new-architecture-intro + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: What react-native version does this appear on? Please test against the latest stable version. Bug reports against older versions are more likely to stall. + placeholder: ex. 0.68.0 + validations: + required: true + - type: textarea + id: react-native-info + attributes: + label: Output of `npx react-native info` + description: Run `npx react-native info` in your terminal, copy and paste the results here. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: Provide a detailed list of steps that reproduce the issue. + validations: + required: true + - type: textarea + id: extra + attributes: + label: Snack, code example, screenshot, or link to a repository + description: | + Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem. + You may provide a screenshot of the application if you think it is relevant to your bug report. + Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve + Please note that a reproducer is mandatory. Issues without reproducer are more likely to stall and will be closed. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/upgrade-regression-form.yml b/.github/ISSUE_TEMPLATE/upgrade-regression-form.yml new file mode 100644 index 00000000000000..51495e4916a373 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/upgrade-regression-form.yml @@ -0,0 +1,46 @@ +name: ⬆️ Upgrade - Build Regression +description: If you are upgrading to a new React Native version (stable or pre-release) and encounter a build regression. +labels: ["Needs: Triage :mag:", "Type: Upgrade Issue"] +body: + - type: markdown + attributes: + value: | + Please use this form to file an issue if you have upgarded or are upgrading to [latest stable release](https://github.com/facebook/react-native/releases/latest) and have experienced a regression (something that used to work in previous version). + - type: input + id: new-version + attributes: + label: New Version + description: This is the version you are attempting to upgrade to. + placeholder: ex. 0.66.1 + validations: + required: true + - type: input + id: old-version + attributes: + label: Old Version + description: This is the version you were on where the behavior was working. + placeholder: ex. 0.65.1 + validations: + required: true + - type: input + id: target + attributes: + label: Build Target(s) + description: What target(s) are encountering this issue? + placeholder: iOS simulator in release flavor + validations: + required: true + - type: textarea + id: react-native-info + attributes: + label: Output of `react-native info` + description: Run `react-native info` in your terminal, copy and paste the results here. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Issue and Reproduction Steps + description: Please describe the issue and list out commands run to reproduce. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d8a1469f329099..073b9af6d242da 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,50 +1,17 @@ - -Help us understand your motivation by explaining why you decided to make this change. +## Summary -You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html + -Happy contributing! +## Changelog + -## Motivation - -(Write your motivation here.) +[CATEGORY] [TYPE] - Message ## 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!) - -## 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 - + diff --git a/.github/RELEASE_TEMPLATE.md b/.github/RELEASE_TEMPLATE.md new file mode 100644 index 00000000000000..302daf94622667 --- /dev/null +++ b/.github/RELEASE_TEMPLATE.md @@ -0,0 +1,23 @@ + + + + +- + +--- + +To test it, run: + +npx react-native init RN__SHORT_VERSION__ --version __VERSION__ + +--- + +You can participate in the conversation on the status of this release in the [working group](https://github.com/reactwg/react-native-releases/discussions). + +--- + +To help you upgrade to this version, you can use the [upgrade helper](https://react-native-community.github.io/upgrade-helper/) ⚛️ + +--- + +See changes from this release in the [changelog PR](https://github.com/facebook/react-native/labels/%F0%9F%93%9D%20Changelog) diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 00000000000000..e597d1e3334e5a --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,35 @@ +Thanks for using React Native! If you need help with your React Native app, the right place to go depends on the type of help that you need. + + +## 🤔 I have a question or need help with my React Native app. + +If you have a coding question related to React Native, it might be better suited for Stack Overflow. It's a great place to browse through [frequent questions about using React Native](https://stackoverflow.com/questions/tagged/react-native?sort=frequent&pageSize=15), as well as [ask for help with specific questions](https://stackoverflow.com/questions/tagged/react-native). + +[Reactiflux](https://www.reactiflux.com/) is an active community of React and React Native developers. If you are looking for immediate assistance or have a general question about React Native, the #react-native channel is a good place to start. + + +## 📃 I found something that seems wrong in the documentation. + +The React Native website is hosted on a [separate repository](https://github.com/facebook/react-native-website). If you want to report something that is wrong or missing from the documentation, [please open a new issue there](https://github.com/facebook/react-native-website/issues). + + +## 🐛 I found a bug in React Native. + +If you want to report a reproducible bug or regression in the React Native library, you can [create a new issue](https://github.com/facebook/react-native/issues/new?labels=Type%3A+Bug+Report&template=bug_report.md). It's a good idea to look through [open issues](https://github.com/facebook/react-native/issues) before doing so, as someone else may have reported a similar issue. + + +## 🚀 I want to discuss the future of React Native. + +If you'd like to discuss topics related to the future of React Native, please check out the [React Native Community Discussions and Proposals](https://github.com/react-native-community/discussions-and-proposals) repository. + + +## 💬 I want to talk to other React Native developers. + +If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums: + +- [Reactiflux Discord Server](https://www.reactiflux.com) +- [Spectrum Chat](https://spectrum.chat/react-native) +- [React Native Community Facebook Group](https://www.facebook.com/groups/react.native.community) + + +> For a full list of community resources, check out [React Native's Community page](https://reactnative.dev/help). diff --git a/.github/no-response.yml b/.github/no-response.yml deleted file mode 100644 index 24203633432e73..00000000000000 --- a/.github/no-response.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Configuration for probot-no-response - https://github.com/probot/no-response - -# Number of days of inactivity before an Issue is closed for lack of response -daysUntilClose: 14 -# Label requiring a response -responseRequiredLabel: Needs more information -# Comment to post when closing an Issue for lack of response. Set to `false` to disable -closeComment: > - Closing this issue as more information is needed to debug this and we have not heard back from the author. diff --git a/.github/respond-to-issue-based-on-label.yml b/.github/respond-to-issue-based-on-label.yml new file mode 100644 index 00000000000000..57cc3eeb02a60f --- /dev/null +++ b/.github/respond-to-issue-based-on-label.yml @@ -0,0 +1,52 @@ +# Configuration for Respond To Issue Based on Label https://github.com/marketplace/actions/respond-to-issue-based-on-label + +"Type: Invalid": + close: true +"Type: Question": + comment: > + We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/). + close: true +"Type: Docs": + comment: > + Please report documentation issues in the [`react-native-website`](https://github.com/facebook/react-native-website/issues) repository. + close: true +"Resolution: For Stack Overflow": + comment: > + We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question. + close: true +"Needs: Issue Template": + comment: > +
:warning: + Missing Required Fields +
:information_source: + It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a new issue is created. Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one. +
+ labels: + - "Needs: Author Feedback" +"Needs: Environment Info": + comment: > +
:warning: + Missing Environment Information +
:information_source: + Your issue may be missing information about your development environment. You can obtain the missing information by running react-native info in a console. +
+ labels: + - "Needs: Author Feedback" +"Needs: Verify on Latest Version": + comment: > +
:warning: + Using Old Version +
:information_source: + It looks like you are using an older version of React Native. Please upgrade to the latest version, and verify if the issue persists. If it does not, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the current release. +
+ labels: + - "Needs: Author Feedback" +"Needs: Repro": + comment: > +
:warning: + Missing Reproducible Example +
:information_source: + It looks like your issue is missing a reproducible example. Please provide a Snack or a repository that demonstrates the issue you are reporting in a minimal, complete, and reproducible manner. +
+ labels: + - "Needs: Author Feedback" diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index a738daf3783295..00000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 180 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 60 -# Issues with these labels will never be considered stale -exemptLabels: - - Good first issue - - For Discussion - - Core Team -# Label to use when marking an issue as stale -staleLabel: Stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. - You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. - Thank you for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information. -only: issues \ No newline at end of file diff --git a/.github/workflows/apply-version-label-issue.yml b/.github/workflows/apply-version-label-issue.yml new file mode 100644 index 00000000000000..121e280cfcafe6 --- /dev/null +++ b/.github/workflows/apply-version-label-issue.yml @@ -0,0 +1,21 @@ +name: Apply version label to issue + +on: + issues: + types: [opened, edited] + +permissions: + contents: read + +jobs: + add-version-label-issue: + permissions: + issues: write # for react-native-community/actions-apply-version-label to label issues + runs-on: ubuntu-latest + continue-on-error: true + + steps: + - uses: react-native-community/actions-apply-version-label@v0.0.3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + required-label: "Type: Upgrade Issue" diff --git a/.github/workflows/danger_pr.yml b/.github/workflows/danger_pr.yml new file mode 100644 index 00000000000000..d3e8072065a891 --- /dev/null +++ b/.github/workflows/danger_pr.yml @@ -0,0 +1,22 @@ +name: Run Danger on PR + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: + contents: read + +jobs: + danger: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: yarn install + working-directory: bots + - name: Danger + run: DANGER_GITHUB_API_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" yarn danger ci --use-github-checks --failOnErrors --id danger_pr + working-directory: bots + env: + PUBLIC_PULLBOT_GITHUB_TOKEN_A: a6edf8e8d40ce4e8b11a + PUBLIC_PULLBOT_GITHUB_TOKEN_B: 150e1341f4dd9c944d2a diff --git a/.github/workflows/needs-attention.yml b/.github/workflows/needs-attention.yml new file mode 100644 index 00000000000000..f1ec7e8813475c --- /dev/null +++ b/.github/workflows/needs-attention.yml @@ -0,0 +1,27 @@ +name: Issue Needs Attention +# This workflow is triggered on issue comments. +on: + issue_comment: + types: created + +permissions: + contents: read + +jobs: + applyNeedsAttentionLabel: + permissions: + contents: read # for actions/checkout to fetch code + issues: write # for hramos/needs-attention to label issues + name: Apply Needs Attention Label + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Apply Needs Attention Label + uses: hramos/needs-attention@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + response-required-label: "Needs: Author Feedback" + needs-attention-label: "Needs: Attention" + id: needs-attention + - name: Result + run: echo '${{ steps.needs-attention.outputs.result }}' diff --git a/.github/workflows/on-issue-labeled.yml b/.github/workflows/on-issue-labeled.yml new file mode 100644 index 00000000000000..f99404e94fae8a --- /dev/null +++ b/.github/workflows/on-issue-labeled.yml @@ -0,0 +1,22 @@ +name: On Issue Labeled +# This workflow is triggered when a label is added to an issue. +on: + issues: + types: labeled + +permissions: + contents: read + +jobs: + respondToIssueBasedOnLabel: + permissions: + contents: read # for hramos/respond-to-issue-based-on-label to fetch config file + issues: write # for hramos/respond-to-issue-based-on-label to update issues + name: Respond to Issue Based on Label + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Respond to Issue Based on Label + uses: hramos/respond-to-issue-based-on-label@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml new file mode 100644 index 00000000000000..326a54eb71e9aa --- /dev/null +++ b/.github/workflows/stale-bot.yml @@ -0,0 +1,19 @@ +name: Mark stale issues and pull requests +on: + schedule: + - cron: "30 1 * * *" +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 180 + stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.' + stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.' + close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' diff --git a/.github/workflows/test-docker-android.yml b/.github/workflows/test-docker-android.yml new file mode 100644 index 00000000000000..6951e180eb81ce --- /dev/null +++ b/.github/workflows/test-docker-android.yml @@ -0,0 +1,26 @@ +name: Test Docker Android Image +# This workflow is triggered on commits to main and pull requests. +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + test-docker-android: + name: Test Docker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Free up space by removing unnecessary folders + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Build Docker image with Android test app + run: npm run docker-build-android diff --git a/.gitignore b/.gitignore index 6b290712dbd6d4..c954afb75a0f3b 100644 --- a/.gitignore +++ b/.gitignore @@ -20,22 +20,40 @@ DerivedData *.ipa *.xcuserstate project.xcworkspace +**/.xcode.env.local # Gradle /build/ -/RNTester/android/app/build/ -/RNTester/android/app/gradle/ -/RNTester/android/app/gradlew -/RNTester/android/app/gradlew.bat +/packages/react-native-gradle-plugin/build/ +/packages/rn-tester/build +/packages/rn-tester/android/app/.cxx/ +/packages/rn-tester/android/app/build/ +/packages/rn-tester/android/app/gradle/ +/packages/rn-tester/android/app/gradlew +/packages/rn-tester/android/app/gradlew.bat /ReactAndroid/build/ +/ReactAndroid/.cxx/ +/ReactAndroid/gradle/ +/ReactAndroid/gradlew +/ReactAndroid/gradlew.bat +/ReactAndroid/hermes-engine/build/ +/ReactAndroid/hermes-engine/.cxx/ +/template/android/app/build/ +/template/android/build/ # Buck .buckd buck-out -/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/ -/ReactAndroid/src/main/jni/prebuilt/lib/x86/ +/.lsp.buckd +/.lsp-buck-out +/ReactAndroid/src/main/jni/prebuilt/lib/ /ReactAndroid/src/main/gen +# Android Studio +.project +.settings +.classpath + # Watchman .watchmanconfig @@ -51,10 +69,6 @@ node_modules *.log .nvm /bots/node_modules/ - -# TODO: Check in yarn.lock in open source. Right now we need to keep it out -# from the GitHub repo as importing it might conflict with internal workspaces -yarn.lock package-lock.json # OS X @@ -66,3 +80,48 @@ package-lock.json /coverage /third-party + +# Root dir shouldn't have Xcode project +/*.xcodeproj + +# ReactCommon subdir shouldn't have Xcode project +/ReactCommon/**/*.xcodeproj + +# Libs that shouldn't have Xcode project +/Libraries/FBLazyVector/**/*.xcodeproj +/Libraries/RCTRequired/**/*.xcodeproj +/React/CoreModules/**/*.xcodeproj +/React/FBReactNativeSpec/**/*.xcodeproj +/packages/react-native-codegen/**/*.xcodeproj + +# Ruby Gems (Bundler) +/vendor +/template/vendor + +# iOS / CocoaPods +/template/ios/build/ +/template/ios/Pods/ +/template/ios/Podfile.lock +/packages/rn-tester/Gemfile.lock + +# Ignore RNTester specific Pods, but keep the __offline_mirrors__ here. +/packages/rn-tester/Pods/* +!/packages/rn-tester/Pods/__offline_mirrors__ + +# react-native-codegen +/React/FBReactNativeSpec/FBReactNativeSpec +/packages/react-native-codegen/lib +/ReactCommon/react/renderer/components/rncore/ +/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec* + +# Additional SDKs +/sdks/download +/sdks/hermes +/sdks/hermesc + +# Visual studio +.vscode +.vs + +# Android memory profiler files +*.hprof diff --git a/.node-version b/.node-version new file mode 100644 index 00000000000000..b6a7d89c68e0ca --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +16 diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 2e0f2c1f8c6230..00000000000000 --- a/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -# rnpm -/local-cli/rnpm -/local-cli/server/middleware/heapCapture/bundle.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000000..600a26c495189d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "arrowParens": "avoid", + "bracketSameLine": true, + "bracketSpacing": false, + "requirePragma": true, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000000000..a603bb50a29e35 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.5 diff --git a/BUCK b/BUCK new file mode 100644 index 00000000000000..69a9a077041b0b --- /dev/null +++ b/BUCK @@ -0,0 +1,1462 @@ +load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") +load("//tools/build_defs/apple:fb_apple_test.bzl", "fb_apple_test") +load("//tools/build_defs/apple:flag_defs.bzl", "get_objc_arc_preprocessor_flags", "get_preprocessor_flags_for_build_mode", "get_static_library_ios_flags") +load("//tools/build_defs/apple/plugins:plugin_defs.bzl", "plugin") +load("//tools/build_defs/oss:metro_defs.bzl", "rn_library") +load( + "//tools/build_defs/oss:rn_codegen_defs.bzl", + "rn_codegen", + "rn_codegen_components", +) +load( + "//tools/build_defs/oss:rn_defs.bzl", + "HERMES_BYTECODE_VERSION", + "IOS", + "RCT_IMAGE_DATA_DECODER_SOCKET", + "RCT_IMAGE_URL_LOADER_SOCKET", + "RCT_URL_REQUEST_HANDLER_SOCKET", + "YOGA_CXX_TARGET", + "get_react_native_ios_target_sdk_version", + "react_fabric_component_plugin_provider", + "react_module_plugin_providers", + "react_native_root_target", + "react_native_xplat_dep", + "react_native_xplat_target", + "rn_apple_library", + "rn_extra_build_flags", + "rn_xplat_cxx_library2", + "subdir_glob", +) +load("//tools/build_defs/third_party:yarn_defs.bzl", "yarn_workspace") + +RCTCXXBRIDGE_PUBLIC_HEADERS = { + "React/" + x: "React/CxxBridge/" + x + for x in [ + "JSCExecutorFactory.h", + "NSDataBigString.h", + "RCTCxxBridgeDelegate.h", + "RCTJSIExecutorRuntimeInstaller.h", + "RCTMessageThread.h", + ] +} + +fb_native.genrule( + name = "codegen_rn_components_schema_rncore", + srcs = glob( + [ + "Libraries/**/*NativeComponent.js", + "jest/**/*NativeComponent.js", + "packages/**/*NativeComponent.js", + ], + exclude = [ + "**/__*__/**", + + # Subfolders with their own BUCK files, referenced below + "packages/rn-tester/**", + ], + ) + [ + react_native_root_target("packages/rn-tester:nativecomponent-srcs"), + ], + labels = ["uses_hg"], + cmd = "$(exe {}) $OUT $SRCS".format(react_native_root_target("packages/react-native-codegen:write_to_json")), + out = "schema-rncore.json", +) + +rn_codegen_components( + name = "rncore", + schema_target = ":codegen_rn_components_schema_rncore", +) + +rn_xplat_cxx_library2( + name = "RCTCxxBridge", + srcs = glob([ + "React/CxxBridge/*.mm", + ]), + headers = subdir_glob( + [ + ( + "React/CxxBridge", + "*.h", + ), + ], + exclude = RCTCXXBRIDGE_PUBLIC_HEADERS.values(), + prefix = "React", + ), + header_namespace = "", + exported_headers = RCTCXXBRIDGE_PUBLIC_HEADERS, + compiler_flags = [ + "-fobjc-arc-exceptions", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + exported_preprocessor_flags = rn_extra_build_flags(), + fbobjc_enable_exceptions = True, + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + # Used via objc_lookupClass in RCTBridge. Semantics are meant to be "if + # it's linked in your app, transparently use it". + labels = [ + "depslint_never_remove", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + [ + "-DWITH_FBSYSTRACE=1", + "-DRCT_USE_HERMES=0", # This is the default. + ], + visibility = ["PUBLIC"], + deps = [ + ":RCTCxxModule", + ":RCTCxxUtils", + ":ReactInternal", + "//fbobjc/Libraries/FBReactKit:RCTFBSystrace", + "//xplat/folly:molly", + react_native_root_target("React/CoreModules:CoreModules"), + react_native_xplat_target("cxxreact:bridge"), + react_native_xplat_target("cxxreact:jsbigstring"), + react_native_xplat_target("jsi:JSCRuntime"), + react_native_xplat_target("jsiexecutor:jsiexecutor"), + react_native_xplat_target("reactperflogger:reactperflogger"), + ], +) + +RCTCXXMODULE_PUBLIC_HEADERS = { + "React/" + x: "React/CxxModule/" + x + for x in [ + "RCTCxxMethod.h", + "RCTCxxModule.h", + "RCTCxxUtils.h", + ] +} + +rn_xplat_cxx_library2( + name = "RCTCxxModule", + srcs = glob([ + "React/CxxModule/*.mm", + ]), + headers = subdir_glob( + [ + ( + "React/CxxModule", + "*.h", + ), + ], + exclude = RCTCXXMODULE_PUBLIC_HEADERS.values(), + prefix = "React", + ), + header_namespace = "", + exported_headers = RCTCXXMODULE_PUBLIC_HEADERS, + compiler_flags = [ + "-fobjc-arc-exceptions", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + fbobjc_enable_exceptions = True, + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + ["-DWITH_FBSYSTRACE=1"], + visibility = ["PUBLIC"], + deps = [ + ":RCTCxxUtils", + ":ReactInternal", + "//xplat/fbsystrace:fbsystrace", + "//xplat/folly:headers_only_do_not_use", + react_native_xplat_target("cxxreact:module"), + react_native_xplat_target("cxxreact:bridge"), + react_native_xplat_target("reactperflogger:reactperflogger"), + react_native_xplat_dep("jsi:jsi"), + ], +) + +rn_xplat_cxx_library2( + name = "RCTCxxUtils", + srcs = glob([ + "React/CxxUtils/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "React/CxxUtils", + "*.h", + ), + ], + exclude = RCTCXXMODULE_PUBLIC_HEADERS.values(), + prefix = "React", + ), + apple_sdks = (IOS,), + contacts = ["oncall+react_native@xmail.facebook.com"], + fbobjc_enable_exceptions = True, + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + "//xplat/folly:molly", + ], +) + +rn_xplat_cxx_library2( + name = "RCTCxxLogUtils", + srcs = glob([ + "React/CxxLogUtils/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "React/CxxLogUtils", + "*.h", + ), + ], + prefix = "React", + ), + contacts = ["oncall+react_native@xmail.facebook.com"], + fbobjc_enable_exceptions = True, + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:ReactInternal", + react_native_xplat_target("logger:logger"), + ], +) + +RCTLIB_PATH = "Libraries/" + +RCTBASE_PATH = "React/Base/" + +RCTDEVSUPPORT_PATH = "React/DevSupport/" + +RCTMODULES_PATH = "React/Modules/" + +RCTVIEWS_PATH = "React/Views/" + +REACT_PUBLIC_HEADERS = { + "React/RCTAnimationType.h": RCTVIEWS_PATH + "RCTAnimationType.h", + "React/RCTAssert.h": RCTBASE_PATH + "RCTAssert.h", + "React/RCTAutoInsetsProtocol.h": RCTVIEWS_PATH + "RCTAutoInsetsProtocol.h", + "React/RCTBorderDrawing.h": RCTVIEWS_PATH + "RCTBorderDrawing.h", + "React/RCTBorderStyle.h": RCTVIEWS_PATH + "RCTBorderStyle.h", + "React/RCTBridge+Private.h": RCTBASE_PATH + "RCTBridge+Private.h", + "React/RCTBridge.h": RCTBASE_PATH + "RCTBridge.h", + "React/RCTBridgeDelegate.h": RCTBASE_PATH + "RCTBridgeDelegate.h", + "React/RCTBridgeMethod.h": RCTBASE_PATH + "RCTBridgeMethod.h", + "React/RCTBridgeModule.h": RCTBASE_PATH + "RCTBridgeModule.h", + "React/RCTBridgeModuleDecorator.h": RCTBASE_PATH + "RCTBridgeModuleDecorator.h", + "React/RCTBundleURLProvider.h": RCTBASE_PATH + "RCTBundleURLProvider.h", + "React/RCTComponent.h": RCTVIEWS_PATH + "RCTComponent.h", + "React/RCTComponentData.h": RCTVIEWS_PATH + "RCTComponentData.h", + "React/RCTComponentEvent.h": RCTBASE_PATH + "RCTComponentEvent.h", + "React/RCTConstants.h": RCTBASE_PATH + "RCTConstants.h", + "React/RCTConvert.h": RCTBASE_PATH + "RCTConvert.h", + "React/RCTCxxConvert.h": RCTBASE_PATH + "RCTCxxConvert.h", + "React/RCTDefines.h": RCTBASE_PATH + "RCTDefines.h", + "React/RCTDevLoadingViewProtocol.h": RCTDEVSUPPORT_PATH + "RCTDevLoadingViewProtocol.h", + "React/RCTDevLoadingViewSetEnabled.h": RCTDEVSUPPORT_PATH + "RCTDevLoadingViewSetEnabled.h", + "React/RCTDisplayLink.h": RCTBASE_PATH + "RCTDisplayLink.h", + "React/RCTErrorCustomizer.h": RCTBASE_PATH + "RCTErrorCustomizer.h", + "React/RCTErrorInfo.h": RCTBASE_PATH + "RCTErrorInfo.h", + # NOTE: RCTEventDispatcher.h is exported from CoreModules:CoreModulesApple + "React/RCTEventDispatcherProtocol.h": RCTBASE_PATH + "RCTEventDispatcherProtocol.h", + "React/RCTEventEmitter.h": RCTMODULES_PATH + "RCTEventEmitter.h", + "React/RCTFont.h": RCTVIEWS_PATH + "RCTFont.h", + "React/RCTFrameUpdate.h": RCTBASE_PATH + "RCTFrameUpdate.h", + "React/RCTI18nUtil.h": RCTMODULES_PATH + "RCTI18nUtil.h", + "React/RCTImageSource.h": RCTBASE_PATH + "RCTImageSource.h", + "React/RCTInitializing.h": RCTBASE_PATH + "RCTInitializing.h", + "React/RCTInspector.h": "React/Inspector/RCTInspector.h", + "React/RCTInspectorDevServerHelper.h": RCTDEVSUPPORT_PATH + "RCTInspectorDevServerHelper.h", + "React/RCTInspectorPackagerConnection.h": "React/Inspector/RCTInspectorPackagerConnection.h", + "React/RCTInvalidating.h": RCTBASE_PATH + "RCTInvalidating.h", + "React/RCTJSScriptLoaderModule.h": RCTBASE_PATH + "RCTJSScriptLoaderModule.h", + "React/RCTJSStackFrame.h": RCTBASE_PATH + "RCTJSStackFrame.h", + "React/RCTJSThread.h": RCTBASE_PATH + "RCTJSThread.h", + "React/RCTJavaScriptExecutor.h": RCTBASE_PATH + "RCTJavaScriptExecutor.h", + "React/RCTJavaScriptLoader.h": RCTBASE_PATH + "RCTJavaScriptLoader.h", + "React/RCTKeyCommands.h": RCTBASE_PATH + "RCTKeyCommands.h", + "React/RCTLayout.h": RCTVIEWS_PATH + "RCTLayout.h", + "React/RCTLayoutAnimation.h": RCTMODULES_PATH + "RCTLayoutAnimation.h", + "React/RCTLayoutAnimationGroup.h": RCTMODULES_PATH + "RCTLayoutAnimationGroup.h", + "React/RCTLog.h": RCTBASE_PATH + "RCTLog.h", + "React/RCTManagedPointer.h": RCTBASE_PATH + "RCTManagedPointer.h", + "React/RCTMockDef.h": RCTBASE_PATH + "RCTMockDef.h", + "React/RCTModalHostViewController.h": RCTVIEWS_PATH + "RCTModalHostViewController.h", + "React/RCTModalHostViewManager.h": RCTVIEWS_PATH + "RCTModalHostViewManager.h", + "React/RCTModalManager.h": RCTVIEWS_PATH + "RCTModalManager.h", + "React/RCTModuleData.h": RCTBASE_PATH + "RCTModuleData.h", + "React/RCTModuleMethod.h": RCTBASE_PATH + "RCTModuleMethod.h", + "React/RCTMultipartStreamReader.h": RCTBASE_PATH + "RCTMultipartStreamReader.h", + "React/RCTNullability.h": RCTBASE_PATH + "RCTNullability.h", + "React/RCTPLTag.h": RCTBASE_PATH + "RCTPLTag.h", + "React/RCTPackagerClient.h": RCTDEVSUPPORT_PATH + "RCTPackagerClient.h", + "React/RCTPackagerConnection.h": RCTDEVSUPPORT_PATH + "RCTPackagerConnection.h", + "React/RCTPerformanceLogger.h": RCTBASE_PATH + "RCTPerformanceLogger.h", + "React/RCTPerformanceLoggerLabels.h": RCTBASE_PATH + "RCTPerformanceLoggerLabels.h", + "React/RCTPointerEvents.h": RCTVIEWS_PATH + "RCTPointerEvents.h", + "React/RCTProfile.h": "React/Profiler/RCTProfile.h", + "React/RCTPushNotificationManager.h": RCTLIB_PATH + "PushNotificationIOS/RCTPushNotificationManager.h", + "React/RCTReconnectingWebSocket.h": RCTLIB_PATH + "WebSocket/RCTReconnectingWebSocket.h", + "React/RCTRedBoxExtraDataViewController.h": RCTMODULES_PATH + "RCTRedBoxExtraDataViewController.h", + "React/RCTRedBoxSetEnabled.h": RCTBASE_PATH + "RCTRedBoxSetEnabled.h", + "React/RCTRefreshableProtocol.h": RCTVIEWS_PATH + "RefreshControl/RCTRefreshableProtocol.h", + "React/RCTReloadCommand.h": RCTBASE_PATH + "RCTReloadCommand.h", + "React/RCTRootContentView.h": RCTBASE_PATH + "RCTRootContentView.h", + "React/RCTRootShadowView.h": RCTVIEWS_PATH + "RCTRootShadowView.h", + "React/RCTRootView.h": RCTBASE_PATH + "RCTRootView.h", + "React/RCTRootViewDelegate.h": RCTBASE_PATH + "RCTRootViewDelegate.h", + "React/RCTSRWebSocket.h": RCTLIB_PATH + "WebSocket/RCTSRWebSocket.h", + "React/RCTScrollEvent.h": RCTVIEWS_PATH + "ScrollView/RCTScrollEvent.h", + "React/RCTScrollView.h": RCTVIEWS_PATH + "ScrollView/RCTScrollView.h", + "React/RCTScrollableProtocol.h": RCTVIEWS_PATH + "ScrollView/RCTScrollableProtocol.h", + "React/RCTShadowView+Layout.h": RCTVIEWS_PATH + "RCTShadowView+Layout.h", + "React/RCTShadowView.h": RCTVIEWS_PATH + "RCTShadowView.h", + "React/RCTSurface.h": RCTBASE_PATH + "Surface/RCTSurface.h", + "React/RCTSurfaceDelegate.h": RCTBASE_PATH + "Surface/RCTSurfaceDelegate.h", + "React/RCTSurfaceHostingProxyRootView.h": RCTBASE_PATH + "Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h", + "React/RCTSurfaceHostingView.h": RCTBASE_PATH + "Surface/SurfaceHostingView/RCTSurfaceHostingView.h", + "React/RCTSurfacePresenterStub.h": RCTMODULES_PATH + "RCTSurfacePresenterStub.h", + "React/RCTSurfaceProtocol.h": RCTBASE_PATH + "Surface/RCTSurfaceProtocol.h", + "React/RCTSurfaceRootShadowView.h": RCTBASE_PATH + "Surface/RCTSurfaceRootShadowView.h", + "React/RCTSurfaceRootShadowViewDelegate.h": RCTBASE_PATH + "Surface/RCTSurfaceRootShadowViewDelegate.h", + "React/RCTSurfaceRootView.h": RCTBASE_PATH + "Surface/RCTSurfaceRootView.h", + "React/RCTSurfaceSizeMeasureMode.h": RCTBASE_PATH + "Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.h", + "React/RCTSurfaceStage.h": RCTBASE_PATH + "Surface/RCTSurfaceStage.h", + "React/RCTSurfaceView+Internal.h": RCTBASE_PATH + "Surface/RCTSurfaceView+Internal.h", + "React/RCTSurfaceView.h": RCTBASE_PATH + "Surface/RCTSurfaceView.h", + "React/RCTTextDecorationLineType.h": RCTVIEWS_PATH + "RCTTextDecorationLineType.h", + "React/RCTTouchHandler.h": RCTBASE_PATH + "RCTTouchHandler.h", + "React/RCTUIManager.h": RCTMODULES_PATH + "RCTUIManager.h", + "React/RCTUIManagerObserverCoordinator.h": RCTMODULES_PATH + "RCTUIManagerObserverCoordinator.h", + "React/RCTUIManagerUtils.h": RCTMODULES_PATH + "RCTUIManagerUtils.h", + "React/RCTUIUtils.h": "React/UIUtils/RCTUIUtils.h", + "React/RCTURLRequestDelegate.h": RCTBASE_PATH + "RCTURLRequestDelegate.h", + "React/RCTURLRequestHandler.h": RCTBASE_PATH + "RCTURLRequestHandler.h", + "React/RCTUtils.h": RCTBASE_PATH + "RCTUtils.h", + "React/RCTUtilsUIOverride.h": RCTBASE_PATH + "RCTUtilsUIOverride.h", + "React/RCTVersion.h": RCTBASE_PATH + "RCTVersion.h", + "React/RCTView.h": RCTVIEWS_PATH + "RCTView.h", + "React/RCTViewManager.h": RCTVIEWS_PATH + "RCTViewManager.h", + "React/RCTViewUtils.h": RCTVIEWS_PATH + "RCTViewUtils.h", + "React/RCTWeakProxy.h": RCTBASE_PATH + "RCTWeakProxy.h", + "React/RCTWrapperViewController.h": RCTVIEWS_PATH + "RCTWrapperViewController.h", + "React/UIView+React.h": RCTVIEWS_PATH + "UIView+React.h", +} + +REACT_COMPONENTVIEWS_BASE_FILES = [ + "React/Fabric/Mounting/ComponentViews/Image/*.mm", + "React/Fabric/RCTImageResponseObserverProxy.mm", + "React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm", +] + +rn_xplat_cxx_library2( + name = "ReactInternal", + srcs = glob( + [ + "React/Base/**/*.m", + "React/Base/**/*.mm", + "React/DevSupport/**/*.m", + "React/DevSupport/**/*.mm", + "React/Inspector/**/*.m", + "React/Inspector/**/*.mm", + "React/Modules/**/*.m", + "React/Modules/**/*.mm", + "React/Profiler/**/*.m", + "React/Profiler/**/*.mm", + "React/Profiler/**/*.S", + "React/UIUtils/*.m", + "React/Views/**/*.m", + "React/Views/**/*.mm", + "Libraries/ActionSheetIOS/*.m", + "Libraries/WebSocket/*.m", + ], + ), + headers = glob( + [ + "React/Base/**/*.h", + "React/DevSupport/**/*.h", + "React/Inspector/**/*.h", + "React/Modules/**/*.h", + "React/Profiler/**/*.h", + "React/Views/**/*.h", + "React/UIUtils/**/*.h", + "Libraries/ActionSheetIOS/*.h", + "Libraries/WebSocket/*.h", + ], + ), + header_namespace = "", + exported_headers = REACT_PUBLIC_HEADERS, + compiler_flags = [ + "-Wno-error=unguarded-availability-new", + "-Wno-unknown-warning-option", + "-Wno-global-constructors", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + exported_linker_flags = [ + "-weak_framework", + "UserNotifications", + "-weak_framework", + "WebKit", + ], + exported_preprocessor_flags = rn_extra_build_flags(), + fbobjc_enable_exceptions = True, + frameworks = [ + "$SDKROOT/System/Library/Frameworks/CFNetwork.framework", + "$SDKROOT/System/Library/Frameworks/CoreGraphics.framework", + "$SDKROOT/System/Library/Frameworks/CoreLocation.framework", + "$SDKROOT/System/Library/Frameworks/CoreText.framework", + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/MapKit.framework", + "$SDKROOT/System/Library/Frameworks/QuartzCore.framework", + "$SDKROOT/System/Library/Frameworks/Security.framework", + "$SDKROOT/System/Library/Frameworks/SystemConfiguration.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/UserNotifications.framework", + ], + labels = [ + "depslint_never_add", + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + platform_preprocessor_flags = [( + "linux", + ["-D PIC_MODIFIER=@PLT"], + )], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + [ + "-DHERMES_BYTECODE_VERSION={}".format(HERMES_BYTECODE_VERSION), + ] + rn_extra_build_flags(), + visibility = [ + "//fbobjc/Apps/Internal/SparkLabs/...", + "//fbobjc/Apps/Internal/Venice/...", + "//fbobjc/Apps/Wilde/FBMarketplaceModule/...", + "//fbobjc/Apps/Wilde/FBReactModule2/...", + "//fbobjc/Libraries/FBQPLMetadataProviders/...", + "//fbobjc/Libraries/FBReactKit/...", + "//fbobjc/Libraries/FBiOSSecurityUtils/...", + "//fbobjc/Libraries/RCTPrerendering/...", + "//fbobjc/VendorLib/react-native-maps:react-native-maps", + "//xplat/js:", + "//xplat/js/react-native-github/React/...", + "//xplat/js/react-native-github/ReactCommon/react/nativemodule/core:", + "//xplat/js/react-native-github/ReactCommon/react/nativemodule/samples:", + "//xplat/js/react-native-github/packages/rn-tester:", + "//xplat/rtc/manul/...", + ], + deps = [ + YOGA_CXX_TARGET, + react_native_xplat_target("cxxreact:bridge"), + react_native_xplat_target("reactperflogger:reactperflogger"), + ], +) + +rn_xplat_cxx_library2( + name = "RCTFabric", + srcs = glob( + [ + "React/Fabric/**/*.cpp", + "React/Fabric/**/*.m", + "React/Fabric/**/*.mm", + ], + exclude = glob(REACT_COMPONENTVIEWS_BASE_FILES), + ), + headers = glob( + [ + "React/Fabric/**/*.h", + ], + ), + header_namespace = "", + exported_headers = { + "React/RCTComponentViewDescriptor.h": "React/Fabric/Mounting/RCTComponentViewDescriptor.h", + "React/RCTComponentViewFactory.h": "React/Fabric/Mounting/RCTComponentViewFactory.h", + "React/RCTComponentViewRegistry.h": "React/Fabric/Mounting/RCTComponentViewRegistry.h", + "React/RCTFabricSurface.h": "React/Fabric/Surface/RCTFabricSurface.h", + "React/RCTFabricSurfaceHostingProxyRootView.h": "React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.h", + "React/RCTGenericDelegateSplitter.h": "React/Fabric/Utils/RCTGenericDelegateSplitter.h", + "React/RCTLegacyViewManagerInteropComponentView.h": "React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.h", + "React/RCTLocalizationProvider.h": "React/Fabric/RCTLocalizationProvider.h", + "React/RCTModalHostViewComponentView.h": "React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.h", + "React/RCTMountingManager.h": "React/Fabric/Mounting/RCTMountingManager.h", + "React/RCTMountingManagerDelegate.h": "React/Fabric/Mounting/RCTMountingManagerDelegate.h", + "React/RCTMountingTransactionObserving.h": "React/Fabric/Mounting/RCTMountingTransactionObserving.h", + "React/RCTParagraphComponentAccessibilityProvider.h": "React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.h", + "React/RCTParagraphComponentView.h": "React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.h", + "React/RCTPrimitives.h": "React/Fabric/RCTPrimitives.h", + "React/RCTRootComponentView.h": "React/Fabric/Mounting/ComponentViews/Root/RCTRootComponentView.h", + "React/RCTScheduler.h": "React/Fabric/RCTScheduler.h", + "React/RCTScrollViewComponentView.h": "React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h", + "React/RCTSurfacePresenter.h": "React/Fabric/RCTSurfacePresenter.h", + "React/RCTSurfacePresenterBridgeAdapter.h": "React/Fabric/RCTSurfacePresenterBridgeAdapter.h", + "React/RCTSurfaceRegistry.h": "React/Fabric/RCTSurfaceRegistry.h", + "React/RCTSurfaceTouchHandler.h": "React/Fabric/RCTSurfaceTouchHandler.h", + }, + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++17", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + fbobjc_enable_exceptions = True, + fbobjc_target_sdk_version = get_react_native_ios_target_sdk_version(), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/QuartzCore.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], + header_path_prefix = "React", + labels = [ + "disable_plugins_only_validation", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = [ + react_fabric_component_plugin_provider("SafeAreaView", "RCTSafeAreaViewCls"), + react_fabric_component_plugin_provider("ScrollView", "RCTScrollViewCls"), + react_fabric_component_plugin_provider("PullToRefreshView", "RCTPullToRefreshViewCls"), + react_fabric_component_plugin_provider("ActivityIndicatorView", "RCTActivityIndicatorViewCls"), + react_fabric_component_plugin_provider("Slider", "RCTSliderCls"), + react_fabric_component_plugin_provider("Switch", "RCTSwitchCls"), + react_fabric_component_plugin_provider("UnimplementedNativeView", "RCTUnimplementedNativeViewCls"), + react_fabric_component_plugin_provider("Paragraph", "RCTParagraphCls"), + react_fabric_component_plugin_provider("TextInput", "RCTTextInputCls"), + react_fabric_component_plugin_provider("InputAccessoryView", "RCTInputAccessoryCls"), + react_fabric_component_plugin_provider("View", "RCTViewCls"), + ], + plugins_header = "FBRCTFabricComponentsPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + [ + "-DWITH_FBSYSTRACE=1", + "-DLOG_TAG=\"ReactNative\"", + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ] + rn_extra_build_flags(), + tests = [ + ":MountingTests", + ":TextTests", + ], + visibility = ["PUBLIC"], + deps = [ + ":RCTFabricComponentViewsBase", + "//fbobjc/Libraries/FBReactKit/RCTFabricComponent/RCTFabricComponentPlugin:RCTFabricComponentPlugin", + "//xplat/js/react-native-github:RCTCxxBridge", + "//xplat/js/react-native-github:RCTCxxLogUtils", + "//xplat/js/react-native-github:RCTCxxUtils", + "//xplat/js/react-native-github:RCTImage", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:RCTText", + "//xplat/js/react-native-github:ReactInternal", + react_native_xplat_target("react/renderer/attributedstring:attributedstring"), + react_native_xplat_target("react/renderer/componentregistry:componentregistry"), + react_native_xplat_target("react/renderer/componentregistry/native:native"), + react_native_xplat_target("react/renderer/textlayoutmanager:textlayoutmanager"), + react_native_xplat_target("runtimeexecutor:runtimeexecutor"), + YOGA_CXX_TARGET, + react_native_xplat_target("react/config:config"), + react_native_xplat_target("cxxreact:bridge"), + ], + exported_deps = [ + react_native_xplat_target("react/renderer/animations:animations"), + react_native_xplat_target("react/renderer/components/scrollview:scrollview"), + react_native_xplat_target("react/renderer/components/slider:slider"), + react_native_xplat_target("react/renderer/components/safeareaview:safeareaview"), + react_native_xplat_target("react/renderer/components/modal:modal"), + react_native_xplat_target("react/renderer/components/unimplementedview:unimplementedview"), + react_native_xplat_target("react/renderer/components/text:text"), + react_native_xplat_target("react/renderer/components/legacyviewmanagerinterop:legacyviewmanagerinterop"), + react_native_xplat_target("react/renderer/components/textinput/iostextinput:iostextinput"), + react_native_xplat_target("react/renderer/components/inputaccessory:inputaccessory"), + react_native_xplat_target("react/renderer/core:core"), + react_native_xplat_target("react/renderer/debug:debug"), + react_native_xplat_target("react/renderer/scheduler:scheduler"), + react_native_xplat_target("react/renderer/uimanager:uimanager"), + "//xplat/js/react-native-github:generated_components-rncore", + ], +) + +rn_apple_library( + name = "RCTTypeSafety", + srcs = glob([ + "Libraries/TypeSafety/**/*.mm", + ]), + exported_headers = glob( + [ + "Libraries/TypeSafety/**/*.h", + ], + ), + autoglob = False, + complete_nullability = True, + contacts = ["oncall+react_native@xmail.facebook.com"], + extension_api_only = True, + frameworks = [ + "$PLATFORM_DIR/Developer/Library/Frameworks/Foundation.framework", + ], + inherited_buck_flags = get_static_library_ios_flags(), + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + reexport_all_header_dependencies = True, + deps = [ + ":ReactInternalApple", + "//xplat/folly:optionalApple", + "//xplat/js/react-native-github/Libraries/FBLazyVector:FBLazyVector", + ], +) + +yarn_workspace( + name = "yarn-workspace", + srcs = [ + "package.json", + ], + visibility = ["PUBLIC"], +) + +fb_apple_test( + name = "TextTestsApple", + srcs = ["React/Tests/Text/RCTParagraphComponentViewTests.mm"], + frameworks = [ + "$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework", + ], + oncall = "react_native", + deps = [ + ":RCTFabricApple", + react_native_xplat_target("react/renderer/element:elementApple"), + "//xplat/js/react-native-github:RCTFabricComponentViewsBaseApple", + "//xplat/js/react-native-github:RCTTextApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/attributedstring:attributedstringApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/componentregistry:componentregistryApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/components/legacyviewmanagerinterop:legacyviewmanagerinteropApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/components/text:textApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/components/textinput/iostextinput:iostextinputApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/scheduler:schedulerApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/textlayoutmanager:textlayoutmanagerApple", + "//xplat/js/react-native-github/ReactCommon/runtimeexecutor:runtimeexecutorApple", + ], +) + +fb_apple_test( + name = "MountingTestsApple", + srcs = ["React/Tests/Mounting/RCTComponentViewRegistryTests.mm"], + frameworks = [ + "$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework", + ], + oncall = "react_native", + deps = [ + ":ImageView", + ":RCTFabricApple", + "//xplat/js/react-native-github:RCTFabricComponentViewsBaseApple", + "//xplat/js/react-native-github:RCTTextApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/attributedstring:attributedstringApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/componentregistry:componentregistryApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/components/legacyviewmanagerinterop:legacyviewmanagerinteropApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/components/textinput/iostextinput:iostextinputApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/scheduler:schedulerApple", + "//xplat/js/react-native-github/ReactCommon/react/renderer/textlayoutmanager:textlayoutmanagerApple", + "//xplat/js/react-native-github/ReactCommon/runtimeexecutor:runtimeexecutorApple", + ], +) + +rn_apple_library( + name = "ImageView", + autoglob = False, + compiler_flags = ["-Wall"], + contacts = ["oncall+react_native@xmail.facebook.com"], + labels = [ + "disable_plugins_only_validation", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = [react_fabric_component_plugin_provider("Image", "RCTImageCls")], + visibility = ["PUBLIC"], + exported_deps = [ + ":RCTFabricComponentViewsBaseApple", + ], +) + +# Reduce the RCTFabric target by moving OSS RCTViewComponentViews here, so that +# eventually we can move all of React/Fabric/Mounting/ComponentViews/* here. +# Ideally, each component view gets its own target, and each target uses react_fabric_component_plugin_provider. +# For each component, an app can import the base component view, or an app-specific subclass. +# i.e. Apps depend on "ImageView" target for RCTImageComponentView.h, and "FBReactImageView" target for FBReactImageComponentView.h +rn_xplat_cxx_library2( + name = "RCTFabricComponentViewsBase", + srcs = glob(REACT_COMPONENTVIEWS_BASE_FILES), + header_namespace = "", + exported_headers = { + "React/RCTComponentViewProtocol.h": "React/Fabric/Mounting/RCTComponentViewProtocol.h", + "React/RCTConversions.h": "React/Fabric/RCTConversions.h", + "React/RCTImageComponentView.h": "React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.h", + "React/RCTImageResponseDelegate.h": "React/Fabric/RCTImageResponseDelegate.h", + "React/RCTImageResponseObserverProxy.h": "React/Fabric/RCTImageResponseObserverProxy.h", + "React/RCTTouchableComponentViewProtocol.h": "React/Fabric/RCTTouchableComponentViewProtocol.h", + "React/RCTViewComponentView.h": "React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h", + "React/UIView+ComponentViewProtocol.h": "React/Fabric/Mounting/UIView+ComponentViewProtocol.h", + }, + compiler_flags = ["-Wall"], + contacts = ["oncall+react_native@xmail.facebook.com"], + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:RCTImage", + "//xplat/js/react-native-github:RCTLinking", + react_native_xplat_target("react/renderer/imagemanager:imagemanager"), + react_native_xplat_target("react/renderer/components/image:image"), + react_native_xplat_target("react/renderer/components/view:view"), + react_native_xplat_target("react/renderer/componentregistry:componentregistry"), + ], +) + +rn_library( + name = "react-native", + srcs = [ + "package.json", + "index.js", + ] + glob( + [ + "Libraries/**/*.js", + "Libraries/NewAppScreen/**/*.png", + "Libraries/LogBox/**/*.png", + ], + exclude = [ + "**/__*__/**", + "**/gulpfile.js", + "Libraries/Components/Switch/SwitchSchema.js", + "**/*._reactvr.js", + ], + ), + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.core", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js:node_modules__abort_19controller", + "//xplat/js:node_modules__anser", + "//xplat/js:node_modules__base64_19js", + "//xplat/js:node_modules__event_19target_19shim", + "//xplat/js:node_modules__invariant", + "//xplat/js:node_modules__memoize_19one", + "//xplat/js:node_modules__nullthrows", + "//xplat/js:node_modules__pretty_19format", + "//xplat/js:node_modules__promise", + "//xplat/js:node_modules__react_19devtools_19core", + "//xplat/js:node_modules__react_19refresh", + "//xplat/js:node_modules__react_19shallow_19renderer", + "//xplat/js:node_modules__regenerator_19runtime", + "//xplat/js:node_modules__stacktrace_19parser", + "//xplat/js:node_modules__use_19sync_19external_19store", + "//xplat/js:node_modules__whatwg_19fetch", + "//xplat/js/RKJSModules/Libraries/Polyfills:Polyfills", + "//xplat/js/RKJSModules/vendor/react:react", + "//xplat/js/RKJSModules/vendor/react-test-renderer:react-test-renderer", + "//xplat/js/RKJSModules/vendor/scheduler:scheduler", + "//xplat/js/react-native-github/packages/assets:assets", + "//xplat/js/react-native-github/packages/normalize-color:normalize-color", + "//xplat/js/react-native-github/packages/polyfills:polyfills", + "//xplat/js/tools/metro/packages/metro-runtime/src/modules:modules", + "//xplat/js/tools/metro/packages/metro-runtime/src/polyfills:polyfills", + ], +) + +rn_codegen( + name = "FBReactNativeSpec", + android_package_name = "com.facebook.fbreact.specs", + codegen_modules = True, + ios_assume_nonnull = False, + library_labels = [ + "supermodule:xplat/default/public.react_native.infra", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + ], + native_module_spec_name = "FBReactNativeSpec", + src_prefix = "Libraries/", +) + +# TODO: Merge this into FBReactNativeSpec +rn_codegen( + name = "FBReactNativeComponentSpec", + codegen_components = True, + ios_assume_nonnull = False, + library_labels = [ + "supermodule:xplat/default/public.react_native.infra", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + ], + src_prefix = "Libraries/", +) + +rn_apple_library( + name = "RCTAnimationApple", + srcs = glob([ + "Libraries/NativeAnimation/**/*.m", + "Libraries/NativeAnimation/**/*.mm", + ]), + headers = glob( + [ + "Libraries/NativeAnimation/**/*.h", + ], + ), + header_namespace = "", + exported_headers = glob( + [ + "Libraries/NativeAnimation/*.h", + "Libraries/NativeAnimation/Drivers/*.h", + "Libraries/NativeAnimation/Nodes/*.h", + ], + ), + autoglob = False, + frameworks = [ + "Foundation", + "QuartzCore", + "UIKit", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "extension_api_allow_unsafe_unavailable_usages", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "NativeAnimatedModule", + native_class_func = "RCTNativeAnimatedModuleCls", + ) + react_module_plugin_providers( + name = "NativeAnimatedTurboModule", + native_class_func = "RCTNativeAnimatedTurboModuleCls", + ), + plugins_header = "FBRCTAnimationPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + ], +) + +rn_apple_library( + name = "RCTBlobApple", + srcs = glob([ + "Libraries/Blob/*.m", + "Libraries/Blob/*.mm", + ]), + headers = glob( + [ + "Libraries/Blob/*.h", + ], + ), + exported_headers = glob( + [ + "Libraries/Blob/*.h", + ], + ), + autoglob = False, + enable_exceptions = True, + frameworks = [ + "Foundation", + "UIKit", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "FileReaderModule", + native_class_func = "RCTFileReaderModuleCls", + ) + react_module_plugin_providers( + name = "BlobModule", + native_class_func = "RCTBlobManagerCls", + ) + [ + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "BlobModule", + ), + ], + plugins_header = "FBRCTBlobPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + ":RCTNetworkApple", + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + "//xplat/js/react-native-github/React/CoreModules:CoreModulesApple", + "//xplat/jsi:jsiApple", + ], +) + +rn_apple_library( + name = "RCTLinkingApple", + srcs = glob([ + "Libraries/LinkingIOS/*.m", + "Libraries/LinkingIOS/*.mm", + ]), + headers = glob( + [ + "Libraries/LinkingIOS/*.h", + ], + ), + exported_headers = glob( + [ + "Libraries/LinkingIOS/*.h", + ], + ), + autoglob = False, + enable_exceptions = True, + frameworks = [ + "Foundation", + "UIKit", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "extension_api_allow_unsafe_unavailable_usages", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "LinkingManager", + native_class_func = "RCTLinkingManagerCls", + ), + plugins_header = "FBRCTLinkingPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + "//xplat/jsi:jsiApple", + ], +) + +rn_apple_library( + name = "RCTPushNotificationApple", + srcs = glob([ + "Libraries/PushNotificationIOS/*.m", + "Libraries/PushNotificationIOS/*.mm", + ]), + headers = glob( + [ + "Libraries/PushNotificationIOS/*.h", + ], + ), + exported_headers = glob( + [ + "Libraries/PushNotificationIOS/*.h", + ], + ), + autoglob = False, + enable_exceptions = True, + frameworks = [ + "Foundation", + "UIKit", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "extension_api_allow_unsafe_unavailable_usages", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "PushNotificationManager", + native_class_func = "RCTPushNotificationManagerCls", + ), + plugins_header = "FBRCTPushNotificationPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:ReactInternalApple", + "//xplat/jsi:jsiApple", + ], +) + +rn_apple_library( + name = "RCTImageApple", + srcs = glob([ + "Libraries/Image/*.m", + "Libraries/Image/*.mm", + ]), + headers = glob( + [ + "Libraries/Image/*.h", + ], + ), + exported_headers = glob( + [ + "Libraries/Image/*.h", + ], + ), + autoglob = False, + frameworks = [ + "AVFoundation", + "Accelerate", + "CoreMedia", + "Foundation", + "ImageIO", + "MobileCoreServices", + "QuartzCore", + "UIKit", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "extension_api_allow_unsafe_unavailable_usages", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "GIFImageDecoder", + native_class_func = "RCTGIFImageDecoderCls", + ) + react_module_plugin_providers( + name = "ImageEditingManager", + native_class_func = "RCTImageEditingManagerCls", + ) + react_module_plugin_providers( + name = "ImageLoader", + native_class_func = "RCTImageLoaderCls", + ) + react_module_plugin_providers( + name = "ImageStoreManager", + native_class_func = "RCTImageStoreManagerCls", + ) + react_module_plugin_providers( + name = "LocalAssetImageLoader", + native_class_func = "RCTLocalAssetImageLoaderCls", + ) + [ + plugin( + RCT_IMAGE_DATA_DECODER_SOCKET, + name = "GIFImageDecoder", + ), + plugin( + RCT_IMAGE_URL_LOADER_SOCKET, + name = "LocalAssetImageLoader", + ), + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "ImageLoader", + ), + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "ImageStoreManager", + ), + ], + plugins_header = "FBRCTImagePlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + ":RCTNetworkApple", + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + ], +) + +RCTNETWORK_PUBLIC_HEADERS = [ + "Libraries/Network/RCTNetworkTask.h", + "Libraries/Network/RCTNetworking.h", +] + +rn_apple_library( + name = "RCTNetworkApple", + srcs = glob([ + "Libraries/Network/*.m", + "Libraries/Network/*.mm", + ]), + headers = glob( + [ + "Libraries/Network/*.h", + ], + exclude = RCTNETWORK_PUBLIC_HEADERS, + ), + exported_headers = RCTNETWORK_PUBLIC_HEADERS, + autoglob = False, + enable_exceptions = True, + frameworks = [ + "CoreTelephony", + "Foundation", + "MobileCoreServices", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + "extension_api_allow_unsafe_unavailable_usages", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = + react_module_plugin_providers( + name = "Networking", + native_class_func = "RCTNetworkingCls", + ) + react_module_plugin_providers( + name = "DataRequestHandler", + native_class_func = "RCTDataRequestHandlerCls", + ) + react_module_plugin_providers( + name = "FileRequestHandler", + native_class_func = "RCTFileRequestHandlerCls", + ) + react_module_plugin_providers( + name = "HTTPRequestHandler", + native_class_func = "RCTHTTPRequestHandlerCls", + ) + [ + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "DataRequestHandler", + ), + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "FileRequestHandler", + ), + plugin( + RCT_URL_REQUEST_HANDLER_SOCKET, + name = "HTTPRequestHandler", + ), + ], + plugins_header = "FBRCTNetworkPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + ], +) + +rn_apple_library( + name = "RCTSettingsApple", + srcs = glob([ + "Libraries/Settings/*.m", + "Libraries/Settings/*.mm", + ]), + exported_headers = glob( + [ + "Libraries/Settings/*.h", + ], + ), + autoglob = False, + frameworks = [ + "Foundation", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "disable_plugins_only_validation", + ], + plugins = react_module_plugin_providers( + name = "SettingsManager", + native_class_func = "RCTSettingsManagerCls", + ), + plugins_header = "FBRCTSettingsPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + ], +) + +rn_xplat_cxx_library2( + name = "RCTText", + srcs = glob([ + "Libraries/Text/**/*.m", + "Libraries/Text/**/*.mm", + ]), + headers = glob( + [ + "Libraries/Text/**/*.h", + ], + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "Libraries/Text", + "*.h", + ), + ( + "Libraries/Text/BaseText", + "*.h", + ), + ( + "Libraries/Text/RawText", + "*.h", + ), + ( + "Libraries/Text/Text", + "*.h", + ), + ( + "Libraries/Text/TextInput", + "*.h", + ), + ( + "Libraries/Text/TextInput/Multiline", + "*.h", + ), + ( + "Libraries/Text/TextInput/Singleline", + "*.h", + ), + ( + "Libraries/Text/VirtualText", + "*.h", + ), + ], + prefix = "React", + ), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:RCTLinking", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:ReactInternal", + YOGA_CXX_TARGET, + ], +) + +rn_apple_library( + name = "RCTVibrationApple", + srcs = glob([ + "Libraries/Vibration/**/*.m", + "Libraries/Vibration/**/*.mm", + ]), + exported_headers = glob( + [ + "Libraries/Vibration/*.h", + ], + ), + autoglob = False, + frameworks = [ + "AudioToolbox", + "Foundation", + ], + header_path_prefix = "React", + labels = [ + "depslint_never_remove", + "disable_plugins_only_validation", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + plugins = react_module_plugin_providers( + name = "Vibration", + native_class_func = "RCTVibrationCls", + ), + plugins_header = "FBRCTVibrationPlugins.h", + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [ + "-DRN_DISABLE_OSS_PLUGIN_HEADER", + ], + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:FBReactNativeSpecApple", + "//xplat/js/react-native-github:RCTLinkingApple", + "//xplat/js/react-native-github:RCTPushNotificationApple", + "//xplat/js/react-native-github:ReactInternalApple", + ], +) + +rn_xplat_cxx_library2( + name = "RCTWrapper", + srcs = glob([ + "Libraries/Wrapper/*.m", + "Libraries/Wrapper/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "Libraries/Wrapper", + "*.h", + ), + ], + prefix = "RCTWrapper", + ), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + labels = [ + "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + "//xplat/js/react-native-github:RCTLinking", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:ReactInternal", + ], +) + +rn_xplat_cxx_library2( + name = "RCTWrapperExample", + srcs = glob([ + "Libraries/Wrapper/Example/*.m", + "Libraries/Wrapper/Example/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "Libraries/Wrapper/Example", + "*.h", + ), + ], + prefix = "RCTWrapperExample", + ), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + ], + labels = [ + "depslint_never_remove", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + ":RCTWrapper", + "//xplat/js/react-native-github:RCTLinking", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:ReactInternal", + ], +) + +rn_xplat_cxx_library2( + name = "RCTSurfaceHostingComponent", + srcs = glob([ + "Libraries/SurfaceHostingComponent/**/*.m", + "Libraries/SurfaceHostingComponent/**/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "Libraries/SurfaceHostingComponent", + "*.h", + ), + ], + prefix = "RCTSurfaceHostingComponent", + ), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], + labels = [ + "depslint_never_remove", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + "//fbobjc/Libraries/MobileUI/ComponentKit:ComponentKit", + "//xplat/js/react-native-github:RCTFabric", + "//xplat/js/react-native-github:RCTLinking", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:ReactInternal", + ], +) + +rn_xplat_cxx_library2( + name = "RCTSurfaceBackedComponent", + srcs = glob([ + "Libraries/SurfaceBackedComponent/**/*.m", + "Libraries/SurfaceBackedComponent/**/*.mm", + ]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ( + "Libraries/SurfaceBackedComponent", + "*.h", + ), + ], + prefix = "RCTSurfaceBackedComponent", + ), + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], + labels = [ + "depslint_never_remove", + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + preprocessor_flags = get_objc_arc_preprocessor_flags() + get_preprocessor_flags_for_build_mode(), + visibility = ["PUBLIC"], + deps = [ + ":RCTSurfaceHostingComponent", + "//fbobjc/Libraries/MobileUI/ComponentKit:ComponentKit", + "//xplat/js/react-native-github:RCTFabric", + "//xplat/js/react-native-github:RCTLinking", + "//xplat/js/react-native-github:RCTPushNotification", + "//xplat/js/react-native-github:ReactInternal", + ], +) + +rn_xplat_cxx_library2( + name = "RCTMapView_RNHeader", + header_namespace = "", + exported_headers = { + "React/RCTConvert+CoreLocation.h": RCTVIEWS_PATH + "RCTConvert+CoreLocation.h", + }, + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.infra", + ], + visibility = [ + "//fbobjc/Libraries/FBReactKit:RCTMapView", + "//fbobjc/VendorLib/react-native-maps:react-native-maps", + ], +) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000000..255539250f8b6f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4509 @@ +# Changelog + +## v0.69.1 + +### Changed + +#### iOS specific + +- Make all Yoga headers public and add #ifdef __cplusplus ([43f831b23c](https://github.com/facebook/react-native/commit/43f831b23caf22e59af5c6d3fdd62fed3d20d4ec) by [@janicduplessis](https://github.com/janicduplessis)) + +### Fixed + +- Use monotonic clock for performance.now() ([114d31feee](https://github.com/facebook/react-native/commit/114d31feeeb47f5a57419e5088c3cbe9340f757a)) + +#### iOS specific + +- Fix build for React-RCTText ([4ea38e16bf](https://github.com/facebook/react-native/commit/4ea38e16bf533955557057656cba5346d2372acd) by [@ph4r05](https://github.com/ph4r05)) +- Fix RCT-Folly build error when use_frameworks! and hermes are both enabled ([79baca678a](https://github.com/facebook/react-native/commit/79baca678a743560fa16fdd551f1d0d018d34304) by [@Kudo](https://github.com/Kudo)) +- Fix use_frameworks! for 0.69 ([f97c6a5b49](https://github.com/facebook/react-native/commit/f97c6a5b498eec95e99a02c7842cb2ae160cd6cd) by [@Kudo](https://github.com/Kudo)) + +## v0.69.0 + +### Breaking + +- Support for `console.disableYellowBox` [has been dropped](https://github.com/facebook/react-native/commit/b633cc130533f0731b2577123282c4530e4f0abe) +- Already deprecated prop types have been removed ([cdfddb4dad](https://github.com/facebook/react-native/commit/cdfddb4dad7c69904850d7e5f089a32a1d3445d1), [3e229f27bc](https://github.com/facebook/react-native/commit/3e229f27bc9c7556876ff776abf70147289d544b), [10199b1581](https://github.com/facebook/react-native/commit/10199b158138b8645550b5579df87e654213fe42)) +- `removeListener`, deprecated since RN0.65, [was removed](https://github.com/facebook/react-native/commit/8dfbed786b40082a7a222e00dc0a621c0695697d) from Appearance +- If you were using `SegmentedComponentIOS`, you will now need to move to the [segmented-control](https://github.com/react-native-segmented-control/segmented-control) library ([235f168574](https://github.com/facebook/react-native/commit/235f1685748442553e53f8ec6d904bc0314a8ae6)) + +### Added + +- Add Hermes scripts to package ([004b8609d9](https://github.com/facebook/react-native/commit/004b8609d97b14a6d5cb8c9e63afdbe343c500da) by [@hramos](https://github.com/hramos)) +- Expose scheduler through FabricUIManager ([1730949e94](https://github.com/facebook/react-native/commit/1730949e94aa23927a90d2a64d91977b7e2904d6) by [@cortinico](https://github.com/cortinico)) +- Add event listeners to Scheduler ([e51e19ecc1](https://github.com/facebook/react-native/commit/e51e19ecc1d1b8ac5c860eac55338ef13471844f) by [@cortinico](https://github.com/cortinico)) +- C++ TurboModule methods can return functions ([c7380ba113](https://github.com/facebook/react-native/commit/c7380ba1131b26b487ecae87239a4cf82afefd15) by [@appden](https://github.com/appden)) +- Add support for devtools' profiler ([fefa7b6ac8](https://github.com/facebook/react-native/commit/fefa7b6ac8a1e0e33fa7a1070936c5c83c873c0a) by [@jpporto](https://github.com/jpporto)) +- Add getAll function to FormData class for getting all parts containing that key. This is also available in web API. ([d05a5d1551](https://github.com/facebook/react-native/commit/d05a5d15512ab794ef80b31ef91090d5d88b3fcd) by [@matinzd](https://github.com/matinzd)) +- Automatic type conversions for C++ TurboModules ([31f0796237](https://github.com/facebook/react-native/commit/31f079623732fb017b1fa38d56abe855d7738ece) by [@appden](https://github.com/appden)) +- New bridging API for JSI <-> C++ ([30cb78e709](https://github.com/facebook/react-native/commit/30cb78e709bccb4f7bf7aab3f6b0f1ba4261f577) by [@appden](https://github.com/appden)) +- Add asBool() method to JSI ([603620b739](https://github.com/facebook/react-native/commit/603620b7394da5855e2255790bfea9ad7d80ddf9) by [@appden](https://github.com/appden)) +- CustomEvent and Event polyfills for React Native ([6abbef1200](https://github.com/facebook/react-native/commit/6abbef1200af9adab1848de17955d77fbe0ad5da) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Implement Runtime.getHeapUsage for hermes chrome inspector ([cff9590864](https://github.com/facebook/react-native/commit/cff9590864c4be153a4eb49757b7cac8b3f23f66) by [@janicduplessis](https://github.com/janicduplessis)) +- Introduce ReactNativeFeatureFlags file to control FeatureFlags in React Native ([33aba77456](https://github.com/facebook/react-native/commit/33aba774564acdec216e02e28f17ad08ad7bc26b) by [@mdvacca](https://github.com/mdvacca)) +- Added fail-safe check to catch MissingWebViewPackage Exception ([8c573d9336](https://github.com/facebook/react-native/commit/8c573d933652ae4da1008502c53fce93057101c0) by [@Kunal-Airtel2022](https://github.com/Kunal-Airtel2022)) +- Add ability to access properties with symbol keys through JSI ([9010bfe457](https://github.com/facebook/react-native/commit/9010bfe457b77862024214ce6210504ff1786ef5) by [@neildhar](https://github.com/neildhar)) +- Allow color styles to be animated using native driver ([201f355479](https://github.com/facebook/react-native/commit/201f355479cafbcece3d9eb40a52bae003da3e5c) by [@genkikondo](https://github.com/genkikondo)) +- Make react-native depend on react-native-gradle-plugin ([3346efb7d4](https://github.com/facebook/react-native/commit/3346efb7d422bd8eb7f48650b454071f9981fa0b) by [@cortinico](https://github.com/cortinico)) +- Add RawEventTelemetryEventEmitter interface to ReactNativePrivateInterface ([1f15a64028](https://github.com/facebook/react-native/commit/1f15a6402869b001cae049facc17126924b97197) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Implement Runtime.getHeapUsage for hermes chrome inspector ([3568a72987](https://github.com/facebook/react-native/commit/3568a7298738a651d76c70763362c297ab601ee8) by [@janicduplessis](https://github.com/janicduplessis)) +- Add support for C++17 in OSS ([c2e4ae39b8](https://github.com/facebook/react-native/commit/c2e4ae39b8a5c6534a3fa4dae4130166eda15169) by [@sammy-SC](https://github.com/sammy-SC)) + +#### Android specific + +- Generate `Nullable` for optional objects and arrays in module codegen. ([ffaa5d69bc](https://github.com/facebook/react-native/commit/ffaa5d69bc268918891121e2d60e7ca08ee82530)) +- Expose an API to enable Concurrent Root on Android ([d7b64b8d4b](https://github.com/facebook/react-native/commit/d7b64b8d4b2f403ce00b27c5df89ffb3a64dc6de) by [@cortinico](https://github.com/cortinico)) +- Add scrollEventThrottle prop support in Android ([cf55fd587e](https://github.com/facebook/react-native/commit/cf55fd587e6cc82a73079be6076d244ab72fa359) by [@ryancat](https://github.com/ryancat)) +- Accessibility announcement for list and grid in FlatList ([dd6325bafe](https://github.com/facebook/react-native/commit/dd6325bafe1a539d348f3710e717a6344576b859) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Introduce ModuleDataCleaner.cleanDataFromModules(ReactContext) ([184dfb8f8b](https://github.com/facebook/react-native/commit/184dfb8f8bd4dfbb8d1575e9554e3f3361793015) by [@RSNara](https://github.com/RSNara)) +- Introduce ReactContext.getNativeModules() ([b978308519](https://github.com/facebook/react-native/commit/b978308519f71c6c7fda4b38a842aa219a349275) by [@RSNara](https://github.com/RSNara)) +- MapBuffer implementation for JVM -> C++ communication ([cf6f3b680b](https://github.com/facebook/react-native/commit/cf6f3b680b43fae31e97b14af681293503025a0c)) +- Make links independently focusable by Talkback ([7b5b114d57](https://github.com/facebook/react-native/commit/7b5b114d578142d18bf4a7a5279b179a9ac8d958) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Support animating text color with native driver ([87cdb607e4](https://github.com/facebook/react-native/commit/87cdb607e4792156d433c44b89932e7dae3371da) by [@genkikondo](https://github.com/genkikondo)) +- Added an experimental prop serialization path based on MapBuffer ([cbcdaae2b5](https://github.com/facebook/react-native/commit/cbcdaae2b5dda2a44c95d83dcb5b5aa0f43bc6f9)) +- Allow to setup a Gradle Enterprise instance via an external script ([f11dcfaea1](https://github.com/facebook/react-native/commit/f11dcfaea14249b059aea2474ce36a0665140d4f) by [@cortinico](https://github.com/cortinico)) +- Support platform color with AnimatedColor ([cb42049e0a](https://github.com/facebook/react-native/commit/cb42049e0ae262afe907ace1099414836ab0018d) by [@genkikondo](https://github.com/genkikondo)) +- Support running animations with AnimatedColor with native driver ([3f49e6763e](https://github.com/facebook/react-native/commit/3f49e6763e66447f6ae17dc2f032e27330b7b74a) by [@genkikondo](https://github.com/genkikondo)) +- Add public API to ReactRootView to control if JS touch events are dispatched ([0a517ae438](https://github.com/facebook/react-native/commit/0a517ae43892fb764d829f8bae56c1ac58356b1b) by [@ryancat](https://github.com/ryancat)) + +#### iOS specific + +- Prepare a method in the AppDelegate to control the concurrentRoot. ([8ac8439e0d](https://github.com/facebook/react-native/commit/8ac8439e0dcc0cc4a9c0cc99f614a5e19bae56eb) by [@cipolleschi](https://github.com/cipolleschi)) +- `hotkeysEnabled` property is added to `RCTDevMenu` which allows enabling/disabling hotkeys that triggers developer menu popup ([1a1a304ed2](https://github.com/facebook/react-native/commit/1a1a304ed2023d60547aef65b1a7bf56467edf08)) +- Allow modifying iOS image cache limits ([61b013e7ad](https://github.com/facebook/react-native/commit/61b013e7ad8a1cc28ee39434d2fd96b74b96cf5f) by [@danilobuerger](https://github.com/danilobuerger)) +- Add dismissActionSheet method to ActionSheetIOS ([64ebe5bbdd](https://github.com/facebook/react-native/commit/64ebe5bbdd32fc3b3a243a8a81a6f724d8f81267) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- Integrated the `accessibilityLanguage` prop to all the available components. The prop is available for any platform but it will work only on iOS. ([7b05b091fd](https://github.com/facebook/react-native/commit/7b05b091fd97f95b778369277ac2147730abc7b8) by [@dgopsq](https://github.com/dgopsq)) +- Support running animations with AnimatedColor with native driver ([49f3f47b1e](https://github.com/facebook/react-native/commit/49f3f47b1e9b840e4374d46b105604f4d2c22dd5) by [@genkikondo](https://github.com/genkikondo)) + +### Changed + +- Update direct Metro dependencies to 0.70.1 ([b74e964e70](https://github.com/facebook/react-native/commit/b74e964e705c40834acad7020562e870cdad9db1), ([c92b64b16a](https://github.com/facebook/react-native/commit/c92b64b16a5710c1dfaea9af4c271931e4669636) by [@arushikesarwani94](https://github.com/arushikesarwani94)), ([f89a0b765c](https://github.com/facebook/react-native/commit/f89a0b765c09c9aba573f03777cc76673989628f) by [@robhogan](https://github.com/robhogan)) +- Upgrade RN CLI to v8.0.0 ([0605880c9e](https://github.com/facebook/react-native/commit/0605880c9ed0aec812f3198eb5075db64fba969a), [1e0226f933](https://github.com/facebook/react-native/commit/1e0226f933814bf9ada87eaa14348bfff863ead1), [24bb7f7380](https://github.com/facebook/react-native/commit/24bb7f7380662925f078d78a03fbc954af2da3d6), [7dceb9b63c](https://github.com/facebook/react-native/commit/7dceb9b63c0bfd5b13bf6d26f9530729506e9097) by [@thymikee](https://github.com/thymikee)) +- Replace use-subscripton with use-sync-external-store ([93b50be8c2](https://github.com/facebook/react-native/commit/93b50be8c2341a0daf41f6fdc656896c4907c4dc) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Expose UIManager from Scheduler ([54db5f2012](https://github.com/facebook/react-native/commit/54db5f201292ebf267800d92b7dd5bfa22431963) by [@cortinico](https://github.com/cortinico)) +- Optimized VirtualizedList context when used with nested lists ([ceb0a54608](https://github.com/facebook/react-native/commit/ceb0a546083509192c059cdd93d6aa379e38ef4e) by [@javache](https://github.com/javache)) +- Remove usage of std::string in EarlyJsErrorHandler. ([30051b2c41](https://github.com/facebook/react-native/commit/30051b2c4185bff015c72069488b5f6ba3391ad7) by [@sshic](https://github.com/sshic)) +- `eslint-config`: add support for ESLint 8 ([864a8c11b2](https://github.com/facebook/react-native/commit/864a8c11b2a7540f607ebc0e084edd7393169359) by [@wcandillon](https://github.com/wcandillon)) +- `eslint-config`: add support for TypeScript 4.5+ ([199ac680c7](https://github.com/facebook/react-native/commit/199ac680c7867a982e25620219bffa18f85f5404) by [@rnike](https://github.com/rnike)) +- Upgraded react-devtools-core dependency to 4.24.0 ([a7a781ff4a](https://github.com/facebook/react-native/commit/a7a781ff4a13e744f4eb3007ef0657740b277a72)) +- Avoid flattening nodes with event props ([980c52de41](https://github.com/facebook/react-native/commit/980c52de41258f6cf2d2360144ea7ca16a19c9f8)) +- Type the argument of Animated.interpolate as read-only ([6584304c10](https://github.com/facebook/react-native/commit/6584304c100ce4d51a5c4d606170a6ad0dc00875) by [@motiz88](https://github.com/motiz88)) +- Update gradle-download-task to 5.0.1 to support concurrent downloads ([a86cae7aac](https://github.com/facebook/react-native/commit/a86cae7aacc9837536e7d679870a57dcd0f45475) by [@michel-kraemer](https://github.com/michel-kraemer)) +- Logging a soft error when ReactRootView has an id other than -1 instead of crashing the app in hybrid apps ([1ca2c24930](https://github.com/facebook/react-native/commit/1ca2c2493027c1b027146cd41e17dd8a4fc33a41) by [@Kunal-Airtel2022](https://github.com/Kunal-Airtel2022)) + +#### Android specific + +- Gradle: extend the algoritm to find hermesc paths ([aeac6ab677](https://github.com/facebook/react-native/commit/aeac6ab6773cd2c0ca7abe9e5aa3f22fa81683e5) by [@cortinico](https://github.com/cortinico)) +- Bump boost for Android to 1.76 to align with iOS ([5cd6367f0b](https://github.com/facebook/react-native/commit/5cd6367f0b86543274a15bb6d0e53a8545fed845) by [@kelset](https://github.com/kelset)) +- Adopt `MapBuffer` interface for `ReadableMapBuffer` ([81e4249315](https://github.com/facebook/react-native/commit/81e42493158edd5e7b88f98c19c87e9d61ba4aba)) +- Mark intent as nullable ([5ffa0b0aa6](https://github.com/facebook/react-native/commit/5ffa0b0aa6c523234c634167be1f94b0d9edb0f7) by [@sshic](https://github.com/sshic)) +- Use CMake to build ReactAndroid module ([e3830ddffd](https://github.com/facebook/react-native/commit/e3830ddffd9260fe071e0c9f9df40b379d54cf26)) +- Update template/android and RN Tester to use `hermes-engine` from the `react-native` NPM package. ([4d91f40fbd](https://github.com/facebook/react-native/commit/4d91f40fbdf0012689b04084113299676342c0dc) by [@cortinico](https://github.com/cortinico)) +- Build Hermes from Source ([a3d9892ed9](https://github.com/facebook/react-native/commit/a3d9892ed9c993d16fa36fa6b713e2ead43fcc77) by [@cortinico](https://github.com/cortinico)) +- Rename field with default values for ReactConfig to DEFAULT_CONFIG ([964e816752](https://github.com/facebook/react-native/commit/964e81675286c80a8e322127aa7c052f62621098)) +- Moved `com/react/facebook/uimanager/interfaces` files into `com/react/facebook/uimanager` to enable Kotlin build ([b1a779392d](https://github.com/facebook/react-native/commit/b1a779392d483c649d428debfe4a6405247b8c0e)) +- Bump AGP to 7.1.0 and fix bundle inclusion in release mode ([200488e87c](https://github.com/facebook/react-native/commit/200488e87cf4bc355e03c78cd814b97b23452117) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- Release react-native-gradle-plugin 0.0.5 ([42272211e4](https://github.com/facebook/react-native/commit/42272211e4a1b7cff7770b59cf1bcf649cbdd6fc) by [@cortinico](https://github.com/cortinico)) +- ViewPagerAndroid recommendation link. ([7e8cce3d2d](https://github.com/facebook/react-native/commit/7e8cce3d2ddffbe36bcb3c9ec2f006f7e1b42a79) by [@maaxg](https://github.com/maaxg)) +- Bump android Appcompat to 1.4.1 ([6b61995647](https://github.com/facebook/react-native/commit/6b61995647c789a567845521fed7b0cc1e0cddb7) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- Remove `react-native-gradle-plugin` as a dependency from template's package.json ([cd79317672](https://github.com/facebook/react-native/commit/cd79317672e5c99636346f2abb641a688a4ceb82) by [@cortinico](https://github.com/cortinico)) +- Use 2g as a default heap size for gradle builds ([09e418ef8e](https://github.com/facebook/react-native/commit/09e418ef8e98fd026cf828696ff2475993b76ac2)) +- Use new StatusBar API on Android 11 (API 30)+ ([50c8e973f0](https://github.com/facebook/react-native/commit/50c8e973f067d4ef1fc3c2eddd360a0709828968) by [@ieatfood](https://github.com/ieatfood)) +- Change static string to public ([ab45138394](https://github.com/facebook/react-native/commit/ab45138394f41aeb13370882837968636de04c24) by [@sshic](https://github.com/sshic)) + +#### iOS specific + +- Use pre-built HermesC if available in current React Native release ([644fe430fd](https://github.com/facebook/react-native/commit/644fe430fdecc0bf1fa098d1c2d52178da6c987c) by [@hramos](https://github.com/hramos)) +- When building Hermes from source, the filesystem will now be prepared using the new hermes-utils.js scripts, outside of CocoaPods ([aaa01f7710](https://github.com/facebook/react-native/commit/aaa01f77106f891696d9ec508e2ee71111a6af2a) by [@hramos](https://github.com/hramos)) +- Expose scheduler through RCTSurfacePresenter ([614aa86916](https://github.com/facebook/react-native/commit/614aa86916394d8ee2ecb236f38de6bb7e161ca2) by [@cortinico](https://github.com/cortinico)) +- Adopt UIGraphicsImageRenderer API ([d70d7fd0b3](https://github.com/facebook/react-native/commit/d70d7fd0b3984ee54622afc4692a6c945618c345) by [@matrush](https://github.com/matrush)) +- Build Hermes from source when Hermes is used ([bb01b75637](https://github.com/facebook/react-native/commit/bb01b75637edc1159a3bdb3af86936e1c92f39c1) by [@hramos](https://github.com/hramos)) +- Update CodeGen scripts to accept custom node executable ([323db75c36](https://github.com/facebook/react-native/commit/323db75c36d26d771f6b231c8eabc5afc0da74d3) by [@cipolleschi](https://github.com/cipolleschi)) +- Fixed the fallback behavior when the `.xcode.env` file is missing, actually using the old `find-node-for-xcode.sh` script ([705c6f57d6](https://github.com/facebook/react-native/commit/705c6f57d66b4499f43489292183a58413402a74) by [@cipolleschi](https://github.com/cipolleschi)) +- Adding a link in a message for the users. ([2c52131f5e](https://github.com/facebook/react-native/commit/2c52131f5e0eb4668681242fcdd8150afe3c5827) by [@cipolleschi](https://github.com/cipolleschi)) +- Bump ruby to 2.7.5 ([2c87b7466e](https://github.com/facebook/react-native/commit/2c87b7466e098c5cd230e02b279fc7bc7a357615) by [@danilobuerger](https://github.com/danilobuerger)) +- This PR removes the `find-node.sh` scripts and replaces it with an `.xcode.env` file that is sourced by the script phases that needs it. The `.xcode.env` file is versioned: to customize a local environment, an unversioned `.xcode.local.env` can be used. ([0480f56c5b](https://github.com/facebook/react-native/commit/0480f56c5b5478b6ebe5ad88e347cad2810bfb17) by [@cipolleschi](https://github.com/cipolleschi)) +- Update `PushNotificationIOS.checkPermissions` to include iOS 10+ notification settings. ([17ecd2fb5b](https://github.com/facebook/react-native/commit/17ecd2fb5b3cfb8aa0282ed406b16dc3b9777018)) +- Enable SonarKit in React-Core when the configuration is `'Debug'` ([b5343a6b0d](https://github.com/facebook/react-native/commit/b5343a6b0dd07c1b4ef9dac549df67a4d68ebd1e) by [@cipolleschi](https://github.com/cipolleschi)) +- When Hermes is enabled, the Hermes Engine will be built from source instead of using the pre-built `hermes-engine` CocoaPod. ([12ad1fffe8](https://github.com/facebook/react-native/commit/12ad1fffe87c0c5ab2e001f318ff4f8d3eda7479) by [@hramos](https://github.com/hramos)) +- Replaced folly::Optional with std::optional from C++17 in Objc module generator. ([45e2941367](https://github.com/facebook/react-native/commit/45e2941367fbf13584193bbda598173802289167) by [@philIip](https://github.com/philIip)) +- Removed methodName parameter that was used only for a warning message and moved the warning parameter to be calculated inline. ([cfb11ca2f6](https://github.com/facebook/react-native/commit/cfb11ca2f67c59c090b8a58b2b7bdaacef0e19df)) +- Fix the crash caused by nil partialLoadHandler ([46bc521513](https://github.com/facebook/react-native/commit/46bc521513c9c78e5ffc49cf3e571757e1a91cef)) +- Synchronously render cached images ([189c2c8958](https://github.com/facebook/react-native/commit/189c2c8958442541c6b4f42860b2943ece612da2)) +- Updated Flipper-Glog to 0.5.0.4 ([cd60ffdb62](https://github.com/facebook/react-native/commit/cd60ffdb62b2183cd24baef3075d56f758cea24a)) +- Add function to report early js errors ([1804951595](https://github.com/facebook/react-native/commit/180495159517dc0bfa103621e5ff62fc04cb3c8b) by [@sshic](https://github.com/sshic)) + +### Deprecated + +- Deprecate the use of `react-native/jest/preprocessor.js` by external projects ([c1e9aa9a27](https://github.com/facebook/react-native/commit/c1e9aa9a272aed3cba60c4aeff783eeb8bffce68) by [@motiz88](https://github.com/motiz88)) +- Deprecate the Promise.prototype.done method and log a warning when it's called in development. ([35800962c1](https://github.com/facebook/react-native/commit/35800962c16a33eb8e9ff1adfd428cf00bb670d3) by [@motiz88](https://github.com/motiz88)) + +#### iOS specific + +- Deprecating support for iOS/tvOS SDK 11.0, 12.4+ is now required ([5f2835b14d](https://github.com/facebook/react-native/commit/5f2835b14d35681c268dd64d6ec284ea5f053be3), ([c71e6efbcd](https://github.com/facebook/react-native/commit/c71e6efbcd2b95faee327d9763d321488120bc5e), ([982ca30de0](https://github.com/facebook/react-native/commit/982ca30de079d7e80bd0b50365d58b9048fb628f) by [@philIip](https://github.com/philIip)) + +#### iOS specific + +- Removed lint restricting `DynamicColorIOS` to only two properties ([13b0b06522](https://github.com/facebook/react-native/commit/13b0b0652259ada468cc044b0b604edb666b2eb9)) + +### Fixed + +- Remove unactionable warning about `codegenNativeComponent` when on 'Paper' ([494b73cb33](https://github.com/facebook/react-native/commit/494b73cb33197fa865e9ead8fdca11bce6822917) by [@tido64](https://github.com/tido64)) +- Fix typo in Value's constructor with a Symbol ([a7a0f86a73](https://github.com/facebook/react-native/commit/a7a0f86a73ab51be31fb2c3205612d7ff1fb5384) by [@jpporto](https://github.com/jpporto)) +- Avoid full copy of large folly::dynamic objects by switching to std::move semantics ([3f98c8e4c2](https://github.com/facebook/react-native/commit/3f98c8e4c2c8f40b81c1a90aa65c1bdc9327faed) by [@NikoAri](https://github.com/NikoAri)) +- Fix performance issue on Animated.interpolate with big input range ([f503b21203](https://github.com/facebook/react-native/commit/f503b212039f79f00ea56b65ecf3cd150b82f087) by [@Almouro](https://github.com/Almouro)) +- Update function spacing linting rules ([8650220cf9](https://github.com/facebook/react-native/commit/8650220cf99739c4b904a37ce4f19ce7dfd3bdbb) by [@joeframbach](https://github.com/joeframbach)) +- Add supportsFromJs and supportsToJs template variables ([087624ccaf](https://github.com/facebook/react-native/commit/087624ccaf2e484c0b6425e57edf9afd62a06e9a) by [@appden](https://github.com/appden)) +- The Array appended to FormData is transmitted as a string ([d2e8e7d58e](https://github.com/facebook/react-native/commit/d2e8e7d58e680e0bb3b4da1f820dd4dd840639f5) by [@bang9](https://github.com/bang9)) +- AppState.removeEventListener correctly removes listener for blur and focus events ([9aab25ec53](https://github.com/facebook/react-native/commit/9aab25ec536473ffe6d22c5efeae8fea6bd769be) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) +- `focus()` on TextInput to respect its `editable` state ([8a5460ce80](https://github.com/facebook/react-native/commit/8a5460ce80e69c11a007121d4278d55642f6b10e) by [@vonovak](https://github.com/vonovak)) +- Restore Windows build with RawPropsParser.cpp ([2d64d1d693](https://github.com/facebook/react-native/commit/2d64d1d69360161c047c86a026403d8074ba28bb) by [@TatianaKapos](https://github.com/TatianaKapos)) +- Fix babel-plugin-codegen crash when export init is null ([ae756647c9](https://github.com/facebook/react-native/commit/ae756647c9b8a88ba615fd30185f621825a33427) by [@janicduplessis](https://github.com/janicduplessis)) +- Fixed compilation warning due to `using namespace` being used as part of header ([009d80bf5a](https://github.com/facebook/react-native/commit/009d80bf5a55dd74be448960b1344ac7599c6bae) by [@arhelmus](https://github.com/arhelmus)) +- Allow including TurboModule.h in mixed rtti/no-rtti environment, even if TurboModule.h/cpp is compiled without RTTI. ([1f87729697](https://github.com/facebook/react-native/commit/1f87729697370a4ab31e2bb9ab1780438d9e146f) by [@nlutsenko](https://github.com/nlutsenko)) +- Remove prettier from dependencies in eslint-config ([2db1bca952](https://github.com/facebook/react-native/commit/2db1bca95224ce39484c3f27508aec9a21ce126a) by [@Kerumen](https://github.com/Kerumen)) +- Switch Component doesn't disable click functionality when disabled ([b2e625a517](https://github.com/facebook/react-native/commit/b2e625a51723becea4cef0433448fbec679669ee) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Support numeric color values in StyleSheet's Flow types ([83b1975b90](https://github.com/facebook/react-native/commit/83b1975b90569a36020da33156615a13fcc7ba92) by [@motiz88](https://github.com/motiz88)) +- Fix build break on Windows with ReactCommon ([42b391775f](https://github.com/facebook/react-native/commit/42b391775f663df335f6f2553104fc2fa35b1bee) by [@chiaramooney](https://github.com/chiaramooney)) +- Fixed opacity value in TouchableOpacity ([3eddc9abb7](https://github.com/facebook/react-native/commit/3eddc9abb70eb54209c68aab7dbd69e363cc7b29) by [@hetanthakkar1](https://github.com/hetanthakkar1)) +- Remove illegal private property access in VirtualizedSectionList.scrollToLocation ([b2f871a6fa](https://github.com/facebook/react-native/commit/b2f871a6fa9c92dd0712055384b9eca6d828e37d) by [@motiz88](https://github.com/motiz88)) +- JS animated node value updates properly when listener is attached ([1f778014a7](https://github.com/facebook/react-native/commit/1f778014a7e95c5c473898c38d5b1e0725cd373c) by [@genkikondo](https://github.com/genkikondo)) +- Working around Long paths limitation on Windows ([7b76abc0d3](https://github.com/facebook/react-native/commit/7b76abc0d3a0a5bec37f314c80954e412fc5f5ec) by [@mganandraj](https://github.com/mganandraj)) +- Fix VirtualizedList with initialScrollIndex not rendering all elements when data is updated ([c5c17985da](https://github.com/facebook/react-native/commit/c5c17985dae402725abb8a3a94ccedc515428711) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) + +#### Android specific + +- Add back hermes inspector support ([6b6adcc111](https://github.com/facebook/react-native/commit/6b6adcc111123bec2c4c110070b2506385e74664) by [@Kudo](https://github.com/Kudo)) +- Fixed issue where any node with an AccessibilityDelegate set (which was any node with any accessibility propoerty), was using ExploreByTouchHelper's built in AccessibilityNodeProvider, and not properly populating their AccessibilityNodeInfo's, leading to focus issues and issues with automated test services like UIAutomator. ([70fcab76a4](https://github.com/facebook/react-native/commit/70fcab76a4dcf65e628ac897620fe050758574e3) by [@blavalla](https://github.com/blavalla)) +- Fix Extras usage in Android implementation of Linking.sendIntent() ([86f8d0bb52](https://github.com/facebook/react-native/commit/86f8d0bb528a75777c357ae214643ed58c326ca9)) +- Fix typo in gradle plugin deprecation message ([41cfd2f976](https://github.com/facebook/react-native/commit/41cfd2f9768e4742eedd299ab467d316d016705e) by [@mikehardy](https://github.com/mikehardy)) +- Fixed `TimingModule` related functions for headless JS tasks, eg. `setTimeout` ([dac56ce077](https://github.com/facebook/react-native/commit/dac56ce0776e0e4d23ed4f8b324f2e2432aefa6a) by [@marcesengel](https://github.com/marcesengel)) +- Improve support for Android users on M1 machine ([c5babd993a](https://github.com/facebook/react-native/commit/c5babd993a2bed2994ecc4710fa9e424b3e6cfc2) by [@cortinico](https://github.com/cortinico)) +- Do not use `rootProject` directly in Gradle scripts ([b2bc5aa5c9](https://github.com/facebook/react-native/commit/b2bc5aa5c903ad057a53d4caa82b0fe74e01c07c) by [@cortinico](https://github.com/cortinico)) +- Adding null check for context in redbox surface delegate ([9527ab1584](https://github.com/facebook/react-native/commit/9527ab1584869d7966c562e8aa7cbf48788156a3) by [@ryancat](https://github.com/ryancat)) +- Fix crash on empty snapToOffsets array ([145fd041c7](https://github.com/facebook/react-native/commit/145fd041c7afe9a18f08f461487bb515ab2f516a) by [@ryancat](https://github.com/ryancat)) +- Fix StatusBar not updating to use translucent values when set to the same value across different activities ([d34a75e9e5](https://github.com/facebook/react-native/commit/d34a75e9e5932adcac4a16f5b815bb909c3aa0dd)) +- Fix underlineColorAndroid transparent not working on API 21 ([52aee50a70](https://github.com/facebook/react-native/commit/52aee50a704bbeab91f5fa05fe3220dee304422f) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fixed regression on content in scroll view not responding to touch when fling got interrupted ([bb8ff9260f](https://github.com/facebook/react-native/commit/bb8ff9260fe6a783171f35ce1a459927d8179d08) by [@ryancat](https://github.com/ryancat)) +- Fixes android build error when compiling as library ([c34ef5841c](https://github.com/facebook/react-native/commit/c34ef5841cf3a63a9cc96add577d6bf6d52e4397) by [@nickfujita](https://github.com/nickfujita)) +- Cancel post touch process when new touch is received ([0368081858](https://github.com/facebook/react-native/commit/0368081858193d7c2537acd9080d11bb701ee98b) by [@ryancat](https://github.com/ryancat)) +- Improve rendering of images when resampled and corner radius applied ([f743bed657](https://github.com/facebook/react-native/commit/f743bed657591b078300a6519e3d68db542fd757) by [@javache](https://github.com/javache)) +- Fix transform when calculate overflowInset ([0975e96d53](https://github.com/facebook/react-native/commit/0975e96d53546ac05b2154352fe56e5d82e2a1f8) by [@ryancat](https://github.com/ryancat)) +- Fix ReactHorizontalScrollView contentOffset ([9f6f97151c](https://github.com/facebook/react-native/commit/9f6f97151c44a0f727c9dd938222be1860ecf3f9) by [@genkikondo](https://github.com/genkikondo)) +- Text Component does not announce disabled and disables click functionality when disabled ([7b2d8178b1](https://github.com/facebook/react-native/commit/7b2d8178b155f5f1b247614c46e5e20f31bbd438) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix StatusBar on Android API 30 ([9ed2df628d](https://github.com/facebook/react-native/commit/9ed2df628ddd410cc3383e68b0386471432445c0) by [@ieatfood](https://github.com/ieatfood)) +- Use root locale when converting string case. ([5341ad8962](https://github.com/facebook/react-native/commit/5341ad896245c40a00b6faead1b90d01aac58f8c) by [@halaei](https://github.com/halaei)) +- Fix DarkMode on Calendar DateTimePicker ([97064ae1fb](https://github.com/facebook/react-native/commit/97064ae1fbf84a8a6b653c02c5872191b7d2d622) by [@mdvacca](https://github.com/mdvacca)) +- Fix ScrollView contentOffset ([be260b9f47](https://github.com/facebook/react-native/commit/be260b9f479a3b55ee43d2959d2c49fd3c1eb4ac) by [@genkikondo](https://github.com/genkikondo)) +- Do not bundle libhermes.so or libjsc.so inside the React Native Android AAR ([fa85417179](https://github.com/facebook/react-native/commit/fa854171798e67b8a10820f77d7198315e1784ed) by [@cortinico](https://github.com/cortinico)) +- Enable hitSlop to be set using a single number. ([d682753244](https://github.com/facebook/react-native/commit/d682753244feba28c6a15c31966a3da075a090e6) by [@javache](https://github.com/javache)) +- Fix crash caused by Image.queryCache parsing null ([ae3d4f7008](https://github.com/facebook/react-native/commit/ae3d4f700843ae4cbb6927ee620095136d1abc3f) by [@skychx](https://github.com/skychx)) +- Fix NullPointerException when disaptching events ([fbeb51ef51](https://github.com/facebook/react-native/commit/fbeb51ef5133303a5cb71569507d44403ded3447) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- ScrollView's contentInsetAdjustmentBehavior is reset to Never at every reuse to avoid layout artifacts. ([28a65f4387](https://github.com/facebook/react-native/commit/28a65f438789c29309d6e7c58063a73ca721ef43)) +- Prevent Nullptr segfault in TurboModule init path ([7f3cc256b5](https://github.com/facebook/react-native/commit/7f3cc256b5bcbf2e64540ca69401f62ec6869f0e) by [@RSNara](https://github.com/RSNara)) +- Expose the extraData dict attached to JavaScript errors to the native ExceptionManager on iOS, similar to Android ([a65ae8eff6](https://github.com/facebook/react-native/commit/a65ae8eff6ec6f9ad283ac8e96f00802421a14da) by [@GijsWeterings](https://github.com/GijsWeterings)) +- `RCTLocalizationProvider` Fall back to input when no localization is available ([18196512db](https://github.com/facebook/react-native/commit/18196512db6b8b4469a5e1b098d8892ae72d743a) by [@robhogan](https://github.com/robhogan)) +- Update iOS LogBox to render its UIWindow with the key window's UIWindowScene ([d31d83f410](https://github.com/facebook/react-native/commit/d31d83f4109c167ec612058c805fd65f69b82476) by [@vincentriemer](https://github.com/vincentriemer)) +- Remove Gemfile.lock from template ([1907bd31f0](https://github.com/facebook/react-native/commit/1907bd31f066865aa1c5fe4ec88e98ee46448771) by [@danilobuerger](https://github.com/danilobuerger)) +- Fix `pod install` when `RCT-Folly` version has been updated. ([b2517c3bdc](https://github.com/facebook/react-native/commit/b2517c3bdccc3f9d935f4ee06f959d6ce8f27bbe) by [@fortmarek](https://github.com/fortmarek)) +- Fix usage of cocoapods with --project-directory flag and new arch ([2f813f873a](https://github.com/facebook/react-native/commit/2f813f873a1692044ea3461e59ca732a4d952300) by [@danilobuerger](https://github.com/danilobuerger)) +- Ensure LogBoxView is sized relative to the key window instead of the full screen ([84f8c9ad55](https://github.com/facebook/react-native/commit/84f8c9ad550f98295d2e718b4b1d6b1ac724b898) by [@vincentriemer](https://github.com/vincentriemer)) +- Improved template fastlane gitignore ([f43f05d292](https://github.com/facebook/react-native/commit/f43f05d292fd2fbdf3d5fdfd194ed81b0e346657) by [@danilobuerger](https://github.com/danilobuerger)) +- Set RCTView borderColor to UIColor ([267d36d0af](https://github.com/facebook/react-native/commit/267d36d0afb4b3713df9b679c2019c44ac6bcc3f) by [@danilobuerger](https://github.com/danilobuerger)) +- Fix action sheet callback invoked more than once on iPad ([8935d6e697](https://github.com/facebook/react-native/commit/8935d6e697dffb0971f5a8ee1dfbc580080de3e0) by [@janicduplessis](https://github.com/janicduplessis)) +- Resolve border platform color based on current trait collection ([9a35818797](https://github.com/facebook/react-native/commit/9a3581879764f3f1b2743905e3e54611e96bb618) by [@danilobuerger](https://github.com/danilobuerger)) +- Enable custom sound for local push notifications. ([eb19499484](https://github.com/facebook/react-native/commit/eb1949948406195c4c02c6041d07cba074ae820c)) +- Invoke registerForRemoteNotifications on main UI thread. ([3633a05299](https://github.com/facebook/react-native/commit/3633a05299d99b12acc5c3c056b977463df1924e)) +- Bump flipper pods to get arm64 catalyst slice ([f811da7cc2](https://github.com/facebook/react-native/commit/f811da7cc20cc49ca5c8d4e023d6c61e36e15dd1) by [@fortmarek](https://github.com/fortmarek)) +- Fix `pod install --project-directory=ios` failing when Hermes is enabled ([1b22e8a039](https://github.com/facebook/react-native/commit/1b22e8a039081887ffd450596d822bff975d6900), ([eb7cc85a91](https://github.com/facebook/react-native/commit/eb7cc85a9146d058694247178f03d57cc125c97a) by [@tido64](https://github.com/tido64)) +- Fix compilation warning in yoga ([52d8a797e7](https://github.com/facebook/react-native/commit/52d8a797e7a6be3fa472f323ceca4814a28ef596) by [@cuva](https://github.com/cuva)) +- Prevent deadlock when dispatching events from observers on the same thread. ([68fd1e5508](https://github.com/facebook/react-native/commit/68fd1e55085e871a854563721ee29ca698239607) by [@Pickleboyonline](https://github.com/Pickleboyonline)) +- In RCTSurfaceHostingComponent, access ckComponent from main queue to pass assertion ([1874c81003](https://github.com/facebook/react-native/commit/1874c81003b468554c227541fec5e29c4adfb82f) by [@p-sun](https://github.com/p-sun)) +- Fix modal redbox for onDismiss ([46f68aceb2](https://github.com/facebook/react-native/commit/46f68aceb20a10c95c92b5ffeb90f289b015a559) by [@HeyImChris](https://github.com/HeyImChris)) +- Attempt to fix crash during app termination ([9cd43340a7](https://github.com/facebook/react-native/commit/9cd43340a7e2443564c2ff5e8e85d37f6e1e47ef) by [@sammy-SC](https://github.com/sammy-SC)) + +### Security + +- Encode URL params in URLSearchParams.toString() ([1042a8012f](https://github.com/facebook/react-native/commit/1042a8012fb472bd5c882b469fe507dd6279d562) by [@sshic](https://github.com/sshic)) + + +## v0.68.2 + +### Changed + +- Bump used version of react-native-codegen to 0.0.17 ([dfda480a98](https://github.com/facebook/react-native/commit/dfda480a9888d95c542cea40f25e8e783565c1db) by [@cortinico](https://github.com/cortinico)) +- Bump react-native-codegen to 0.0.17 ([a5ddc2e165](https://github.com/facebook/react-native/commit/a5ddc2e16523ea336ffbecf7acfd4820469a29e7) by [@cortinico](https://github.com/cortinico)) + +### Fixed + +#### Android specific + +- Working around Long paths limitation on Windows ([62ef6f5fa1](https://github.com/facebook/react-native/commit/62ef6f5fa1ecb918bde130a6024b65afcd34c7e3) by [@mganandraj](https://github.com/mganandraj)) + +## v0.68.1 + +### Changed + +#### Android specific + +- Bump React Native Gradle plugin to 0.0.6 ([9573d7b84d](https://github.com/facebook/react-native/commit/9573d7b84d35233fbb39a4067cfef65490aa34a7) by [@cortinico](https://github.com/cortinico)) +- Don't require yarn for codegen tasks ([d5da70e17e](https://github.com/facebook/react-native/commit/d5da70e17e8c8210cd79a4d7b09c6a5ded4b5607) by [@danilobuerger](https://github.com/danilobuerger)) + +### Fixed + +- Fix dynamic_cast (RTTI) by adding key function to ShadowNodeWrapper and related classes ([58a2eb7f37](https://github.com/facebook/react-native/commit/58a2eb7f37c2dc27ad3575618778ad5b23599b27) by [@kmagiera](https://github.com/kmagiera)) +- Pin use-subscription to < 1.6.0 ([5534634892](https://github.com/facebook/react-native/commit/5534634892f47a3890e58b661faa2260373acb25) by [@danilobuerger](https://github.com/danilobuerger)) + +#### Android specific + +- Use NDK 23 only for Windows users. ([e48a580080](https://github.com/facebook/react-native/commit/e48a580080bdae58b375f30fbcf8a83cc1915b2f) by [@cortinico](https://github.com/cortinico)) +- Improve support for Android users on M1 machine ([4befd2a29c](https://github.com/facebook/react-native/commit/4befd2a29cb94b026d9c048a041aa9f1817295b5) by [@cortinico](https://github.com/cortinico)) +- Template: Specify abiFilters if enableSeparateBuildPerCPUArchitecture is not set. ([5dff920177](https://github.com/facebook/react-native/commit/5dff920177220ae5f4e37c662c63c27ebf696c83) by [@cortinico](https://github.com/cortinico)) +- Fix for building new architecture sources on Windows ([5a8033df98](https://github.com/facebook/react-native/commit/5a8033df98296c941b0a57e49f2349e252339bf9) by [@mganandraj](https://github.com/mganandraj)) + +## v0.68.0 + +### Breaking Changes + +- CI moved to Node 16. ([f1488db109](https://github.com/facebook/react-native/commit/f1488db109d13e748b071c02b40e90cdca1cc79d) by [@kelset](https://github.com/kelset)). + This change enforces Node >= 14 for React Native builds. +- Bump Android Gradle Plugin to 7.0.1. ([272cfe5d13](https://github.com/facebook/react-native/commit/272cfe5d1371c38a281cf3883ff0254a8d3505a3) by [@dulmandakh](https://github.com/dulmandakh)) + This version of Android Gradle plugin enforces JDK 11 for Android builds. Do not upgrade to AGP 7.1 as it is not supported by this version of react-native. +- Removed `fallbackResource` from `RCTBundleURLProvider` API ([0912ee179c](https://github.com/facebook/react-native/commit/0912ee179c210fb6b2ed9afbb3f2fbc5fb8a2bb3)) by [@philIip](https://github.com/philIip) + +### New Architecture + +*If you are interested in enabling the new architecture, please refer to [the dedicated documentation](https://reactnative.dev/docs/next/new-architecture-intro).* + +- Do not include Facebook license on users codegen'd code ([450967938a](https://github.com/facebook/react-native/commit/450967938ab25c4dabb9d5ecd9f7b57afb1c78dd) by [@cortinico](https://github.com/cortinico)) + +#### Android specific + +- Setup a `newArchEnabled` property to Opt-in the New Architecture in the template ([8d652fba4c](https://github.com/facebook/react-native/commit/8d652fba4ce07256784a1b7e86713c810336856d) by [@cortinico](https://github.com/cortinico)) + +#### iOS specific + +- Add fabric option to the default app template. ([2e9a376c84](https://github.com/facebook/react-native/commit/2e9a376c8488d1fb11c0b5d604137712321fd90d) by [@sota000](https://github.com/sota000)) +- Add turbo module support in the default app template. ([8ec0e6919c](https://github.com/facebook/react-native/commit/8ec0e6919c5fab118c8b54538860ee36009bfaa7) by [@sota000](https://github.com/sota000)) +- Rename the new architecture flag to RCT_NEW_ARCH_ENABLED. ([c0c5439959e](https://github.com/facebook/react-native/commit/c0c5439959e21d7806178bb9139c2cd19b857506) by [@sota000](https://github.com/sota000)) + +### Added + +- Create @fb-tools-support/yarn package ([7db294d6d5](https://github.com/facebook/react-native/commit/7db294d6d5b00a38f305dd52be3e0961f35695c8) by [@motiz88](https://github.com/motiz88)) +- Support string color values in Animated.Color ([d3a0c4129d](https://github.com/facebook/react-native/commit/d3a0c4129d6a5a7beced4e9aa62b2da4e3f4fed4)) +- New Animated.Color node ([ea90a76efe](https://github.com/facebook/react-native/commit/ea90a76efef60df0f46d29228289f8fc1d26f350)) +- Added linter warning config for unstable nested components ([988fefc44d](https://github.com/facebook/react-native/commit/988fefc44d39957e8c5e1eecb02dfd1ce119f34c) by [@javache](https://github.com/javache)) +- Option to supply `platformConfig` to NativeAnimated ([4a227ce2ab](https://github.com/facebook/react-native/commit/4a227ce2ab3f8c181150461ab28b831979093db0) by [@rozele](https://github.com/rozele)) +- Animated.event can be used to extract values with numeric keys from native events ([b2105711a0](https://github.com/facebook/react-native/commit/b2105711a0b90859f8e3fc1aaec4998e252c2d14) by [@javache](https://github.com/javache)) +- Adds a setSelection imperative method to TextInput ([771ca921b5](https://github.com/facebook/react-native/commit/771ca921b59cc3b3fd12c8fe3b08ed150bcf7a04) by [@lyahdav](https://github.com/lyahdav)) +- Native-only prop to optimize text hit testing on some RN platforms ([f3bf2e4f51](https://github.com/facebook/react-native/commit/f3bf2e4f51897f1bb71e37002c288ebf3b23cf78) by [@rozele](https://github.com/rozele)) + +#### Android specific + +- Added DoNotStripAny proguard rules ([48318b1542](https://github.com/facebook/react-native/commit/48318b1542910b939ab977c0bc82e98f098abe50) by [@ShikaSD](https://github.com/ShikaSD)) +- Add new API in ScrollView and HorizontalScrollView to process pointerEvents prop. ([48f6967ae8](https://github.com/facebook/react-native/commit/48f6967ae88100110160e1faf03e6c0d37e404bd) by [@ryancat](https://github.com/ryancat)) +- Add `accessibilityLabelledBy` props ([36037fa81b](https://github.com/facebook/react-native/commit/36037fa81bbdcc460057e7e7cf608cd364ca48a6) by [@grgr-dkrk](https://github.com/grgr-dkrk)) +- Added missing constructor to WritableNativeArray ([c68c47d2ba](https://github.com/facebook/react-native/commit/c68c47d2bafa8e8e25b534d6cdd1a63bc77a1cf4) by [@piaskowyk](https://github.com/piaskowyk)) +- Add new API for custom fling animator to provide predicted travel distance for its fling animation. ([fe6277a30d](https://github.com/facebook/react-native/commit/fe6277a30d3ec19e4772991e30ae20c3a9cfe565) by [@ryancat](https://github.com/ryancat)) +- Adding new API `onChildEndedNativeGesture` to the RootView interface to let its implementations notify the JS side that a child gesture is ended. ([9b33c31ee0](https://github.com/facebook/react-native/commit/9b33c31ee024bae30e441107f838e1b5044525ba) by [@ryancat](https://github.com/ryancat)) +- Make the `reactNativeArchitectures` property more discoverable ([0f39a1076d](https://github.com/facebook/react-native/commit/0f39a1076dc154995a2db79352adc36452f46210) by [@cortinico](https://github.com/cortinico)) +- Added `isAccessibilityServiceEnabled` to get if accessibility services are enabled ([c8b83d4e0b](https://github.com/facebook/react-native/commit/c8b83d4e0b33c2af45093f7b2262ee578ece2faf) by [@grgr-dkrk](https://github.com/grgr-dkrk)) +- Add bundleForVariant option ([d2c10da5d5](https://github.com/facebook/react-native/commit/d2c10da5d5687833545691f281473381e4466c2e) by [@grit96](https://github.com/grit96)) +- Add ACCEPT_HANDOVER, ACTIVITY_RECOGNITION, ANSWER_PHONE_CALLS, READ_PHONE_NUMBERS & UWB_RANGING to PermissionsAndroid ([4b25a0aaa0](https://github.com/facebook/react-native/commit/4b25a0aaa077caf9c437bcfeef8a226eda5a102e) by [@iBotPeaches](https://github.com/iBotPeaches)) + +#### iOS specific + +- Add new argument to announceForAccessibility to allow queueing on iOS ([4d1357918a](https://github.com/facebook/react-native/commit/4d1357918a4dcb331ccea2140699f487ca45ea30) by [@peterc1731](https://github.com/peterc1731)) +- Add volta support to find-node.sh ([765844055b](https://github.com/facebook/react-native/commit/765844055ba0d02262a11114bad5da67e935d8df) by [@liamjones](https://github.com/liamjones)) +- Support fnm when detecting node binary ([c9e4d34885](https://github.com/facebook/react-native/commit/c9e4d3488578d65e55198ad597252a2ac8cc5f73) by [@MoOx](https://github.com/MoOx)) +- Find-node.sh now respects .nvmrc ([35bcf934b1](https://github.com/facebook/react-native/commit/35bcf934b186e581d100d43e563044300759557f) by [@igrayson](https://github.com/igrayson)) +- Add macros to be able to stub C functions in tests ([749a9207b6](https://github.com/facebook/react-native/commit/749a9207b6f0545c03ca83efbda7971ffd4d2d57) by [@philIip](https://github.com/philIip)) + + +### Changed + +- Bump RN CLI to v7.0.3, and Metro to 67 ([848ba6fb1d](https://github.com/facebook/react-native/commit/848ba6fb1db81bbb44efd373af9e81f31f227aef) by [@kelset](https://github.com/kelset)) and ([df2e934a69](https://github.com/facebook/react-native/commit/df2e934a697b5b207053db3bbcf71492932a6062) by [@kelset](https://github.com/kelset)) +- Upgraded react-devtools-core dependency to 4.23.0 ([1cc217d5ef](https://github.com/facebook/react-native/commit/1cc217d5effdbee4cf2f64063a443ecb331673d4) by [@bvaughn](https://github.com/bvaughn)) +- Bump Flipper to 0.125.0 ([50057158ca](https://github.com/facebook/react-native/commit/50057158ca32842d70160541e3cb5d4bd512f8f5) by [@cortinico](https://github.com/cortinico)) +- Export Flow type for deceleration rate for use in other files to keep deceleration rate prop values consistently typed ([9b0ed920ef](https://github.com/facebook/react-native/commit/9b0ed920ef087c4c18504adacf9d4f557812cf1b)) +- Upgrade deprecated-react-native-prop-types dependency ([badd30885f](https://github.com/facebook/react-native/commit/badd30885fb999124b6b54b3fb016edbd988c16b) by [@chiaramooney](https://github.com/chiaramooney)) +- Improved error message in react.gradle ([7366a866b3](https://github.com/facebook/react-native/commit/7366a866b381db6fc5615153e7788aa4828cfd96) by [@vonovak](https://github.com/vonovak)) +- Upgraded packages to the latest versions for ESLint v7. ([cf763cdf81](https://github.com/facebook/react-native/commit/cf763cdf816e1cad20caf2347c54bc96c7f6dd47) by [@yungsters](https://github.com/yungsters)) +- Updated the links for the discussions and changelog ([daf37a1fce](https://github.com/facebook/react-native/commit/daf37a1fce43403e6320e1e3023e86fd1b970bdf) by [@MikeyAlmighty](https://github.com/MikeyAlmighty)) +- XMLHttpRequest.getAllResponseHeaders() now returns headers with names lowercased and sorted in ascending order, as per specification ([b2415c4866](https://github.com/facebook/react-native/commit/b2415c48669391ee1ab7c6450748c4d91097a666) by [@ascherkus](https://github.com/ascherkus)) +- Bump react-native-codegen to 0.0.9 ([e3a71b019f](https://github.com/facebook/react-native/commit/e3a71b019fa78e2b4b3454ccc59ea9c8cc543b29) by [@cortinico](https://github.com/cortinico)) +- Accessing `Image.propTypes`, `Text.propTypes`, `TextInput.propTypes`, `ColorPropType`, `EdgeInsetsPropType`, `PointPropType`, or `ViewPropTypes` now emits a deprecation warning. ([3f629049ba](https://github.com/facebook/react-native/commit/3f629049ba9773793978cf9093c7a71af15e3e8d) by [@yungsters](https://github.com/yungsters)) +- Bump `core-workflow-apply-version-label` version ([e973b3afc2](https://github.com/facebook/react-native/commit/e973b3afc274f892a0e5a6fdea9004dc5d84eb2b) by [@lucasbento](https://github.com/lucasbento)) +- Add `vendor/bundle` into .gitignore template ([2f67f5d68b](https://github.com/facebook/react-native/commit/2f67f5d68b17010c49f2996a788fe68c1fe2e9f6) by [@MoOx](https://github.com/MoOx)) + +#### Android specific + +- Add allowsEdgeAntialiasing on views with rotations or skew transforms ([e6a3410afe](https://github.com/facebook/react-native/commit/e6a3410afe7d9a4cecf3db0a95503d2ff05bb862)) +- Bump Kotlin version to 1.6.10 ([d0f0234656](https://github.com/facebook/react-native/commit/d0f0234656dc981b422d1e9aa0885afd5fd29879) by [@AKB48](https://github.com/AKB48)) +- Bump Soloader to 0.10.3 ([f45889ef95](https://github.com/facebook/react-native/commit/f45889ef95ec694520e91b0032e591a087e088e5) by [@osartun](https://github.com/osartun)) +- Bump Gradle to 7.3 ([c180627ac7](https://github.com/facebook/react-native/commit/c180627ac7e5e155707b3c9433c4582839e1820e) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Android compile and target SDK to 31 ([00ac034353](https://github.com/facebook/react-native/commit/00ac034353cbc867991bf79cb1dd103353f47126) by [@ShikaSD](https://github.com/ShikaSD)) +- Use side-by-side NDK for Android ([bd7caa64f5](https://github.com/facebook/react-native/commit/bd7caa64f5d6ee5ea9484e92c3629c9ce711f73c) by [@cortinico](https://github.com/cortinico)) +- Leverage Gradle implicit dependency substitution for Gradle Plugin ([0fccbd53af](https://github.com/facebook/react-native/commit/0fccbd53af86083a8742a33282dc183d07eb27a2) by [@cortinico](https://github.com/cortinico)) +- Remove unused import of JMessageQueueThread.h ([705236e363](https://github.com/facebook/react-native/commit/705236e3637e4f80e5fa4bd7234df9f1e14a5d3d) by [@sshic](https://github.com/sshic)) +- Made `MessageQueueThread#runOnQueue` return a boolean. Made `MessageQueueThreadImpl#runOnQueue` return false when the runnable is not submitted. ([89faf0c9a8](https://github.com/facebook/react-native/commit/89faf0c9a87f6de68ca416d10566dbcbe80d9450)) +- Assume *.ktx assets are packaged as Android drawables ([cb610ddca7](https://github.com/facebook/react-native/commit/cb610ddca79fe29b88568545ab011671fc392c9a) by [@motiz88](https://github.com/motiz88)) +- Add ViewConfigs to support onEnter/onExit/onMove events ([44143b50fd](https://github.com/facebook/react-native/commit/44143b50fdcafe22caa43d76ec3210132ce3af21) by [@mdvacca](https://github.com/mdvacca)) +- Let react_native_assert really abort the app ([2ae06df58f](https://github.com/facebook/react-native/commit/2ae06df58f5f5eaf4386c14d28af25b643401bf3) by [@cortinico](https://github.com/cortinico)) +- Bugfix for multiple shadow threads rendered at the same time, small probability crash. ([9d71b166a6](https://github.com/facebook/react-native/commit/9d71b166a6c9d9afec7186c6a33aedc6975aa43c) by [@chenmo187](https://github.com/chenmo187)) +- RootView's onChildStartedNativeGesture now takes the child view as its first argument ([03e513de41](https://github.com/facebook/react-native/commit/03e513de41bf60f071eacbbb9604c83605abf625) by [@javache](https://github.com/javache)) +- Add ReactInstanceEventListenerV2 for migration ([ce74aa4ed3](https://github.com/facebook/react-native/commit/ce74aa4ed335d4c36ce722d47937b582045e05c4) by [@sshic](https://github.com/sshic)) +- Improved logic of findTargetPathAndCoordinatesForTouch ([dfe42d6b75](https://github.com/facebook/react-native/commit/dfe42d6b75005f519c0e2c87c75e7886dce3345c) by [@javache](https://github.com/javache)) +- Set a resolution strategy for com.facebook.react:react-native when on New Architecture ([e695bc0bb5](https://github.com/facebook/react-native/commit/e695bc0bb50fc1c712e9862ed8fe4e7cc6619fae) by [@cortinico](https://github.com/cortinico)) +- Make hermes-executor-common a static lib ([b2cf24f41c](https://github.com/facebook/react-native/commit/b2cf24f41cb5f15653b34d396ef2a1c90defdf43) by [@janicduplessis](https://github.com/janicduplessis)) +- Static link for hermes-inspector ([20b0eba581](https://github.com/facebook/react-native/commit/20b0eba581a00e5e7e300f6377379b836617c147) by [@janicduplessis](https://github.com/janicduplessis)) + +#### iOS specific + +- Don't capitalize the first letter of a word that is starting by a number ([8b5a5d4645](https://github.com/facebook/react-native/commit/8b5a5d4645136ef3d6ee043348e583cbbac87ee3) by [@MaeIg](https://github.com/MaeIg)) +- updated `jsBundleURLForBundleRoot:fallbackResource` to `jsBundleURLForBundleRoot:` ([aef843bfe6](https://github.com/facebook/react-native/commit/aef843bfe60bda6bcc98d3fb4a6c295c9f4b66e3) by [@philIip](https://github.com/philIip)) +- Remove iOS 11 availability check ([9b059b6709](https://github.com/facebook/react-native/commit/9b059b67092f4e7d568867a2b3a51dfd7c6f1db6) by [@ken0nek](https://github.com/ken0nek)) +- Refactor: Assign string label to each case in RCTPLTag enum for startup performance logging ([60e60a9b3d](https://github.com/facebook/react-native/commit/60e60a9b3d42d342eaf5ddee4841b121f6474a6c) by [@p-sun](https://github.com/p-sun)) +- IOS Ruby Updates ([1e6add1a43](https://github.com/facebook/react-native/commit/1e6add1a43355bb88c57400a7420a656966bef97) by [@barbieri](https://github.com/barbieri)) +- Update Flipper pods to support re-enable macCatalyst ([2a5265dff7](https://github.com/facebook/react-native/commit/2a5265dff7e654f57b43335804840692313f2a56) by [@mikehardy](https://github.com/mikehardy)) +- Apple Silicon builds of glog & Flipper-Glog ([274c617f5b](https://github.com/facebook/react-native/commit/274c617f5bda263ff29115b3dcc013e47085a78d) by [@rayzr522](https://github.com/rayzr522)) + +### Fixed + +- Fix error "mockModal is not a function" ([507b05f4c0](https://github.com/facebook/react-native/commit/507b05f4c02b46109f483a2b79c924a775fd7bd3) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) +- Fixes execution of animation when a toValue of AnimatedValue is used. ([8858c21124](https://github.com/facebook/react-native/commit/8858c2112421be5212c024f9e404e66437a41389)) +- Fix RN version syntax to match new nightly build structure. ([3d1d4ee457](https://github.com/facebook/react-native/commit/3d1d4ee4572600425b8eb5d0d6512bb0d2a6ea44) by [@chiaramooney](https://github.com/chiaramooney)) +- Fix typo in _updateBottomIfNecessary function on KeyboardAvoidingView component ([0cc80b4d0c](https://github.com/facebook/react-native/commit/0cc80b4d0cb78a835977dbe5100262a16882bbea) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- Fix: Removes interface only check from third party components GenerateThirdPartyFabricComponentsProvider ([3e6902244a](https://github.com/facebook/react-native/commit/3e6902244a0d189261dbbe327296db1349e37410) by [@Ubax](https://github.com/Ubax)) +- Set CxxModules' Instance before retrieving their Method vector. ([1d45b20b6c](https://github.com/facebook/react-native/commit/1d45b20b6c6ba66df0485cdb9be36463d96cf182) by [@JunielKatarn](https://github.com/JunielKatarn)) +- AnimatedValue.__detach should store getValue result with offset deducted ([fe53cae954](https://github.com/facebook/react-native/commit/fe53cae954b37528eeaa1258ac0060c4298473bb) by [@rozele](https://github.com/rozele)) +- AnimatedValue.stopAnimation callback with correct value for NativeAnimated ([8ba771c3dd](https://github.com/facebook/react-native/commit/8ba771c3ddc00b1499e95a2812b4cd5ac904c8df) by [@rozele](https://github.com/rozele)) +- ESLint no-undef rule clashing with TypeScript compiler for TS files ([ae67c5ac45](https://github.com/facebook/react-native/commit/ae67c5ac45a8044fc1db66aee8eae6e881d660e4) by [@fiznool](https://github.com/fiznool)) +- ESLint `no-shadow` rule returning false positive for TypeScript enums ([722a0ff6f8](https://github.com/facebook/react-native/commit/722a0ff6f88bed4d54579a2b8bc574e87948187f) by [@fiznool](https://github.com/fiznool)) +- Fix support for custom port ([b399c2e3d1](https://github.com/facebook/react-native/commit/b399c2e3d10fa521dbec87243d3e96f6bca7df1e) by [@enniel](https://github.com/enniel)) +- `onLayout` prop is handled correctly in `` ([9c5e177a79](https://github.com/facebook/react-native/commit/9c5e177a79c64c77f281ce727538973e8222e975)) +- Modal accepts a testID but didn't forward it to RCTModalHostView, therefore not making it show up for e2e tests depending on viewhierarchy. ([5050e7eaa1](https://github.com/facebook/react-native/commit/5050e7eaa17cb417baf7c20eb5c4406cce6790a5) by [@GijsWeterings](https://github.com/GijsWeterings)) +- Remove unused and incorrect type declarations in WebSocketInterceptor ([91728e2266](https://github.com/facebook/react-native/commit/91728e2266375b954302ba0cd4b5daf641aefc23) by [@mischnic](https://github.com/mischnic)) +- Complete missing Flow declarations in URL ([98abf1b02f](https://github.com/facebook/react-native/commit/98abf1b02f64ad40d523335e677a2ede15b3650d) by [@mischnic](https://github.com/mischnic)) +- Pressable not passing hover props and event handlers to PressabilityConfig ([1b30dd074b](https://github.com/facebook/react-native/commit/1b30dd074b579c2ae138a1111d07ddb56761315d) by [@Saadnajmi](https://github.com/Saadnajmi)) +- Composite animations will now be ran immediately when the app is in testing mode ([b03e824c52](https://github.com/facebook/react-native/commit/b03e824c52123219a5c8fbd89473391bf0bc31c8) by [@javache](https://github.com/javache)) +- Remove duplicate class members ([c0e489b729](https://github.com/facebook/react-native/commit/c0e489b7293f15858cb706f1b8587600e429af28) by [@bradzacher](https://github.com/bradzacher)) +- Fix: Use same implementation for `performance.now()` on iOS and Android ([1721efb54f](https://github.com/facebook/react-native/commit/1721efb54ff9cc4f577b5ae27f13fcf56801a92c) by [@mrousavy](https://github.com/mrousavy)) + +#### Android specific + +- Enable cliPath to have an absolute path value ([5d560ca99f](https://github.com/facebook/react-native/commit/5d560ca99ff7220de11d2d76dbe77d73990894a8) by [@Krisztiaan](https://github.com/Krisztiaan)) +- Make sure configureNdkBuild* tasks are depending on preBuild ([2fdbf6a10f](https://github.com/facebook/react-native/commit/2fdbf6a10fe67fa3209a51a1105a97c16991f561) by [@cortinico](https://github.com/cortinico)) +- Added a null check to native.value in Switch to fix https://github.com/facebook/react-native/issues/32594 ([8d50bf1133](https://github.com/facebook/react-native/commit/8d50bf113352a6ccdf74c979e1022c6c2ccf6e56) by [@jonathanmos](https://github.com/jonathanmos)) +- Fix overflowInset calculation by using transform values ([8aa87814f6](https://github.com/facebook/react-native/commit/8aa87814f62e42741ebb01994796625473c1310f) by [@ryancat](https://github.com/ryancat)) +- Add missing sources jar into published android artifacts ([384e1a0c7b](https://github.com/facebook/react-native/commit/384e1a0c7bc50d2aab5b59bcedcea5a3e98f1659) by [@Kudo](https://github.com/Kudo)) +- Fix math for detecting if children views are in parent's overflowInset area. ([45244ebce2](https://github.com/facebook/react-native/commit/45244ebce228dfbc3412670e64c11491ba8d8c47) by [@ryancat](https://github.com/ryancat)) +- Fixed empty screen after retrying a BundleDownloader failure in dev mode ([c8d823b9bd](https://github.com/facebook/react-native/commit/c8d823b9bd9619dfa1f5851af003cc24ba2e8830) by [@samkline](https://github.com/samkline)) +- Fix crash from ScrollView that occurs while reporting an error from JS ([2151d11527](https://github.com/facebook/react-native/commit/2151d1152719a230565165f1a8dcfab172689eb3) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Enable hitSlop to be set using a single number. ([589b129581](https://github.com/facebook/react-native/commit/589b129581903a737a64e14eab3f2e29620831d5) by [@javache](https://github.com/javache)) +- Fix fling and snap with recycler viewgroup where fling to the end of scrollable distance when it goes over current rendered children views. ([ead7b97944](https://github.com/facebook/react-native/commit/ead7b97944522e3066ceb2bd50c63c268c961277) by [@ryancat](https://github.com/ryancat)) +- Fixed edge case for quick small scrolls causing unexpected scrolling behaviors. ([f70018b375](https://github.com/facebook/react-native/commit/f70018b37532622f08f20b2c51cdbfca55d730ea) by [@ryancat](https://github.com/ryancat)) +- Fix crash on ReactEditText with AppCompat 1.4.0 ([e21f8ec349](https://github.com/facebook/react-native/commit/e21f8ec34984551f87a306672160cc88e67e4793) by [@cortinico](https://github.com/cortinico)) +- Do not .lowerCase the library name when codegenerating TurboModule Specs ([28aeb7b865](https://github.com/facebook/react-native/commit/28aeb7b8659b38ee3a27fae213c4d0800f4d7e31) by [@cortinico](https://github.com/cortinico)) +- Enable hitSlop to be set using a single number. ([a96bdb7154](https://github.com/facebook/react-native/commit/a96bdb7154b0d8c7f43977d8a583e8d2cbdcb795) by [@javache](https://github.com/javache)) +- Updated TextInput prop types to accomodate for new autoComplete values ([9eb0881c8f](https://github.com/facebook/react-native/commit/9eb0881c8fecd0e974b1cb9f479bad3075854340) by [@TheWirv](https://github.com/TheWirv)) +- Don't reconstruct app components https://github.com/facebook/react-native/issues/25040 ([fc962c9b6c](https://github.com/facebook/react-native/commit/fc962c9b6c4bf9f88decbe014ab9a9d5c1cf51bc) by [@Somena1](https://github.com/Somena1)) +- Do NOT skip the first child view in the scroll view group when measuring the lower and upper bounds for snapping. ([61e1b6f86c](https://github.com/facebook/react-native/commit/61e1b6f86cf98d8a74eeb9353143fe0c624fe6e6) by [@ryancat](https://github.com/ryancat)) +- Fix crash when a Switch is initialised with both backgroundColor and thumbColor. ([456cf3db14](https://github.com/facebook/react-native/commit/456cf3db14c443c483d63aa97c88b45ffd25799b) by [@smarki](https://github.com/smarki)) +- Fix devDisabledIn not working with multiple productFlavors ([055ea9c7b7](https://github.com/facebook/react-native/commit/055ea9c7b7dea030ef16da72d1f6ecb5d95ac468) by [@grit96](https://github.com/grit96)) +- Revert `ReactScrollView` to use `Context` instead of `ReactContext` in the constructor to be less restrictive. ([7b77cc637e](https://github.com/facebook/react-native/commit/7b77cc637e1faf4a2b134853f8415f277d0decdc) by [@ryancat](https://github.com/ryancat)) +- Fix onPress event for nested Text in RN Android ([e494e4beb6](https://github.com/facebook/react-native/commit/e494e4beb6a124008fd116178cbc38335bd87809) by [@mdvacca](https://github.com/mdvacca)) +- Fix enableVmCleanup not working for apps with product flavors ([a2b5e4cd82](https://github.com/facebook/react-native/commit/a2b5e4cd825a358419cef1e3823b72215b689686) by [@cortinico](https://github.com/cortinico)) +- Prevent NPE on ThemedReactContext ([f1b5fe1d3e](https://github.com/facebook/react-native/commit/f1b5fe1d3ea49294d8c89accfa27d76a1a97ccea) by [@sshic](https://github.com/sshic)) +- fix: jvm 11 error message from ReactPlugin.kt and react.gradle ([4e947ecb2d](https://github.com/facebook/react-native/commit/4e947ecb2dabfa0226af7f859c828847b4d891c0) by [@nomi9995](https://github.com/nomi9995)) + +#### iOS specific + +- ScrollView: Respect `contentInset` when animating new items with `autoscrollToTopThreshold`, make `automaticallyAdjustKeyboardInsets` work with `autoscrollToTopThreshold` (includes vertical, vertical-inverted, horizontal and horizontal-inverted ScrollViews) ([49a1460a37](https://github.com/facebook/react-native/commit/49a1460a379e3a71358fb38888477ce6ea17e81a) by [@mrousavy](https://github.com/mrousavy)) +- Prevent RCTConvert error for allowed null blob types ([e1b698c5f2](https://github.com/facebook/react-native/commit/e1b698c5f2b1d689fb3940f8c6a3e298d381ea3a) by [@habovh](https://github.com/habovh)) +- Migrate ScreenshotManager from NativeModule to TurboModule ([b13e41d98e](https://github.com/facebook/react-native/commit/b13e41d98e818279d1941f3425707d3c0ce407fc) by [@p-sun](https://github.com/p-sun)) +- Fix usage of cocoapods with --project-directory flag and new arch ([9e7d91f2fc](https://github.com/facebook/react-native/commit/9e7d91f2fc4d576b8fba81304a24e50134da93d6) by [@danilobuerger](https://github.com/danilobuerger)) +- Post RCTContentDidAppearNotification with new arch ([75105e692c](https://github.com/facebook/react-native/commit/75105e692c2be9bd192089a6a6ffde7572ee1ce1) by [@danilobuerger](https://github.com/danilobuerger)) +- Remove absolute paths from pods project ([42b01a32a1](https://github.com/facebook/react-native/commit/42b01a32a137f18ae9fd2f00914f2edb0e107421) by [@danilobuerger](https://github.com/danilobuerger)) +- Respect RCTSetDefaultFontHandler chosen font ([89efa1a0c1](https://github.com/facebook/react-native/commit/89efa1a0c1b633bf9edee66583800ad3fc54ce63) by [@danilobuerger](https://github.com/danilobuerger)) +- Fixed duplicated UUIDs problem during pod install phase. ([f595a4e681](https://github.com/facebook/react-native/commit/f595a4e681e75aaf737b6582f69855d76a1f33dd)) +- Fix `Time.h` patch not being applied when running `pod install --project-directory=ios` ([60cef850bd](https://github.com/facebook/react-native/commit/60cef850bd3fd12c32ee1196bd19a559592d1465) by [@tido64](https://github.com/tido64)) +- Fix WebSocket control frames having payloads longer than 125 bytes ([86db62b7a8](https://github.com/facebook/react-native/commit/86db62b7a8b28ac82dd0a0627a8b6c351875f682) by [@asmeikal](https://github.com/asmeikal)) +- Stop RedBox from appearing for LogBox handled errors ([9d2df5b8ae](https://github.com/facebook/react-native/commit/9d2df5b8ae95b3cfeae26f64bd1d50bd2b0bbae9) by [@liamjones](https://github.com/liamjones)) +- Enable hitSlop to be set using a single number. ([3addafa525](https://github.com/facebook/react-native/commit/3addafa5257ade685216900bebbad8c35e24e124) by [@javache](https://github.com/javache)) +- Fix `__apply_Xcode_12_5_M1_post_install_workaround` failing when one of the Pods has no IPHONEOS_DEPLOYMENT_TARGET set ([9cd4092336](https://github.com/facebook/react-native/commit/9cd40923362ff717a722f8f36c8250a29a5142b7) by [@Yonom](https://github.com/Yonom)) +- This is a quick speculative fix since we know `CFRunLoopPerformBlock` does not push/pop an autorelease pool. ([3fff164dfa](https://github.com/facebook/react-native/commit/3fff164dfa1c97f69b1701e974effc92a94152d6) by [@christophpurrer](https://github.com/christophpurrer)) +- Fixed RCTImageLoaderTests ([1542f83527](https://github.com/facebook/react-native/commit/1542f835273c08776b960929b5aa7cefbd225971) by [@philIip](https://github.com/philIip)) +- Fix Rosetta2 CocoaPods warning on Apple Silicon ([e918362be3](https://github.com/facebook/react-native/commit/e918362be3cb03ae9dee3b8d50a240c599f6723f) by [@oblador](https://github.com/oblador)) +- Fix `pod install --project-directory=ios` failing due to wrong path to `React-Codegen` ([ebb26cf2e4](https://github.com/facebook/react-native/commit/ebb26cf2e420616c8bf01a5148ca4f8419b238d3) by [@tido64](https://github.com/tido64)) + +### Deprecated + +#### Android specific + +- Gradle: Deprecate `reactRoot` in favor of `root` and `reactNativeDir` ([8bc324fd34](https://github.com/facebook/react-native/commit/8bc324fd34337ab159e2e21e213a6c5b06c548da) by [@cortinico](https://github.com/cortinico)) + + +### Removed + +- DeprecatedPropTypes (deep-link) modules removed from React Native. ([23717c6381](https://github.com/facebook/react-native/commit/23717c6381a41b1c5f189376bfa5bc73c7a4da87) by [@yungsters](https://github.com/yungsters)) +- `accessibilityStates` no longer passed through to RCTView. ([1121ed94ab](https://github.com/facebook/react-native/commit/1121ed94ab470be27207b0c8dbae5d19860c08da) by [@luism3861](https://github.com/luism3861)) + +#### iOS specific + +- Remove RCTUIManagerObserver from RCTNativeAnimatedTurboModule ([e9ed115bab](https://github.com/facebook/react-native/commit/e9ed115babbc82968380dae22fa928d4ce3cd6da) by [@p-sun](https://github.com/p-sun)) + + +## v0.67.4 + +### Fixed + +#### Android specific + +- Added a null check to native.value in Switch to fix https://github.com/facebook/react-native/issues/32594 ([8d50bf1133](https://github.com/facebook/react-native/commit/8d50bf113352a6ccdf74c979e1022c6c2ccf6e56) by [@jonathanmos](https://github.com/jonathanmos)) + +## v0.67.3 + +### Fixed + +#### Android specific + +- Text with adjustsFontSizeToFit changes the text layout infinitely ([c1db41f060](https://github.com/facebook/react-native/commit/c1db41f060908e6ab001aaace7c20c610056f59a)) + +#### iOS specific + +- Fix a broken input for the Korean alphabet in TextInput ([1a83dc36ce](https://github.com/facebook/react-native/commit/1a83dc36ce0af33ac7a3c311354fce4bfa5ba1a3) by [@bernard-kms](https://github.com/bernard-kms)) + +## v0.67.2 + +### Fixed + +- Fix error "mockModal is not a function" ([507b05f4c0](https://github.com/facebook/react-native/commit/507b05f4c02b46109f483a2b79c924a775fd7bd3) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) + +#### Android specific + +- Fix potential crash if ReactRootView does not have insets attached. ([6239e2f5ce](https://github.com/facebook/react-native/commit/6239e2f5ce82f7c2e683eb4699b9ce3ff1b58ac5) by [@enahum](https://github.com/enahum)) +- Upgrading OkHttp from 4.9.1 to 4.9.2 to fix CVE-2021-0341. ([e896d21](https://github.com/facebook/react-native/commit/e896d21ced3c0c917c2fc0044d2b93b44df9a081) by [@owjsub](https://github.com/owjsub)) + +#### iOS specific + +- Fix `Time.h` patch not being applied when running `pod install --project-directory=ios` ([60cef850bd](https://github.com/facebook/react-native/commit/60cef850bd3fd12c32ee1196bd19a559592d1465) by [@tido64](https://github.com/tido64)) +- Find-node.sh now respects .nvmrc ([35bcf934b1](https://github.com/facebook/react-native/commit/35bcf934b186e581d100d43e563044300759557f) by [@igrayson](https://github.com/igrayson)) + +## v0.67.1 + +### Fixed + +#### Android specific + +- Do not remove libjscexecutor.so from release builds ([574a773f8f](https://github.com/facebook/react-native/commit/574a773f8f55fe7808fbb672066be8174c64d76d) by [@cortinico](https://github.com/cortinico)) + +#### iOS specific + +- Remove alert's window when call to `hide`. ([a46a99e120](https://github.com/facebook/react-native/commit/a46a99e12039c2b92651af1996489d660e237f1b) by [@asafkorem](https://github.com/asafkorem)) + +## v0.67.0 + +### Added + +#### Android specific +- Add `ACCESS_MEDIA_LOCATION` permission to PermisionsAndroid library. ([79db483568](https://github.com/facebook/react-native/commit/79db4835681f5d0149620ec8e0990411cb882241) by [@Skrilltrax](https://github.com/Skrilltrax)) +- Implement `SnapToAlignment` in `ReactScrollView` ([e774c037bc](https://github.com/facebook/react-native/commit/e774c037bce40a4b48e78d2d0a1085a1e4f5a328)), `ReactScrollViewManager` ([c6e5640e87](https://github.com/facebook/react-native/commit/c6e5640e87e7cb5b514ded2c8d4cbb039bd02c5f)), `ReactHorizontalScrollView` ([b12256394e](https://github.com/facebook/react-native/commit/b12256394e34c375942ca508ef79a8c816317976)), `ReactHorizontalScrollViewManager` ([deec1db9fd](https://github.com/facebook/react-native/commit/deec1db9fdf2848941326ba5bebc11f3592a301e)) and update `ScrollView.js` ([a54cfb9e57](https://github.com/facebook/react-native/commit/a54cfb9e5794f196d3834e19762f3aacf47a177d)) and reach parity with iOS ([04184ef851](https://github.com/facebook/react-native/commit/04184ef851c71141009c523ba59838ae6af19ba5)) by [@mdvacca](https://github.com/mdvacca) +- Show Redbox for C++ errors. ([d6c879edba](https://github.com/facebook/react-native/commit/d6c879edbad068d0f461381875b7fae6db99d18d) by [@sota000](https://github.com/sota000)) +- Added an experimental touch dispatch path ([a2feaeb5f1](https://github.com/facebook/react-native/commit/a2feaeb5f1121a860a9416b5d4e0e96debd45b09) by [@ShikaSD](https://github.com/ShikaSD)) + +#### iOS specific +- Added `cancelButtonTintColor` prop for `ActionSheetIOS` to change only the text color of the cancel button ([01856633a1](https://github.com/facebook/react-native/commit/01856633a1d42ed3b26e7cc93a007d7948e1f76e) by [@nomi9995](https://github.com/nomi9995)) +- Added [`LSApplicationQueriesSchemes`](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW14) in info.plist with entries tel, telprompt, http, fb, geo ([b26f277262](https://github.com/facebook/react-native/commit/b26f2772624c863c91fa1ff627b481c92d7562fb) by [@utkarsh-dixit](https://github.com/utkarsh-dixit)) +- Add `UIAccessibilityTraitUpdatesFrequently` to progressBar role ([1a42bd6e97](https://github.com/facebook/react-native/commit/1a42bd6e97ae44a3b38ca552865bac63a6f35da5) by [@jimmy623](https://github.com/jimmy623)) +- Add `asdf-vm` support in `find-node.sh` ([3e7c310b1d](https://github.com/facebook/react-native/commit/3e7c310b1dcf5643920535eea70afa451888792a) by [@pastleo](https://github.com/pastleo)) + + +### Changed +- `ImageBackground` now respects `imageStyle` width and height ([dbd5c3d8e5](https://github.com/facebook/react-native/commit/dbd5c3d8e5e35685be89156194a96cead553a330) by [@Naturalclar](https://github.com/Naturalclar)) +- Rename deprecated `Keyboard.removeEventListener` to `Keyboard.removeListener`. ([8880c09076](https://github.com/facebook/react-native/commit/8880c09076e4727768ace26a74766cbe6f64021c) by [@yungsters](https://github.com/yungsters)) +- Update `Modal`'s mock to not render its children when it is not visible ([ec614c16b3](https://github.com/facebook/react-native/commit/ec614c16b331bf3f793fda5780fa273d181a8492) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) +- Upgraded `react-devtools-core` dependency to 4.19.1 ([356236471a](https://github.com/facebook/react-native/commit/356236471abc6b5b8c139223e15388fd1eecd2d1) by [@jstejada](https://github.com/jstejada)) +- React-native/normalize-color now supports Node.js ([65e58f26e1](https://github.com/facebook/react-native/commit/65e58f26e1fbd06b1ae32e2ab3a2616c8eef08e0) by [@yungsters](https://github.com/yungsters)) +- Updated to Contributor Covenant v2.1 ([19f8d2f7da](https://github.com/facebook/react-native/commit/19f8d2f7da13f4524f31acf7aa10cc0aa91b5da4)) + + +#### Android specific +- Hermes initialization will no longer need an explicit configuration. ([a40f973f58](https://github.com/facebook/react-native/commit/a40f973f58609ca717fac63bc501d5cf93b748ad) by [@Ashoat](https://github.com/Ashoat)) +- Setting `overflow: scroll` in View component style will clip the children in the View container ([93beb83abe](https://github.com/facebook/react-native/commit/93beb83abef42b92db43ee3bb8b156f486a6c00f) by [@ryancat](https://github.com/ryancat)) +- Native views backing `Animated.View` (w/ JavaScript-driven animations) will no longer be flattened; this should be a transparent change. ([4fdbc44ab5](https://github.com/facebook/react-native/commit/4fdbc44ab5945399338e4ed94ea5611098bd2142) by [@yungsters](https://github.com/yungsters)) +- Use new Locale API on Android 11 (API 30)+ ([b7c023a8c1](https://github.com/facebook/react-native/commit/b7c023a8c1122500c6ceb7de2547569b3b9251ba)) +- Changed `react.gradle` `detectCliPath` function logic for `cliPath` case ([ce51b62494](https://github.com/facebook/react-native/commit/ce51b6249449361ee50b8c99a427c28af7ab3531) by [@vitalyiegorov](https://github.com/vitalyiegorov)) +- Remove `"high"` and `"balanced"` as values for `android_hyphenationFrequency` on `Text` ([a0d30b848a](https://github.com/facebook/react-native/commit/a0d30b848a07480d0fccec608a62a505c71f8cac)) +- Bump Gradle version to 7.2, Bump Kotlin version to 1.5.31 ([9ae3367431](https://github.com/facebook/react-native/commit/9ae3367431428748f5486c782199beb4f9c6b477) by [@svbutko](https://github.com/svbutko)) +- Move mavenCentral repo below local paths ([046b02628d](https://github.com/facebook/react-native/commit/046b02628d32eadd6d44160ab79932f6c26b188d) by [@friederbluemle](https://github.com/friederbluemle)) + +#### iOS specific +- Optimized font handling for iOS ([4ac42d88ef](https://github.com/facebook/react-native/commit/4ac42d88ef60ae3fed7319851d47b93e98ac9afa) by [@Adlai-Holler](https://github.com/Adlai-Holler)) +- Remove iOS 11 version check as minimum deployment is iOS 11 ([398595e074](https://github.com/facebook/react-native/commit/398595e07483fa8f45579de4ca1aee9585e20620) by [@ken0nek](https://github.com/ken0nek)) +- Don't hang app for 60s if packager can't be reached, changed to 10s ([c0e04460f5](https://github.com/facebook/react-native/commit/c0e04460f546dfef2623bff367eb8db8fd75fa34) by [@radex](https://github.com/radex)) + +### Removed + +- Removed unnecessary global variable `GLOBAL`. ([a101fc768c](https://github.com/facebook/react-native/commit/a101fc768cedc7ac9754006e5b7292bb7084ab54) by [@rubennorte](https://github.com/rubennorte)) +- Removed unused files: `StaticContainer.react.js`, `ensurePositiveDelayProps.js`, `InteractionMixin.js`, `queryLayoutByID.js` ([64aa1e5ffe](https://github.com/facebook/react-native/commit/64aa1e5ffe5d577c04cabb3692246b956f65597b) by [@ecreeth](https://github.com/ecreeth)) + +#### Android specific + +- Remove `DatePickerAndroid` from react-native. ([7a770526c6](https://github.com/facebook/react-native/commit/7a770526c626e6659a12939f8c61057a688aa623) by [@andresantonioriveros](https://github.com/andresantonioriveros)) + +#### iOS specific + +### Fixed + +- Update metro config language to `blockList` ([7923804c28](https://github.com/facebook/react-native/commit/7923804c28aac731396f0db112cb6c3a9d30c08f) by [@rh389](https://github.com/rh389)) +- Ignores global npm prefix ([6334ac35ac](https://github.com/facebook/react-native/commit/6334ac35ac3cbc2c84b2d46d46ec118bf9bf714d) by [@redreceipt](https://github.com/redreceipt)) +- Support `Animated.ValueXY` when validating `Animated.event`. ([27dd2ecb70](https://github.com/facebook/react-native/commit/27dd2ecb70f1d08787c93a2e18250ffaff328e5f) by [@javache](https://github.com/javache)) +- Add a function `getIgnorePatterns` in `LogBoxData.js` for tests or other usecases. ([a950634424](https://github.com/facebook/react-native/commit/a950634424cddf31c0adb6c9799adf1cc5f83bf0)) + +#### Android specific + +- TextInput Drawable to avoid Null Pointer Exception RuntimeError https://github.com/facebook/react-native/issues/17530 ([254493e1fb](https://github.com/facebook/react-native/commit/254493e1fb0c3a1e279e2c957e83edac6252d041) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Nested Text Android `onPress` does not work with last character ([132d1d00f8](https://github.com/facebook/react-native/commit/132d1d00f885fe5a45d712fd7698db285c22bc4b) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix non selectable Text in FlatList ([c360b1d92b](https://github.com/facebook/react-native/commit/c360b1d92b69e1d298b390ec88c4d29c1023945a) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Set `textBreakStrategy` default to be `'highQuality'` ([3b2d541989](https://github.com/facebook/react-native/commit/3b2d5419899363d84aea4f5cc3a4c75253dd6406)) +- Fix error handling when loading JSC or Hermes ([d839b24b06](https://github.com/facebook/react-native/commit/d839b24b06d31b4ce91fb459742831b942972f56) by [@iqqmuT](https://github.com/iqqmuT)) +- Fix encoding for gradlew.bat files ([ab2bdee735](https://github.com/facebook/react-native/commit/ab2bdee735cd0d53d3dbfbac5cd31f96eefb7e61) by [@yungsters](https://github.com/yungsters)) +- Fix `hermesFlags` not working with multiple variants ([91adb761cf](https://github.com/facebook/react-native/commit/91adb761cf1583598d4d63ce879fd7e0f4ae793c) by [@grit96](https://github.com/grit96)) +- `ScrollTo` API in ScrollView will check the actual scroll position before setting the scroll state ([1a9e2d5d55](https://github.com/facebook/react-native/commit/1a9e2d5d5589ce5cee92868ea5bccceb6e161eff) by [@ryancat](https://github.com/ryancat)) +- Compute Android Notch in `keyboardDidShow` height calculation API 28+ ([8bef3b1f11](https://github.com/facebook/react-native/commit/8bef3b1f1136ab5c2f2309a3101a7d9626ced1f5) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix `currentActivity` being null when launching Redbox ([f4fdf4b55e](https://github.com/facebook/react-native/commit/f4fdf4b55e4489c21f4552b4ac01ef253c038b2d)) +- When sending OS intents, always set `"FLAG_ACTIVITY_NEW_TASK"` flag (required by OS). ([04fe3ed80d](https://github.com/facebook/react-native/commit/04fe3ed80d9c201a483a2b477aeebd3d4169fd6d) by [@Krizzu](https://github.com/Krizzu)) +- Fix missing WebView provider crash in ForwardingCookieHandler ([d40cb0e1b0](https://github.com/facebook/react-native/commit/d40cb0e1b0fb233a27b9d476167814d2853acf2a) by [@RodolfoGS](https://github.com/RodolfoGS)) +- Fix `keyboardDismissMode="on-drag"` on Android ([7edf9274cf](https://github.com/facebook/react-native/commit/7edf9274cf6d3398075c19cd1cb020a5d6a346a2) by [@janicduplessis](https://github.com/janicduplessis)) +- Fixed `alignItems: baseline` for elements on Android ([1acf334614](https://github.com/facebook/react-native/commit/1acf33461451834097463f43e70d90bae0f67198)) +- `OnKeyPress` event not fired with numeric keys ([ee3e71f536](https://github.com/facebook/react-native/commit/ee3e71f536127295ba4ea377e618499409a2e9ba) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Exclude unused .so files for reduce android .apk and .aab ([6f126740fa](https://github.com/facebook/react-native/commit/6f126740fa560d7a831979b9f3747baacfb28dba) by [@enniel](https://github.com/enniel)) + +#### iOS specific + +- Fixed an edge case when scroll to item/index is called without animation, the offset position is not updated. This caused the measurement of the position to be wrong. ([55392f65a6](https://github.com/facebook/react-native/commit/55392f65a6addbdd8214b61d4ae286f26d63a94f) by [@ryancat](https://github.com/ryancat)) +- Fixed the issue when moving cursor in multi-line TextInput. ([22801870f0](https://github.com/facebook/react-native/commit/22801870f0613c2544ade1ebc5363e6e2f015c79) by [@xiankuncheng](https://github.com/xiankuncheng)) +- Fix NSInvalidArgumentException for invalid font family names. ([5683932862](https://github.com/facebook/react-native/commit/5683932862ab870e735342342c68e03fb5ca9e09) by [@yungsters](https://github.com/yungsters)) +- Fix Image `defaultSource` not showing on iOS ([900210cacc](https://github.com/facebook/react-native/commit/900210cacc4abca0079e3903781bc223c80c8ac7) by [@cristianoccazinsp](https://github.com/cristianoccazinsp)) +- Warn if Rosetta2 is being used (x86_64 on arm64) ([51bf557948](https://github.com/facebook/react-native/commit/51bf55794899284e1c465d346a3f6ebd8a485da2) by [@barbieri](https://github.com/barbieri)) +- Source map path for schemes containing whitespaces ([f3fe7a0fb5](https://github.com/facebook/react-native/commit/f3fe7a0fb5fc0325fbe062c6df4cbf8b58779189) by [@andersonvom](https://github.com/andersonvom)) +- Fix build error after running `pod install` with `--project-directory=ios` ([ef5ff3e055](https://github.com/facebook/react-native/commit/ef5ff3e055482771cbe866d4961ee2d0a9e00e45) by [@sonicdoe](https://github.com/sonicdoe)) +- Fixed inability to build apps when gflags is installed ([ab8dbdf663](https://github.com/facebook/react-native/commit/ab8dbdf66363f3d65f0dfbcc4ec7c71b1cd69b2a) by [@KDederichs](https://github.com/KDederichs)) + +### Security + +- Avoiding logging root view params outside of dev / debug mode builds ([e612d3a116](https://github.com/facebook/react-native/commit/e612d3a116f39ab354169643bab0d4bb9cfc1a85) by [@sterlingwes](https://github.com/sterlingwes)) + +## v0.66.4 + +### Fixed + +#### iOS specific + +- Revert "Fix Deadlock in RCTi18nUtil (iOS)" ([70ddf46](https://github.com/facebook/react-native/commit/70ddf46c8afcd720e188b6d82568eac6ac8125e6) by [@Saadnajmi](https://github.com/Saadnajmi)) +- `apply_Xcode_12_5_M1_post_install_workaround` causing pods targetting iOS 12 and above to fail ([a4a3e67554](https://github.com/facebook/react-native/commit/a4a3e675542827bb281a7ceccc7b8f5533eae29f) by [@Yonom](https://github.com/Yonom)) + +## v0.66.3 + +### Changed + +- Rename deprecated `Keyboard.removeEventListener` to `Keyboard.removeListener`. ([8880c09076](https://github.com/facebook/react-native/commit/8880c09076e4727768ace26a74766cbe6f64021c) by [@yungsters](https://github.com/yungsters)) + +### Fixed + +- Revert changes in Jest preprocessor to fix tests in external projects ([142090a5f3fa7](https://github.com/facebook/react-native/commit/142090a5f3fa7c3ab2ed4c536792e3f26582bd3b) by [@rubennorte](https://github.com/rubennorte)) + +## v0.66.2 + +### Fixed + +- Add a function `getIgnorePatterns` in `LogBoxData.js` for tests or other usecases. ([a950634424](https://github.com/facebook/react-native/commit/a950634424cddf31c0adb6c9799adf1cc5f83bf0)) +- Reintroduce generated codegen files ([7382f556d3](https://github.com/facebook/react-native/commit/7382f556d327d51bd09456efda83edec7e05ecd2) by [@kelset](https://github.com/kelset)) + +#### iOS specific + +- Hide the logbox window explicitly. New behavior in iOS SDK appears to retain UIWindow while visible. ([72ea0e111f](https://github.com/facebook/react-native/commit/72ea0e111fccd99456abf3f974439432145585e3) by [@paddlefish](https://github.com/paddlefish)) + +## v0.66.1 + +### Fixed + +- For Android, general fixes to Appearance API and also fixes AppCompatDelegate.setDefaultNightMode(). For iOS, now works correctly when setting window.overrideUserInterfaceStyle ([25a2c608f7](https://github.com/facebook/react-native/commit/25a2c608f790f42cbc4bb0a90fc06cc7bbbc9b95) by [@mrbrentkelly](https://github.com/mrbrentkelly)) + +#### Android specific + +- Fix Android border positioning regression ([d1a33cd139](https://github.com/facebook/react-native/commit/d1a33cd139fab4565b1fc691f5751c4af99d5849) by [@oblador](https://github.com/oblador)) + +#### iOS specific + +- Fix for unable to find `find-node.sh` in `react-native-xcode.sh` script ([cc59a7cbde](https://github.com/facebook/react-native/commit/cc59a7cbde1c0fc6d6ef059321d23bf287f08218) by [@garethknowles](https://github.com/garethknowles)) + +## v0.66.0 + +### Highlights + +- Hermes 0.9.0 + - This Hermes release is primarily about closing gap between Hermes cut and this React Native release. Among ~400 commits, contains memory and size wins, bugfixes and other progress behind the scenes. See [issue for more details](https://github.com/facebook/hermes/issues/586). +- Allow taps on views outside the bounds of a parent with `overflow: visible` ([e35a963bfb](https://github.com/facebook/react-native/commit/e35a963bfb93bbbdd92f4dd74d14e2ad6df5e14a) by [@hsource](https://github.com/hsource)) +- Fixes for building on Apple Silicon and Xcode 13 ([ac4ddec542](https://github.com/facebook/react-native/commit/ac4ddec542febda744de218dae3a3d34edc7da84) thanks to [@mikehardy](https://github.com/mikehardy)) +- New bluetooth permissions for Android ([2bcc6fac38](https://github.com/facebook/react-native/commit/2bcc6fac3844f0752bc7067517c92a643679575e), [eeb8e58](https://github.com/facebook/react-native/commit/eeb8e5829e183f6b5cd5fd327cf6da03a7db0541) by [@iBotPeaches](https://github.com/iBotPeaches)) + +### Breaking + +- Remove Picker and PickerIOS components + [cddb97ad18](https://github.com/facebook/react-native/commit/cddb97ad18cfdb663dcf015af3c9426d5414e396), [77366cd869](https://github.com/facebook/react-native/commit/77366cd8696cb8ada3f84d7fb4d36a27f7007b06), [ad0ccac0d6](https://github.com/facebook/react-native/commit/ad0ccac0d6471fa5428bf137c3aa0646883e8446) +- Remove StatusBarIOS component ([7ce0f40f5c](https://github.com/facebook/react-native/commit/7ce0f40f5cd8c0928ce720d6d121bcc5963958a2) by [@ecreeth](https://github.com/ecreeth)) + +#### Android specific + +- Updated `autoCompleteType` prop of `TextInput` to `autoComplete` ([27fec9569e](https://github.com/facebook/react-native/commit/27fec9569e08a04e0dbdbd5de063a599ad0416fa) by [@jeswinsimon](https://github.com/jeswinsimon)) + +### Added + +- Add `global.queueMicrotask` ([be189cd819](https://github.com/facebook/react-native/commit/be189cd81905a735f08a8519c62a707658c7fb27) by [@Huxpro](https://github.com/Huxpro)) +- Added data field to `markerPoint` to allow callers to add additional arbitrary string data to logged points ([aa98978302](https://github.com/facebook/react-native/commit/aa9897830293955b7cc77fd818a50e8d736e715d)) +- Adds accessibility actions to Button ([44717152ca](https://github.com/facebook/react-native/commit/44717152cadb18c7aff74e9465fdb70efdb1bf81) by [@dennisurtubia](https://github.com/dennisurtubia)) +- Add accessibilityState prop to Slider component ([35dd86180b](https://github.com/facebook/react-native/commit/35dd86180ba730425b97592ef6e5c4d449caee06) by [@sladyn98](https://github.com/sladyn98)) +- Add support for "togglebutton" `accessibilityRole` ([da899c0cc4](https://github.com/facebook/react-native/commit/da899c0cc4372830e5ca053a096b74fff2a19cb8) by [@kacieb](https://github.com/kacieb)) + +#### Android specific + +- Add INFO, and MENU key event support ([bb33c1050b](https://github.com/facebook/react-native/commit/bb33c1050ba6098a68d70055e33186d9438c4374) by [@havlasme](https://github.com/havlasme)) +- Added all autofill types to TextEdit ([d9e0ea77f0](https://github.com/facebook/react-native/commit/d9e0ea77f0111fd8400c65d68e45d54e2f84287b) by [@safaiyeh](https://github.com/safaiyeh)) +- Add support to URI keyboard type in Android ([1465c8f387](https://github.com/facebook/react-native/commit/1465c8f3874cdee8c325ab4a4916fda0b3e43bdb)) +- Add `MEDIA_STOP`, `MEDIA_NEXT`, and `MEDIA_PREVIOUS` event support to Android TV ([3e2bb331fc](https://github.com/facebook/react-native/commit/3e2bb331fc0974bc870b2e7bd3171e585183ed1b) by [@havlasme](https://github.com/havlasme)) +- Allow configuring ndk build architectures ([d6ed1ff58b](https://github.com/facebook/react-native/commit/d6ed1ff58b2ca4d1c8b45416e56fa1da75633c07) by [@janicduplessis](https://github.com/janicduplessis)) +- Added support for accessibility role of "list" for flatlist and sectioned list ([25a16123a6](https://github.com/facebook/react-native/commit/25a16123a610ae377ced23ef81ed4c03ad7d06d9) by [@anaskhraza](https://github.com/anaskhraza)) +- Support for foreground ripple in Pressable ([0823f299e5](https://github.com/facebook/react-native/commit/0823f299e560efda5c0f344fcec86cf68801f4ab) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) +- Support for ScrollAway native nav bars added to `ReactScrollView` ([0ef5beee85](https://github.com/facebook/react-native/commit/0ef5beee855afa592cc647383ba6a3ceae9cc40a) by [@JoshuaGross](https://github.com/JoshuaGross)) + +#### iOS specific + +- Added new prop "selection" to `TextInputProps` ([8434177722](https://github.com/facebook/react-native/commit/8434177722f70a9325f9a6adf46b5315b1f4ffa4)) +- Support for onRequestClose for iOS Modal component. ([c29ec46b0e](https://github.com/facebook/react-native/commit/c29ec46b0eee99670ce7762898fe3a4810db968b) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) +- Allow `PlatformColor` to return user-defined named asset color ([36c0a7dec1](https://github.com/facebook/react-native/commit/36c0a7dec121bd3a4b92d02c03a24771d3c4cf84) by [@oblador](https://github.com/oblador)) +- Add support for the `UIAccessibilityTraitsTabBar` ([11f8d9c7cd](https://github.com/facebook/react-native/commit/11f8d9c7cd4bae0b1a5e880ea9b2da7447ad76c2) by [@jimmy623](https://github.com/jimmy623)) +- Added "altitudeAngle" property to touch events from Apple Pencil/Stylus devices. ([f1b1ba8963](https://github.com/facebook/react-native/commit/f1b1ba8963ff152d995c3cd132bc0755413bc44f) by [@swittk](https://github.com/swittk)) +- Introduce `RCTInitializing` to allow NativeModules to initialize themselves ([9b45df1fce](https://github.com/facebook/react-native/commit/9b45df1fced066f40034b0a58be6f4caafd5f785) by [@RSNara](https://github.com/RSNara)) +- Introduce `RCTCallableJSModules` API for NativeModules ([ece373d244](https://github.com/facebook/react-native/commit/ece373d24421d96e62dafa9a064b38acd6b71e46) by [@RSNara](https://github.com/RSNara)) +- Attach `RCTBundleManager` to NativeModules ([329f58ee46](https://github.com/facebook/react-native/commit/329f58ee461e7afade36d8c249d3f4930c485312) by [@RSNara](https://github.com/RSNara)) +- Introduce RCTBundleManager for bundleURL access ([4a1bafe591](https://github.com/facebook/react-native/commit/4a1bafe591917482d78be998d45552e2568e3e23) by [@RSNara](https://github.com/RSNara)) + +### Changed + +- Initialized LogBox earlier and centralized access in LogBox module ([8abe737068](https://github.com/facebook/react-native/commit/8abe737068a54a874571c8b5560b2118b1df31ad) by [@rubennorte](https://github.com/rubennorte)) +- ExceptionsManager will no longer report exceptions with `type === 'warn'`. ([883e0d5752](https://github.com/facebook/react-native/commit/883e0d5752b952c829c8d45504d3532f52bb272f) by [@yungsters](https://github.com/yungsters)) +- Disable TouchableOpacity when `accessibilityState.disabled` is set ([ea609defe8](https://github.com/facebook/react-native/commit/ea609defe8462a6beeac4da3aa7a43397ee9a77f) by [@chakrihacker](https://github.com/chakrihacker)) +- Upgrade Babel from 7.12.3 to 7.14.1 ([58a0f9b4e2](https://github.com/facebook/react-native/commit/58a0f9b4e202a921ab0820c79d6a3dd54204da46) by [@MichaReiser](https://github.com/MichaReiser)) +- Upgrade `react-devtools-core` from ~4.6.0 to ^4.13.0 ([9e020ef476](https://github.com/facebook/react-native/commit/9e020ef476e24bb5703fc421225f1a94ae14512b) by [@bvaughn](https://github.com/bvaughn)) +- Update Flipper to 0.99.0 ([41f45a77ad](https://github.com/facebook/react-native/commit/41f45a77ad09b46de328fb2a72775a052dac1e93) by [@swrobel](https://github.com/swrobel)) +- Bump CLI to ^6.0.0 ([c677e196a9](https://github.com/facebook/react-native/commit/c677e196a9c4d6cfdf84d97e4746922bb4ed4823) by [@thymikee](https://github.com/thymikee)) +- Upgrade ESLint TS parser and plugin ([3b751d396b](https://github.com/facebook/react-native/commit/3b751d396ba0acaa1b4c8e1115c79eb45dab403d) by [@wcandillon](https://github.com/wcandillon)) +- Upgrade folly to 2021.06.28.00 and boost to 1.76.0 ([b77948e33b](https://github.com/facebook/react-native/commit/b77948e33bc5e0df422fffca3b4c9253f611d298) by [@Kudo](https://github.com/Kudo)) + +#### Android specific + +- Add BLUETOOTH_ADVERTISE to `PermissionsAndroid` ([2bcc6fac38](https://github.com/facebook/react-native/commit/2bcc6fac3844f0752bc7067517c92a643679575e) by [@iBotPeaches](https://github.com/iBotPeaches)) +- Native ScrollView listeners list maintains weak references to listeners to avoid memory leaks ([b673e352fb](https://github.com/facebook/react-native/commit/b673e352fb0ea44b545edf5a7e8c1b422180838a) by [@dalves](https://github.com/dalves)) +- Rename the "Toggle Inspector" DevMenu item to "Hide/Show Element Inspector" ([e91fb05db7](https://github.com/facebook/react-native/commit/e91fb05db7f576e07114755b9db1eee91c672f25) by [@RSNara](https://github.com/RSNara)) +- Localize "search", "button", and "togglebutton" accessibility roles by using the platform roles ([399285f91c](https://github.com/facebook/react-native/commit/399285f91c2f675dea16fe61a86049ef7fecf35b) by [@kacieb](https://github.com/kacieb)) +- Refactor `AndroidTextInput.AndroidTextInput.color` prop to use SharedColor instead of int ([bc57056cc3](https://github.com/facebook/react-native/commit/bc57056cc3263431d54982426d890ba60b4cadb7) by [@mdvacca](https://github.com/mdvacca)) +- Upgraded `infer-annotation` to 0.18.0. ([b5c94e316c](https://github.com/facebook/react-native/commit/b5c94e316cc9b4ff090d8daa8970bf1becf77959) by [@yungsters](https://github.com/yungsters)) +- Bumped AGP to 4.2.2 ([ae494e7ce1](https://github.com/facebook/react-native/commit/ae494e7ce199cc5d524f791d45ddce51535cdadb) by [@cortinico](https://github.com/cortinico)) +- Upgrade folly to 2021.06.28.00 ([ebe939b18a](https://github.com/facebook/react-native/commit/ebe939b18aa859eb0f7f265222874c292ed771a4) by [@Kudo](https://github.com/Kudo)) +- Bump NDK to 21.4.7075529 ([aa43aab77c](https://github.com/facebook/react-native/commit/aa43aab77c8571632a2b0913c80fbf822dac01bc) by [@dulmandakh](https://github.com/dulmandakh)) + +#### iOS specific + +- ScrollView scrollIndicatorInsets to not automatically add safe area on iOS13+ ([bc1e602e0c](https://github.com/facebook/react-native/commit/bc1e602e0c7922da6bf238675b7bf8b4c3faa493) by [@justinwh](https://github.com/justinwh)) + +### Removed + +- `StyleSheet.create` will no longer do DEV-time validation. ([2e8c0bd7ea](https://github.com/facebook/react-native/commit/2e8c0bd7ea7db1aac183eb7f656772d3cffcb132) by [@yungsters](https://github.com/yungsters)) + +### Fixed + +- Fix `window` not existing in jest setup ([bc1c533833](https://github.com/facebook/react-native/commit/bc1c533833bfe25a22f1abd105b8bcb1babce3b5) by [@timomeh](https://github.com/timomeh)) +- Clamp negative values for `numberOfLines` in component ([3bc883c6c6](https://github.com/facebook/react-native/commit/3bc883c6c60632f6a41df3867368f16f684b3865) by [@ShikaSD](https://github.com/ShikaSD)) +- Add missing `jest/create-cache-key-function` dep root package.json ([9a43eac7a3](https://github.com/facebook/react-native/commit/9a43eac7a32a6ba3164a048960101022a92fcd5a) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix Switch ref forwarding ([1538fa4455](https://github.com/facebook/react-native/commit/1538fa4455fa7095879aceba7f74a519c1337a8b) by [@janicduplessis](https://github.com/janicduplessis)) +- Report fatal errors even if its `type` is "warn". ([e4a4c4d6d7](https://github.com/facebook/react-native/commit/e4a4c4d6d71ab1a747d768e4b518e64e100ddfde) by [@yungsters](https://github.com/yungsters)) +- Parse `accessibilityAction` props into object instead of string ([faaeb778df](https://github.com/facebook/react-native/commit/faaeb778dfe25df67fc00b599d023819c10406e8) by [@ShikaSD](https://github.com/ShikaSD)) +- Avoid downgrading `console.error` when passed warning-like objects. ([0dba0aff18](https://github.com/facebook/react-native/commit/0dba0aff185f4fd46e1146362235e68e52c59556) by [@yungsters](https://github.com/yungsters)) +- Fix natively driven animations not getting reset properly ([129180c77b](https://github.com/facebook/react-native/commit/129180c77b0b99a3acedbeb04ce6ec4667f74cac) by [@tienphaw](https://github.com/tienphaw)) +- Fix compilation errors on Windows. ([6d04a46f74](https://github.com/facebook/react-native/commit/6d04a46f7427b9e107608f8f620fe2a1a84ff42d)) +- Fixed bug parsing hermes call stacks when the file name is empty ([e539e7d0be](https://github.com/facebook/react-native/commit/e539e7d0bed4fef42f458f28d06100ae23f52cb7) by [@MartinSherburn](https://github.com/MartinSherburn)) +- Upgrade dependencies / version of eslint package ([463ec22bb9](https://github.com/facebook/react-native/commit/463ec22bb9f2938164fef6133dfd94d2e428e5b0) by [@mikehardy](https://github.com/mikehardy)) + +#### Android specific + +- Allow taps on views outside the bounds of a parent with `overflow: visible` ([e35a963bfb](https://github.com/facebook/react-native/commit/e35a963bfb93bbbdd92f4dd74d14e2ad6df5e14a) by [@hsource](https://github.com/hsource)) +- Fixed to use correct Android theme color for dark theme ([b3a715f6ea](https://github.com/facebook/react-native/commit/b3a715f6ea3d0faaf6d09e2a49267f2a5fb3fad2) by [@sidverma32](https://github.com/sidverma32)) +- Fixed dynamic behavior of `` on Android ([59021521e7](https://github.com/facebook/react-native/commit/59021521e7aba0f70b91b5c7778ccdd1b30eaae4)) +- Fix Dimensions not updating ([c18a492858](https://github.com/facebook/react-native/commit/c18a492858e94b31e632560ad17499012e688158) by [@jonnyandrew](https://github.com/jonnyandrew)) +- Fix dashed/dotted border-drawing when `borderRadius` is 0 ([3e5998e651](https://github.com/facebook/react-native/commit/3e5998e651eba840603dcb1a9c0be564fc3f868d) by [@IjzerenHein](https://github.com/IjzerenHein)) +- Fix selectionColor doesn't style Android TextInput selection handles ([5819538a08](https://github.com/facebook/react-native/commit/5819538a087f1f48d564e7b4e273fe43dfb026cc) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix Modal being dismissed incorrectly when pressing escape on a hardware keyboard ([f51773ecde](https://github.com/facebook/react-native/commit/f51773ecdedbac19d25eb20894e532edef2cb304) by [@levibuzolic](https://github.com/levibuzolic)) +- Avoid calling setHint with a null parameter causing cursor to jump to the right ([3560753559](https://github.com/facebook/react-native/commit/356075355908f4901b87ad6ce33c157f01c8e748) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Create slider accessibility delegate in createViewInstance ([91cac20289](https://github.com/facebook/react-native/commit/91cac2028900cd18d17e70f9050cc125ed1eb12e) by [@janicduplessis](https://github.com/janicduplessis)) +- Quickfix individual border style dotted or dashed rendering as solid ([cb0e1d603a](https://github.com/facebook/react-native/commit/cb0e1d603aa4439a4d4804ad2987e4cb1f9bbf90) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Make `mHybridData` thread safe ([7929551623](https://github.com/facebook/react-native/commit/7929551623d4e3fbd849500d795755d0c41fdbbd)) +- Exit early from layout in textview if text layout is null ([8dfc3bcda1](https://github.com/facebook/react-native/commit/8dfc3bcda1e77fc982bc98da20dc129c23d8cc77) by [@ShikaSD](https://github.com/ShikaSD)) +- Fix `NullPointerException` caused by race condition in `ReactInstanceManager.getViewManagerNames` method ([fb386fccdd](https://github.com/facebook/react-native/commit/fb386fccddfe381fd6af5656c13fac802bffd316) by [@mdvacca](https://github.com/mdvacca)) +- Pressable ripple subsequent press coordinates. ([961b00d8c0](https://github.com/facebook/react-native/commit/961b00d8c0117750ce147c0b27c59af93f64b65c) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) +- TouchableNativeFeedback ripple starts on previous touch location. ([d85d72d0d9](https://github.com/facebook/react-native/commit/d85d72d0d9143693f73cef24c8e5bbb4d539a620) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) +- Fix Crash in `ViewProps.isLayoutOnly` ([e6b9508f12](https://github.com/facebook/react-native/commit/e6b9508f12ffd732d773ddcf9c2f633b0eca4232) by [@javache](https://github.com/javache)) +- Fixed a crash when updating `snapToOffsets` to a null value ([ba387b91d3](https://github.com/facebook/react-native/commit/ba387b91d3c7c9c1acd4b08f07fcd45629f3edfb) by [@maxoumime](https://github.com/maxoumime)) +- Adding `setAccessible` to `ReactImageManager` to allow screenreader announce Image accessibilityState of "disabled" ([333b46c4b0](https://github.com/facebook/react-native/commit/333b46c4b0ddee286e6d1d4b971fe8554a5c14cb) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fixed Android library builds with react.gradle file ([88f0676ae4](https://github.com/facebook/react-native/commit/88f0676ae49fd629331495101248c8e13423aed2) by [@Legion2](https://github.com/Legion2)) + +#### iOS specific + +- Fix deadlock on `RCTi18nUtil` ([fcead14b0e](https://github.com/facebook/react-native/commit/fcead14b0effe2176a5d08ad50ee71e48528ddbd) by [@Saadnajmi](https://github.com/Saadnajmi)) +- Avoid re-encoding images when uploading local files ([f78526ce3d](https://github.com/facebook/react-native/commit/f78526ce3d4004eb4bf8ca5178ca7e2c1c9abc1a) by [@arthuralee](https://github.com/arthuralee)) +- content is reset when emoji is entered at the max length ([f3b8d4976f](https://github.com/facebook/react-native/commit/f3b8d4976f8608c2cda1f071923f14b6d4538967)) +- Use `actionName` in accessibility event callback ([fed6ad5bad](https://github.com/facebook/react-native/commit/fed6ad5badb4196a1895370fc81c522572cb34b4) by [@ShikaSD](https://github.com/ShikaSD)) + +## v0.65.2 + +### Fixed + +- For Android, general fixes to Appearance API and also fixes AppCompatDelegate.setDefaultNightMode(). For iOS, now works correctly when setting window.overrideUserInterfaceStyle ([25a2c608f7](https://github.com/facebook/react-native/commit/25a2c608f790f42cbc4bb0a90fc06cc7bbbc9b95) by [@mrbrentkelly](https://github.com/mrbrentkelly)) + +## v0.65.1 + +### Changed + +- Set `react-test-renderer` to `17.0.2` in the template ([d272880](https://github.com/facebook/react-native/commit/d27288044e94a248982f596e9885d55d066bc72e) by [@@rickhanlonii](https://github.com/@rickhanlonii)) + +### Fixed + +- Resolve `NODE_BINARY` after finding the right path to node ([d75683](https://github.com/facebook/react-native/commit/d75683ac943205d64dd4142cca713ab2356094b8) by [@santiagofm](https://github.com/santiagofm)) + +#### Android specific + +- `ColorProps` with value null should be defaultColor instead of transparent ([842bcb902e](https://github.com/facebook/react-native/commit/842bcb902ed27928255b60cb20524e9318d9bf70) by [@hank121314](https://github.com/hank121314)) +- Android Gradle Plugin 7 compatibility ([06e31c748f](https://github.com/facebook/react-native/commit/06e31c748fe87a866dbaf4d0c2019e76ec00e309) by [@dulmandakh](https://github.com/dulmandakh)) + +## v0.65.0 + +### Highlights + +- Hermes 0.8.1. Please see the highlighted changes from its [0.8.0](https://github.com/facebook/hermes/releases/tag/v0.8.0) and [0.8.1](https://github.com/facebook/hermes/releases/tag/v0.8.1) release notes. +- `react-native-codegen` version `0.0.7` is now needed as a `devDependency` in the `package.json`. + +### Breaking Changes + +#### iOS specific + +- Replace `flipper_post_install` with `react_native_post_install` hook. Will automatically detect if Flipper is enabled. ([42dde12aac](https://github.com/facebook/react-native/commit/42dde12aac81208c4e69da991f4e08b9e62d18f6) by [@grabbou](https://github.com/grabbou)) + +### Added + +- Add `onPressIn` & `onPressOut` props to Text ([1d924549ca](https://github.com/facebook/react-native/commit/1d924549cad75912191005c8f68dd73e15b07183) by [@adrienharnay](https://github.com/adrienharnay)) +- Stabilize `RootTagContext`. And temporarily export both `unstable_RootTagContext` and `RootTagContext` ([9d489354ae](https://github.com/facebook/react-native/commit/9d489354ae373614b20cd91f588eb25743686ee0) by [@nadiia](https://github.com/nadiia)) +- Implement `sendAccessibilityEvent` in the React(Fabric/non-Fabric) renderer ([99b7052248](https://github.com/facebook/react-native/commit/99b7052248202cee172e0b80e7ee3dfb41316746) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Re-added `localeIdentifier` to `I18nManager` constants ([6b91ae73cd](https://github.com/facebook/react-native/commit/6b91ae73cdf096e15a3235ae76276f9d7fb12f7b) by [@acoates-ms](https://github.com/acoates-ms)) +- Add PressabilityPerformanceEventEmitter ([c4c0065b00](https://github.com/facebook/react-native/commit/c4c0065b0009ced0049c5abc4dddd327ac638928) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Added `displayName` to some RN contexts to make them more easy to differentiate when debugging. ([68a476103a](https://github.com/facebook/react-native/commit/68a476103a95be77f4fc7c582e52cc94946de1b4) by [@bvaughn](https://github.com/bvaughn)) +- Add `displayName` to `TouchableHighlight` and `TouchableOpacity` ([c4e40b81c0](https://github.com/facebook/react-native/commit/c4e40b81c01d061c189a7d28a4f56a588c3d1aea) by [@brunohkbx](https://github.com/brunohkbx)) +- Added context to URL's error messages when the feature is not implemented ([452240bafa](https://github.com/facebook/react-native/commit/452240bafa970578144aedaea0223e17863d2d26) by [@Crash--](https://github.com/Crash--)) +- Add a `stickyHeaderHiddenOnScroll` option to keep the sticky header hidden during scrolling down, and only slide in when scrolling up ([ffba25c648](https://github.com/facebook/react-native/commit/ffba25c648152021dd3fb9e79afd8cade7008d05)) +- Added `debugName` parameter to `renderApplication` to use as the display name for the React root tree ([eeb36f4709](https://github.com/facebook/react-native/commit/eeb36f470929c2fdd8e1ed69898a5ba9144b8715) by [@rubennorte](https://github.com/rubennorte)) +- Adding support for `cancelOnBackground` for UserFlow ([0d4985900b](https://github.com/facebook/react-native/commit/0d4985900b52d5def22fce4371c2259ee65368ee) by [@dmitry-voronkevich](https://github.com/dmitry-voronkevich)) +- Introducing RuntimeScheduler module ([eb13baf2a6](https://github.com/facebook/react-native/commit/eb13baf2a687b53dde04b9a336f18629d94f4b79) by [@sammy-SC](https://github.com/sammy-SC)) +- Roll out TurboModule Promise Async Dispatch ([5c4f145e33](https://github.com/facebook/react-native/commit/5c4f145e33d92969f8a86284360a5a2f09308500) by [@RSNara](https://github.com/RSNara)) + +#### Android specific + +- Add `getRecommendedTimeoutMillis` to AccessibilityInfo ([d29a7e7a89](https://github.com/facebook/react-native/commit/d29a7e7a89f4e5e3489e9723979426bb1b6f0674) by [@grgr-dkrk](https://github.com/grgr-dkrk)) +- TalkBack now announces "unselected" when changing `accessibilityState.selected` to false. ([73bc96ecf9](https://github.com/facebook/react-native/commit/73bc96ecf9a16d420533c12e9e1812ffe21c10a2) by [@yungsters](https://github.com/yungsters)) +- Fbjni version bump to 0.0.3 ([24f9f75bf6](https://github.com/facebook/react-native/commit/24f9f75bf66b8f32a117ba9f9dea3c65b35b1e00) by [@IvanKobzarev](https://github.com/IvanKobzarev)) +- Add `onFocus` and `onBlur` for Pressable on Android. ([cab4da7288](https://github.com/facebook/react-native/commit/cab4da728814bf9d3c0cc7c9921e982bfc090730)) +- Introduce API to allow applications to register `TurboModuleManagerDelegates` with `ReactInstanceManager` ([eb7e89e286](https://github.com/facebook/react-native/commit/eb7e89e2864e941b4a21d55a7403a6028e9a26a2) by [@RSNara](https://github.com/RSNara)) +- Added convenience methods to simplify native Event classes and ease migrations ([72d0ddc16f](https://github.com/facebook/react-native/commit/72d0ddc16f2f631003c3486e0a59e50c145ec613) by [@JoshuaGross](https://github.com/JoshuaGross)) + +#### iOS specific + +- High contrast dynamic color options for dark and light mode. ([4b9d9dda27](https://github.com/facebook/react-native/commit/4b9d9dda270acd4e0314f40490c699ffd0f6e30e) by [@birkir](https://github.com/birkir)) +- Adds an ability to retrieve the notifications authorization status from JavaScript side. ([b86e52a9ec](https://github.com/facebook/react-native/commit/b86e52a9ec9ec828388eb4a717a3782a54c7b3d9)) +- Added reset method to `RCTFabricSurface` to help with reloads ([53858ceaa3](https://github.com/facebook/react-native/commit/53858ceaa3beab02726b1bd6e125e506477d445e) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Allow `RCTRootView` to be initialized with a frame ([00bc09c8f7](https://github.com/facebook/react-native/commit/00bc09c8f76879eb1f9a92a6a643191da9355de8) by [@appden](https://github.com/appden)) +- Allow for configuring the `NSURLSessionConfiguration` ([58444c74f5](https://github.com/facebook/react-native/commit/58444c74f5c18b74e88a6c1cd0f059fe434c1a21) by [@hakonk](https://github.com/hakonk)) +- Use react-native-codegen in iOS app template ([e99b8bbb40](https://github.com/facebook/react-native/commit/e99b8bbb404f8cd1f11b6c7998083be530d7b8a4) by [@hramos](https://github.com/hramos)) + +### Changed + +- Bump Flipper + Bump hermes (#31872 by [@Titozzz](https://github.com/Titozzz)) +- Show warning when native module without `addListener` or `removeListeners` is passed to `NativeEventEmitter` ([114be1d217](https://github.com/facebook/react-native/commit/114be1d2170bae2d29da749c07b45acf931e51e2) by [@rubennorte](https://github.com/rubennorte)) +- Disable `accessibilityState` when the `TouchableWithoutFeedback` is `disabled`. ([697164077c](https://github.com/facebook/react-native/commit/697164077c362cfa9a384b0f4e246d6bd9c470ba) by [@carloscuesta](https://github.com/carloscuesta)) +- Upgraded `react-devtools-core dependency` to 4.12.0 ([5a2693d78f](https://github.com/facebook/react-native/commit/5a2693d78f1a886f0aa5b7f86830d3ddb54a57e9) by [@bvaughn](https://github.com/bvaughn)) +- Set disabled `accessibilityState` when `TouchableHighlight` is disabled ([f69e096bb4](https://github.com/facebook/react-native/commit/f69e096bb4df67474351786f674b1bb1e42d3363) by [@Naturalclar](https://github.com/Naturalclar)) +- Add checks and logs to for better error handling ([ea1f9531f0](https://github.com/facebook/react-native/commit/ea1f9531f00b5cd834e03f58cdfa117a93634624)) +- CreateAnimatedComponent: removed deprecated lifecycles usage ([ba61267015](https://github.com/facebook/react-native/commit/ba61267015567bf180dd3272a295dc262b3e2c97) by [@nadiia](https://github.com/nadiia)) +- Hide caret in the `TextInput` during test runs. ([397bfa6ad7](https://github.com/facebook/react-native/commit/397bfa6ad7dff71f4b6d27ac17acc76fe8a6bbb5) by [@nadiia](https://github.com/nadiia)) +- Use `usePressability` hook in TextInput ([c4aa411ee3](https://github.com/facebook/react-native/commit/c4aa411ee374f2320343b900f1f8b24a47b633c9) by [@nadiia](https://github.com/nadiia)) +- `Keyboard` no longer inherits from `NativeEventEmitter`, so it no longer implements `removeAllListeners`, and `removeSubscription`. ([1049835b50](https://github.com/facebook/react-native/commit/1049835b504cece42ee43ac5b554687891da1349) by [@yungsters](https://github.com/yungsters)) +- `AppState` no longer inherits from `NativeEventEmitter`, so it no longer implements `addListener`, `removeAllListeners`, and `removeSubscription`. ([6f22989e92](https://github.com/facebook/react-native/commit/6f22989e920246a2cd611b93e170024d89903027) by [@yungsters](https://github.com/yungsters)) +- `DevSettings` no longer inherits from `NativeEventEmitter` ([70cd569e7e](https://github.com/facebook/react-native/commit/70cd569e7e4cceac81023eae4ea5089cff2f9b59) by [@yungsters](https://github.com/yungsters)) +- LogBox will not initially collapse stack frames if every frame would be collapsed. ([88a41f180c](https://github.com/facebook/react-native/commit/88a41f180c315bc55e05d77ddc3fc671ad8630e6) by [@yungsters](https://github.com/yungsters)) +- Update package name warning of deprecated modules ([34e1b0ef98](https://github.com/facebook/react-native/commit/34e1b0ef981559adc09cd9f994bef9584f1c82b7) by [@Naturalclar](https://github.com/Naturalclar)) +- Update react-native-codegen to 0.0.7 ([cd6c9f3273](https://github.com/facebook/react-native/commit/cd6c9f3273fbe41052c4ec8512d3b1129daf149b) by [@Naturalclar](https://github.com/Naturalclar)) +- Update template devDependencies ([652e3953f4](https://github.com/facebook/react-native/commit/652e3953f48938580e1bf8ea1ba70105997e59d2) by [@Bardiamist](https://github.com/Bardiamist)) +- Don't minify JS bundle by default when using hermes ([1a67dda668](https://github.com/facebook/react-native/commit/1a67dda668c71d961a4bb3b0cdf6aa22c0e5c138) by [@janicduplessis](https://github.com/janicduplessis)) +- Migrate warnings in index.js to point to new lean core repos ([4421a64ac1](https://github.com/facebook/react-native/commit/4421a64ac1ea9df3827fb99194c8576a0750beab) by [@Naturalclar](https://github.com/Naturalclar)) +- Update Flipper to 0.93.0 ([06c33e9abe](https://github.com/facebook/react-native/commit/06c33e9abe6ed51b1c8bba03982ebce2b6da3860) by [@mweststrate](https://github.com/mweststrate)) +- Update Flipper to 0.91.1, fixed iOS build support for i386, `use_flipper!()` will no longer need custom overrides to build with XCode 12.5 ([4246c75d0d](https://github.com/facebook/react-native/commit/4246c75d0d5b9dccbe0fd5ecec66b4cc0331f815) by [@mweststrate](https://github.com/mweststrate)) +- Find node on m1 via homebrew node managers ([4d40b53c12](https://github.com/facebook/react-native/commit/4d40b53c12c8ad52760c63cacde417ee876bdfb1) by [@danilobuerger](https://github.com/danilobuerger)) +- Clean up EventObjectPropertyType ([0e46080847](https://github.com/facebook/react-native/commit/0e46080847595fb7577b18042c932db958bc0959) by [@RSNara](https://github.com/RSNara)) +- `Appearance.addChangeListener` now returns an `EventSubscription`. ([305b4253c2](https://github.com/facebook/react-native/commit/305b4253c2a9ed4d71be33e02cb12b6d570e2fb1) by [@yungsters](https://github.com/yungsters)) +- `Dimensions.addEventListener` now returns an `EventSubscription`. ([c47a03563d](https://github.com/facebook/react-native/commit/c47a03563db72d1580bf87b7729bd22ce6ca63dd) by [@yungsters](https://github.com/yungsters)) +- Updated react-native-community/cli to v6 (hence updating metro to 0.66) ([0d32aef3aa](https://github.com/facebook/react-native/commit/0d32aef3aa9a75b00d99503b8e4f502c52380dea) by [@Titozzz](https://github.com/Titozzz)) +- Reflect Hermes release version from HermesBadge ([c54aeccf1a](https://github.com/facebook/react-native/commit/c54aeccf1a8e16240e400d783dda5ec07fcf3808) by [@Huxpro](https://github.com/Huxpro)) + +#### Android specific + +- Modified `NativeEventEmitter` to also use the passed native module to report subscriptions on Android ([f5502fbda9](https://github.com/facebook/react-native/commit/f5502fbda9fe271ff6e1d0da773a3a8ee206a453) by [@rubennorte](https://github.com/rubennorte)) +- RefreshControl.size prop changed its type to string, the valid values are: 'default' and 'large' ([dd60414578](https://github.com/facebook/react-native/commit/dd604145781ac07c8db8d9100043bd76f6d6e913), [65975dd28d](https://github.com/facebook/react-native/commit/65975dd28de0a7b8b8c4eef6479bf7eee5fcfb93) by [@mdvacca](https://github.com/mdvacca)) +- TouchableNativeFeedback: sync disabled prop with accessibilityState ([88f2356eed](https://github.com/facebook/react-native/commit/88f2356eedf71183d02cde0826c8a0c6910f83dd) by [@kyamashiro](https://github.com/kyamashiro)) +- Rename `hasActiveCatalystInstance` to `hasActiveReactInstance` ([dfa8eb0558](https://github.com/facebook/react-native/commit/dfa8eb0558338f18ea01f294a64d355f6deeff06)) +- Record latest error type in dev support ([423453e105](https://github.com/facebook/react-native/commit/423453e1050c9aedda2df050a5ee6d40e7c82031)) +- Passing accessibility state in button so it can announce disabled in talkback ([5889cbebe3](https://github.com/facebook/react-native/commit/5889cbebe392dd19c6ce0cfd5fa1f725ece1060a) by [@huzaifaaak](https://github.com/huzaifaaak)) +- Fixed issue that causes HorizontalScrollView to shift to the right when a TextInput is selected and keyboard pops up ([b9b23e1ab1](https://github.com/facebook/react-native/commit/b9b23e1ab138189d2a4c22b13ba6ad8f8957579e) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Fixed jumpy RTL horizontal ScrollViews. If you have Android-specific JS hacks for handling RTL in ScrollViews, you probably can/probably want to remove them, because they should be reliable now and require fewer hacks. ([fc032cd8d8](https://github.com/facebook/react-native/commit/fc032cd8d889d828edad3ea4b735205092cf0d40) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Add a new check to avoid calling this method ([2b708560fc](https://github.com/facebook/react-native/commit/2b708560fc002c26f0b09f09cfa451827a3425ac)) +- Clipping subviews has been temporarily disabled in HorizontalScrollView in RTL mode. Minor/negligible perf impact. ([da8ed6b625](https://github.com/facebook/react-native/commit/da8ed6b6252fd53a83f14ab6da7e9b467f12ffe1) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Change StatusBar style handling strategy ([7324b92dc4](https://github.com/facebook/react-native/commit/7324b92dc45679d3b38526378b7d3e78ad082641)) +- Clean listeners during destroy of `ReactContext` ([d79212120b](https://github.com/facebook/react-native/commit/d79212120b7168015d3d0225ef372ed851a230fa) by [@mdvacca](https://github.com/mdvacca)) +- Bump buildToolsVersion to 30.0.2, ([5d01110b53](https://github.com/facebook/react-native/commit/5d01110b5370f884907b6dbdc56773f03518a54d) by [@dulmandakh](https://github.com/dulmandakh)) +- Initial replacement of jcenter with mavenCentral. ([704dd2812f](https://github.com/facebook/react-native/commit/704dd2812f7b8c79971274cc9e4c717e56847ac0) by [@ShikaSD](https://github.com/ShikaSD)) +- Remove developer tool guard for android ([c7d28bca30](https://github.com/facebook/react-native/commit/c7d28bca308c1654c576df9a0328a3116ed65d54)) +- Bump Android compileSdkVersion and targetSdkVersion from 29 to 30 ([55c8833817](https://github.com/facebook/react-native/commit/55c8833817c3e9cf9882a712c8b9946a262df231), [c7efd5b369](https://github.com/facebook/react-native/commit/c7efd5b369aa7605a1017791440735ab72bc9fa8) by [@mdvacca](https://github.com/mdvacca)) +- Upgrade jsc-android to 250230.2.1 ([341f061ce3](https://github.com/facebook/react-native/commit/341f061ce3ae057f3a958654e0ec3a9c4c8211ad) by [@Kudo](https://github.com/Kudo)) +- Bump Gradle to 6.9, Android Gradle Plugin to 4.2.1 ([547b4c92e4](https://github.com/facebook/react-native/commit/547b4c92e4743f5b5816297f48a608ace9de6bb5) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump gradle wrapper to 6.8.3 ([7258afeea3](https://github.com/facebook/react-native/commit/7258afeea38949dc408c0af79924f6f36f7ade84) by [@dulmandakh](https://github.com/dulmandakh)) +- Bumping OkHttp from 4.9.0 to 4.9.1. ([6caec9d91f](https://github.com/facebook/react-native/commit/6caec9d91fe71bcd80d670218d752c4f251bde81) by [@gedeagas](https://github.com/gedeagas)) +- Bumping OkHttp from v3 to v4. ([8207e97f91](https://github.com/facebook/react-native/commit/8207e97f9174a04e319431193c0f63d47a093c44) by [@arazabishov](https://github.com/arazabishov)) +- Update Okhttp to version 3.14.19 ([6bfd89d277](https://github.com/facebook/react-native/commit/6bfd89d27724f2aac602fa2acbf4753950f4152e) by [@LukasFPV](https://github.com/LukasFPV)) +- Bump Fresco to 2.5.0 ([8fa8934011](https://github.com/facebook/react-native/commit/8fa8934011e4d9f1f7a49c8519fcc97f30a5c74b) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Fresco to 2.3.0 ([280f524b49](https://github.com/facebook/react-native/commit/280f524b491e7a36bb9f9a26e354bb8e125375ed) by [@dulmandakh](https://github.com/dulmandakh)) + +#### iOS specific + +- Give RCTNetworking handler provider block RCTModuleRegistry ([4c5182c1cc](https://github.com/facebook/react-native/commit/4c5182c1cc8bafb15490adf602c87cb5bf289ffd) by [@RSNara](https://github.com/RSNara)) +- Give RCTImageURLLoader's loader/decoder provider blocks RCTModuleRegistry ([af6bcfa3ab](https://github.com/facebook/react-native/commit/af6bcfa3ab0ef6e1b0f669dda6cd7d6a5e8975ba) by [@RSNara](https://github.com/RSNara)) +- Make RCTTurboModule `getTurboModule`: required ([e0b8f5080f](https://github.com/facebook/react-native/commit/e0b8f5080f814ba2a75807ed6d7f2944aab98d7e) by [@RSNara](https://github.com/RSNara)) +- Update React.podspec to require cocoapods >= 1.10.1 ([b50b7e3a19](https://github.com/facebook/react-native/commit/b50b7e3a191dfa95aa122c259e0df8699cbaccae) by [@sunnylqm](https://github.com/sunnylqm)) +- Fix glog pod install with Xcode 12 ([8a5fd8ea95](https://github.com/facebook/react-native/commit/8a5fd8ea95678a0b4423db2cbcbefc1a33595813) by [@dulmandakh](https://github.com/dulmandakh)) +- Only show Dev Menu on shake if RN view is visible ([7186c4de4f](https://github.com/facebook/react-native/commit/7186c4de4fc76e87fa1386f2839f178dd220a02b) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- `progressViewOffset` prop of `RefreshControl` and `VirtualizedList` now works on iOS ([310a6bcf4b](https://github.com/facebook/react-native/commit/310a6bcf4ba7ca162d3ba1c03e0ab07ff41f9ead) by [@davidbiedenbach](https://github.com/davidbiedenbach)) +- Roll out TurboModule block copy ([5275895af5](https://github.com/facebook/react-native/commit/5275895af5136bc278c0c5eb07ae93e395c5b29b) by [@RSNara](https://github.com/RSNara)) +- Add instructions to template/ios/Podfile for enabling hermes ([a326a30e32](https://github.com/facebook/react-native/commit/a326a30e322f6cdff880734aafe965b299febb8d) by [@SConaway](https://github.com/SConaway)) + +### Deprecated + +- `EventEmitter#removeSubscription` is now deprecated. ([cb6cbd12f8](https://github.com/facebook/react-native/commit/cb6cbd12f80152b4ce742f37e2e6eefadf89d927) by [@yungsters](https://github.com/yungsters)) +- It is now deprecated to pass a constructor argument to `EventEmitter(...)`. ([14f7a2b707](https://github.com/facebook/react-native/commit/14f7a2b70754c92804d746959d1ff091bf49af69) by [@yungsters](https://github.com/yungsters)) +- Deprecate `AccessibilityInfo.removeEventListener`. ([003d63d6e5](https://github.com/facebook/react-native/commit/003d63d6e501411f870ff5dbef819ad2aca20974) by [@yungsters](https://github.com/yungsters)) +- Deprecate `Linking.removeEventListener`. Instead, call `remove()` on the subscription returned by `Linking.addEventListener`. ([6d1aca806c](https://github.com/facebook/react-native/commit/6d1aca806cee86ad76de771ed3a1cc62982ebcd7), [035718ba97](https://github.com/facebook/react-native/commit/035718ba97bb44c68f2a4ccdd95e537e3d28690c) by [@yungsters](https://github.com/yungsters)) +- Old Native method to create ScrollEvent has been deprecated and will be removed at some point in the (distant) future ([62f0dee235](https://github.com/facebook/react-native/commit/62f0dee2353b14ce1524dc62de5e1d2f1883a089) by [@JoshuaGross](https://github.com/JoshuaGross)) + +#### Android specific + +- Deprecate `NativeModule.onCatalystInstanceDestroy()` for `NativeModule.invalidate()` ([18c8417290](https://github.com/facebook/react-native/commit/18c8417290823e67e211bde241ae9dde27b72f17) by [@RSNara](https://github.com/RSNara)) +- Mark `hasActiveCatalystInstance()` as Deprecated ([1b50722a7e](https://github.com/facebook/react-native/commit/1b50722a7e84cd8acffd3f0f84d77057e1e0d955)) + +### Removed + +- Stabilize `RootTagContext` ([9b98edcd01](https://github.com/facebook/react-native/commit/9b98edcd0155a4a8a1f71d19e565c485910a6137) by [@nadiia](https://github.com/nadiia)) +- Removed `getNode()` from animated component refs. ([b914153286](https://github.com/facebook/react-native/commit/b914153286ea537d4a57ff934e63e07172c576a0) by [@yungsters](https://github.com/yungsters)) +- Remove legacy context API usage in AppContainer ([17be3a0032](https://github.com/facebook/react-native/commit/17be3a0032c181a100efc7af17b7366a3d636c52) by [@nadiia](https://github.com/nadiia)) +- Removed `AccessibilityInfo.fetch`, use `isScreenReaderEnabled` instead. ([d831134d51](https://github.com/facebook/react-native/commit/d831134d514c5db6be1ee35cc7e9994b777179c1) by [@yungsters](https://github.com/yungsters)) +- Remove unused VR-only props ([95f7c791c5](https://github.com/facebook/react-native/commit/95f7c791c56b527dadbe0b4ec7a1be5af12d7afe) by [@Simek](https://github.com/Simek)) +- Removed `RCTDeviceEventEmitter.sharedSubscribers`. ([3af0c84aa5](https://github.com/facebook/react-native/commit/3af0c84aa5d1633f058ea3e7aef0d125fe33e01d) by [@yungsters](https://github.com/yungsters)) +- Moved `ScrollResponder.Mixin` methods into ScrollView to Remove ScrollResponder.js ([099f67cf8a](https://github.com/facebook/react-native/commit/099f67cf8aa290592092cfa0cb4e938d0543b696) by [@kacieb](https://github.com/kacieb)) +- `NativeEventEmitter` no longer inherits from `EventEmitter`, so it no longer implements `removeListener` and `removeSubscription`. Instead, use the `remove()` method on the subscription object returned by `addListener`. ([d39643b9de](https://github.com/facebook/react-native/commit/d39643b9de11c6b44984166ede34a7f44de76fe5) by [@yungsters](https://github.com/yungsters)) +- `RCTDeviceEventEmitter` no longer throws for `StatusBar`, `Keyboard`, and `AppState` events. However, you are still recommended to use the more appropriate modules for listening to these events. ([c8c975f0d7](https://github.com/facebook/react-native/commit/c8c975f0d7b8a57e9e90373a2be4d630ed9dd65e) by [@yungsters](https://github.com/yungsters)) +- Removed second optional argument of `NativeEventEmitter` constructor ([f5f47879b8](https://github.com/facebook/react-native/commit/f5f47879b8320a9934914cb8ce7a72269840a83a) by [@yungsters](https://github.com/yungsters)) +- Removed warning on Android for `setTimeout` with delays greater than 1 minute. ([480dabd665](https://github.com/facebook/react-native/commit/480dabd66547a60522249eda203a3eb1934b02e5) by [@yungsters](https://github.com/yungsters)) +- Removed `Touchable.TOUCH_TARGET_DEBUG` property. ([ef765d423c](https://github.com/facebook/react-native/commit/ef765d423cb188957a9fb2fd92c62b0efe8a36a6) by [@yungsters](https://github.com/yungsters)) + +#### Android specific + +- Remove okhttp3 proguard rules ([b4c9f13fe7](https://github.com/facebook/react-native/commit/b4c9f13fe794283d76766c1baef87888d174cb1c) by [@doniwinata0309](https://github.com/doniwinata0309)) +- Remove filter pills ([5cf4ab8dd2](https://github.com/facebook/react-native/commit/5cf4ab8dd28b5a336d7af29d295ede51f0d19587) by [@suminkimm](https://github.com/suminkimm)) +- Remove `ReactFragmentActivity` class. ([2798e7172b](https://github.com/facebook/react-native/commit/2798e7172b01b9e2dbe2937d0163f98ab29230cf) by [@dulmandakh](https://github.com/dulmandakh)) +- Remove jcenter ([70da640946](https://github.com/facebook/react-native/commit/70da64094608f5f2e3c554ed719e9aad624e3459) by [@dulmandakh](https://github.com/dulmandakh)) + +#### iOS specific + +- Removed event methods except `addListener` from `Networking` ([a81b7d18fa](https://github.com/facebook/react-native/commit/a81b7d18fa65a727539c6c7ea17f787673d3c889) by [@yungsters](https://github.com/yungsters)) +- Delete deprecated "live reloading" setting ([b512beb0c4](https://github.com/facebook/react-native/commit/b512beb0c497158f9c861fcc16af960655b1feb5) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Remove iOS10/tvOS10 support ([f2c6279ca4](https://github.com/facebook/react-native/commit/f2c6279ca497b34d5a2bfbb6f2d33dc7a7bea02a), [a1d626739d](https://github.com/facebook/react-native/commit/a1d626739d95d6cbbb1be169b93952cdd1465486) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Remove iOS10/tvOS10 support from remaining podfiles ([f0faa7843c](https://github.com/facebook/react-native/commit/f0faa7843c5a0e9041edb6e77fd6631335ab2b12) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Delete RCTTurboModuleManagerDelegate `getTurboModule:initParams` ([c4c34a1237](https://github.com/facebook/react-native/commit/c4c34a1237ec584c667c62358dc577174bf11033) by [@RSNara](https://github.com/RSNara)) + +### Fixed + +- Don't disconnect DevTools WebSocket connection on Cmd+D ([60a18c138c](https://github.com/facebook/react-native/commit/60a18c138c51d3adcfeba7785315fc222cdfeb35) by [@bvaughn](https://github.com/bvaughn)) +- For native components that accept color arrays, invalid elements will now fallback to transparent with a console error. ([bb6cd56fae](https://github.com/facebook/react-native/commit/bb6cd56fae4118f44ae47fd6978710a22f9e1510) by [@yungsters](https://github.com/yungsters)) +- Fixes usage of std::thread in runtime executor ([75d9ba733f](https://github.com/facebook/react-native/commit/75d9ba733f4a041e4320098b52903f69747df02b) by [@asklar](https://github.com/asklar)) +- Fix sticky header not sticking on first render in ScrollView ([921c9ff165](https://github.com/facebook/react-native/commit/921c9ff165d47a73e9978df918b1761b95f9979d) by [@kacieb](https://github.com/kacieb)) +- Fix ScrollView `getInnerViewNode` and `getInnerViewRef` ref methods ([6e36d046a3](https://github.com/facebook/react-native/commit/6e36d046a313c7961cc2f91e0422f4bf29005eb6) by [@vshab](https://github.com/vshab)) +- Fix stalling UI due to a bug in KeyboardAvoidingView ([67309277fe](https://github.com/facebook/react-native/commit/67309277fe588c4dd64fe0c680d1d00d2f3fb2b6) by [@sammy-SC](https://github.com/sammy-SC)) +- Avoid eating clicks/taps into ScrollView when using physical keyboard ([6d2a527984](https://github.com/facebook/react-native/commit/6d2a5279841886a9a14f82057202bf8950c3f917) by [@NickGerleman](https://github.com/NickGerleman)) +- Fix nested FlatList not firing `onScrollDragEnd` and `onMomentum` methods ([46be292f67](https://github.com/facebook/react-native/commit/46be292f671c70aac4ecc178c96e3a2a6a3d16da) by [@kacieb](https://github.com/kacieb)) +- Fix race condition in Debug Inspector shutdown ([d021000b9e](https://github.com/facebook/react-native/commit/d021000b9e358a9379ca5d6208f24757c0c8ce97) by [@MartinSherburn](https://github.com/MartinSherburn)) +- Fixes layout of nodes with `YGDisplayNone` and `YGPositionTypeAbsolute` ([b15f8a30e7](https://github.com/facebook/react-native/commit/b15f8a30e75b54a8de5cc9456aaa07ebe8d8a176) by [@rozele](https://github.com/rozele)) +- Fix changes of View visibilities ([4076293aa1](https://github.com/facebook/react-native/commit/4076293aa1059005704576530d8fe948b85e6a6d) by [@mdvacca](https://github.com/mdvacca)) +- Fix: save connection url as class variable ([8facc865ab](https://github.com/facebook/react-native/commit/8facc865ab2ec032da34f6f755ee8870ee4741aa) by [@sirpy](https://github.com/sirpy)) +- Fix Hermes build on folly version 2021.04.26.00 ([8eceee744e](https://github.com/facebook/react-native/commit/8eceee744ed9fee1eb2402f6b13bb606f6046f62) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix disabled handling for Text ([33ff4445dc](https://github.com/facebook/react-native/commit/33ff4445dcf858cd5e6ba899163fd2a76774b641) by [@lunaleaps](https://github.com/lunaleaps)) +- Fix disabled prop not disabling onPress for voice assistant ([1c7d9c8046](https://github.com/facebook/react-native/commit/1c7d9c8046099eab8db4a460bedc0b2c07ed06df) by [@kacieb](https://github.com/kacieb)) +- Fix unsafe cast and detect overflow in MapBuffer. ([e69f1c9f50](https://github.com/facebook/react-native/commit/e69f1c9f50c64bfcaeb684d763f02b9ccadec960)) +- Fix(deps): bump metro to 0.66.2 + dedup ([e40f58272d](https://github.com/facebook/react-native/commit/e40f58272d51a40e7b5fa77c14767ddaf9ecc006) by [@Titozzz](https://github.com/Titozzz)) + +#### Android specific + +- Fixed crash when using style `borderRadius: any` with `backgroundColor: null` ([42b6e6682c](https://github.com/facebook/react-native/commit/42b6e6682ce0fa9ac6eb5c1bf8ef0c224d2d80c0)) +- Fix font weight numeric values ([3827ca6171](https://github.com/facebook/react-native/commit/3827ca61714b699c866e17d58b4697dde86e3d00) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix wrong ripple color on Switch component ([1b0683533a](https://github.com/facebook/react-native/commit/1b0683533a07aa8875b4d494d8c2a3d18ef69438) by [@rnike](https://github.com/rnike)) +- Fix Selected State does not announce when TextInput Component selected on Android ([7ee2acc6c8](https://github.com/facebook/react-native/commit/7ee2acc6c84c9ea6a51908495a6f14a26f346b29) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix layout bug in ReactTextView. ([dec1b6ba15](https://github.com/facebook/react-native/commit/dec1b6ba15df8f255d30b696a7c08ef543d1d19c)) +- Fix source build on Windows machines vol. 2 ([c37d49492b](https://github.com/facebook/react-native/commit/c37d49492b20c3815ca10133f971755f659b1b6a)) +- Make NativeModules immediately initializable ([2bf866e401](https://github.com/facebook/react-native/commit/2bf866e4018ea72c1f1c92c806db85378c801fb7) by [@RSNara](https://github.com/RSNara)) +- Restore `android_hyphenationFrequency` on `Text`. ([1433ed6333](https://github.com/facebook/react-native/commit/1433ed6333162189730d6f92cf80f3077ac69120) by [@yungsters](https://github.com/yungsters)) +- Display the `testID` as the `resource-id` for black-box testing frameworks ([381fb395ad](https://github.com/facebook/react-native/commit/381fb395ad9d2d48717a5d082aaedbecdd804554) by [@jdeff](https://github.com/jdeff)) +- Fix support for blobs larger than 64 KB ([f00e348ca7](https://github.com/facebook/react-native/commit/f00e348ca7f031c3577b1335a3163bc3e4eb4b41) by [@tomekzaw](https://github.com/tomekzaw)) +- Fix building React Android on Windows. ([5dc15222b2](https://github.com/facebook/react-native/commit/5dc15222b256e32517df553c5fe7f6f5b7d0d31f)) +- Fix race-condition on the initialization of ReactRootViews ([74a756846f](https://github.com/facebook/react-native/commit/74a756846fdab1ef7d183c4df3069a23fcd0d49e) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Animated images without loop no longer animate twice ([17aa1e320e](https://github.com/facebook/react-native/commit/17aa1e320e75393d46a54ec0fee8b068eeef142f) by [@comvenger-brandon](https://github.com/comvenger-brandon)) +- Allow PlatformColor to work with border colors ([c974cbff04](https://github.com/facebook/react-native/commit/c974cbff04a8d90ac0f856dbada3fc5a75c75b49) by [@danilobuerger](https://github.com/danilobuerger)) +- RCTSurfaceHostingView default background color is now consistent with RCTRootView ([f31497354b](https://github.com/facebook/react-native/commit/f31497354b72ad51b452a4b8bd3b70de16830025) by [@fkgozali](https://github.com/fkgozali)) +- Invalidate TurboModules with infra-generated method queues on their method queues ([497eb578ab](https://github.com/facebook/react-native/commit/497eb578ab32614744a4ef61d7a6bca0d4251885) by [@RSNara](https://github.com/RSNara)) +- Fix RefreshControl layout when removed from window ([e67811e7a6](https://github.com/facebook/react-native/commit/e67811e7a6df0937ed61d3367ab10fab95b31bfa) by [@janicduplessis](https://github.com/janicduplessis)) +- Tab Accessibility Role had incorrect localization string ([80a10953f9](https://github.com/facebook/react-native/commit/80a10953f9de8cc251e9b8c1e59a173af87febb9) by [@adkenyon](https://github.com/adkenyon)) +- Incorrect ScrollView offset on update ([a4526bcc3f](https://github.com/facebook/react-native/commit/a4526bcc3f89f5b9d3f86c814ade8f55c86e819e) by [@rnike](https://github.com/rnike)) +- Modal's `onDismiss` prop will now be called successfully. ([d85d5d2e19](https://github.com/facebook/react-native/commit/d85d5d2e1974b463318e4c86da29a5ccdd60a977) by [@kkoudev](https://github.com/kkoudev)) +- Fix DatePicker sizing issue ([84d55868e8](https://github.com/facebook/react-native/commit/84d55868e8b4e5a555d324c6162b8e38571524d8) by [@sammy-SC](https://github.com/sammy-SC)) +- First press not working after pull to refresh ([c4950610e4](https://github.com/facebook/react-native/commit/c4950610e40f2019c828bc99e29769cd4089c217) by [@rnike](https://github.com/rnike)) +- Fix Codegen silently failing when Yarn is not installed, or when Yarn v2 is active. ([07e4953514](https://github.com/facebook/react-native/commit/07e4953514636aaadc5915944cc64c12028516f2) by [@ivanmoskalev](https://github.com/ivanmoskalev)) +- Make codegen more reliable on iOS ([12fccdeea3](https://github.com/facebook/react-native/commit/12fccdeea33324b8ddaa3ac0e2dbf81a44ca1eb2) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix crash in RCTCoreModulesClassProvider during quit ([2f62c2892d](https://github.com/facebook/react-native/commit/2f62c2892d9979f80752350d1b949f2770511956) by [@appden](https://github.com/appden)) +- Fix an issue calling stopSurface in bridgeless mode before surface is started ([81096901a8](https://github.com/facebook/react-native/commit/81096901a8a6da75744cef7b663ccea2ff9c4c09)) +- Move hermes to a separate podspec ([0959ff36d1](https://github.com/facebook/react-native/commit/0959ff36d1f3264e117021eb1999d0bdb71377c3) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix cli bundle platform for Mac Catalyst in `react-native-xcode.sh` ([b496a531e0](https://github.com/facebook/react-native/commit/b496a531e0b4b5d828077b0e7dff43dd28fed5eb) by [@robertying](https://github.com/robertying)) +- Fix `prefetchImageWithMetadata` redbox([f27e305056](https://github.com/facebook/react-native/commit/f27e305056152ff9ad7aeb9018bf289d51719eb9) by [@p-sun](https://github.com/p-sun)) +- Roll out RCTNetworking extraneous NativeModule call removal ([0e0d2e84f5](https://github.com/facebook/react-native/commit/0e0d2e84f56ea233e72d980ff6bd9797df250553) by [@RSNara](https://github.com/RSNara)) +- Fix Hermes + no Flipper build on Xcode 12.5 ([b9243e00e3](https://github.com/facebook/react-native/commit/b9243e00e30be057a45af6ed1916af4328c458e4) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix(hermes): fixed hermes build on iOS ([59abb5f378](https://github.com/facebook/react-native/commit/59abb5f378e116288cdea2f619de0c128bb0b0eb) by [@Titozzz](https://github.com/Titozzz)) +- Fix builds on Xcode 12.5 ([36b58a824e](https://github.com/facebook/react-native/commit/36b58a824ea20daa22fe7c528a3bf0ff4e6a4cb5) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix running React Native project with Xcode 12 in Release on iPhone Simulator ([fdcacd7f76](https://github.com/facebook/react-native/commit/fdcacd7f76ea8ca6dafda32ac431c8adc7bdad00) by [@grabbou](https://github.com/grabbou)) + +## v0.64.3 + +### Fixed + +- For Android, general fixes to Appearance API and also fixes AppCompatDelegate.setDefaultNightMode(). For iOS, now works correctly when setting window.overrideUserInterfaceStyle ([25a2c608f7](https://github.com/facebook/react-native/commit/25a2c608f790f42cbc4bb0a90fc06cc7bbbc9b95) by [@mrbrentkelly](https://github.com/mrbrentkelly)) + +## v0.64.2 + +### Changed + +- Find-node.sh supports Homebrew on M1 ([502b819049](https://github.com/facebook/react-native/commit/502b81904998b800f2d960bb4a8e244988c72958) by [@dulmandakh](https://github.com/dulmandakh)) +- Refactor UIManagerHelper.getUIManager to return null when there's no UIManager registered ([b0e8c1eac0](https://github.com/facebook/react-native/commit/b0e8c1eac0a9edda12ecfa264209a8b3222afe27) by [@mdvacca](https://github.com/mdvacca)) + +### Fixed + +- Fix ScrollViewStickyHeader to push up header above it ([d754bdefc6](https://github.com/facebook/react-native/commit/d754bdefc68ff757ac2b5a2ffa38d5aad234d484) by [@kacieb](https://github.com/kacieb)) + +#### Android specific + +- Font family is not apply when secureTextEntry is true ([cda77c77dd83cba07e6c2e56e938c3e4f7faf8fc](https://github.com/facebook/react-native/commit/cda77c77dd83cba07e6c2e56e938c3e4f7faf8fc) by [@hank121314](https://github.com/hank121314)) +- Dimension update events are now properly sent following orientation change ([a6a4d3365f17332e367c34357a07a73f97d6ec83](https://github.com/facebook/react-native/commit/a6a4d3365f17332e367c34357a07a73f97d6ec83) by [@ajpaulingalls](https://github.com/ajpaulingalls)) + +## v0.64.1 + +### Fixed + +#### iOS specific + +- Fixes to ensure Xcode 12.5 builds ([cf8a364767](https://github.com/facebook/react-native/commit/cf8a364767df830d7255339741350bb53ab1a68a), [1c4ac48a55](https://github.com/facebook/react-native/commit/1c4ac48a55cf0703f0c8a32cbb07474a2d126f3e) and [76f45d35e7](https://github.com/facebook/react-native/commit/76f45d35e710f84a1cc44c90bc128494bc4280ce) by [@kelset](https://github.com/kelset)) + +### Security + +- Update validateBaseUrl to use latest regex ([33ef82ce6d](https://github.com/facebook/react-native/commit/33ef82ce6dfd31e1f990d438c925a0e52723e16b) by [@FBNeal](https://github.com/FBNeal)) + +## v0.64.0 + +### Breaking + +- Enable `inlineRequires` by default in new projects' `metro.config.js`. Gives a performance benefit but slightly different JS execution order ([959365a902](https://github.com/facebook/react-native/commit/959365a90216ee14d0f8b5d2f4653a1ab4c10d7e) by [@GantMan](https://github.com/GantMan)) +- Minimum supported Node version changed to 12 ([4b92e2e53d](https://github.com/facebook/react-native/commit/4b92e2e53d9c79f5b5858b3eb0d1654da79a4a68) by [@safaiyeh](https://github.com/safaiyeh)) +- Remove deprecated `CameraRoll` API (deprecated in 0.61) ([824d3a9770](https://github.com/facebook/react-native/commit/824d3a977057b336d81237ec3cec3a49a9d5e34d) by [@seanyusa](https://github.com/seanyusa)) +- Remove deprecated `CheckBox` component (deprecated in 0.60) ([dff17effe5](https://github.com/facebook/react-native/commit/dff17effe54dc58dda19fcc81ebacbd8f46e9005) by [@poteto](https://github.com/poteto)) +- Removed `DEPRECATED_sendUpdatedChildFrames` prop from `ScrollView` component (deprecated in 0.47) ([345d0c1abb](https://github.com/facebook/react-native/commit/345d0c1abb1afe937a06982c4328caee57820832) by [@ZHUANGPP](https://github.com/ZHUANGPP)) +- On `Image`, `onLoad` event objects' `source.url` is now renamed to `source.uri`. ([74ab8f6e5a](https://github.com/facebook/react-native/commit/74ab8f6e5a61999f1132351ff52df43c91360a09) by [@yungsters](https://github.com/yungsters)) + +#### Android specific + +- Remove support of Android API levels 16 through 20. The new minSDK version will be 21+ moving forward. ([973198667d](https://github.com/facebook/react-native/commit/973198667d7bbbf3b5d8890fc0a53dc99d0bce18), [25a40cbc61](https://github.com/facebook/react-native/commit/25a40cbc61e6c718d8cdea6d67fd82c6309963b1), [f829722b54](https://github.com/facebook/react-native/commit/f829722b54b34f145c41a95edfa5b522c837f9fc), [b133427778](https://github.com/facebook/react-native/commit/b13342777856bc4024d8489de790e7f90cd6b33b), [9b34aa261c](https://github.com/facebook/react-native/commit/9b34aa261c272d96829c9a7d5b166594b3162f9d), and [79d0a7d711](https://github.com/facebook/react-native/commit/79d0a7d71119122d2a2b9954e6038bbee119b8fa) by [@mdvacca](https://github.com/mdvacca); [49f10fd2e5](https://github.com/facebook/react-native/commit/49f10fd2e526b64294777357ab2fef8880739f26) and [a17ff44adc](https://github.com/facebook/react-native/commit/a17ff44adcf003dd4e4ef2301e1f80b77913f712) by [@JoshuaGross](https://github.com/JoshuaGross); [dd4298a377](https://github.com/facebook/react-native/commit/dd4298a3770eee7f66846ef0cc4c41a628b7bf01) by [@safaiyeh](https://github.com/safaiyeh)) +- Fix ReadableArray null annotations. Possibly breaking change for Kotlin apps. ([d76556543f](https://github.com/facebook/react-native/commit/d76556543f96f4d739be3a708b8f6314bb32cc87) by [@dulmandakh](https://github.com/dulmandakh)) +- On `Image`, `onLoad` and `onError` event objects will no longer have an extra `uri` property. ([74ab8f6e5a](https://github.com/facebook/react-native/commit/74ab8f6e5a61999f1132351ff52df43c91360a09) by [@yungsters](https://github.com/yungsters)) +- Deletes the method PlayTouchSound method from UIManagerModule, this method was moved to the SoundManagerModule class. ([d0c4c5eaf9](https://github.com/facebook/react-native/commit/d0c4c5eaf90430c7004621d1596c5f2a55ad03e0) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Remove `calculateChildFrames` from `RCTScrollView` ([62aa84a325](https://github.com/facebook/react-native/commit/62aa84a3257bd3c513df3fcb4b4eaa350ecf77bb) by [@PeteTheHeat](https://github.com/PeteTheHeat)) + +### Deprecated + +#### Android specific + +- Deprecated method `UIManagerModule.getUIImplementation`. This method will not be part of the new architecture of React Native. ([fe79abb32c](https://github.com/facebook/react-native/commit/fe79abb32ca3425ff689b7641d9200461ea8166d) by [@mdvacca](https://github.com/mdvacca)) + +### Added + +- Adds the Hermes runtime bytecode version number to the JS bundle requestURL. This allows Metro with Bytecode to work with prebuilt binaries. ([34c405462f](https://github.com/facebook/react-native/commit/34c405462f890afbccdfeaa7804791f7e9bcaa83)) +- TextInput now supports `onPressIn` and `onPressOut`. ([b7b0e23202](https://github.com/facebook/react-native/commit/b7b0e232028723794af4c79fc6366c483ae2350b) by [@yungsters](https://github.com/yungsters)) +- Allow setting a custom performance logger in XMLHttpRequest ([57b10f759e](https://github.com/facebook/react-native/commit/57b10f759efed786b46cfe082367f929aa2925d3) by [@rubennorte](https://github.com/rubennorte)) +- Add mock for `DevSettings` to jest preset ([a50f736bb6](https://github.com/facebook/react-native/commit/a50f736bb6ade9ea9caae45e41ca4b92f6707b17) by [@MarcoScabbiolo](https://github.com/MarcoScabbiolo)) +- Added Inspector overlay support for Pressable ([8ac467c51b](https://github.com/facebook/react-native/commit/8ac467c51b94c82d81930b4802b2978c85539925) by [@yungsters](https://github.com/yungsters)) +- Introduce NativeModulePerfLogger ([0486640571](https://github.com/facebook/react-native/commit/0486640571c89a0ce067c0437655a6b375308bcd) by [@RSNara](https://github.com/RSNara)) +- Add default `titlePlaceholder` in template configuration. ([8ffa180d80](https://github.com/facebook/react-native/commit/8ffa180d80b9c9acb76a0631b5a709d2c0adcd86) by [@Esemesek](https://github.com/Esemesek)) +- Modified `renderApplication` to forward `initialProps` to `WrapperComponent` ([4f5a092bf6](https://github.com/facebook/react-native/commit/4f5a092bf68a0cd825328ce4a1e6bb41a8fad2e3) by [@rubennorte](https://github.com/rubennorte)) +- Add warning to `VirtualizedList` when incorrectly using nested Lists or custom scroll components ([7f2515ece8](https://github.com/facebook/react-native/commit/7f2515ece8833f7a8adba025ef544013f89ae26f) by [@kacieb](https://github.com/kacieb)) +- Add native module for loading split JS bundles in development ([fca3a39da5](https://github.com/facebook/react-native/commit/fca3a39da5f1c31514e8969738e7b2c2d22bc230) by [@makovkastar](https://github.com/makovkastar)) +- Added `listenerCount()` to `DeviceEventEmitter` and `NativeEventEmitter`. ([b11d6ecbb8](https://github.com/facebook/react-native/commit/b11d6ecbb8bb2f0d6f423be6775e587f4e9b1c4d) by [@yungsters](https://github.com/yungsters)) + +#### Android specific + +- Upgrade Hermes to version 0.7 and turn on ES6 Proxy support ([776a415d98](https://github.com/facebook/react-native/commit/776a415d98dffd04b11200812a32204aa1c5e157) and [bb003816a3](https://github.com/facebook/react-native/commit/bb003816a389b8655c53fa34444417c14516459c) by [@Huxpro](https://github.com/Huxpro), [a28dd38909](https://github.com/facebook/react-native/commit/a28dd3890974d699070f08ab43781324411e6f5c) by [@janicduplessis](https://github.com/janicduplessis)) +- Add support for `shadowColor` on API level >= 28 ([cfa4260598](https://github.com/facebook/react-native/commit/cfa42605989eee5a9de42bdb1259fb7f4d9451fb) by [@IjzerenHein](https://github.com/IjzerenHein)) +- Add `android_hyphenationFrequency` prop to Text component ([0fda91ffff](https://github.com/facebook/react-native/commit/0fda91ffffa4972ebe58e3d0b610692a1286eaa1) and [7d8aeb4955](https://github.com/facebook/react-native/commit/7d8aeb4955a4101ca7e8e486f935309c21ab76ff) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Add `accessibilityHint` to TouchableNativeFeedback ([72285d808d](https://github.com/facebook/react-native/commit/72285d808dfce748287a19e2620d58517a5f76e7) by [@CMDadabo](https://github.com/CMDadabo)) +- Adds support for the `onProgress` event on `Image` ([fa0e6f8051](https://github.com/facebook/react-native/commit/fa0e6f8051d2208af467b789a2a9306ec7ddad76) by [@yungsters](https://github.com/yungsters)) +- ScrollView now supports `contentOffset` ([ed29ba13f9](https://github.com/facebook/react-native/commit/ed29ba13f97f240c91fdf6c0ef3fb601046697b9) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Add an explicit NDK version to Android template ([18ffe12203](https://github.com/facebook/react-native/commit/18ffe12203d03b4e960d61d7bb50cd02bba94663) by [@safaiyeh](https://github.com/safaiyeh)) +- Exposed `getFlex` method as part of ReactShadowNode API ([6570f7887b](https://github.com/facebook/react-native/commit/6570f7887b8824705ae09b5653d631428e17bc5f) by [@mdvacca](https://github.com/mdvacca)) +- Add `\*.hprof` files to gitignore ([69ce9c21d4](https://github.com/facebook/react-native/commit/69ce9c21d433a23ffb9934062b46fa64277ee255) by [@enesozturk](https://github.com/enesozturk)) +- Move `DevSettingsActivity` from main to debug ([d8e6c45782](https://github.com/facebook/react-native/commit/d8e6c45782a5c9132bb7ec315fe0b9ba3999e830) by [@invalid-email-address](https://github.com/invalid-email-address)) + +#### iOS specific + +- `PlatformColor`: add missing `clearColor` ([b7167c23fc](https://github.com/facebook/react-native/commit/b7167c23fc052f8d9f8c27a7f4ad9c5cdf51281e) by [@Simek](https://github.com/Simek)) +- Update template to Xcode 12 ([6685aba462](https://github.com/facebook/react-native/commit/6685aba462699c696cb6ac95626b9592deb292fc) by [@janicduplessis](https://github.com/janicduplessis)) +- Add `importantForAccessibility` to `AccessibilityProps` ([fd660fd0c5](https://github.com/facebook/react-native/commit/fd660fd0c50a0acca730bd1ecd427e574bbe81c7) by [@ZHUANGPP](https://github.com/ZHUANGPP)) +- Allow hotkeys to be used without command key ([f2b9ec7981](https://github.com/facebook/react-native/commit/f2b9ec798172db76dfb55f390e1fcea90dd341da) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Add `disableButtonsIndices` option to `ActionSheetIOS` component ([a7c1c5aff2](https://github.com/facebook/react-native/commit/a7c1c5aff24671bba609caeb82092a8de3d3b232) by [@lukewalczak](https://github.com/lukewalczak)) +- Add `showSoftInputOnFocus` to `TextInput` ([d54113d8c4](https://github.com/facebook/react-native/commit/d54113d8c4bcd0e0c7a09acca60819724eb69926) by [@gurs1kh](https://github.com/gurs1kh)) +- Added hostname to loading banner. ([96999339b6](https://github.com/facebook/react-native/commit/96999339b6a7aeabd0cd706ef7736fd91d9ecf80) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Allow iOS `PlatformColor` strings to be ObjC or Swift UIColor selectors ([25793eab56](https://github.com/facebook/react-native/commit/25793eab56217a9961620761ea65ec2fcb97dcb0) by [@tom-un](https://github.com/tom-un)) +- Add Dark Mode support to loading banner ([94c45af136](https://github.com/facebook/react-native/commit/94c45af136f44245b5f2e56bded60c8ebd9b1235) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Allow image loaders to enable/disable image telemetry ([e37708dfb6](https://github.com/facebook/react-native/commit/e37708dfb605dd9ee9f4b2dac5d841d98b7d376c) by [@p-sun](https://github.com/p-sun)) +- Add `RCTDevSplitBundleLoader` native module ([ad879e50bc](https://github.com/facebook/react-native/commit/ad879e50bcd51caca76b1073720f2b63df485ff1) by [@cpojer](https://github.com/cpojer)) + +### Changed + +- Update flipper to 0.75.1 ([3399896ae7](https://github.com/facebook/react-native/commit/3399896ae756719b238e837001077a46508849be) by [@janicduplessis](https://github.com/janicduplessis)) +- Refined Flow type for `Text` component. ([a911efaecd](https://github.com/facebook/react-native/commit/a911efaecd005237816ddb480218eb5388460d16) by [@yungsters](https://github.com/yungsters)) +- Changed type definition of IPerformanceLogger from object to interface ([b90f4d978f](https://github.com/facebook/react-native/commit/b90f4d978fa27e37926d9f4a1d13c9168243798c) by [@rubennorte](https://github.com/rubennorte)) +- Removed `fbjs` dependency from `react-native`. ([54e19a6b7f](https://github.com/facebook/react-native/commit/54e19a6b7f217ffc0611e660f2a6b1a8ad14775b) by [@yungsters](https://github.com/yungsters)) +- Refined `ImageSource` Flow type for array-variant and headers. ([a0dc252dc8](https://github.com/facebook/react-native/commit/a0dc252dc89699f7bd0d733642b98762d0db423a) by [@yungsters](https://github.com/yungsters)) +- Some warnings changed to use `console.warn` without the "Warning:" prefix. ([982272932c](https://github.com/facebook/react-native/commit/982272932cee3be599076bd18b290bc812285533) by [@yungsters](https://github.com/yungsters)) +- Core/Differ: detect and optimize reparenting ([1e4d8d902d](https://github.com/facebook/react-native/commit/1e4d8d902daca8e524ba67fc3c1f4b77698c4d08) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Improve "not a registered callable module" error message ([e27d656ef3](https://github.com/facebook/react-native/commit/e27d656ef370958c864b052123ec05579ac9fc01) by [@vonovak](https://github.com/vonovak)) +- Use `VirtualizedList`'s `onEndReachedThreshold` default value when null is provided ([10b4b9505a](https://github.com/facebook/react-native/commit/10b4b9505a51f8bf3fbc12d296a087b784a9201a) by [@fatalsun](https://github.com/fatalsun)) +- Migrate large amount of modules to flow strict and strict-local ([4409642811](https://github.com/facebook/react-native/commit/4409642811c787052e0baeb92e2679a96002c1e3) by [@rubennorte](https://github.com/rubennorte)) +- Enable exact objects by default in the project template Flow config ([050a7dd019](https://github.com/facebook/react-native/commit/050a7dd019be435b848de0a86030599d83f8791d) by [@rubennorte](https://github.com/rubennorte)) +- Minor fix in Hermes Inspector cli tool help message ([6ffb983f83](https://github.com/facebook/react-native/commit/6ffb983f83afdee5d9290c683c5060d2a959818d)) +- Updated the React Hooks ESLint Plugin in the community ESLint config ([ac87e90fa5](https://github.com/facebook/react-native/commit/ac87e90fa517676440c1adf9575cb48f90de8069) by [@gaearon](https://github.com/gaearon)) +- Don't scroll to `initialScrollIndex` if `contentOffset` is provided to the same `VirtualizedList` ([3346ac7f96](https://github.com/facebook/react-native/commit/3346ac7f96d2fd3f77dca5acb283b28e02ad21fa) by [@markv](https://github.com/markv)) +- Migrated `VirtualizedList` legacy context implementation to `React.Context`. ([7bd694fc6f](https://github.com/facebook/react-native/commit/7bd694fc6f4bb027b6d7ee04034cad41a43e5695) by [@yungsters](https://github.com/yungsters)) +- Changed Flow type of `BackHandler` to be more specific. ([a903d1b86a](https://github.com/facebook/react-native/commit/a903d1b86ab56163abcdcb584f335949ba0c85fc) by [@Naturalclar](https://github.com/Naturalclar)) +- Updated transitive dependency `kind-of` to 6.0.3 to resolve vulnerability ([abde0154ba](https://github.com/facebook/react-native/commit/abde0154ba4247d2c9f1451b5de8b3cba1abd316) by [@TheSavior](https://github.com/TheSavior)) +- Upgrade eslint-config dependencies. ([93019dc190](https://github.com/facebook/react-native/commit/93019dc19072776053a88f9ab595e435b83fead0) by [@wcandillon](https://github.com/wcandillon)) +- Upgrade to Jest 25 ([f248ba1c8b](https://github.com/facebook/react-native/commit/f248ba1c8b15a12a0c590ce8211855cde31defe8) by [@cpojer](https://github.com/cpojer)) +- Use `React.Children.count` for counting children of `TextInput` ([92160f3144](https://github.com/facebook/react-native/commit/92160f3144dcfa510ff14b5f2eb231643f107af9) by [@vonovak](https://github.com/vonovak)) +- Annotate components in QPL logging using ImageAnalyticsTagContext ([60b7a3085c](https://github.com/facebook/react-native/commit/60b7a3085c0d83c126023b98e666ecda6f769454) by [@p-sun](https://github.com/p-sun)) +- Upgrade to React 17 ([24bca492c3](https://github.com/facebook/react-native/commit/24bca492c349ab90d40f9444df0f477145a4c311) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Made promise polyfill conditionalized on Hermes ([0a28b34dac](https://github.com/facebook/react-native/commit/0a28b34dacb91a7e74cd5feec59cf8f8fb0487c9) by [@Huxpro](https://github.com/Huxpro)) +- Flow: Remove type union in PickeriOS/PickerNativeComponent ([3113e47b9b](https://github.com/facebook/react-native/commit/3113e47b9bc92e3b0efb96db776f650848093dfc) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Flow: export ColorValue from StyleSheet instead of StyleSheetTypes ([0a67133124](https://github.com/facebook/react-native/commit/0a6713312467d3f5b5dc993e91db9e7b1aa4fc8c)) +- Forward URL parameters from main bundle to hot reloaded bundles ([b4785e5144](https://github.com/facebook/react-native/commit/b4785e514430dc3ba45ed6d136ec63574be88e26) by [@motiz88](https://github.com/motiz88)) +- Add package name / bundle ID to bundle URL in development ([9b5359133b](https://github.com/facebook/react-native/commit/9b5359133b46b16be200e37dba0b03d82b73b4a0) by [@motiz88](https://github.com/motiz88)) + +#### Android specific + +- Bump Gradle Wrapper to 6.7 ([8988a073b4](https://github.com/facebook/react-native/commit/8988a073b48df0f0cd4a7126edf1a421f4537d58), [5bc67b658e](https://github.com/facebook/react-native/commit/5bc67b658e581e0176deb7ed95b51a5c1cbe65c2), and [3a8559b86c](https://github.com/facebook/react-native/commit/3a8559b86c3c0b0ab6d6c6904c6efd97ab2c7b38) by [@friederbluemle](https://github.com/friederbluemle); [e559aee642](https://github.com/facebook/react-native/commit/e559aee64275126eaa135486e6bf09138be70f4d) and [e9fd93f53f](https://github.com/facebook/react-native/commit/e9fd93f53f8b14f921578cd401b3a6529e4e0c9f) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Android Gradle Plugin to 4.1.0 ([cf8368f204](https://github.com/facebook/react-native/commit/cf8368f2046ae1ff0f6b02bb6857eeeff8f57d7d) and [553fb8b28d](https://github.com/facebook/react-native/commit/553fb8b28d0ad332d75a944d244832be3390b6ba) by [@friederbluemle](https://github.com/friederbluemle), [dfa9db49e3](https://github.com/facebook/react-native/commit/dfa9db49e34c6f54c04148b877de938bf103a059) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Okio to 1.17.5 ([1e78e0655d](https://github.com/facebook/react-native/commit/1e78e0655d53ac947f523bcadf9c5339ab07bbb8) by [@dulmandakh](https://github.com/dulmandakh)) +- Make Android versionCodeOverride for new apps using the template human-readable ([e1bf515ae8](https://github.com/facebook/react-native/commit/e1bf515ae8e77fb24f76037d9f22e903799fb637) by [@gedeagas](https://github.com/gedeagas)) +- Bump SoLoader to 0.9.0 ([7465239230](https://github.com/facebook/react-native/commit/7465239230881f453d64364d51272f28614c8653) by [@dulmandakh](https://github.com/dulmandakh)) +- Update Okhttp to version 3.12.12 ([0f6fcb2c27](https://github.com/facebook/react-native/commit/0f6fcb2c2788dc7150f6c3673a8f4f9d8f929441) by [@halaei](https://github.com/halaei)) +- Update Android build tools to 29.0.3 ([e629e94b46](https://github.com/facebook/react-native/commit/e629e94b466ebbd5924b1d4493c026004dad707d) by [@friederbluemle](https://github.com/friederbluemle)) +- ViewCommands on Android now execute earlier, as a perf optimization. ([c6b9cc36da](https://github.com/facebook/react-native/commit/c6b9cc36da4f7d190d05122048aa4ada9c152b73) by [@JoshuaGross](https://github.com/JoshuaGross)) +- Effect of `blurRadius` now more closely matches other platforms. ([64860972be](https://github.com/facebook/react-native/commit/64860972be828fb601acbef11b4c2dbc672dee8a) by [@yungsters](https://github.com/yungsters)) +- Migrate Android tests to Robolectric v4 ([6a78b32878](https://github.com/facebook/react-native/commit/6a78b32878aea1b0dac98ff36378fb9392d4aeb1) by [@jselbo](https://github.com/jselbo), [d373a8d88c](https://github.com/facebook/react-native/commit/d373a8d88c30af910133d97ae973d256c4479929) and [18f7abae07](https://github.com/facebook/react-native/commit/18f7abae07b8ea60c7530a5d9f34541c50f5edd9) by [@fkgozali](https://github.com/fkgozali)) +- Get ripple drawables by id instead of by name ([c8ed2dbbb2](https://github.com/facebook/react-native/commit/c8ed2dbbb287deed05a8782fb8665c1edf45bbac) by [@vonovak](https://github.com/vonovak)) +- `TextInput`: Set `caretHidden` default value to `true` on Xiaomi devices to fix the crash ([b5b4a70410](https://github.com/facebook/react-native/commit/b5b4a7041027fd767850a564b5d80fa4a98ba2a2)) +- Update loading banner text and colors ([6afc984e81](https://github.com/facebook/react-native/commit/6afc984e8187ac91f780f125dad4421576131c83) by [@makovkastar](https://github.com/makovkastar)) +- Declare all attrs used in res targets ([05abbd245c](https://github.com/facebook/react-native/commit/05abbd245c2326b12d24698bb13007a7ce11e586) by [@IanChilds](https://github.com/IanChilds)) + +#### iOS specific + +- Upgraded JSI with a new HERMES_ENABLE_BITCODE flag ([311d4e9ef0](https://github.com/facebook/react-native/commit/311d4e9ef080aa429f840236cc23c013c0ae644c) by [@grabbou](https://github.com/grabbou)) +- Remove main queue execution of constantsToExport in NativeModules ([d7ac21cec5](https://github.com/facebook/react-native/commit/d7ac21cec5492e180fbf3817af7be64ab121cb75) by [@RSNara](https://github.com/RSNara)) +- Updated loading banner messages and color ([3729fe8de0](https://github.com/facebook/react-native/commit/3729fe8de0109c80014f6c20fae8b949b3628de2) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Speed up loading banner animations ([3fb37b4326](https://github.com/facebook/react-native/commit/3fb37b4326090def3aea43bd8189a0df648ccb34) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Shrink loading bar down to not cover safe area. ([f0dfd35108](https://github.com/facebook/react-native/commit/f0dfd35108dd3f092d46b65e77560c35477bf6ba) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Build macOS framework and add CocoaPods podspec ([ffa3d7f638](https://github.com/facebook/react-native/commit/ffa3d7f638c820dc208320193e6ba65667d751eb) by [@alloy](https://github.com/alloy)) +- Set `NSAllowsArbitraryLoads` to `false` by default in template ([7b61a968fd](https://github.com/facebook/react-native/commit/7b61a968fd774a6ca2196a731b6cec4282ab25cc) by [@wddwycc](https://github.com/wddwycc)) + +### Removed + +- `Text.viewConfig` is no longer exported. ([06ce643565](https://github.com/facebook/react-native/commit/06ce64356594a921cd9ae4f71c15dd56dd0e53a3) by [@yungsters](https://github.com/yungsters)) +- Removed `once()` and `removeCurrentListener()` from `DeviceEventEmitter` and `NativeEventEmitter`. ([87a2e29f59](https://github.com/facebook/react-native/commit/87a2e29f5928c2e09ac9a98c54732d5f697d8e61) by [@yungsters](https://github.com/yungsters)) +- Removed tvOS related files from the template ([df03228a61](https://github.com/facebook/react-native/commit/df03228a61881cdfa520fa6d8a9d9cfb6e77fdde) by [@Naturalclar](https://github.com/Naturalclar)) + +#### Android specific + +- Remove undocumented ColorAndroid function ([411c344794](https://github.com/facebook/react-native/commit/411c3447946c18743476e7d613358233464d6f58) by [@tom-un](https://github.com/tom-un)) + +### Fixed + +- Fix handling of very deeply nested data across the bridge ([a8c90e6af4](https://github.com/facebook/react-native/commit/a8c90e6af4a4e5ac115016a3e8977ecff90e99a0) by [@mhorowitz](https://github.com/mhorowitz)) +- Prevent TypeError in TaskQueue when cancelling a started but not resolved promise. ([14042fb76f](https://github.com/facebook/react-native/commit/14042fb76fee3573529d590ec6f8ad216aa0b820) by [@robwalkerco](https://github.com/robwalkerco)) +- Fix typo in `ActionSheetManager` invariant message ([9c353b5ab0](https://github.com/facebook/react-native/commit/9c353b5ab060be9392a7aaf437bba4ffc56d78ca) by [@sweatherall](https://github.com/sweatherall)) +- `TouchableHighlight` now correctly fires `onPress` when pressed for >500ms, when `onLongPress` is not supplied. ([bdf3c79110](https://github.com/facebook/react-native/commit/bdf3c7911007f547101d753903da11ea4ee095f9) by [@yungsters](https://github.com/yungsters)) +- `Pressability` now consistently fires `onPressIn` and `onPressOut`, even without an `onPress`. ([0c392bc405](https://github.com/facebook/react-native/commit/0c392bc4052784de7497bf7b5eaf207b02409877) by [@yungsters](https://github.com/yungsters)) +- Remove extraneous argument for `onResponderGrant` Flow type on `Text`. ([49015b0f5b](https://github.com/facebook/react-native/commit/49015b0f5bda83794b88b17dd3cbd834fa235b72) by [@yungsters](https://github.com/yungsters)) +- Prevent `ScrollView` From Stealing Responder Capture When Using Physical Keyboard ([93e7a7a70d](https://github.com/facebook/react-native/commit/93e7a7a70dc2f41fccd3c1e4cce80d92913c4243) by [@NickGerleman](https://github.com/NickGerleman)) +- Fix failure when debugging code in a browser; was caused by `performanceNow()` function. ([db474a47b7](https://github.com/facebook/react-native/commit/db474a47b70e4fa50f594f4dea8a2f531ca9fc07) by [@zerkella](https://github.com/zerkella)) +- Fix test renderer mocks to use the `displayName` more often. ([4b935ae95f](https://github.com/facebook/react-native/commit/4b935ae95f09e4a1eb1e5ac8089eb258222a0f8b) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Make sure `LogBox` is not included in production bundles ([d3b937f990](https://github.com/facebook/react-native/commit/d3b937f990012a31b8d917e220f4ed2f0a4fd2d3) by [@janicduplessis](https://github.com/janicduplessis)) +- Mark `force` as an optional property of the PressEvent object ([ad2f98df8f](https://github.com/facebook/react-native/commit/ad2f98df8f2ad8aff1dcdc11b187f35b372e3f0e) by [@Simek](https://github.com/Simek)) +- Fix invalid `event` objects from `onPressOut` in certain cases ([2c600b7c5a](https://github.com/facebook/react-native/commit/2c600b7c5a0e79bfc632b39b471e6ba774d7b0b3) by [@yungsters](https://github.com/yungsters)) +- When Hermes debugger is enabled continue to send log messages to the console ([77ef8f881f](https://github.com/facebook/react-native/commit/77ef8f881f2e4067894b412f308e2a80042c946f) by [@MartinSherburn](https://github.com/MartinSherburn)) +- Handle nullish `initialProps` correctly in `renderApplication` ([26c120c632](https://github.com/facebook/react-native/commit/26c120c6329d45e27318d82aaf5a50338bd6fa7d) by [@rubennorte](https://github.com/rubennorte)) +- Fix Flow type of Touchable{Opacity,Bounce,Highlight} being exported as `any` ([de7f69a58e](https://github.com/facebook/react-native/commit/de7f69a58ed4e18887f4b9d4d853293fb136afb7) by [@draperunner](https://github.com/draperunner)) +- Clarified the boundaries in error message of `scrollToIndex` ([78d2b3c813](https://github.com/facebook/react-native/commit/78d2b3c8138f54c2433958b0ad6b9f52ca59115a) by [@sasurau4](https://github.com/sasurau4)) +- Fix jsi cmake include dirs ([f5d00e5a29](https://github.com/facebook/react-native/commit/f5d00e5a2922d35a0b44935592da5700518c422b) by [@ryantrem](https://github.com/ryantrem)) +- Fix race condition in `KeyboardAvoidingView` ([b08fff6f86](https://github.com/facebook/react-native/commit/b08fff6f869e00c20c0dcdf7aca71284c2f276f0) by [@sammy-SC](https://github.com/sammy-SC)) +- Fix clone issue in YogaNodeJNIBase ([2707c17b07](https://github.com/facebook/react-native/commit/2707c17b0727f241d404f4a21090021c27c66f2c) by [@pasqualeanatriello](https://github.com/pasqualeanatriello)) +- Fix "Cannot read property 'getNativeScrollRef' of undefined" in createAnimatedComponent ([629e10e91b](https://github.com/facebook/react-native/commit/629e10e91b728c4251f1ed78a50df62820ce0dc4) by [@sammy-SC](https://github.com/sammy-SC)) + +#### Android specific + +- Fix App Bundle/Release build missing index.android.bundle with gradle plugin 4.1.0/gradle 6.5 ([53f55001af](https://github.com/facebook/react-native/commit/53f55001afbf07494de0df064a92dfdd42f37c98) by [@tomoima525](https://github.com/tomoima525)) +- Do not crash when `ScrollView` `snapToOffsets` array is empty ([d238da71aa](https://github.com/facebook/react-native/commit/d238da71aa8cdd7ce519de617a9a200406da794c) by [@makovkastar](https://github.com/makovkastar)) +- Fixed `TextInput` not being selectable in `removeClippedSubviews` FlatLists ([12a50c0a44](https://github.com/facebook/react-native/commit/12a50c0a442b78d9095398d955bec307cfcb0f69) by [@hsource](https://github.com/hsource)) +- Make nested `Text` components accessible as links ([b352e2da81](https://github.com/facebook/react-native/commit/b352e2da8137452f66717cf1cecb2e72abd727d7) by [@ejanzer](https://github.com/ejanzer)) +- Move selection to the end of the text input on accessibility click ([f0e80ae229](https://github.com/facebook/react-native/commit/f0e80ae2292ebf7ce32666900007845724844fb5) by [@ejanzer](https://github.com/ejanzer)) +- Fix secure text entry setting to always hide text ([f19372361f](https://github.com/facebook/react-native/commit/f19372361f22201a453ff38eb69c5fa052b57474) by [@smeenai](https://github.com/smeenai)) +- Make promise NativeModule methods dispatch to NativeModules thread ([9c35b5b8c4](https://github.com/facebook/react-native/commit/9c35b5b8c4710dfe6a4b689a5565aa78ae5b37d3) by [@RSNara](https://github.com/RSNara)) +- Fix `NoSuchMethodException` when calling `DisplayMetricsHolder.initDisplayMetrics` in Android API level <= 16 (though those Android versions are no longer supported) ([35128f45d1](https://github.com/facebook/react-native/commit/35128f45d1ba97010e437423d14fa5ea0faf5fa3) by [@mdvacca](https://github.com/mdvacca)) +- Fixed error message in `DebugCorePackage.getModule` ([a71f37b951](https://github.com/facebook/react-native/commit/a71f37b951ca49c180b037ea8955851654b09afa) by [@TheWirv](https://github.com/TheWirv)) +- ScrollView, HorizontalScrollView: do not ignore `null` `contentOffset` prop ([9e85b7ad88](https://github.com/facebook/react-native/commit/9e85b7ad889900cd57cd2f82286aa8e034b0a32b) by [@vonovak](https://github.com/vonovak)) +- Picker - fix usage of setNativeSelectedPosition in onSelect ([078e386024](https://github.com/facebook/react-native/commit/078e386024474edc9b464f6c0fd8a1429e922289)) +- Fix intermittent crash of ReactSlider on Android ([32888a8b4a](https://github.com/facebook/react-native/commit/32888a8b4a9d75b9d3f6cc4578ce6a6ccd932407) by [@mdvacca](https://github.com/mdvacca)) +- Use actual constructor when throwing GradleScriptException ([8ef0f1d90b](https://github.com/facebook/react-native/commit/8ef0f1d90bbb2fa98e48ce89281718e5ac79365a)) +- Fix `skewX` transform decomposition ([797367c089](https://github.com/facebook/react-native/commit/797367c0890a38ec51cfaf7bd90b9cc7db0e97c7) by [@wcandillon](https://github.com/wcandillon)) +- Allow passing partial contentOffset to ScrollView on Android ([0348953914](https://github.com/facebook/react-native/commit/03489539146556ec5ba6ba07ac338ce200f5b0f4) by [@janicduplessis](https://github.com/janicduplessis)) +- Check if NativeModules returned from CatalystInstanceImpl.getNativeModule are null before using them. ([9263eb5d38](https://github.com/facebook/react-native/commit/9263eb5d3864a42925b699343db2c09cc8934ed0) by [@RSNara](https://github.com/RSNara)) +- Fix calculating view position within the window in split-screen mode ([b020e7c440](https://github.com/facebook/react-native/commit/b020e7c440f58dabd4cc64b72869f3ae9680ef30) by [@jakubkinst](https://github.com/jakubkinst)) +- Text layout no longer ignores parent bounds ([025be8148a](https://github.com/facebook/react-native/commit/025be8148a9abc533a8ae108e49cfd3f4512c581) by [@yungsters](https://github.com/yungsters)) +- Fixed excessive space in Text view with word-wrapping ([dda7f82261](https://github.com/facebook/react-native/commit/dda7f82261cc5684564e2c67071c13e379985308) by [@yungsters](https://github.com/yungsters)) +- `Pressable`: ripple should be applied even when borderless == false ([44ec762e41](https://github.com/facebook/react-native/commit/44ec762e41029bf43530b1ff9b36ca3512c526e2) by [@vonovak](https://github.com/vonovak)) +- Fix `ReadableNativeMap.getNullableValue` to match signature and return null instead of throwing ([1015194ba1](https://github.com/facebook/react-native/commit/1015194ba1a81eab99000d589914100e4b9ea037) by [@dulmandakh](https://github.com/dulmandakh)) +- `Picker`: set color filter so that the arrow matches the text color ([bb8d0f5732](https://github.com/facebook/react-native/commit/bb8d0f57328a20c942991f2d19d86639a7791924) by [@ejanzer](https://github.com/ejanzer)) +- `Modal`: fix crash when updating props after the activity disappeared ([7abcaafd66](https://github.com/facebook/react-native/commit/7abcaafd6600535825aa8330af7290ba8acea245) by [@mdvacca](https://github.com/mdvacca)) +- Fix crash while measuring ReactSlider in Android API < 21 ([75e6f7961f](https://github.com/facebook/react-native/commit/75e6f7961fb3f6de6afbe79d49c42ad55fba1673) by [@mdvacca](https://github.com/mdvacca)) +- Fix measureLayout function for VirtualTexts ([5c48c94f8c](https://github.com/facebook/react-native/commit/5c48c94f8c0441bc78a007f0ea0c5b2763ff6875) by [@mdvacca](https://github.com/mdvacca)) +- Smoother scrolling in ScrollView, HorizontalScrollView ([10314fe621](https://github.com/facebook/react-native/commit/10314fe621e1649654e83df197adf657e0ca8363) by [@JoshuaGross](https://github.com/JoshuaGross)) + +#### iOS specific + +- Synchronize RCTImageLoader loaders initialization ([edb6fa7979](https://github.com/facebook/react-native/commit/edb6fa79791beb804e450ca4a562a248abf730e5) by [@p-sun](https://github.com/p-sun)) +- Make sure js bundle still exists at bundle-output path ([3a41f69f9c](https://github.com/facebook/react-native/commit/3a41f69f9ce1ab778112c0727a69a753fe36c77a) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix crash in WebSocket module ([748aa13747](https://github.com/facebook/react-native/commit/748aa137472d6080427f74bb686c795b925c7d43) by [@marksinkovics](https://github.com/marksinkovics)) +- Align multi-line `TextInput` `onSubmitEditing` behavior: don't call onSubmitEditing when blurOnSubmit=false ([521b16730d](https://github.com/facebook/react-native/commit/521b16730dd07d80261086c2f33eed2a766d404e) by [@tido64](https://github.com/tido64)) +- Fix passing react native path in Podfile template ([e599d6c5d3](https://github.com/facebook/react-native/commit/e599d6c5d338c1b4d1a0d988e0d9ff83c179fb54) by [@janicduplessis](https://github.com/janicduplessis)) +- Call [RCTEventEmitter stopObserving] on correct method queue ([23717e48af](https://github.com/facebook/react-native/commit/23717e48aff3d7fdaea30c9b8dcdd6cfbb7802d5) by [@appden](https://github.com/appden)) +- Persist Enable Fast Refresh across app launches ([845e9eaafb](https://github.com/facebook/react-native/commit/845e9eaafb08b4ca87a9987e840798e0ba011676) by [@stigi](https://github.com/stigi)) +- Fix xcodebuild warnings in React-Core ([cb719a16cc](https://github.com/facebook/react-native/commit/cb719a16cc496b0cdb09d8d971b5e95cc8863b77)) +- Fix that RCTModalHostView can't be dismissed while being presented ([8933724d7d](https://github.com/facebook/react-native/commit/8933724d7d0f9ec012b2708d8e737f02f03e4a6f) by [@Mi-ZAZ](https://github.com/Mi-ZAZ)) +- Fix "'RCTBlobPlugins.h' file not found" when building iOS ([aaeffdb49a](https://github.com/facebook/react-native/commit/aaeffdb49a8412a98bb52477933fd208d1dcc096) by [@tido64](https://github.com/tido64)) +- Improved text rendering on macOS Catalyst ([694e22de84](https://github.com/facebook/react-native/commit/694e22de847e5f789b7d5ffe472b63aabbd7a5b0) by [@andymatuschak](https://github.com/andymatuschak)) +- Fixed showing Alert while closing a Modal ([f319ff321c](https://github.com/facebook/react-native/commit/f319ff321c4b7c0929b99e3ebe7e1ce1fa50b34c) by [@devon94](https://github.com/devon94)) +- Fix `refreshControl` messes up navigationBar largeTitles ([1b0fb9bead](https://github.com/facebook/react-native/commit/1b0fb9bead4d158d14df5a994423d06716b5e377) by [@yogevbd](https://github.com/yogevbd)) +- When Sec-WebSocket-Protocol header is empty vaulue, IIS server will return error 502. ([fd85b84a86](https://github.com/facebook/react-native/commit/fd85b84a863cea9f33e5b39230b27af53c1307e7) by [@bill2004158](https://github.com/bill2004158)) +- Fix multiline `TextInput` crash when inserting/removing lots of text ([15dda0ab5a](https://github.com/facebook/react-native/commit/15dda0ab5a491dcc83539f9ef32c9896be41074a) by [@tido64](https://github.com/tido64)) +- Group accessible views using the view hierarchy ([e2fd9d4f22](https://github.com/facebook/react-native/commit/e2fd9d4f22cda85c995c38875fc3a2a20a198c4a) by [@p-sun](https://github.com/p-sun)) +- Fix Flow types for StatusBar showHideTransition ([e5a8f4270e](https://github.com/facebook/react-native/commit/e5a8f4270ea71749a5ce6bd7ae198f695edb4307) by [@Simek](https://github.com/Simek)) +- Better error message when missing entry file ([e73208e2ca](https://github.com/facebook/react-native/commit/e73208e2ca59a2cf6a8a9c5e4e5b33afb5131f09) by [@petrbela](https://github.com/petrbela)) +- Fix imports in `RCTUtilsUIOverride.h` ([b7e8f66795](https://github.com/facebook/react-native/commit/b7e8f667953c2bc65c25b00968051c063a684d01) by [@Fanghao](https://github.com/Fanghao)) +- Fix skewX/skewY/perspective/matrix transforms. ([4b956fe5a6](https://github.com/facebook/react-native/commit/4b956fe5a6b3a05b1c2883efc82a95c2524aeb56) by [@wcandillon](https://github.com/wcandillon)) +- Fix module lookup race condition on bridge invalidation. ([8ad810717e](https://github.com/facebook/react-native/commit/8ad810717ee1769aa5ff6c73e0c9bfa8c43a3bac) by [@fkgozali](https://github.com/fkgozali)) +- Fix duration calculation for `RCTUIImageViewAnimated` ([12f8b2598f](https://github.com/facebook/react-native/commit/12f8b2598fa46533ea59834a0225cc9e36b20111)) +- Cap loading banner percentage at 100% ([e27542bb13](https://github.com/facebook/react-native/commit/e27542bb13d1f8f422cd307c4d43148c8bd82bc0) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Delay loading banner message to prevent flashing messages ([2b771b0129](https://github.com/facebook/react-native/commit/2b771b0129f2ef921c7cdb9c952e004f931927c3) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Do not update loading banner message while hiding the banner ([131c497aa2](https://github.com/facebook/react-native/commit/131c497aa2c081f9dfd03e45b25fb7ae388b98bd) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Search en0 through en8 for the Metro Bundler's IP address when generating iOS debug builds ([b2b23a2017](https://github.com/facebook/react-native/commit/b2b23a20170d12f6d8bf2733b93d7f9ab9c6cb15)) +- Migrate `frameInterval` to `preferredFramesPerSecond`, fixing Xcode warnings ([335f3aabe2](https://github.com/facebook/react-native/commit/335f3aabe28ec8f9b96fd695edabf0d5ab0b402a) by [@safaiyeh](https://github.com/safaiyeh)) +- Animated image should animate at the same speed regardless of framerate ([b0d0e51a77](https://github.com/facebook/react-native/commit/b0d0e51a7724dcefe3ce1c2dfb334a731b2a385c) by [@p-sun](https://github.com/p-sun)) +- Fix logging lifecycle when image is scrolled out and immediately back in ([1f95c9b62e](https://github.com/facebook/react-native/commit/1f95c9b62e306fdaf0ef351b02fb79713941259c) by [@p-sun](https://github.com/p-sun)) +- Fix image instrumentation lifecycle on image cancel ([6cba4d2006](https://github.com/facebook/react-native/commit/6cba4d20068ef4ca9b9832e4c5cf71a7e361ddbe) by [@p-sun](https://github.com/p-sun)) +- Break retain cycle in RCTLegacyViewManagerInteropCoordinator ([8f90ce26a5](https://github.com/facebook/react-native/commit/8f90ce26a55f2b1aab42d7c44b0d527321fa8c21) by [@sammy-SC](https://github.com/sammy-SC)) +- Respect port information if available from RCTBundleURLProvider ([7d44959940](https://github.com/facebook/react-native/commit/7d44959940b7f7b03feefde0e9a15382f04dad6d) by [@jimmy623](https://github.com/jimmy623)) +- Remove port from JSLocation when returning packager host ([12543d557f](https://github.com/facebook/react-native/commit/12543d557f00545a719b4dfd76cc0d0adfa37a01) by [@jimmy623](https://github.com/jimmy623)) +- Remove requestToken being nil check from [RCTNetworkTask validateRequestToken] ([ffc90c7f92](https://github.com/facebook/react-native/commit/ffc90c7f92e63e1a53ed107833e3deed492ab435) by [@sammy-SC](https://github.com/sammy-SC)) +- Remove unnecessary packager running check when saved JSLocation is empty ([bbb7bef539](https://github.com/facebook/react-native/commit/bbb7bef539f418bdb452e40987d399c9369df5a2) by [@jimmy623](https://github.com/jimmy623)) +- Check whether packager is running in RCTBundleURLProvider for saved JSLocation ([3d882495d5](https://github.com/facebook/react-native/commit/3d882495d5e4415c2ebb8f4280e18e16025e0736) by [@jimmy623](https://github.com/jimmy623)) +- Fix crash inside RCTRedBox when trying to present same UIViewController twice ([46c77dc296](https://github.com/facebook/react-native/commit/46c77dc296dfab754356cd9346a01dae8d4869f4) by [@sammy-SC](https://github.com/sammy-SC)) +- Fix outdated CocoaPods version requirement in a React.podspec ([8a6ac1fef3](https://github.com/facebook/react-native/commit/8a6ac1fef369071405a3bf14a89924c66f28d192) by [@sunnylqm](https://github.com/sunnylqm)) + +## v0.63.4 + +### Changed + +- [Maintenance] Bump detox to xcode 12 compatible version ([ccd4efac90](https://github.com/facebook/react-native/commit/ccd4efac90191e57b1dd6e7fff0da13e5764bcc4) by [@kelset](https://github.com/kelset)) + +#### Android specific + +- Bump SoLoader to 0.9.0 ([7465239230](https://github.com/facebook/react-native/commit/7465239230881f453d64364d51272f28614c8653) by [@dulmandakh](https://github.com/dulmandakh)) +- Update Okhttp to version 3.12.12 ([0f6fcb2c27](https://github.com/facebook/react-native/commit/0f6fcb2c2788dc7150f6c3673a8f4f9d8f929441) by [@halaei](https://github.com/halaei)) +- ScrollView now supports `contentOffset` ([ed29ba13f9](https://github.com/facebook/react-native/commit/ed29ba13f97f240c91fdf6c0ef3fb601046697b9) by [@JoshuaGross](https://github.com/JoshuaGross)) + +### Fixed + +#### Android specific + +- Fix ReadableNativeMap.getNullableValue to match signature ([1015194ba1](https://github.com/facebook/react-native/commit/1015194ba1a81eab99000d589914100e4b9ea037) by [@dulmandakh](https://github.com/dulmandakh)) +- Dimension update events are now properly sent following orientation change ([0e9296b95d](https://github.com/facebook/react-native/commit/0e9296b95da06789121f052e6cd6d7cac808464c) by [@ajpaulingalls](https://github.com/ajpaulingalls)) +- Font family is not apply when secureTextEntry is true. ([00d9deaf6b](https://github.com/facebook/react-native/commit/00d9deaf6ba26c605694d303bb0cb072fceae5a1) by [@hank121314](https://github.com/hank121314)) +- Fix App Bundle/Release build missing index.android.bundle with gradle plugin 4.1.0/gradle 6.5 ([53f55001af](https://github.com/facebook/react-native/commit/53f55001afbf07494de0df064a92dfdd42f37c98) by [@tomoima525](https://github.com/tomoima525)) +- ScrollView, HorizontalScrollView: do not ignore `null` `contentOffset` prop ([9e85b7ad88](https://github.com/facebook/react-native/commit/9e85b7ad889900cd57cd2f82286aa8e034b0a32b) by [@vonovak](https://github.com/vonovak)) +- SkewX transforms ([797367c089](https://github.com/facebook/react-native/commit/797367c0890a38ec51cfaf7bd90b9cc7db0e97c7) by [@wcandillon](https://github.com/wcandillon)) +- Allow passing partial contentOffset to ScrollView on Android ([0348953914](https://github.com/facebook/react-native/commit/03489539146556ec5ba6ba07ac338ce200f5b0f4) by [@janicduplessis](https://github.com/janicduplessis)) +- Set color filter so that the arrow matches the text color ([bb8d0f5732](https://github.com/facebook/react-native/commit/bb8d0f57328a20c942991f2d19d86639a7791924) by [@ejanzer](https://github.com/ejanzer)) + +#### iOS specific + +- A crash in WebSocket module ([748aa13747](https://github.com/facebook/react-native/commit/748aa137472d6080427f74bb686c795b925c7d43) by [@marksinkovics](https://github.com/marksinkovics)) +- Fixed showing Alert while closing a Modal ([f319ff321c](https://github.com/facebook/react-native/commit/f319ff321c4b7c0929b99e3ebe7e1ce1fa50b34c) by [@devon94](https://github.com/devon94)) +- Bug with skewX/skewY/perspective/matrix transforms. ([4b956fe5a6](https://github.com/facebook/react-native/commit/4b956fe5a6b3a05b1c2883efc82a95c2524aeb56) by [@wcandillon](https://github.com/wcandillon)) + +## v0.63.3 + +### Added + +#### iOS specific + +- Ability to set which configuration to enable flipper for when using use_flipper! ([dc2df75426](https://github.com/facebook/react-native/commit/dc2df754267df3909631d81c22b9fcab58dfa241) by [@nicoburns](https://github.com/nicoburns)) + +### Changed + +- Update Flipper to 0.54 ([d8b70b19b3](https://github.com/facebook/react-native/commit/d8b70b19b39ead4dd41895d666d116a43c56474e) by [@mweststrate](https://github.com/mweststrate)) +- Removed default 130ms delay from Pressability and Pressable. ([86ffb9c41e](https://github.com/facebook/react-native/commit/86ffb9c41e033f59599e01b7ad016706b5f62fc8) by [@yungsters](https://github.com/yungsters)) + +### Fixed + +#### Android specific + +- `KeyboardDidHide` wrong `screenY` coordinates with `windowTranslucentStatus=true` ([45954ac5dc](https://github.com/facebook/react-native/commit/45954ac5dccdfe05de7553a0f08c4f0e66e3d62e) by [@fabriziobertoglio1987](https://github.com/fabriziobertoglio1987)) +- Fix Xiaomi TextInput crash in native ([07a597ad18](https://github.com/facebook/react-native/commit/07a597ad185c8c31ac38bdd4d022b0b880d02859)) + +#### iOS specific + +- Prefetch images using a lower download priority ([058eeb43b4](https://github.com/facebook/react-native/commit/058eeb43b489f52183f081fb7232be683002a242) by [@p-sun](https://github.com/p-sun)) +- Fix `RCTImageLoader` not using decoders provided. ([663b5a878b](https://github.com/facebook/react-native/commit/663b5a878be9faafd13b41c222a1bc2ac7bb3a65) by [@sjchmiela](https://github.com/sjchmiela)) +- Support Swift based libraries using Xcode 12’s build system. ([6e08f84719](https://github.com/facebook/react-native/commit/6e08f84719c47985e80123c72686d7a1c89b72ed) by [@alloy](https://github.com/alloy)) +- Fix "main.jsbundle does not exist" issue ([83777cb4fb](https://github.com/facebook/react-native/commit/83777cb4fb5dda89c430b7eff9cd1f28d2577831)) +- Fixed headers in `Image.getSizeWithHeaders`. ([0bcc686c1c](https://github.com/facebook/react-native/commit/0bcc686c1cc90fd44de7a28e2f56ea20fe2f5123) by [@PaitoAnderson](https://github.com/PaitoAnderson)) + +### Security + +- Fix security issues with `@react-native-community/cli` by bumping version ([001eb7cbd6](https://github.com/facebook/react-native/commit/001eb7cbd66c7dc1a302ee2a638c1cfc164538f4) by [@alexbrazier](https://github.com/alexbrazier)) + +## v0.63.2 + +### Fixed + +- Restore Previous Behavior for StyleSheet Validation of Null/Undefined Styles ([e75557b48f](https://github.com/facebook/react-native/commit/e75557b48fbee1d136b8b7d1a78ea8f9b9467479) by [@NickGerleman](https://github.com/NickGerleman)) + +#### Android specific + +- Set LogBox windowTranslucentNavigation to false ([23036b38bc](https://github.com/facebook/react-native/commit/23036b38bc4443c8db4865e5c2b21aca7ab4f92f) by [@Ashoat](https://github.com/Ashoat)) +- Fix unable to run in debug mode on Android API < 21 ([7694b32a88](https://github.com/facebook/react-native/commit/7694b32a88078278457dd8721eb61da9c4ac0f5a) by [@Shywim](https://github.com/Shywim)) + +#### iOS specific + +- Fix image cannot show in iOS 14 ([123423c2a9](https://github.com/facebook/react-native/commit/123423c2a9258c9af25ca9bffe1f10c42a176bf3)) + +## v0.63.1 + +### Added + +- Added `minPressDuration` to `Pressable`. ([4aaf629982](https://github.com/facebook/react-native/commit/4aaf62998247bcfd8ebf369d73290096fde08012) by [@yungsters](https://github.com/yungsters)) +- Support for array buffers in the JavaScriptCore implementation of JSI ([9c32140068](https://github.com/facebook/react-native/commit/9c32140068463739b91874689f741ea9630d8c3b) by [@ryantrem](https://github.com/ryantrem)) + +#### Android specific + +- ProGuard rule for hermes ([449dc37720](https://github.com/facebook/react-native/commit/449dc37720b24d9d88661314424c9f982e70ec3a) by [@radko93](https://github.com/radko93)) + +### Fixed + +- LogBox.ignoreAllLogs() should ignore logs ([f28c7505fa](https://github.com/facebook/react-native/commit/f28c7505fa5b4a7ddf1e9311d38dfcd15e8953a2) by [@rickhanlonii](https://github.com/rickhanlonii)) + +#### Android specific + +- Fix font variant crash on Android < 4.4 ([f23feced42](https://github.com/facebook/react-native/commit/f23feced42abd1d18a12e413bf79a51bead61379) by [@Almouro](https://github.com/Almouro)) +- Fix border-drawing when changing border-radius back to 0` ([7757ad0576](https://github.com/facebook/react-native/commit/7757ad05762284c059807d7d75fd03559e86f2b2) by [@IjzerenHein](https://github.com/IjzerenHein)) +- Fix rounded border-drawing when border-radius is smaller than border-width` ([28dce3665d](https://github.com/facebook/react-native/commit/28dce3665d8a63e902c165c060400486fe6234f4) by [@IjzerenHein](https://github.com/IjzerenHein)) + +#### iOS specific + +- TextInput color has the same default (#000) on iOS whether in light or dark mode ([a2f8b9c36e](https://github.com/facebook/react-native/commit/a2f8b9c36e5eba6bc354a2f53bf8d3ca11297d00) by [@JonnyBurger](https://github.com/JonnyBurger)) +- Fixes TextInput shaking when typing Chinese ([9cdc19a936](https://github.com/facebook/react-native/commit/9cdc19a93669b37c0518bd32263e156ffc9193c7) by [@zhongwuzw](https://github.com/zhongwuzw)) + +## v0.63.0 + +### Breaking + +- The `target` field of events is now a native component, not a react tag ([3b813cade1](https://github.com/facebook/react-native/commit/3b813cade1f5d6f248a39f6bbd983f68c5794fe6) by [@TheSavior](https://github.com/TheSavior)) +- Modal: Remove support for `animated` prop (deprecated in 0.26) ([1e9db7bd6d](https://github.com/facebook/react-native/commit/1e9db7bd6df3055b9b81d23f15a54bb250621a41) by [@TheSavior](https://github.com/TheSavior)) +- Alert: Remove deprecated support for passing callback as fourth argument to `Alert.prompt` (deprecated in 0.59) ([a26d622d04](https://github.com/facebook/react-native/commit/a26d622d04451d6872eed2491e5d3f7d4689824d) by [@TheSavior](https://github.com/TheSavior)) +- Switch: Remove support for `thumbTintColor`, `tintColor`, `onTintColor` props (deprecated in 0.57) ([26912bd979](https://github.com/facebook/react-native/commit/26912bd9798aeb38931466b8ddcd3a48973b0528) by [@TheSavior](https://github.com/TheSavior)) +- Multiple deprecations and breaking changes to `TextInputState`. Use native component refs instead of react tags ([6286270e4c](https://github.com/facebook/react-native/commit/6286270e4cb10b40cfd7c8193e31d965f6815150) by [@TheSavior](https://github.com/TheSavior)) +- Bump supported Node engines to >= 10 ([f0c7178a3a](https://github.com/facebook/react-native/commit/f0c7178a3a24e7694b765946f0d884104c8cfa4c) by [@safaiyeh](https://github.com/safaiyeh)) + +### Deprecated + +- Add deprecation warnings for `Clipboard`, `SegmentedControlIOS`, `ProgressViewIOS`, `ProgressBarAndroid`. These modules have been moved to [react-native-community](https://github.com/react-native-community) libraries. ([f295d7f608](https://github.com/facebook/react-native/commit/f295d7f60843a45bb09fc366e497f512c2bc0046) by [@Naturalclar](https://github.com/Naturalclar)) +- Deprecated `console.disableYellowBox` in favor of `LogBox.ignoreAllLogs`. ([87f1e22434](https://github.com/facebook/react-native/commit/87f1e22434210ad22f526422bbda0413f59786ce) by [@rickhanlonii](https://github.com/rickhanlonii)) + +#### Android specific + +- We are deprecating the method `UIManagerModule.resolveRootTagFromReactTag`, this will not be supported in the next version of RN ([acbf9e18ea](https://github.com/facebook/react-native/commit/acbf9e18ea666b07c1224a324602a41d0a66985e) by [@mdvacca](https://github.com/mdvacca)) +- Add warning message for trying to use `ToolbarAndroid` which has been removed from the core since 0.61. ([6249d14a61](https://github.com/facebook/react-native/commit/6249d14a61723b22deb1336457b4295978471885) by [@Naturalclar](https://github.com/Naturalclar)) + +#### iOS specific + +- Deprecate iOS 9.x support ([58a6a40eac](https://github.com/facebook/react-native/commit/58a6a40eac9afb5c4de78a63418cc48ea97da1a4), [829a2237d2](https://github.com/facebook/react-native/commit/829a2237d270c03c80467eb6c2b5b18c87135a45), [674b591809](https://github.com/facebook/react-native/commit/674b591809cd1275b5f1c4d203c2f0ec52303396) by [@fkgozali](https://github.com/fkgozali), [d1265077d6](https://github.com/facebook/react-native/commit/d1265077d6638bb9219180628caf6ff83f8d6019) by [@sunnylqm](https://github.com/sunnylqm)) + +### Added + +- Implement `nativePerformanceNow` and `performance.now()` ([232517a574](https://github.com/facebook/react-native/commit/232517a5740f5b82cfe8779b3832e9a7a47a8d3d) by [@emilisb](https://github.com/emilisb)) +- Support `PerformanceLogger` stopTimespan updates ([08c338eebf](https://github.com/facebook/react-native/commit/08c338eebf67ef6c8c8fb7e3a91bbf89bbc2bb4c) by [@sahrens](https://github.com/sahrens)) +- Added `PlatformColor` implementations for iOS and Android ([f4de45800f](https://github.com/facebook/react-native/commit/f4de45800f25930a1c70f757d12269d859066d3d) by [@tom-un](https://github.com/tom-un)) +- Stamp React Native Version Into C++ Code ([427ba359e0](https://github.com/facebook/react-native/commit/427ba359e0c9411438286dd137bbca67f9829fde) by [@NickGerleman](https://github.com/NickGerleman)) +- New `` Component to make it easier to create touchable elements ([3212f7dfe8](https://github.com/facebook/react-native/commit/3212f7dfe82d187e27f1410c8c3cb1d9fb9f5094) by [@TheSavior](https://github.com/TheSavior), [bd3868643d](https://github.com/facebook/react-native/commit/bd3868643d29e93610e19312571a9736df2cbdf8) by [@vonovak](https://github.com/vonovak)) +- Export `LogBox` module ([799bf56f6f](https://github.com/facebook/react-native/commit/799bf56f6f6a46b6bd42ac5a824f44bd1412f3b6) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Export `LayoutAnimationConfig` flow type ([f0dafd34fe](https://github.com/facebook/react-native/commit/f0dafd34fedb0d63eb2499b978a52da9e6b71ea1) by [@sahrens](https://github.com/sahrens)) +- Added `react-native-community/eslint-plugin` as a dependency for `react-native-community/eslint-config` ([2c2e35c634](https://github.com/facebook/react-native/commit/2c2e35c634cd936bd7ea7a7fe444058268308224) by [@Naturalclar](https://github.com/Naturalclar)) +- `DEBUG_NETWORK_SEND_DELAY` can be used to simulate slow network. ([4aac019176](https://github.com/facebook/react-native/commit/4aac019176e3359068ac671ed4157a6e3ada481f) by [@sahrens](https://github.com/sahrens)) +- Support for `accessibilityLabel` prop to the `Picker` component ([0a525b6d9d](https://github.com/facebook/react-native/commit/0a525b6d9d2a88dddf24b85a2485b928fca23b16) by [@KevinGVargas](https://github.com/KevinGVargas)) +- Allow `zIndex` to `useNativeDriver` ([6a4e06faa8](https://github.com/facebook/react-native/commit/6a4e06faa8afbcb607fc2696c45c4f3257b6665d) by [@mackenziemance](https://github.com/mackenziemance)) + +#### Android specific + +- Support item background color in Dialog `Picker` ([22eb711c84](https://github.com/facebook/react-native/commit/22eb711c84587ac92da97e486fecaa79424fa925)) +- Add OverrideColorScheme interface and setOverrideColorScheme method to AppearanceModule([45d7df6cf7](https://github.com/facebook/react-native/commit/45d7df6cf7482b9790c97db613055ff5d3e59a87)) +- Allow setting custom ripple radius on `TouchableNativeFeedback` ([7f2a79f40b](https://github.com/facebook/react-native/commit/7f2a79f40b4a4c41344ca90cefe318af607675e0) by [@vonovak](https://github.com/vonovak)) +- Add `resizeMode` prop support on `TextInlineView` ([6871416328](https://github.com/facebook/react-native/commit/68714163280695c3148544b95b05a2c1464dbbba) by [@mdvacca](https://github.com/mdvacca)) +- Added an API for checking if there are busy timers to `TimingModule` ([22764e6cdc](https://github.com/facebook/react-native/commit/22764e6cdcf45ca5930676f6e95f9ab2f82bc78d) by [@ejanzer](https://github.com/ejanzer)) +- Make packager location customizable in dev mode ([3714f3648a](https://github.com/facebook/react-native/commit/3714f3648a8ac51f2bb7f2791e2381551d0209b4)) + +#### iOS specific + +- `UIScene` support for `RCTImageView` ([f332fac163](https://github.com/facebook/react-native/commit/f332fac16346d2f03d056575cc988a0b2bbb48c6) by [@tido64](https://github.com/tido64)) +- Status bar style is now correctly changed in multi-window iPadOS 13 apps if you use `RCTRootViewController` and set `UIViewControllerBasedStatusBarAppearance=YES` ([80e6d672f3](https://github.com/facebook/react-native/commit/80e6d672f32fdc860c73eabcc63763dcab3c6269) by [@radex](https://github.com/radex)) +- Added `userInterfaceStyle` for `ActionSheetIOS` and `Share` to override user interface style on IOS 13 ([0a9cc34dd8](https://github.com/facebook/react-native/commit/0a9cc34dd82a3a7dba576997ebd424b12876dbaa) by [@Arjan-Zuidema](https://github.com/Arjan-Zuidema)) +- Add localized accessibility strings to `ReactCore` pod ([aebf54aee4](https://github.com/facebook/react-native/commit/aebf54aee41cc892198b055a7a546743297412bd) by [@xuelgong](https://github.com/xuelgong)) +- Resolve and reject promise for `PushNotificationIOS.requestPermissions` ([edfdafc7a1](https://github.com/facebook/react-native/commit/edfdafc7a14e88a2660b95cb220c62f29b1b28c0) by [@p-sun](https://github.com/p-sun)) +- Use autolink script in template on iOS ([a35efb9400](https://github.com/facebook/react-native/commit/a35efb94006bfa3f541bf3fc3ab5262740f00525) by [@janicduplessis](https://github.com/janicduplessis)) +- Added `Flipper` to template app ([52cd9cd6fe](https://github.com/facebook/react-native/commit/52cd9cd6fec0866177aa02f7129a8b3d8b2bdbea) by [@safaiyeh](https://github.com/safaiyeh)) +- Add `textColor` and `backgroundColor` props to `SegmentedControl` for iOS >=13 ([e8f577e541](https://github.com/facebook/react-native/commit/e8f577e541815bfd8adebdf14f70c9e4205f8e4e) by [@Naturalclar](https://github.com/Naturalclar)) +- Adds `RCTOverrideAppearancePreference` to the iOS `Appearance` module ([fa65b156d4](https://github.com/facebook/react-native/commit/fa65b156d4109e6a3121484b601358b11cf0d541)) +- Changed iOS LaunchScreen from `xib` to `storyboard` ([33b3a1a145](https://github.com/facebook/react-native/commit/33b3a1a1453ca51690e59b758eeb61a4fa8f35bc) by [@jeswinsimon](https://github.com/jeswinsimon)) + +### Changed + +- Update `react-native-community/eslint-config` to 1.1.0, adding the new color rule ([780f06cd47](https://github.com/facebook/react-native/commit/780f06cd477f34da48646a949bd25dd3f883a9a2) by [@TheSavior](https://github.com/TheSavior)) +- Update community eslint plugin in the eslint config ([b2d10bc602](https://github.com/facebook/react-native/commit/b2d10bc60272fc2318835ff38655a9eb4a2bbed0) by [@Naturalclar](https://github.com/Naturalclar)) +- Upgrade `eslint-config` and `metro-preset` in project template ([ad86a18305](https://github.com/facebook/react-native/commit/ad86a183052e8b25d599eb395aef55412c02ff7b) by [@Naturalclar](https://github.com/Naturalclar)) +- Add ES Lint rules for `DynamicColorIOS()`and `ColorAndroid()` ([602070f44b](https://github.com/facebook/react-native/commit/602070f44b02220aeb036a7b3c26dad5c611b636) by [@tom-un](https://github.com/tom-un)) +- Make `ScrollView` use `forwardRef` ([d2f314af75](https://github.com/facebook/react-native/commit/d2f314af75b63443db23e131aaf93c2d064e4f44) by [@kacieb](https://github.com/kacieb)) +- Upgrade embedded React DevTools backend from v4.0.6 to v4.6.0 ([93ee5b2cc8](https://github.com/facebook/react-native/commit/93ee5b2cc8391bc5cb12ca7cf08ed0e44c74d29a) by [@bvaughn](https://github.com/bvaughn)) +- Updated the React Hooks ESLint Plugin ([6ce3f0a4f7](https://github.com/facebook/react-native/commit/6ce3f0a4f7495adb82e655d037dc4e5af462f955) by [@gaearon](https://github.com/gaearon)) +- Update to React 16.13.1 ([9637d6214a](https://github.com/facebook/react-native/commit/9637d6214a47e58d7fa8252a3de8c057e5cfb101) by [@gaearon](https://github.com/gaearon)) +- Relax `RefreshControl`'s `onRefresh` flow typing ([884c86ae02](https://github.com/facebook/react-native/commit/884c86ae02b0be7ea1e4b258dab39f4c5aee0b9d) by [@vonovak](https://github.com/vonovak)) +- Improved flowtype support for `Animated` ([bdafc55f50](https://github.com/facebook/react-native/commit/bdafc55f50c7d580ee2e643a02cb95d0196f721c) by [@javache](https://github.com/javache)) +- Upgrade `eslint-plugin-relay` to 1.6.0 ([0483404d82](https://github.com/facebook/react-native/commit/0483404d827416b7270e8a42b84e424035127892) by [@kassens](https://github.com/kassens)) +- Upgrade to latest dependencies in package.json template ([82e8239337](https://github.com/facebook/react-native/commit/82e82393377ddcedba01c401a5d79d5bbcdc4dc9) by [@codler](https://github.com/codler)) +- Make JSStringToSTLString 23x faster ([733532e5e9](https://github.com/facebook/react-native/commit/733532e5e95c85b8295b6c66009ca9efd2a77622) by [@radex](https://github.com/radex)) +- Changed property `disableIntervalMomentum` to work with both horizontal and vertical ScrollViews ([6237cfb325](https://github.com/facebook/react-native/commit/6237cfb325e39571ede0054a67d60f2c978d6d58) by [@Shaninnik](https://github.com/Shaninnik)) +- Upgraded to Flow v0.114.0 ([aa78457343](https://github.com/facebook/react-native/commit/aa7845734352eab2bd32f7d6e683d6674fd6680d) by [@mroch](https://github.com/mroch)) +- Updated CLI to the latest version ([ddc33007ad](https://github.com/facebook/react-native/commit/ddc33007ad0b4a0a24966b833e797227b9c56cca) by [@grabbou](https://github.com/grabbou)) +- Upgrade Prettier from 1.17 to 2.0.2. ([cf44650b3f](https://github.com/facebook/react-native/commit/cf44650b3f4f13df8208ceded60ec5c48bd6baf3) by [@bolinfest](https://github.com/bolinfest)) +- Only set dimensions if the window attributes change ([35c6bb9ac0](https://github.com/facebook/react-native/commit/35c6bb9ac0fc452428e85fee72affb4fc29f500c) by [@cpojer](https://github.com/cpojer)) +- Upgrade internal packages to support ESLint >= 6 ([89d04b5e4a](https://github.com/facebook/react-native/commit/89d04b5e4a3fd0b0f77b5a390c0aa62a3804e2bc) by [@Barbiero](https://github.com/Barbiero)) +- Make `JSCRuntime::createValue` faster ([24e0bad8be](https://github.com/facebook/react-native/commit/24e0bad8bea95ef7ddf72e2f00a93ffd47872d5b) by [@radex](https://github.com/radex)) +- Add perf markers in `XMLHttpRequest` ([71b8ececf9](https://github.com/facebook/react-native/commit/71b8ececf9b298fbf99aa27d0e363b533411e93d) by [@sahrens](https://github.com/sahrens)) +- Update SoLoader to 0.8.2 ([0a6f058b6b](https://github.com/facebook/react-native/commit/0a6f058b6bd0493f7eece972b1f73be3606ca8d5) by [@passy](https://github.com/passy)) +- `console.error` calls, and uncaught exceptions are now displayed in the Metro logs as well ([ffb82cb2f0](https://github.com/facebook/react-native/commit/ffb82cb2f052f276a94a004d5acea0ab44f8098c) by [@mweststrate](https://github.com/mweststrate)) +- Upgrade Flipper to 0.37.0 ([17f025bc26](https://github.com/facebook/react-native/commit/17f025bc26da13da795845a3f7daee65563420c0) by [@sunnylqm](https://github.com/sunnylqm)) + +#### Android specific + +- Upgraded to Hermes 0.5.0 ([4305a291a9](https://github.com/facebook/react-native/commit/4305a291a9408ca65847994bbec42f1fbc97071d) by [@willholen](https://github.com/willholen)) +- Internal storage now will be preferred for caching images from `ImageEditor`. ([79efa43428](https://github.com/facebook/react-native/commit/79efa4342852a3e9271a56e3a0fb7c15be664e9a) by [@kitttn](https://github.com/kitttn)) +- Update Gradle Wrapper to 6.2 ([d4d8887b50](https://github.com/facebook/react-native/commit/d4d8887b5018782eeb3f26efa85125e6bbff73e4) by [@friederbluemle](https://github.com/friederbluemle)) +- Upgrade Folly to v2020.01.13.00 ([6e2131b8fa](https://github.com/facebook/react-native/commit/6e2131b8fa85da8b3fb0391803e7fbecba890ffb) by [@Kudo](https://github.com/Kudo)) +- Only update dimensions in `ReactRootView` if they've changed ([cc3e27d484](https://github.com/facebook/react-native/commit/cc3e27d484d3a412f632454b7f1c637b2c271af2) by [@ejanzer](https://github.com/ejanzer)) +- `ReactEditText` extends `AppCompatEditText` ([aaa2765a92](https://github.com/facebook/react-native/commit/aaa2765a920de8234f0def4cae05ca5d6c8c8ac8) by [@dulmandakh](https://github.com/dulmandakh)) +- Make `ReactApplicationContext` nullable as the constructor argument of `ReactContextBaseJavaModule` ([f8d5c5bfd7](https://github.com/facebook/react-native/commit/f8d5c5bfd79be0e20a205a1856bd9946143eeacf) by [@RSNara](https://github.com/RSNara)) +- Update Android Gradle plugin to 3.5.3 ([e1e081b00e](https://github.com/facebook/react-native/commit/e1e081b00e5efb32bce74211c850212eca8a9412) by [@SaeedZhiany](https://github.com/SaeedZhiany)) +- Don't emit dimensions update event on initial load ([383934a06e](https://github.com/facebook/react-native/commit/383934a06e22e8e1a5ee50d121722240259f95d0) by [@ejanzer](https://github.com/ejanzer)) +- Bump Android build-tools to 29.0.2, compileSdk to 29 ([edcbfb9821](https://github.com/facebook/react-native/commit/edcbfb98210d9aaa6bb1d7c64281ae9cfb41cac2) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Disambiguate autolinking-ios.rb script from CLI’s “autolinking” feature and bring RNTester & template in line. ([4118d79826](https://github.com/facebook/react-native/commit/4118d798265341061105f3a53550db83c66a71cb) by [@alloy](https://github.com/alloy)) +- Updated Flipper iOS integration to be included by default in the `Debug` configuration ([619d5d60df](https://github.com/facebook/react-native/commit/619d5d60dfa94966e7104febec08166c1b5eca49) by [@alloy](https://github.com/alloy)) +- Use Apple unified logging API (os_log) ([f501ed682a](https://github.com/facebook/react-native/commit/f501ed682ae68136d966aee2b0d3cc0f1e8b90cd) by [@LeoNatan](https://github.com/LeoNatan)) +- Upgrade Folly to v2020.01.13.00 ([a27e31c059](https://github.com/facebook/react-native/commit/a27e31c059971b1d554ad6c7c81706f08eafac87) by [@Kudo](https://github.com/Kudo)) +- Remove the `xcshareddata` from .gitignore ([7980615d37](https://github.com/facebook/react-native/commit/7980615d371a7bf607a3787bca91cfde229c41dc) by [@pvinis](https://github.com/pvinis)) +- Add `complete_nullability = True` to compatible libraries ([796a4ea7e3](https://github.com/facebook/react-native/commit/796a4ea7e31ae05b76e59e02ab05f9c955f7c149)) +- Remove the Flipper pod post install step ([44beb2a685](https://github.com/facebook/react-native/commit/44beb2a685b7ceb0311bde7d0d33cb70bb891d30) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Enable Flipper with CocoaPods `:configuration` ([7bb1c4e1b8](https://github.com/facebook/react-native/commit/7bb1c4e1b8715a5c9cb6f9e4e77a6df783481d3d) by [@alloy](https://github.com/alloy)) + +### Removed + +- Remove unused`ImageProp` ([fbd09b1797](https://github.com/facebook/react-native/commit/fbd09b179759cd90f2be5c24caa11bdb483ad8cd) by [@Naturalclar](https://github.com/Naturalclar)) +- Remove leftover `Incremental` component ([e99800267b](https://github.com/facebook/react-native/commit/e99800267b78aa581aff956d47b8be91858628b9) by [@venits](https://github.com/venits)) +- Remove "Debug with Nuclide" option ([011eb4cea5](https://github.com/facebook/react-native/commit/011eb4cea5d482cef54d7659e7436a04e539ff19) by [@rickhanlonii](https://github.com/rickhanlonii)) + +#### Android specific + +- Remove unused Feature Flag: `lazilyLoadViewManagers` ([3963f34980](https://github.com/facebook/react-native/commit/3963f34980f501ef89a945a1d0e76716af84527d) by [@JoshuaGross](https://github.com/JoshuaGross)) +- `PickFirst` options for RNTester and template ([4bb0b4f205](https://github.com/facebook/react-native/commit/4bb0b4f205b1bc9a91150fe1f609f7d7313eb806) by [@passy](https://github.com/passy)) +- Remove Kotlin version from the default template ([ced959bb3d](https://github.com/facebook/react-native/commit/ced959bb3d6abdab30c5e64af9bff6059b111cdd) by [@grabbou](https://github.com/grabbou)) + +#### iOS specific + +- Remove core `RCTConvert` CoreLocation Libraries ([bcf2a716fb](https://github.com/facebook/react-native/commit/bcf2a716fb8b8954d6f7b801a1699eeea9418e73) by [@maschad](https://github.com/maschad)) +- Remove copyright notices from iOS application template ([9c3fa57337](https://github.com/facebook/react-native/commit/9c3fa573379bb4824bbe02b5b5aa1ae3502772d8) by [@alloy](https://github.com/alloy)) +- Remove three properties: `textColor`, `font` and `textAlignment` from `RCTBackedTextInputViewProtocol`, unifying the usage into `defaultTextAttributes`. ([aff6bad27c](https://github.com/facebook/react-native/commit/aff6bad27c6c2232ba8bde17823d0a0db4ac589b) by [@jimmy623](https://github.com/jimmy623)) + +### Fixed + +- Add support to render `` with no fixed size nested within a `` ([dbb7eacb42](https://github.com/facebook/react-native/commit/dbb7eacb429adb4160e740017c212bfd6df0f03a) by [@mdvacca](https://github.com/mdvacca)) +- Fixes bug where `` would crash. ([66601e755f](https://github.com/facebook/react-native/commit/66601e755fcad10698e61d20878d52194ad0e90c) by [@TheSavior](https://github.com/TheSavior)) +- Use new `setTextCursorDrawable` API for Android 10 ([e7a14b803f](https://github.com/facebook/react-native/commit/e7a14b803fdc8840bbcde51d4bfa9cf9a85a8472) by [@sturmen](https://github.com/sturmen)) +- Fix `Animated.Value` initialized with undefined in `ScrollView` ([cf02bd9b76](https://github.com/facebook/react-native/commit/cf02bd9b765e29ed8aa2bbf62661e89c84bb80e5) by [@janicduplessis](https://github.com/janicduplessis)) +- Do not explicitly include `.js` in Library imports ([161b910494](https://github.com/facebook/react-native/commit/161b9104941663dcc0b08a73789c0ff3410fc661) by [@NickGerleman](https://github.com/NickGerleman)) +- Fixed `Pressability` to properly fire `onLongPress`. ([5ca1d8f260](https://github.com/facebook/react-native/commit/5ca1d8f260bfb64111a6ba39f76a0a935829c0f2) by [@yungsters](https://github.com/yungsters)) +- Fixed typo from `inly` to `only` inside `Modal.js` library code. ([686d8a57f8](https://github.com/facebook/react-native/commit/686d8a57f889fe74dc1c66566c80f0ed6d677729) by [@Darking360](https://github.com/Darking360)) +- Fix viewability calculations for nested `VirtualizedLists` inside of a parent list's `FooterComponent` ([074a2fab74](https://github.com/facebook/react-native/commit/074a2fab74754c28cba0ccc51552a246a3046501) by [@logandaniels](https://github.com/logandaniels)) +- Fix android `TextInput` transitions ([0a17a4fe56](https://github.com/facebook/react-native/commit/0a17a4fe56ff2cabc3c7d1cc5b34bd3fdd032e59)) +- Remove JS autoFocus implementation ([0569d4c431](https://github.com/facebook/react-native/commit/0569d4c4315d61d2d8f4ab628a54eb1e1db45dc2) by [@janicduplessis](https://github.com/janicduplessis)) +- Check null values in `shouldAnimate` ([3498b3b96b](https://github.com/facebook/react-native/commit/3498b3b96b2e27c7c7f6407b3673b44540871a31) by [@axe-fb](https://github.com/axe-fb)) +- Fix `AccessibilityInfo.isScreenReaderEnabled` mock in Jest setup ([ec3327b61a](https://github.com/facebook/react-native/commit/ec3327b61ab1be3fd1565c8a35fe56747bd9069f) by [@rubennorte](https://github.com/rubennorte)) +- Fix crash when passing invalid UTF-16 data from JSC into native code ([011cf3f884](https://github.com/facebook/react-native/commit/011cf3f88428ca83552d0b51c7c3a0c47b9728e5) by [@motiz88](https://github.com/motiz88)) +- Make `YGValue.h` compile with Clang on Windows ([014bc95135](https://github.com/facebook/react-native/commit/014bc95135a38d65b991509492c0979cfd153e71) by [@christophpurrer](https://github.com/christophpurrer)) +- Fix documentation comments for HermesJS's `Function::callWithThis` method to accurately reflect how `this` is handled. ([399bda5284](https://github.com/facebook/react-native/commit/399bda52840161bf7d30c09eca061b4378b8f6e4) by [@Kronopath](https://github.com/Kronopath)) +- Fix resolving assets outside of the project root ([7deeec7396](https://github.com/facebook/react-native/commit/7deeec73966d84140492c2a767819977318c4d2d) by [@janicduplessis](https://github.com/janicduplessis)) +- Transform empty responses into empty `Blob`s ([9a8c06b502](https://github.com/facebook/react-native/commit/9a8c06b502c774f7a0bff1bdc064fbfe16ca75be) by [@RSNara](https://github.com/RSNara)) +- Fix validation of event mappings for `AnimatedEvent` ([19362f6116](https://github.com/facebook/react-native/commit/19362f6116bad441c5e23f2bab420af78664b3d3) by [@javache](https://github.com/javache)) +- Fix `NativeJSCHeapCapture` ([7e3a43c23d](https://github.com/facebook/react-native/commit/7e3a43c23d028a4481bc455dd28c391a81ff1a94) by [@RSNara](https://github.com/RSNara)) +- Add `AnimationInterpolation` as possible type for toValue ([26e8870fbf](https://github.com/facebook/react-native/commit/26e8870fbf310f0fb438a86cb2fe260f0bc419b9) by [@nabati](https://github.com/nabati)) +- Fix return type of `StyleSheet.create` ([4e71a30969](https://github.com/facebook/react-native/commit/4e71a30969d74073309d0350be55cadb84ae43ff) by [@jbrown215](https://github.com/jbrown215)) +- Adjust HelloWorld-tvOSTests/Info.plist `CFBundleIdentifier` to use `PRODUCT_BUNDLE_IDENTIFIER` ([98ebc1ea25](https://github.com/facebook/react-native/commit/98ebc1ea25102049ec53288a458ff16ed5b4ada0) by [@MoOx](https://github.com/MoOx)) +- Fix bug where if `Error` global is not callable when building an error, jsi will throw a JS exception back to JS code. #158 ([a195447539](https://github.com/facebook/react-native/commit/a1954475394dc03704a2e093e6fc4b48188640fa) by [@mhorowitz](https://github.com/mhorowitz)) +- Fix stylesheet validation for functions with custom prototype methods. ([f464dad5d4](https://github.com/facebook/react-native/commit/f464dad5d4f0fbf1cb23e21d22907ffddeaf97e4)) +- Fix sporadic issue with `onEndReached` called on load when not needed ([8ddf231306](https://github.com/facebook/react-native/commit/8ddf231306e3bd85be718940d04f11d23b570a62) by [@sahrens](https://github.com/sahrens)) +- Correct argument types of `NativeJSDevSupport.onSuccess` ([b42371da5a](https://github.com/facebook/react-native/commit/b42371da5a41916522b569a66c0a126333cf9cac) by [@RSNara](https://github.com/RSNara)) +- Add `URLSearchParams` and `Headers` to eslint globals ([7a13a1a88f](https://github.com/facebook/react-native/commit/7a13a1a88fdf26dca817b76399f1c86a8a05eccb) by [@sonnyp](https://github.com/sonnyp)) +- Export exception classes with default visibility ([84adc85523](https://github.com/facebook/react-native/commit/84adc85523770ebfee749a020920e0b216cf69f8) by [@appden](https://github.com/appden)) +- Add `URL` to eslint globals. ([ff9def41ff](https://github.com/facebook/react-native/commit/ff9def41ff3e7760d076bf1b899583d4b36cba0d) by [@sonnyp](https://github.com/sonnyp)) +- Plumb through memory allocation profiler feature to Chrome Inspector ([ed3054927c](https://github.com/facebook/react-native/commit/ed3054927c30c8823f78026b9c4cb42fbe4f8b00) by [@jbower-fb](https://github.com/jbower-fb)) +- Better monorepo support when building release apk ([a8e85026cf](https://github.com/facebook/react-native/commit/a8e85026cfa60056b1bcbcd39cde789e4d65f9cb) by [@grabbou](https://github.com/grabbou)) +- `LogBox` - Fix dependency cycle ([6ba2aeefa8](https://github.com/facebook/react-native/commit/6ba2aeefa8dfe031bf1dc46dbea29235aec31d61) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Always update background color and bar style on Android status bar ([9457efa84c](https://github.com/facebook/react-native/commit/9457efa84c872f029027cdcfc3bae4f403715e48)) +- Disable accessibility state changes of the focused view for Android API < 21 ([f2d58483c2](https://github.com/facebook/react-native/commit/f2d58483c2aec689d7065eb68766a5aec7c96e97) by [@mdvacca](https://github.com/mdvacca)) + +#### Android specific + +- Gradle release config ([0d1fb458ab](https://github.com/facebook/react-native/commit/0d1fb458ab8027dcfac5f2fa11e8c16d6853c59c) by [@vdmtrv](https://github.com/vdmtrv)) +- Change how `TextInput` responds to `requestFocus` to fix a11y focus issue ([d4a498aba2](https://github.com/facebook/react-native/commit/d4a498aba2d2843e7a741a31b0c91c6a79a7386c) by [@ejanzer](https://github.com/ejanzer)) +- Fixed style in `TextInputTestCase` ([8c493804f3](https://github.com/facebook/react-native/commit/8c493804f3f7b3ae3761679a978971ab9d71baa0) by [@ejanzer](https://github.com/ejanzer)) +- Fix template instacrash from missing androidx dependency ([a1b14deb3e](https://github.com/facebook/react-native/commit/a1b14deb3e32df797aae99a75743a4d283e5337b) by [@alloy](https://github.com/alloy)) +- Implement native `TextInput` `autoFocus` on Android ([055a41b081](https://github.com/facebook/react-native/commit/055a41b081c5bc9535b071d9b4b7488b92e71803) by [@janicduplessis](https://github.com/janicduplessis)) +- Support for case insensitive `Origin` headers for `Websockets` ([aeaf286c77](https://github.com/facebook/react-native/commit/aeaf286c77b50a95c4961de0d2355caad8ffa396) by [@brunobar79](https://github.com/brunobar79)) +- RNTester Buck Build ([a3cb377645](https://github.com/facebook/react-native/commit/a3cb377645f2ccb7632ded73c230a41025d38f6f) by [@passy](https://github.com/passy)) +- Fix bug in updating dimensions in JS ([bef845ffd5](https://github.com/facebook/react-native/commit/bef845ffd521aa83d779de584ec370f9f88f27f3) by [@ejanzer](https://github.com/ejanzer)) +- Applied missing changes from bumping Gradle wrapper to 6.0.1 ([aa0ef15335](https://github.com/facebook/react-native/commit/aa0ef15335fe27c0c193e3e968789886d82e82ed) by [@SaeedZhiany](https://github.com/SaeedZhiany)) +- Unregister `JSDevSupport` from `DebugCorePackage` ([c20963e11c](https://github.com/facebook/react-native/commit/c20963e11cc1c10f20a2a0a3c209f5b403c9e899) by [@RSNara](https://github.com/RSNara)) +- Make sure `ServerHost` is optional in `NativePlatformConstants.js` ([048f88a33a](https://github.com/facebook/react-native/commit/048f88a33a53ebd4e45865b319c42291f1d6c7f2) by [@RSNara](https://github.com/RSNara)) +- Removed code that would cause accessibility header role to be spoken twice ([7428271995](https://github.com/facebook/react-native/commit/7428271995adf21b2b31b188ed83b785ce1e9189) by [@KevinGVargas](https://github.com/KevinGVargas)) +- Fail silently in `AppStateModule.sendEvent` if `CatalystInstance` is not available ([c4806fada6](https://github.com/facebook/react-native/commit/c4806fada6532894e2242cf31f7145d2992e3a2b) by [@JoshuaGross](https://github.com/JoshuaGross)) +- RN `Picker` - fix types in `AndroidDialogPickerManagerInterface` ([56b0f5cb6b](https://github.com/facebook/react-native/commit/56b0f5cb6ba48ecefc2890152ebe88e3df61a0ea)) +- Fix Hermes debugger being disabled by default ([b8621f5d30](https://github.com/facebook/react-native/commit/b8621f5d303442ab78dc5d745cfc86a941d4737c) by [@willholen](https://github.com/willholen)) + +#### iOS specific + +- Fixed connection of metro reload command to iOS device ([f9df93385e](https://github.com/facebook/react-native/commit/f9df93385eee0e1cd1144a65e05410dfb48b119c) by [@reyalpsirc](https://github.com/reyalpsirc)) +- Remove `RCTDevLoadingView` jank ([faff19a7c6](https://github.com/facebook/react-native/commit/faff19a7c651c740d8d649b86727b63b63562b20) by [@RSNara](https://github.com/RSNara)) +- Fix crash when passing null value in object parameter of bridged method ([15434c7c43](https://github.com/facebook/react-native/commit/15434c7c435928a40b9cd66fe9f5d1bcdea8d954)) +- Get ready for Clang 10 ([8721ee0a6b](https://github.com/facebook/react-native/commit/8721ee0a6b10e5bc8a5a95809aaa7b25dd5a6043) by [@maxovtsin](https://github.com/maxovtsin)) +- Fix `RCTBlobManager` cleanup crash ([91c5ff4a12](https://github.com/facebook/react-native/commit/91c5ff4a12982ccead56c9c038761e9316d01409) by [@RSNara](https://github.com/RSNara)) +- Make Lambda function called in `NativeModule` mutable ([5290047d09](https://github.com/facebook/react-native/commit/5290047d09c0a41c85a1d47a638877c226d9c191) by [@maschad](https://github.com/maschad)) +- Fix crash in `RCTCxxBridge.executeApplicationScript` ([0c2db3256f](https://github.com/facebook/react-native/commit/0c2db3256f6cbb3ec564e0f183a52a439ed33f52) by [@ahimberg](https://github.com/ahimberg)) +- Fix `RCTDevLoadingView` `RedBox` on reload ([fe5ac2c3f9](https://github.com/facebook/react-native/commit/fe5ac2c3f9e47cfb7c5820a755a5d74d47624953) by [@RSNara](https://github.com/RSNara)) +- Fix `Image` component crashing when `uri` is `null` ([06b8b15b0a](https://github.com/facebook/react-native/commit/06b8b15b0af84b6f8b44d200dc25f29eac51181c) by [@mlazari](https://github.com/mlazari)) +- Fix `RCTDevLoadingView` not showing up with `UIScene` ([74b667dbc2](https://github.com/facebook/react-native/commit/74b667dbc2a48183dec0b9c3b5401bc3f9e54e7b) by [@tido64](https://github.com/tido64)) +- Prevent interactive dismissal for non-fullscreen modals ([1e7ed81d16](https://github.com/facebook/react-native/commit/1e7ed81d16dda4188352e0ccdc0f0bd3ad4741f3)) +- Resolve localization warnings in template ([0e4bcaa296](https://github.com/facebook/react-native/commit/0e4bcaa2960a2b1aa42dbe716fc6a35652aa7207) by [@safaiyeh](https://github.com/safaiyeh)) +- Implement `requiresMainQueueSetup` in `RCTDevSettings.mm` ([adf87dd7ed](https://github.com/facebook/react-native/commit/adf87dd7eddcf65a3300e6ac9092838d9c8a3279) by [@logandaniels](https://github.com/logandaniels)) +- Resolve `React-RCTText` Xcode warning ([04fed6508b](https://github.com/facebook/react-native/commit/04fed6508b74b23c954183af3f6121fb344d2138) by [@safaiyeh](https://github.com/safaiyeh)) +- Resolve Xcode warnings from `React-cxxreact`. ([dc6c57ce0d](https://github.com/facebook/react-native/commit/dc6c57ce0d4f5424bfb047c51fee18eac381a98b) by [@safaiyeh](https://github.com/safaiyeh)) +- `RCTReconnectingWebSocket` is reconnecting infinitely when stopped before getting connected ([0d4b0e9417](https://github.com/facebook/react-native/commit/0d4b0e941725657d8e63940428888aaceff505ad)) +- Fix prop name of `passwordRules` in `TextInput` ([3f5c42f357](https://github.com/facebook/react-native/commit/3f5c42f357d58268d0a0fd1bfc639f41feab937c) by [@Naturalclar](https://github.com/Naturalclar)) +- Remove need for Swift file in the user’s project in order to use Flipper ([8f93dedc6a](https://github.com/facebook/react-native/commit/8f93dedc6a5653edd2220c65ccb4ff8736ee060c) by [@alloy](https://github.com/alloy)) +- Clear all held `jsi::Functions` when `jsi::Runtime` is deleted ([9ae95582e7](https://github.com/facebook/react-native/commit/9ae95582e792a3dca4487bdce9080c6d874c7dd7) by [@RSNara](https://github.com/RSNara)) +- Make framework builds work again by making `RCTImageLoader` C++ requirement opt-in ([25571ec452](https://github.com/facebook/react-native/commit/25571ec4522931193b41723d3f80b3bced1fca3b) by [@alloy](https://github.com/alloy)) +- Enable dev keyboard shortcuts on Mac Catalyst ([56dfc86d64](https://github.com/facebook/react-native/commit/56dfc86d64a2a1f2ad05239b6d11aacac73cbac9) by [@charpeni](https://github.com/charpeni)) +- Fix `RCTTextView` layout issue that happens on some font with `leading` attribute not equal to zero, which causes wrong base-alignment layout ([5d08aab526](https://github.com/facebook/react-native/commit/5d08aab526b2702b46ff75ea7e943a33aa6df288)) +- Fix LAN instead of Wi-Fi device bundle configuration ([d1e6f8d3c4](https://github.com/facebook/react-native/commit/d1e6f8d3c4de1fbb4bddd5205cd3b35c572b495b) by [@Oleg-E-Bakharev](https://github.com/Oleg-E-Bakharev)) +- Add autorelease pool for each run loop for JS Thread ([948cbfdacc](https://github.com/facebook/react-native/commit/948cbfdacc42f8d2640e69f61df55f6adb823fcf) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fixed bug in implementation of ``'s `selectOnFocus` prop ([e020576b34](https://github.com/facebook/react-native/commit/e020576b34fb6ca6d3f9fe38916844b78a45c0e3) by [@shergin](https://github.com/shergin)) +- `RCTRedBox` doesn't appear in apps implementing `UISceneDelegate` ([d0a32c2011](https://github.com/facebook/react-native/commit/d0a32c2011ca00991be45ac3fa320f4fc663b2e8) by [@tido64](https://github.com/tido64)) +- Fixes the `InputAccessoryView.backgroundColor` prop’s typing to use `ColorValue`. ([a43fd60e18](https://github.com/facebook/react-native/commit/a43fd60e18aff9ee6bcaf8ec576adb8678d5bcf4) by [@alloy](https://github.com/alloy)) +- Fix `Animated` image crash when `CADisplayLink` target in `RCTWeakProxy` is `nil` ([e5a6655e71](https://github.com/facebook/react-native/commit/e5a6655e71d41a58ce0e51d37aa9fb8792e37dd5) by [@p-sun](https://github.com/p-sun)) + +## v0.62.3 + +### Security + +- Update validateBaseUrl to use latest regex ([33ef82ce6d](https://github.com/facebook/react-native/commit/33ef82ce6dfd31e1f990d438c925a0e52723e16b) by [@FBNeal](https://github.com/FBNeal)) + +### Fixed + +#### iOS specific + +- Change autolink to match requirements for FlipperFolly working with Xcode 12.5 ([c6f4611dcb](https://github.com/facebook/react-native/commit/c6f4611dcbfbb64d5b54e242570e2a1acffcabef) by [@kelset](https://github.com/kelset)) +- Change podfile to rely on the autolink-ios rb file ([c4ea556d64](https://github.com/facebook/react-native/commit/c4ea556d64c7fc146d1412548788c48bbcc0f6bb) by [@kelset](https://github.com/kelset)) +- Update detox to work on Xcode 12 ([158b558e50](https://github.com/facebook/react-native/commit/158b558e500576f434dec09417bb02cc0bc53f7a) by [@kelset](https://github.com/kelset)) + +## v0.62.2 + +### Fixed + +- Fix Appearance module when using Chrome Debugger ([f7b90336be](https://github.com/facebook/react-native/commit/f7b90336be25b78935549aa140131d4d6d133f7b) by [@TheSavior](https://github.com/TheSavior)) +- Fix mock for TextInput ([5a3c6faee9](https://github.com/facebook/react-native/commit/5a3c6faee9c44a2d99b13d113c91dbf98990f8af) by [@SergioEstevao](https://github.com/SergioEstevao)) +- Flow errors from YellowBox and BubblingEventHandler ([a049130f34](https://github.com/facebook/react-native/commit/a049130f34be951c9c67d2a472c7eb7f3d08f070) by [@thymikee](https://github.com/thymikee)) + +#### iOS specific + +- Make Vibration library compatible with TurboModules. ([3904228704](https://github.com/facebook/react-native/commit/390422870466beba571dda04f669380e14055056) by [@brunobar79](https://github.com/brunobar79)) +- Exclude Flipper from iOS Release builds ([e5497ca8f6](https://github.com/facebook/react-native/commit/e5497ca8f6e3b240948fdbeef0ac2a710f25bb56) by [@javiercr](https://github.com/javiercr)) +- Fix crash when enabling Performance Monitor on iOS 13.4 ([e2c417f7cf](https://github.com/facebook/react-native/commit/e2c417f7cf5ae7efa5ea1f9644a51c4c706a983f) by [@IjzerenHein](https://github.com/IjzerenHein)) + +## v0.62.1 + +### Fixed + +- Bump CLI to 4.5.1 to improve DX ([eac56b9749](https://github.com/facebook/react-native/commit/eac56b9749ed624275d4190b5e48b775583acb3f) by [@alloy](https://github.com/alloy)) +- Fix a YellowBox regression in v0.62.0 where the Flipper network inspector causes YellowBox to crash the app due to using base64 images. ([227aa96bb2](https://github.com/facebook/react-native/commit/227aa96bb23b6ff20eebbd8a9335fd172ed6005b) by [@rickhanlonii](https://github.com/rickhanlonii)) + +#### Android specific + +- Add new DoNotStrip class to proguard config ([cfcf5eba43](https://github.com/facebook/react-native/commit/cfcf5eba4317f80ef8902463b7c0b2e1e7b534a7) by [@janicduplessis](https://github.com/janicduplessis)) + +#### iOS specific + +- Fix Performance Monitor in dark appearance ([576ddfb3a8](https://github.com/facebook/react-native/commit/576ddfb3a84a5461679959f0d3f229a000dcea8d) by [@gorhom](https://github.com/gorhom)) +- Inverted ScrollViews scroll to their bottom when the status bar is pressed ([7a4753d76a](https://github.com/facebook/react-native/commit/7a4753d76aab1c52a09f26ec6f7fd43a68da8a97) by [@emilioicai](https://github.com/emilioicai)) +- Revert [previous incomplete fix](https://github.com/facebook/react-native/commit/bd2b7d6c0366b5f19de56b71cb706a0af4b0be43) for [an issue](https://github.com/facebook/react-native/issues/26473) with `Modal`’s `onDismiss` prop. ([27a3248a3b](https://github.com/facebook/react-native/commit/27a3248a3b37410b5ee6dda421ae00fa485b525c) by [@grabbou](https://github.com/grabbou)) +- Fix double call to onEndReached in VirtualizedList ([d3658bc2b6](https://github.com/facebook/react-native/commit/d3658bc2b6437e858d3b3f5688277dedbca779b8) by [@MartinSherburn](https://github.com/MartinSherburn)) + +### Changed + +- Update warning message of deprecated imports ([405200e9a9](https://github.com/facebook/react-native/commit/405200e9a930cded47954f374f2a779ec769cd4c) by [@Naturalclar](https://github.com/Naturalclar)) + +## v0.62.0 + +This major release includes Flipper support by default, improved dark mode support, moving Apple TV to [react-native-tvos](https://github.com/react-native-community/react-native-tvos), and more. See the [blog post](https://reactnative.dev/blog/2020/03/26/version-0.62) for all of the highlights. + +This release comes in the midst of a global pandemic. We’re releasing this version today to respect the work of hundreds of contributors who made this release possible and to prevent the release from falling too far behind master. Please be mindful of the reduced capacity of contributors to help with issues and prepare to delay upgrading if necessary. + +If you're upgrading, manual intervention may be required for your app. Please see the [upgrade-helper](https://react-native-community.github.io/upgrade-helper/) for a detailed breakdown of the changes required and see [this issue](https://github.com/react-native-community/releases/issues/179) for known issues. + +One known issue with workaround is regarding Android builds and [APK size increases](https://github.com/facebook/react-native/issues/28330). + +### Breaking + +- React DevTools v4 integration ([92a3c9da0a](https://github.com/facebook/react-native/commit/92a3c9da0a38870a8bad7c91bdc3ddb494f6e5f2) by [@bvaughn](https://github.com/bvaughn)) +- Remove `TextInput`'s `onTextInput` prop ([3f7e0a2c96](https://github.com/facebook/react-native/commit/3f7e0a2c9601fc186f25bfd794cd0008ac3983ab) by [@shergin](https://github.com/shergin)) +- Remove `TextInput`'s `inputView` prop ([1804e7cbea](https://github.com/facebook/react-native/commit/1804e7cbea707a35046118090966a54031edfae8) by [@TheSavior](https://github.com/TheSavior)) +- Animated: Remove `defaultProps` Parameter ([a70987cee2](https://github.com/facebook/react-native/commit/a70987cee24bcd027b9c4a5aa85dfd6a1aab74b3) by [@yungsters](https://github.com/yungsters)) +- Remove `TextInput`'s `selectionState` prop ([2becdfd404](https://github.com/facebook/react-native/commit/2becdfd4041f7f28138ba3a61c03e17c06dc2e50) by [@yungsters](https://github.com/yungsters)) +- Remove support for `framesToPop` ([8bc02fdd52](https://github.com/facebook/react-native/commit/8bc02fdd52124d0a24d96e4a61d7688328ef1660) [cf4d45ec2b](https://github.com/facebook/react-native/commit/cf4d45ec2bcd301be7793d5840de21ec7d02275b) [a483f802fd](https://github.com/facebook/react-native/commit/a483f802fddfd927f2baa0d95e2b4094d452cddd) by [@motiz88](https://github.com/motiz88)) +- Remove `TimePickerAndroid` ([dbf070c51e](https://github.com/facebook/react-native/commit/dbf070c51ecd14127a8317faa75cb661697b5a6b) by [@cpojer](https://github.com/cpojer)) +- Remove `scrollWithoutAnimationTo` from ScrollView ([c7e89909da](https://github.com/facebook/react-native/commit/c7e89909da70ac5290f9971080eb897567db3e43) by [@TheSavior](https://github.com/TheSavior)) +- Bump CLI to ^4.2.x ([be5088401f](https://github.com/facebook/react-native/commit/be5088401fd8e19d57adda42d275cab437448064) by [@alloy](https://github.com/alloy)) - for details on what v4 of the CLI improves on (like monorepo support), please refer to the [dedicated blog post](https://callstack.com/blog/react-native-cli-3-1-0-and-4-0-0-whats-new/) and the [release notes](https://github.com/react-native-community/cli/releases) +- Remove `accessibilityStates` property ([7b35f427fd](https://github.com/facebook/react-native/commit/7b35f427fd66cb0f36921b992095fe5b3c14d8b9) by [@marcmulcahy](https://github.com/marcmulcahy)) +- Upgraded to Hermes 0.4.0. If you're using ProGuard you will need to add the following rule to `proguard-rules.pro`: `-keep class com.facebook.jni.** { *; }` ([ab3c184555](https://github.com/facebook/react-native/commit/ab3c184555e382b8693cbfcdfe01ba89583ee726) by [@willholen](https://github.com/willholen)) + +#### Android specific + +- Fix setting keyboardType from breaking autoCapitalize ([233fdfc014](https://github.com/facebook/react-native/commit/233fdfc014bb4b919c7624c90e5dac614479076f) by [@safaiyeh](https://github.com/safaiyeh)) +- Limit size of video uploaded from camera roll in android (< 100 MB) ([d21f695edf](https://github.com/facebook/react-native/commit/d21f695edf367166a03af4c6e9376cd498b38665)) +- Remove "Reload on JS change" from RN Android ([478df155e7](https://github.com/facebook/react-native/commit/478df155e70a4ce30219adcac6f0801c4e4d10ec) by [@cpojer](https://github.com/cpojer)) + +### Added + +- Add support for Flipper by default ([multiple commits](https://github.com/facebook/react-native/pulls?q=is%3Apr+Flipper+is%3Aclosed)) +- Add `getNativeScrollRef` method to FlatList component ([bde1d63c85](https://github.com/facebook/react-native/commit/bde1d63c853630609b22c87121c125775dd1f5cb) by [@kacieb](https://github.com/kacieb)) +- Add missing accessibility props on Touchables ([8c0c860e38](https://github.com/facebook/react-native/commit/8c0c860e38f57e18296f689e47dfb4a54088c260) by [@xuelgong](https://github.com/xuelgong)) +- Added missing `console` polyfills in release builds. ([b7ab922bb3](https://github.com/facebook/react-native/commit/b7ab922bb3fd4f9f103e583bed9e9295a9521578) by [@yungsters](https://github.com/yungsters)) +- Platform.select now supports native as an option. ([a6fc0898de](https://github.com/facebook/react-native/commit/a6fc0898de990959d201b9665501deda215e41a4) by [@koke](https://github.com/koke)) +- Export the DevSettings module, add `addMenuItem` method ([cc068b0551](https://github.com/facebook/react-native/commit/cc068b055185e6fb7341bf945f69a74ed3ef4814) by [@janicduplessis](https://github.com/janicduplessis)) +- Expose RCTNetworking as a public 'Networking' API ([42ee5ec934](https://github.com/facebook/react-native/commit/42ee5ec93425c95dee6125a6ff6864ec647636aa) by [@adamchel](https://github.com/adamchel)) +- Add `useColorScheme` hook ([51681e80ab](https://github.com/facebook/react-native/commit/51681e80ab0d1efdaba684b626994b694d53d2a5) by [@hramos](https://github.com/hramos)) +- Add `unstable_enableLogBox` ([dd8e5f468a](https://github.com/facebook/react-native/commit/dd8e5f468a29e299647ffbd0887f53afd24936e3) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Expose Hermes Sampling Profiler ([15ecb60d6d](https://github.com/facebook/react-native/commit/15ecb60d6deb96fcb7b0ef70faccd10594ededa3) by [@axe-fb](https://github.com/axe-fb)) +- Add `error-subclass-name` lint rule ([6611c4b8f4](https://github.com/facebook/react-native/commit/6611c4b8f42520add983cc48fe4e14f7a02cc7cf) by [@motiz88](https://github.com/motiz88)) +- Add `HostComponent` to the public API of React Native ([a446a38aaa](https://github.com/facebook/react-native/commit/a446a38aaab5bea2e279f1958cfd90090bfd7e09) by [@TheSavior](https://github.com/TheSavior)) +- Add `RCTExceptionsManager.reportException` ([9a57145f52](https://github.com/facebook/react-native/commit/9a57145f52a03678da02d5d00cbe11eed3f5a0fc) by [@motiz88](https://github.com/motiz88)) +- Add `accessibilityValue` property ([7df3eea1a7](https://github.com/facebook/react-native/commit/7df3eea1a79f12c2dfff1976d0cef605a83232ec) by [@marcmulcahy](https://github.com/marcmulcahy)) +- Add `Appearance` module to expose the user's current Night theme preference ([17862a78db](https://github.com/facebook/react-native/commit/17862a78db59d60fe316961f9111efc330ba2abd) [63fa3f21c5](https://github.com/facebook/react-native/commit/63fa3f21c5ab308def450bffb22054241a8842ef) by [@hramos](https://github.com/hramos)) +- Add `onSlidingComplete` callbacks when sliders adjusted via a11y. ([c7aa6dc827](https://github.com/facebook/react-native/commit/c7aa6dc8270c0eabc913fe6c617c8131e3f4b3c5) by [@marcmulcahy](https://github.com/marcmulcahy)) + +#### Android specific + +- Implement `adjustsFontSizeToFit` on Android ([2c1913f0b3](https://github.com/facebook/react-native/commit/2c1913f0b3d12147654501f7ee43af1d313655d8) by [@janicduplessis](https://github.com/janicduplessis)) +- Allow overriding `EditText` construction in `ReactTextInputShadowNode` ([a5b5d1a805](https://github.com/facebook/react-native/commit/a5b5d1a805a9c54d325763b432be1cf2c8811dc9) by [@mchowning](https://github.com/mchowning)) +- Add Android support for `fontVariant` prop ([c2c4b43dfe](https://github.com/facebook/react-native/commit/c2c4b43dfe098342a6958a20f6a1d841f7526e48) by [@mcuelenaere](https://github.com/mcuelenaere)) +- Custom entry file on android using `ENTRY_FILE` environment variable ([a0d8740878](https://github.com/facebook/react-native/commit/a0d87408782fcf191988612198493d9130736c72)) +- Added `statusBarTranslucent` prop to Modal component ([c35a419e5d](https://github.com/facebook/react-native/commit/c35a419e5d2eca4fe9cd0939df085088fa88423b) by [@pfulop](https://github.com/pfulop)) +- Add `fadingEdgeLength` prop to FlatList and ScrollView ([51aacd5241](https://github.com/facebook/react-native/commit/51aacd5241c4b4c0b9b1e1b8f9dabac45e5b5291)) +- Support `removeClippedSubviews` for horizontal ScrollViews ([42152a3fa3](https://github.com/facebook/react-native/commit/42152a3fa3f949f5112461753eb44a436355dfb1)) +- Introducing `ReactCallerContextFactory` interface ([9713b63d9a](https://github.com/facebook/react-native/commit/9713b63d9ac1e1ae85accd86b78b351ac6295d01) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Added web socket support for macOS ([f21fa4ecb7](https://github.com/facebook/react-native/commit/f21fa4ecb73551bdc4c3d70db9fc13e93b19b3a6) by [@andymatuschak](https://github.com/andymatuschak)) +- Added Warning message Linking API with Phones in iOS Simulator ([e1d89fbd9d](https://github.com/facebook/react-native/commit/e1d89fbd9df91679ec36e955a3d0f699c2d5e777) by [@espipj](https://github.com/espipj)) +- Added missing deps for React-CoreModules ([15b2353382](https://github.com/facebook/react-native/commit/15b2353382c46dc5f0130ff44b9deb6a2361e3e5) by [@fkgozali](https://github.com/fkgozali)) +- Expose the `isPackagerRunning` methods on RCTBundleURLProvider ([fe9cba74fa](https://github.com/facebook/react-native/commit/fe9cba74fa6241b4c38a3df9481d3634ebd51bf9) by [@afoxman](https://github.com/afoxman)) +- Add `autoFocus` to TextInput ([6adba409e6](https://github.com/facebook/react-native/commit/6adba409e6256fd2dcc27a4272edcedae89927af) by [@janicduplessis](https://github.com/janicduplessis)) + +### Changed + +- Upgrade metro version to 0.56.3 ([4b487ba500](https://github.com/facebook/react-native/commit/4b487ba50025becb6a83c805b99d45651db6b8c1) by [@EssamEmad](https://github.com/EssamEmad)) +- Upgrade `eslint-plugin-relay` to 1.3.12 ([f0bcfbe9be](https://github.com/facebook/react-native/commit/f0bcfbe9be0eb6a06d096a682717a23e43c39d52) by [@jstejada](https://github.com/jstejada)) +- Upgrade to Flow v0.108.0 ([d34bc5fa64](https://github.com/facebook/react-native/commit/d34bc5fa64a54dfc2e780461ee2997a4b17f8c65) by [@mvitousek](https://github.com/mvitousek)) +- Upgrade metro babel preset ([cef001713f](https://github.com/facebook/react-native/commit/cef001713fc6384353bbcb4d45645ceee44ed1a9) by [@alloy](https://github.com/alloy)) +- TextInput now properly sends native the end selection location on change ([dff490d140](https://github.com/facebook/react-native/commit/dff490d140010913d3209a2f3e987914b9c4eee4) by [@TheSavior](https://github.com/TheSavior)) +- TextInput now uses `forwardRef` allowing it to be used directly by new APIs requiring a host component. ([bbc5c35a61](https://github.com/facebook/react-native/commit/bbc5c35a61cd3af47ccb2dc62430e4b6a4d4e08f) by [@TheSavior](https://github.com/TheSavior)) +- TextInput no longer does an extra round trip to native on focus/blur ([e9b4928311](https://github.com/facebook/react-native/commit/e9b4928311513d3cbbd9d875827694eab6cfa932) by [@TheSavior](https://github.com/TheSavior)) +- Render collapsed JavaScript frames in RedBox ([468d1a2d2e](https://github.com/facebook/react-native/commit/468d1a2d2e6c72d7c6d435ecaad8499997584de6) by [@motiz88](https://github.com/motiz88)) +- Enable `no-useless-escape` lint rule ([90977b0e00](https://github.com/facebook/react-native/commit/90977b0e00acc6b3263502017c27094392e89478) by [@motiz88](https://github.com/motiz88)) +- Update `DevSettings.reload` to accept a reason ([549cac63cb](https://github.com/facebook/react-native/commit/549cac63cb252037f73453c5d4e7ae5f15586607) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Move `react-native-implementation.js` to `index.js` ([e54ecf907e](https://github.com/facebook/react-native/commit/e54ecf907e9f0660d05dc807ec0e67127143ebed) by [@cpojer](https://github.com/cpojer)) +- Delete Long Press Error in Touchable ([9a3d722ccb](https://github.com/facebook/react-native/commit/9a3d722ccb523f227ffd7770a809996e6cfe75d9) by [@yungsters](https://github.com/yungsters)) +- Add Intl to eslint globals. ([f6a62f9ae2](https://github.com/facebook/react-native/commit/f6a62f9ae2278c0f3a1e5c1a6ec3b7cca3421a41)) +- Add WebSocket to eslint globals ([af8ea06bb4](https://github.com/facebook/react-native/commit/af8ea06bb44e84ce51d4ca4e76f0d66bf34323bd) by [@dr2009](https://github.com/dr2009)) +- Change default `accessibilityRole` of Switch component from `button` to `switch` ([36672c3851](https://github.com/facebook/react-native/commit/36672c3851a044a1ab0edcfaa2790c02f7909695) by [@alvinmatias69](https://github.com/alvinmatias69)) + +#### Android specific + +- Bump gradle-download-task to 4.0.2 ([088be260b6](https://github.com/facebook/react-native/commit/088be260b6727ba82167fe58cb1ee4410a6920b2) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Gradle to 6.0.1 ([701e66bde4](https://github.com/facebook/react-native/commit/701e66bde4ea0e404626c7805e2bcdfa0c129c05) by [@dulmandakh](https://github.com/dulmandakh)) +- Bump Gradle wrapper 5.6.4 ([928f4434b9](https://github.com/facebook/react-native/commit/928f4434b9829c90098b1626b03938d932a9c1f6) by [@friederbluemle](https://github.com/friederbluemle)) +- Bump Soloader to 0.8.0 ([614039834b](https://github.com/facebook/react-native/commit/614039834bf255de096f8b1d168832f81b0cf3fa)) +- Update Android Gradle plugin to 3.5.2 ([b41b5ce8ae](https://github.com/facebook/react-native/commit/b41b5ce8ae2902169ae58860da2c70a9233bea53) by [@friederbluemle](https://github.com/friederbluemle)) +- Improve exception message when JSC loading fails ([65d3167a80](https://github.com/facebook/react-native/commit/65d3167a802b2ca04d4f05ff972c2d51765f1e0d) by [@mhorowitz](https://github.com/mhorowitz)) +- Expose `addCookies` method ([cc845ccfb4](https://github.com/facebook/react-native/commit/cc845ccfb4c0f841b876bca55c5f70efd72be538) by [@safaiyeh](https://github.com/safaiyeh)) +- Migrated from libfb to libfbjni for JNI calls ([9ad5e72b77](https://github.com/facebook/react-native/commit/9ad5e72b77013083f925108870ea6b17f4711a1d) by [@passy](https://github.com/passy)) +- Formatted cpp/h code with clang-format ([d5ba113bb2](https://github.com/facebook/react-native/commit/d5ba113bb2cd839ea38768785e527fbbc9636e41) by [@passy](https://github.com/passy)) +- Switch MainActivity launchMode to singleTask ([7a42596438](https://github.com/facebook/react-native/commit/7a42596438018129d52ff04899ab4ddabd27cdcb) by [@dulmandakh](https://github.com/dulmandakh)) +- Changing method signatures for ImageLoaderModule to accept double for requestId ([641e9657dd](https://github.com/facebook/react-native/commit/641e9657ddab5d1b2676e98d86fd369372281d2c) by [@ejanzer](https://github.com/ejanzer)) +- Use Node's module resolution algorithm to find JSC & Hermes ([fc25f288fe](https://github.com/facebook/react-native/commit/fc25f288fe553cb7e8f04b8ce4b56297b7fa40d5) by [@ide](https://github.com/ide)) +- Add `ACCESS_BACKGROUND_LOCATION` to PermissionsAndroid ([8c099b5f53](https://github.com/facebook/react-native/commit/8c099b5f53405fe0806113ca7ccf0bbe1af92a21) by [@dulmandakh](https://github.com/dulmandakh)) + +#### iOS specific + +- Add `xcscheme` files for iOS template back in. ([a715decd2d](https://github.com/facebook/react-native/commit/a715decd2d3bcdab9537f3246c8398ad9869e94e) by [@pvinis](https://github.com/pvinis)) + +### Deprecated + +- Add deprecation warning to `AccessibilityInfo.fetch` ([523ab83338](https://github.com/facebook/react-native/commit/523ab8333800afbfb169c6fd70ab6611fe07cc2a) by [@TheSavior](https://github.com/TheSavior)) +- Make setting `useNativeDriver` required. Add runtime warning if not specified ([5876052615](https://github.com/facebook/react-native/commit/5876052615f4858ed5fc32fa3da9b64695974238) by [@TheSavior](https://github.com/TheSavior)) +- Refs on an Animated component are now the internal component. The `getNode` call has been deprecated. ([66e72bb4e0](https://github.com/facebook/react-native/commit/66e72bb4e00aafbcb9f450ed5db261d98f99f82a) by [@yungsters](https://github.com/yungsters)) + +#### iOS specific + +- Deprecate `[bridge reload]`, prefer `RCTReloadCommand` ([ffe2306164](https://github.com/facebook/react-native/commit/ffe2306164ed7edfe5ab9d75b5122791037a852a) by [@PeteTheHeat](https://github.com/PeteTheHeat)) + +#### Android specific + +- Deprecate `CallerContext` from `ReactImageManager` ([8accd77c45](https://github.com/facebook/react-native/commit/8accd77c45a4b051bf02904c3485d6a0dcd27631) by [@mdvacca](https://github.com/mdvacca)) + +### Removed + +- Removing experimental `IncrementalPresenter` component ([0ef0d3167e](https://github.com/facebook/react-native/commit/0ef0d3167e291f31ce01ceb729df77cc679d2330) by [@TheSavior](https://github.com/TheSavior)) +- TouchableWithoutFeedback no longer exports Props. Use React.ElementConfig, instead. ([7bcae81299](https://github.com/facebook/react-native/commit/7bcae812997f669de5803cc781dcf3ea65baf0e9) by [@yungsters](https://github.com/yungsters)) +- Remove `Sample` and `CrashyCrash` Modules ([8ec7e0966c](https://github.com/facebook/react-native/commit/8ec7e0966cf83ed29a39aab47c686bc60a124983) by [@RSNara](https://github.com/RSNara)) +- Remove `propTypes` from Animated components. ([86d90c03eb](https://github.com/facebook/react-native/commit/86d90c03ebe39ebc4b2c6dcc0747b4f3a34f5f2f) by [@yungsters](https://github.com/yungsters)) +- Remove `propTypes` from TouchableHighlight. ([7c01172bef](https://github.com/facebook/react-native/commit/7c01172befd07f1d082b18993b87fc880e4b718f) by [@yungsters](https://github.com/yungsters)) +- Remove `propTypes` from TouchableNativeFeedback. ([2185dd298a](https://github.com/facebook/react-native/commit/2185dd298a788c2b713ea17878fd36e06205b4da) by [@yungsters](https://github.com/yungsters)) +- Remove `propTypes` from TouchableOpacity. ([88ae24f719](https://github.com/facebook/react-native/commit/88ae24f719d365b004696aff6461535188ca9f41) by [@yungsters](https://github.com/yungsters)) +- Remove `propTypes` from TouchableWithoutFeedback. ([ebf7d75816](https://github.com/facebook/react-native/commit/ebf7d758164873169937321a4dccc3782359a0d3) by [@yungsters](https://github.com/yungsters)) +- Remove `__skipSetNativeProps_FOR_TESTS_ONLY` from Animated components. ([dcd63078bd](https://github.com/facebook/react-native/commit/dcd63078bdab864830168005b940f638f1e08b23) by [@yungsters](https://github.com/yungsters)) +- Remove Apple TV Props ([548aad4ff1](https://github.com/facebook/react-native/commit/548aad4ff1dfef0d71bdd39aa83ad71e522a2546) by [@yungsters](https://github.com/yungsters)) + +#### Android specific + +- Remove `NativeRunnableDeprecated` ([973253af8a](https://github.com/facebook/react-native/commit/973253af8a47d9ebd137f554054e7a95f8ef2e45) by [@passy](https://github.com/passy)) +- Remove `com.facebook.react.modules.debug.NativeSourceCodeSpec` ([4d9e5f8481](https://github.com/facebook/react-native/commit/4d9e5f8481531000380cf4d3d485fcde1321a37b) by [@RSNara](https://github.com/RSNara)) + +### Fixed + +- Fix `require` cycle warning in ScrollResponder. ([674ac69cee](https://github.com/facebook/react-native/commit/674ac69cee7c1ce6096bee258880e79966322ee0) by [@Naturalclar](https://github.com/Naturalclar)) +- Restore behavior for `underlayColor={null}` in `TouchableHighlight`. ([37d8440a8e](https://github.com/facebook/react-native/commit/37d8440a8e35a53b81914e429502db527790b3cd) by [@yungsters](https://github.com/yungsters)) +- Fix stack traces showing the wrong function name in some cases ([60b4ba16c0](https://github.com/facebook/react-native/commit/60b4ba16c008c23959ebd27ea7215f83878d1343) by [@motiz88](https://github.com/motiz88)) +- Fix `requestAnimationFrame` when focusing input on mount ([5798cf2aa9](https://github.com/facebook/react-native/commit/5798cf2aa9b86bbcb40016aae14eca88fca19fde) by [@janicduplessis](https://github.com/janicduplessis)) +- Reduce overhead of setting up timers in DEV ([75a154b449](https://github.com/facebook/react-native/commit/75a154b4499e44b4ab31ccf28f9eb1dbf21578ac) by [@motiz88](https://github.com/motiz88)) +- Fixed an issue where margin and padding were resolved incorrectly. ([1d683faf1d](https://github.com/facebook/react-native/commit/1d683faf1dc89e4950e7e1f5c5a67f9a7ca1ee24) by [@SidharthGuglani](https://github.com/SidharthGuglani)) +- Fix using width for calculating margin top percent ([0599af2282](https://github.com/facebook/react-native/commit/0599af2282ffbf636604bce1cb4c049201fed393) by [@SidharthGuglani](https://github.com/SidharthGuglani)) +- Don't restore default values in NativeAnimated when components unmount ([686ab49107](https://github.com/facebook/react-native/commit/686ab49107df8ed20d4e810f1366715cd70b4a31) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix eslint-config peer dependency warnings ([1353da5a53](https://github.com/facebook/react-native/commit/1353da5a538d4a6f76fc9530711394cf981034a0) by [@friederbluemle](https://github.com/friederbluemle)) +- Remove style rules from eslint config for prettier options ([e4b62bb139](https://github.com/facebook/react-native/commit/e4b62bb139c258b65a9ebf2a8ee692ea52c3afab) by [@iRoachie](https://github.com/iRoachie)) +- Fix separators displays in wrong places with the inverted list ([dfb4f4af68](https://github.com/facebook/react-native/commit/dfb4f4af68726d2e05f63689a9c74c9bb9a0611b) by [@dy93](https://github.com/dy93)) +- Fix issue where we attempt to connect to React devtools every 2 seconds ([e7f6210d5d](https://github.com/facebook/react-native/commit/e7f6210d5d417c5b6d4ba7f5cf96b40dbf70b9cd) by [@ejanzer](https://github.com/ejanzer)) +- Fix so that early logs don't get dropped by Metro ([4ed05ca241](https://github.com/facebook/react-native/commit/4ed05ca241b791ad629fd154429a4a53c7731556) by [@gaearon](https://github.com/gaearon)) +- Fix to announce accessibility state changes happening in the background ([baa66f63d8](https://github.com/facebook/react-native/commit/baa66f63d8af2b772dea8ff8eda50eba264c3faf) by [@xuelgong](https://github.com/xuelgong)) +- Fix `JSBigString` not compiling on Windows due to Unix-specific headers ([80857f295c](https://github.com/facebook/react-native/commit/80857f295c17e5f8966b3d1c1207d3c4570a1b26) by [@empyrical](https://github.com/empyrical)) +- Fix exception in `scrollResponderScrollNativeHandleToKeyboard` when ref is null ([da8ae011bb](https://github.com/facebook/react-native/commit/da8ae011bbabc8acb7ef7f6903f68dd60aaa1f9d) by [@TheSavior](https://github.com/TheSavior)) +- Fix excessive toggles on the Switch component ([b782934f3f](https://github.com/facebook/react-native/commit/b782934f3f2a80ae7e3872cc7d7a610aa6680ec4) by [@rurikoaraki](https://github.com/rurikoaraki)) +- Fix bare hosts in `URL`. Add missing / between url ([20ab946f34](https://github.com/facebook/react-native/commit/20ab946f34b1d9727ff08c733b2006e84fd79349) by [@jeswinsimon](https://github.com/jeswinsimon)) +- Fix the non-standard usage of `ATOMIC_VAR_INIT` macro from code with systrace enabled ([75a7a52db4](https://github.com/facebook/react-native/commit/75a7a52db496bd3892a367372eea25bf50840fc3)) +- Fix `useWindowDimensions` hook firing continuously after dimensions change ([3b3c95b017](https://github.com/facebook/react-native/commit/3b3c95b0170e60983eb6e89b910d100d08eee141) by [@dulmandakh](https://github.com/dulmandakh)) +- Fix throttling in ScrollView ([4159e20146](https://github.com/facebook/react-native/commit/4159e201462c346c456de1fa869d88a9cce7b6d4) by [@sammy-SC](https://github.com/sammy-SC)) +- Fix `TimingAnimation` rounding error issue ([77b6e26538](https://github.com/facebook/react-native/commit/77b6e2653835af61b186903eae45d67f35351ade) by [@MartinSherburn](https://github.com/MartinSherburn)) +- Fix recycling of Switch ([a261e6dfb2](https://github.com/facebook/react-native/commit/a261e6dfb2680a955943db53c4b0a7bb887bfe22) by [@sammy-SC](https://github.com/sammy-SC)) + +#### Android specific + +- Fix to reset sMatrixDecompositionContext before applying transformations ([bf01dfbc97](https://github.com/facebook/react-native/commit/bf01dfbc97ea8be9d88214ab31809f2f42d6c064) by [@makovkastar](https://github.com/makovkastar)) +- Fix animations in OSS debug builds by modifying `Platform.isTesting()` behavior ([1fbc6a7c17](https://github.com/facebook/react-native/commit/1fbc6a7c178d13421b0b84d6ea01f9174105325f) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix Modal not disappearing when reloading ([5ddf00ee1a](https://github.com/facebook/react-native/commit/5ddf00ee1acbf66c7204227c398a58c13e4545cf) by [@sunnylqm](https://github.com/sunnylqm)) +- Fix to support nullable returns NativeModule methods returning Boxed Primitives ([f57b0caaa4](https://github.com/facebook/react-native/commit/f57b0caaa4452c64006c159cd28a1a562b332c21) by [@RSNara](https://github.com/RSNara)) +- Fix crash in TextInput ([6ebd3b046e](https://github.com/facebook/react-native/commit/6ebd3b046e5b71130281f1a7dbe7220eff95d74a) by [@MarcoPolo](https://github.com/MarcoPolo)) +- Fix View.getGlobalVisibleRect() to clip result rect properly when overflow is 'hidden' ([df9abf7983](https://github.com/facebook/react-native/commit/df9abf798351c43253c449fe2c83c2cca0479d80) by [@davidbiedenbach](https://github.com/davidbiedenbach)) +- Fix throwing "Unknown array type" exception ([4b9350061f](https://github.com/facebook/react-native/commit/4b9350061fa3d186fdd3a973e1b46f60a7ac03b9) by [@petterh](https://github.com/petterh)) +- Fix issue with refresh control not working properly on an inverted ScrollView ([0a282c42b4](https://github.com/facebook/react-native/commit/0a282c42b4d1c2316513cd5588a0a92b54db2991) by [@migbot](https://github.com/migbot)) +- Fix to listen to NFC actions for linking url events ([8d8c3d4e1e](https://github.com/facebook/react-native/commit/8d8c3d4e1eb88366074e87385c4d96a46dfdd544) by [@cimitan](https://github.com/cimitan)) +- Fix onPress prop for Touchable Components being called twice on AndroidTV. ([21890e964d](https://github.com/facebook/react-native/commit/21890e964df7674fcf13cefc8cb939441f6eddef) by [@dbarr33](https://github.com/dbarr33)) +- Fix `includeFontPadding` for `TextInput` placeholder ([211ea485cd](https://github.com/facebook/react-native/commit/211ea485cd993ca25d6640be41e54f327ca1629c) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix medium font weights for TextInput on Android ([8b9f790069](https://github.com/facebook/react-native/commit/8b9f7900697b2e4bb72b37ed2e6c3d113185d327) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix close button issue in KeyboardAvoidingView ([f1c6029e48](https://github.com/facebook/react-native/commit/f1c6029e4868084e5a10d81c15ee3cc5e301599a) by [@saxenanickk](https://github.com/saxenanickk)) +- Fix activity recreation on theme change ([83a16b16c9](https://github.com/facebook/react-native/commit/83a16b16c9afa0fe0328ab818470d4fce098876b) by [@Esemesek](https://github.com/Esemesek)) +- Fix ForwardingCookieHandler missing WebView exceptions. ([314eba98b2](https://github.com/facebook/react-native/commit/314eba98b2f2755cb26ed7a268d3fe83a7626efa) by [@siddhantsoni](https://github.com/siddhantsoni)) +- Fix ReactInstanceManagerBuilder.build crashing if SoLoader has not been explicitly initialized ([60e00d9d96](https://github.com/facebook/react-native/commit/60e00d9d96d7b186c1d4c1542caddc1b74eeb3da) by [@petterh](https://github.com/petterh)) +- Fix default accessibility hint not being read. ([f8dff0bcb3](https://github.com/facebook/react-native/commit/f8dff0bcb3147b7a1aa8ac7159952d848e198e29)) +- Fix JS bundle loading progress bar ([7b9d6d19e2](https://github.com/facebook/react-native/commit/7b9d6d19e2c0854aa53587ef68ce715fb7803e2a) by [@makovkastar](https://github.com/makovkastar)) +- Fix Android Q related NaN error - don't try to do math with NaN values ([db5994980d](https://github.com/facebook/react-native/commit/db5994980df136c5cce6cd90348b4bf18180562f)) +- Fix throwing exceptions when the host activity is not FragmentActivity ([7cfabf42b8](https://github.com/facebook/react-native/commit/7cfabf42b816de758d8e52896bbab0c50e3a802a) by [@mganandraj](https://github.com/mganandraj)) +- Fix crash when using `TextInput.FontVariant` prop in Android API level < 26 ([e885ddedb9](https://github.com/facebook/react-native/commit/e885ddedb9b0a025cb8031414dcc4bd22744a0eb) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Fix support for `onRequestClose` in Modal on iOS 13+ ([8e5fac89bb](https://github.com/facebook/react-native/commit/8e5fac89bbdcc3028bb5d81a358969a235abf991) by [@koke](https://github.com/koke)) +- Fix `Dimensions` module to update on initial split screen ([7a72c35a20](https://github.com/facebook/react-native/commit/7a72c35a20a18c19bf6ab883cb2c53a85bd4c5c0) by [@sahrens](https://github.com/sahrens)) +- Fix spinner visibility on `beginRefreshingProgrammatically` ([e341489521](https://github.com/facebook/react-native/commit/e341489521ad495e68e8aba01ff4dd25a5e4ff3e) by [@nnabinh](https://github.com/nnabinh)) +- Reconnect to debugger websocket after Metro is restarted. ([13992f90e4](https://github.com/facebook/react-native/commit/13992f90e48fc11e0b7217ee6d9413f97c32268a) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Fix Slider not disabling properly if the disabled prop is set. ([fa9ff07017](https://github.com/facebook/react-native/commit/fa9ff07017edbc76595fe2f2d964ee13c5f4088a)) +- Fix apps crashing on iOS 13.x when running timer in the background ([e1d03b4cc0](https://github.com/facebook/react-native/commit/e1d03b4cc00c361e10687eb4a9f902563cd1cbe1) by [@radko93](https://github.com/radko93)) +- Fix TextInput blur when tabbing in iOS simulator. ([a7437710d2](https://github.com/facebook/react-native/commit/a7437710d25adfc9150fc079e4525ed59d5404e2) by [@fat](https://github.com/fat)) +- Fix promised returned by `Share.share(content, options)` not resolving if share dialog dismissed ([7468a6c903](https://github.com/facebook/react-native/commit/7468a6c9033ffe8cc2315a3de3a759b8745fe43d) by [@v-fernandez](https://github.com/v-fernandez)) +- Fix maximum searching depth while measuring layout by removing it. ([2f8328dbb0](https://github.com/facebook/react-native/commit/2f8328dbb0d9813c904c0b888b2b7500cf4a4bce) by [@draws](https://github.com/dratwas)) +- Fix SafeAreaInsets call to not crash on older versions of iOS ([03acf57b76](https://github.com/facebook/react-native/commit/03acf57b767553acbee4ff589055fbd239ffffbb) by [@mmmulani](https://github.com/mmmulani)) +- Fix to retain `cropData` struct arg in ImageEditingManager.cropImage call ([002d3c179d](https://github.com/facebook/react-native/commit/002d3c179dd2515f0a4d894d9b7f70c4e538f728) by [@RSNara](https://github.com/RSNara))) +- Fix bug rendering nested text on iOS13 ([06599b3e59](https://github.com/facebook/react-native/commit/06599b3e594355a1d5062ede049ff3e333285516) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix longstanding bug where RCTNullIfNil() can return nil ([79b573511b](https://github.com/facebook/react-native/commit/79b573511bd55e6c82c0044e1930549ccfa8a923) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix crash in RCTScrollViewComponentView ([e7ef9921d3](https://github.com/facebook/react-native/commit/e7ef9921d3f91b02cfec4bbfd88b4968434e201c) by [@shergin](https://github.com/shergin)) +- Fix how the amount of free memory is calculated to mimic the logic Apple uses. ([b53d3d80f9](https://github.com/facebook/react-native/commit/b53d3d80f991937915a87ba8515f403551de139e)) +- Fix animated gifs incorrectly looping ([6f2e6f170e](https://github.com/facebook/react-native/commit/6f2e6f170e3ee785d1ba844971447ea24f91185e) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix `tintColor` in SegmentedControlIOS component ([be89e4d928](https://github.com/facebook/react-native/commit/be89e4d928a504de304f5afb19bd3cc15ae3eb7d) by [@sammy-SC](https://github.com/sammy-SC)) + +## v0.61.5 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/151) to improve the quality of the 0.61 release. Thanks to everyone who contributed! + +### Fixes + +#### Android specific + +- Fix bundling assets in monorepo ([a3b0804867](https://github.com/facebook/react-native/commit/a3b08048674e324dbe1f0ca816f35607e9e06a2f) by [@Esemesek](https://github.com/Esemesek)) +- Fix multiple `set-cookie` not aggregated correctly in response headers ([1df8bd4932](https://github.com/facebook/react-native/commit/1df8bd4932f42958c01dccf44cee92b75a6988ed) by **Vincent Cheung**) + +## v0.61.4 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/150) to improve the quality of the 0.61 release. Thanks to everyone who contributed! + +### Fixed + +- Fix build with Hermes on Windows ([81a6b6ed3c](https://github.com/facebook/react-native/commit/81a6b6ed3c54498f6f2148c106846352405949bf) by [@B27](https://github.com/B27)) +- Fix Chrome debugger showing console.logs at incorrect locations ([42ac240bce](https://github.com/facebook/react-native/commit/42ac240bceb104474494c6007df0089baec00f7a) by [@rickhanlonii](https://github.com/rickhanlonii)) + +#### iOS specific + +- Fix bug in iOS 13 when application would be terminated immediately when in background ([d7c9173b07](https://github.com/facebook/react-native/commit/d7c9173b07171164bcadf73855454e90e07b31be) by [@radko93](https://github.com/radko93)) + +## v0.61.3 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/148) to improve the quality of the 0.61 release. Thanks to everyone who contributed! + +### Fixed + +- Fix bug where ScrollView contentInset top set to undefined wouldn't default to 0 ([d576a5bcc0](https://github.com/facebook/react-native/commit/d576a5bcc0e03dd9c4ccd982f723d6e376e5b680) by [TheSavior](https://github.com/TheSavior)) +- Fix TimingAnimation rounding error issue ([bfd01552af](https://github.com/facebook/react-native/commit/bfd01552af6c074a425da2e7cc1a5908faba2644) by [MartinSherburn](https://github.com/MartinSherburn)) + +#### iOS specific + +- Fix selecting videos from library in iOS 13 ([63769518e0](https://github.com/facebook/react-native/commit/63769518e0c7db60eb39bb5f47fe24f4bc664862) by [fatalsun](https://github.com/fatalsun)) +- Fix bug in iOS13 nested text rendering ([7cf43afa8d](https://github.com/facebook/react-native/commit/7cf43afa8d6a03ccb4cfdc09f81891eabe8b8b70) by [PeteTheHeat](https://github.com/PeteTheHeat)) + +#### Android specific + +- Release underlying resources when JS instance is GC'ed on Android try ([9b2374b542](https://github.com/facebook/react-native/commit/9b2374b542f87b7baefcfb4a3eb4f57029069b57) by [janicduplessis](https://github.com/janicduplessis)) + +## v0.61.2 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/146) to improve the quality of the 0.61 release. Thanks to everyone who contributed! + +### Fixed + +#### Android specific + +- Fix elevation issues on Android ([8fd9ab2f54](https://github.com/facebook/react-native/pull/26682) by [@grabbou](https://github.com/grabbou)) + +### Added + +- Use `warnOnce` for excessive number of callbacks error ([0cafa0f5d1](https://github.com/facebook/react-native/commit/0cafa0f5d1e7fa5369b765f4b97f38bf1608230a) by [@janicduplessis](https://github.com/anicduplessis)) +- Include transform in OUTER_PROPS ([b94438](https://github.com/facebook/react-native/commit/b94438) by [@migbot](https://github.com/migbot)) + +#### iOS specific + +- Better iOS13 support in `StatusBar` API ([796b3a1f88](https://github.com/facebook/react-native/commit/796b3a1f8823c87c9a066ea9c51244710dc0b9b5) by [@gaodeng](https://github.com/gaodeng)) + +#### Android specific + +- Improve error message in NativeModuleRegistryBuilder ([113c4e229c](https://github.com/facebook/react-native/commit/113c4e229c374232c46a89afd74df7117a3447c1) by [@vonovak](https://github.com/vonovak)) + +## v0.61.1 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/144) to improve the quality of the 0.60 release. Thanks to everyone who contributed! + +### Fixed + +#### iOS specific + +- Fix ShareSheet crash on iOS 13 ([a4fbb8e75b](https://github.com/facebook/react-native/commit/a4fbb8e75bd9f521037926a68a8b75eaca2eca74) by [@tomtargosz](https://github.com/tomtargosz)) + +#### Android specific + +- Allow again for injecting custom root view via ReactActivityDelegate ([9f0dede1c9](https://github.com/facebook/react-native/commit/9f0dede1c913612e1241432f4cbccdc74d23a1e4) by [@kmagiera](https://github.com/kmagiera)) + +## v0.61.0 + +This is a major release that includes the new reloading experience Fast Refresh. It also removes the React `.xcodeproj`, fixes `use_frameworks!` for CocoaPods support, adds a `useWindowDimensions` hook, and upgrades to React 16.9. + +### Added + +- Add Fast Refresh by default ([17f8e5810f](https://github.com/facebook/react-native/commit/17f8e5810f3260ce1b24c61665883bab8847aabe) by [@gaearon](https://github.com/gaearon)) +- Add `useWindowDimensions` hook to replace most `Dimensions` usage ([103ec2f770](https://github.com/facebook/react-native/commit/103ec2f770dbb785ef4bc26f8662c74edded796a) by [@sahrens](https://github.com/sahrens)) + +#### Android specific + +- Add exception in .gitignore for `debug.keystore` to the android template. ([d55025694b](https://github.com/facebook/react-native/commit/d55025694be8b4ee5d09c8fdc910d42a5f144883) by [@bondehagen](https://github.com/bondehagen)) +- Add jitpack repository to template ([1a92cf9b2a](https://github.com/facebook/react-native/commit/1a92cf9b2afa718a81299b4be5ab6bdff16f4863) by [@iyegoroff](https://github.com/iyegoroff)) + +#### iOS specific + +- Add RCTWeakProxy to properly deallocate RCTUIImageViewAnimated ([947e71a922](https://github.com/facebook/react-native/commit/947e71a922c0db5d3d3780d249d1a8d183534c22) by [@mmmulani](https://github.com/mmmulani)) + +### Changed + +- Use prettyFormat for Metro logging ([abd7faf354](https://github.com/facebook/react-native/commit/abd7faf3547e165abfc52383d3709b9d4d2e9006) by [@cpojer](https://github.com/cpojer)) +- Tweak messages and fix the warning condition ([2a3ac0429b](https://github.com/facebook/react-native/commit/2a3ac0429b0e4c443d185807a39b41fc5a2ab1d2) by [@gaearon](https://github.com/gaearon)) +- Allow jest globals in **mocks** directories ([e78c01375a](https://github.com/facebook/react-native/commit/e78c01375aef88e0bb4029479acac9e85ecaf080) by [@artdent](https://github.com/artdent)) +- Make Animation EndCallback type allow any return value ([306c8d64d9](https://github.com/facebook/react-native/commit/306c8d64d91f87b248f627333de7f24355248088) by [@draperunner](https://github.com/draperunner)) +- create two layout pass reason flexLayout and flexMeasure instead of flex ([6ce985463b](https://github.com/facebook/react-native/commit/6ce985463b2724451baed8b0486b298f969e36e7) by [@SidharthGuglani](https://github.com/SidharthGuglani)) +- Use shorthand for Fragment in App.js ([7cac6a4b6c](https://github.com/facebook/react-native/commit/7cac6a4b6cfa8c1b54db62f2b1510f7c52f4574d) by [@ferdicus](https://github.com/ferdicus)) +- Use eslint-plugin-prettier recommended config ([d2b92fffb1](https://github.com/facebook/react-native/commit/d2b92fffb1d14dd0ec628e9dcdfd76e39f2067ff) by [@Justkant](https://github.com/Justkant)) +- Support string command arguments ([0314305e12](https://github.com/facebook/react-native/commit/0314305e1202e48c74091e15da8574f1b92ce441) by [@TheSavior](https://github.com/TheSavior)) +- chore: Link to CLA wiki and CLA form. ([e2d55d5c5e](https://github.com/facebook/react-native/commit/e2d55d5c5ef40ccae3220dc0e1fca7cf3592c676) by [@JustinTRoss](https://github.com/JustinTRoss)) +- CLI is now ^3.0.0-alpha.1 ([5edd1c674c](https://github.com/facebook/react-native/commit/5edd1c674c911a6c59aaad8ed36ce12fa98787ff) by [@thymikee](https://github.com/thymikee)) +- Flow is now v0.104.0 ([59db059dbd](https://github.com/facebook/react-native/commit/59db059dbddb8101212f3739eecf0db494cfab41) by [@mroch](https://github.com/mroch)) +- React is now at 16.9 ([40e8a5f685](https://github.com/facebook/react-native/commit/40e8a5f685376300aa5365de4557cd395996b9a2), [0ccedf3964](https://github.com/facebook/react-native/commit/0ccedf3964b1ebff43e4631d1e60b3e733096e56) by [@TheSavior](https://github.com/TheSavior)) +- Use Metro for auto-collapsing internal stack frames ([77125a1ac3](https://github.com/facebook/react-native/commit/77125a1ac364a6b7e2382fdc86cc19a3e2eba089) by [@motiz88](https://github.com/motiz88)) +- Move React error message formatting into ExceptionsManager ([2dadb9e2b0](https://github.com/facebook/react-native/commit/2dadb9e2b0ba26223ed83a30af620ce3e62e245f) by [@motiz88](https://github.com/motiz88)) +- Improve VirtualizedList error message ([bef87b648c](https://github.com/facebook/react-native/commit/bef87b648c4bed228f1c5889abe0181a271edf76) by [@vonovak](https://github.com/vonovak)) + +#### Android specific + +- Bump Hermes to v0.2.1 ([811401bcac](https://github.com/facebook/react-native/commit/811401bcac02f3e6e154c7e0f76f9f82eeaa6959) by [@sunnylqm](https://github.com/sunnylqm)) +- Use centralized package for DoNotStrip annotation ([35fc0add2d](https://github.com/facebook/react-native/commit/35fc0add2d3a278bf90257284fe23e03898008de) by [@passy](https://github.com/passy)) + +#### iOS specific + +- Do not override ActivityIndicator color when setting its size ([14b0ed4c5d](https://github.com/facebook/react-native/commit/14b0ed4c5d872cd992f6e1ca072a2c44c8ece25f) by [@cabelitos](https://github.com/cabelitos)) +- fix display problems when image fails to load ([71d7d6883c](https://github.com/facebook/react-native/commit/71d7d6883cb9a3d18666f04a444de7b4a611b304)) +- Renamed yoga podspec to Yoga ([82a8080f07](https://github.com/facebook/react-native/commit/82a8080f0704e83079d0429e4e367f5131052e64) by [@axe-fb](https://github.com/axe-fb)) +- Update loading pre-bundled message ([eb92f8181f](https://github.com/facebook/react-native/commit/eb92f8181f3119bbc69ff7cb5aff2e03d993b8b3) by [@rickhanlonii](https://github.com/rickhanlonii)) + +### Deprecated + +- Deprecate method UIManagerModule.playTouchSound() ([e3ec8dbe15](https://github.com/facebook/react-native/commit/e3ec8dbe15a07e86530e1fd801c27ad8c1023b5c) by [@mdvacca](https://github.com/mdvacca)) +- Deprecate UIManager.measureLayoutRelativeToParent ([e42009b784](https://github.com/facebook/react-native/commit/e42009b7849f1cfd6d6d34e28c564ec5e39680bb) by [@mdvacca](https://github.com/mdvacca)) + +#### Android specific + +- DrawerLayoutAndroid drawerPosition now expects a string, number is deprecated ([305b0a2814](https://github.com/facebook/react-native/commit/305b0a28142414d559d2d08795a5963716dc4b0f) by [@TheSavior](https://github.com/TheSavior)) + +### Removed + +#### Android specific + +- Remove supportLibVersion variable in build.gradle ([fee7f0617e](https://github.com/facebook/react-native/commit/fee7f0617ee6e4f10edf6b8e36da6c5fb00d22ac) by [@ferdicus](https://github.com/ferdicus)) + +#### iOS Specific + +- Remove 's.static_framework = true' requirement for podspec ([ca9e108110](https://github.com/facebook/react-native/commit/ca9e108110e4a3cc39044805f879d9a9cb637c41) by [@jtreanor](https://github.com/jtreanor)) + +### Fixed + +- Add ErrorUtils to eslint globals ([76af5f9163](https://github.com/facebook/react-native/commit/76af5f916303d7906ea522076c965292145a1370) by [@rodineijf](https://github.com/rodineijf)) +- URL: Do not prepend baseUrl if the URL is not a relative URL ([e104204ae0](https://github.com/facebook/react-native/commit/e104204ae083d31e0b9967373ce79f2f1ca8fbb6) by [@jeswinsimon](https://github.com/jeswinsimon)) +- Memory Leak due to JSStringRelease not called ([b8d6ef3726](https://github.com/facebook/react-native/commit/b8d6ef372663fe6d467144abfc5d2c9352dc28d6) by [@sachinservicemax](https://github.com/sachinservicemax)) +- Fixed rotateZ native animation ([f4f08d3c54](https://github.com/facebook/react-native/commit/f4f08d3c549f2af7cd04ef78fe800d3bc12af1f0) by [@Titozzz](https://github.com/Titozzz)) +- Fix indentation in Gradle files ([9b0adb5ad1](https://github.com/facebook/react-native/commit/9b0adb5ad132b8ff37e707a4943411d92b4e58dc) by [@sonicdoe](https://github.com/sonicdoe)) +- Fix handling of failed image downloads ([71d7d6883c](https://github.com/facebook/react-native/commit/71d7d6883cb9a3d18666f04a444de7b4a611b304) by [@sammy-SC](https://github.com/sammy-SC)) +- Fix SectionList scrollToLocation and prevent regressions ([8a82503b54](https://github.com/facebook/react-native/commit/8a82503b54e3c63230a07de99ec082b2dcb54bc7) by [@vonovak](https://github.com/vonovak)) +- [General][internal] Fix incorrect `module.name_mapper` in template .flowconfig ([e6b2cf0418](https://github.com/facebook/react-native/commit/e6b2cf04188fc9647bae4bef4cca5d4dde22a657) by [@MoOx](https://github.com/MoOx)) +- Fall back to `JSON.stringify` in `console.log` if Symbol is unavailable ([179889704b](https://github.com/facebook/react-native/commit/179889704b6f9d56cb990d5b9bba6ee5ea2cd13f) by [@cpojer](https://github.com/cpojer)) +- Pop frames correctly in console.error handler ([3eaf245540](https://github.com/facebook/react-native/commit/3eaf2455402b5ad73c8a059311f0cb213df9dd28) by [@motiz88](https://github.com/motiz88)) +- Add documentation to TextInput's Flow types ([d00f0882fb](https://github.com/facebook/react-native/commit/d00f0882fbdd532f8698d2569bd771ca5843d0f5) by [@empyrical](https://github.com/empyrical)) + +#### Android specific + +- Add missing Hermes include ([1db96a3c46](https://github.com/facebook/react-native/commit/1db96a3c469b872e851553207e5420d54afc731a) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix UIManager.measure to consider scale and rotation transforms ([28d50189f3](https://github.com/facebook/react-native/commit/28d50189f3350e7550bf03ea5bd1363839ee2911) by [@floriancargoet](https://github.com/floriancargoet)) + +#### iOS specific + +- Fixed iOS packager connection ([4ab9da134c](https://github.com/facebook/react-native/commit/4ab9da134c988db832b1a2daa90ce38bf8c419eb) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fixed compatibility with CocoaPods frameworks. ([8131b7bb7b](https://github.com/facebook/react-native/commit/8131b7bb7b4794e0e7003a6e3d34e1ebe4b8b9bc) by [@jtreanor](https://github.com/jtreanor)) +- Don't call sharedApplication in App Extension ([c5ea18f738](https://github.com/facebook/react-native/commit/c5ea18f7389fe821e7a9882e4b1b30b0a1b266f4) by [@zhongwuzw](https://github.com/zhongwuzw)) + +## v0.60.6 + +This is a small patch release with a commit to fix the build break in MSVC to help the users of react-native-windows. ([9833ee7bc1](https://github.com/facebook/react-native/commit/9833ee7bc19982acd6ccaf6ac222bc24a97667a8) by [@acoates-ms](https://github.com/acoates-ms)) + +## v0.60.5 + +This is a patch release that consist of a few commits requested in the [dedicated conversation](https://github.com/react-native-community/releases/issues/130) to improve the quality of the 0.60 release. Thanks to everyone who contributed! + +### Added + +- Added a default Prettier config for new projects ([7254bab0b3](https://github.com/facebook/react-native/commit/7254bab0b3fa129cd238783ab993fbae1102d60a) by [@jpdriver](https://github.com/jpdriver)) + +#### Android specific + +- Add showSoftInputOnFocus to TextInput ([d88e4701fc](https://github.com/facebook/react-native/commit/d88e4701fc46b028861ddcfa3e6ffb141b3ede3d)) + +### Changed + +- Bump CLI to ^2.6.0 ([fafe5ee072](https://github.com/facebook/react-native/commit/fafe5ee0726061e3590b91d3b5cff04e33781f87) by [@thymikee](https://github.com/thymikee)) + +### Fixed + +- Ensure right version of Metro bundler is used ([1bb197afb1](https://github.com/facebook/react-native/commit/1bb197afb191eab134354386700053914f1ac181) by [@kelset](https://github.com/kelset)) + +#### Android specific + +- Fix `ClassNotFound` exception in Android during Release builds ([ffdf3f22c6](https://github.com/facebook/react-native/commit/ffdf3f22c68583fe77517f78dd97bd2e97ff1b9e) by [@thecodrr](https://github.com/thecodrr)) +- Remove unnecessary flag when running JS server ([a162554f5d](https://github.com/facebook/react-native/commit/a162554f5dc36fa0647b5bf52119a62bd20046e3) by [@thecodrr](https://github.com/thecodrr)) +- Correctly set the border radius on android ([b432b8f13b](https://github.com/facebook/react-native/commit/b432b8f13b4871dcafd690e57d37298662712b50) by [@cabelitos](https://github.com/cabelitos)) +- Fix addition of comma at the end of accessibility labels on Android. ([812abfdbba](https://github.com/facebook/react-native/commit/812abfdbba7c27978a5c2b7041fc4a900f3203ae) by [@marcmulcahy](https://github.com/marcmulcahy)) + +#### iOS specific + +- Don't call sharedApplication in App Extension ([c5ea18f738](https://github.com/facebook/react-native/commit/c5ea18f7389fe821e7a9882e4b1b30b0a1b266f4) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Delete fishhook ([46bdb4161c](https://github.com/facebook/react-native/commit/46bdb4161c84b33f1d0612e9c7cdd824462a31fd) by [@mmmulani](https://github.com/mmmulani)) + +You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/130). + +## v0.60.4 + +This is a patch release that contains two more Hermes related fixes, thanks to the contributors for helping improving the support! + +### Fixed + +#### Android specific + +- Generate correct source map if hermes not enabled ([b1f81be4bc](https://github.com/facebook/react-native/commit/b1f81be4bc21eb9baa39dd7ef97709d9927ad407) by [@HazAT](https://github.com/HazAT)) +- Generate source maps outside of assets/ ([60e75dc1ab](https://github.com/facebook/react-native/commit/60e75dc1ab73b2893ec2e25c0320f32b3cf12b80) by [@motiz88](https://github.com/motiz88)) + +You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/130). + +## v0.60.3 + +This is a patch release that fixes the binary path to Hermes package, thanks to [@zoontek](https://github.com/zoontek)) for creating the PR! + +You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/130). + +## v0.60.2 + +This is a patch release that fixes the path to Hermes package. + +You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/130). + +## v0.60.1 + +This is a patch release that includes the Hermes JavaScript Engine announced at Chain React Conf 2019. + +Check out the documentation to opt-in and give [Hermes a try](https://reactnative.dev/docs/hermes). + +You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/130). + +## v0.60.0 + +This feature release of React Native includes many milestone changes for the platform. Please refer to the [blog post](https://reactnative.dev/blog/2019/07/03/version-60) for selected details. For upgrading users, some of the progress comes with breaking changes; manual intervention may be required for your app. We're also aware that existing CocoaPods integrations using `use_frameworks` are not out-of-the-box compatible with this version, but please consider [various workarounds](https://github.com/facebook/react-native/issues/25349) while we prepare a long-term solution for a future release. If you're interested in helping evaluate our next release (0.61), subscribe to the dedicated issue [here](https://github.com/react-native-community/react-native-releases/issues/130). + +Have you ever considered contributing to React Native itself? Be sure to check out [Contributing to React Native](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md). + +### Added + +- CLI autolinking support ([5954880875](https://github.com/facebook/react-native/commit/5954880875d8dfb9b7868aa316647f8fe2b3d8c3), [da7d3dfc7d](https://github.com/facebook/react-native/commit/da7d3dfc7d3bd83e7522175a720b30fee4c9b3d3) by [@zhongwuzw](https://github.com/zhongwuzw) and [@hramos](https://github.com/hramos)) +- New Intro screen ([6b393b27e1](https://github.com/facebook/react-native/commit/6b393b27e18e663d39b66fd121ee302bce29d77d), [233fddbe01](https://github.com/facebook/react-native/commit/233fddbe012098dce3719ba066d3dc653e05e6c9), [fe88e9e48c](https://github.com/facebook/react-native/commit/fe88e9e48ce99cb8b9da913051cc36575310018b), [aa926e349b](https://github.com/facebook/react-native/commit/aa926e349b1656b02b8c1a2048cc56b25f9567c1), [a9e8a71e53](https://github.com/facebook/react-native/commit/a9e8a71e531510baf126780cecdcbc64c934f4dd), [ad4a5d9a3e](https://github.com/facebook/react-native/commit/ad4a5d9a3eac9794038e93158d45e7f1ceb9e495), and [0245fd713e](https://github.com/facebook/react-native/commit/0245fd713ea9ff6fe334980f537e2254a9e3126c) by [@cpojer](https://github.com/cpojer), [@eliperkins](https://github.com/eliperkins), [@lucasbento](https://github.com/lucasbento), [@orta](https://github.com/orta), [@adamshurson](https://github.com/adamshurson), [@karanpratapsingh](https://github.com/karanpratapsingh) and [@glauberfc](https://github.com/glauberfc)) +- Add enhanced accessibility actions support ([7fb02bd908](https://github.com/facebook/react-native/commit/7fb02bd90884f0a717e8151d4d30767fe38392c1) by [@xuelgong](https://github.com/xuelgong)) +- Add additional accessibility roles and states ([1aeac1c625](https://github.com/facebook/react-native/commit/1aeac1c62528004d994200664368dc85fba1795d)) +- Add `isReduceMotionEnabled()` plus `reduceMotionChanged` to `AccessibilityInfo` ([0090ab32c2](https://github.com/facebook/react-native/commit/0090ab32c2aeffed76ff58931930fe40a45e6ebc) by [@estevaolucas](https://github.com/estevaolucas)]) +- Add support for cancelling fetch requests with `AbortController` ([h5e36b0c](https://github.com/facebook/react-native/commit/5e36b0c6eb2494cefd11907673aa018831526750) by [@janicduplessis](https://github.com/janicduplessis)) + +#### Android specific + +- Enable views to be nested within **Text**; this brings feature parity to Android, but be aware that it [has some limitations](https://github.com/facebook/react-native/commit/a2a03bc68ba062a96a6971d3791d291f49794dfd) ([5c399a9f74](https://github.com/facebook/react-native/commit/5c399a9f74f22c58c11f75abde32ac7dc269ccc0) by [@rigdern](https://github.com/rigdern)) +- Add a `touchSoundDisabled` prop to **Button**, **Touchable**, and **TouchableWithoutFeedback** ([45e77c8324](https://github.com/facebook/react-native/commit/45e77c8324f7dc2d53109e45a4e0b18cbab6a877) by [@yurykorzun](https://github.com/yurykorzun)) + +#### iOS specific + +- Add `announceForAccessibility` and `announcementFinished` APIs for making screen reader announcements ([cfe003238a](https://github.com/facebook/react-native/commit/cfe003238ab8c5686d185f6ce9e0776eeb4bb729) by [@rigdern](https://github.com/rigdern)) +- Ability to force network requests to use WiFi using the `allowsCellularAccess` property. This can ensure that network requests are sent over WiFi if communicating with a local hardware device and is accomplished by setting a flag. Default behavior of allowing network connections over cellular networks when available is unchanged. ([01c70f2fb9](https://github.com/facebook/react-native/commit/01c70f2fb9e8ac78a4d0cbd016d4de47316fe4d1) and [916186a7e6](https://github.com/facebook/react-native/commit/916186a7e6c43b1a1c68652ab82862bcd8fb1e01) by [@bondparkerbond](https://github.com/bondparkerbond)and [@zhongwuzw](https://github.com/zhongwuzw)) +- `$RN_CACHE_DIR` can now be used to manually specify the iOS build cache directory ([845eee403e](https://github.com/facebook/react-native/commit/845eee403e1cd3cb36935ef142f411f2b5075346) by [@hramos](https://github.com/hramos)) + +### Changed + +- _BREAKING_ Migrated to AndroidX; please see [this thread](https://github.com/react-native-community/discussions-and-proposals/issues/129#issuecomment-503829184) for more details on this change +- Cleanup **RedBox** message and stack output; it's now far easier to understand ([49d26eb0c4](https://github.com/facebook/react-native/commit/49d26eb0c4aeb611c6cb37a568708afa67b48c18) by [@thymikee](https://github.com/thymikee)) +- Add default `scrollEventThrottle` value to **Animated.FlatList** and **Animated.SectionList**; this now behaves consistently with **Animated.ScrollView** ([933e65e245](https://github.com/facebook/react-native/commit/933e65e245b30f7dc5a26aa51881153fb7c3628e) by [@janicduplessis](https://github.com/janicduplessis)) +- Remove invariant on nested sibling **VirtualizedLists** without unique listKey props; they now trigger a **RedBox** ([af5633bcba](https://github.com/facebook/react-native/commit/af5633bcba224f71f035ba4214a93b69723c9b93)) +- **FlatList** and **VirtualizedList**'s default `keyExtractor` now checks `item.id` and `item.key` ([de0d7cfb79](https://github.com/facebook/react-native/commit/de0d7cfb79c7f4011d4b6748b1afc656d33fd5ac) by [@sahrens](https://github.com/sahrens)) +- **SectionList**'s `scrollToLocation` on iOS now counts `itemIndex` like Android; both platforms are now consistent, and the `itemIndex` value 0 now represents scrolling to the first heading ([248a108abf](https://github.com/facebook/react-native/commit/248a108abf206b7ae32208537f0b73a8192a4829) by [@vonovak](https://github.com/vonovak)) +- Slightly speedup core initialization by moving native version check to DEV only ([5bb2277245](https://github.com/facebook/react-native/commit/5bb22772452e49dbcfbf183f6ebeee4576e67947) by [@mmmulani](https://github.com/mmmulani)) +- `react` is now at v16.8.6 ([53cec2dc1f](https://github.com/facebook/react-native/commit/53cec2dc1f1f5d143d0bb9752629b72350ebd112), [ee681b72ce](https://github.com/facebook/react-native/commit/ee681b72ce89539e5764ed59e5dfea4fab04d48c), and [6001acb319](https://github.com/facebook/react-native/commit/6001acb319958242f8d8e2dd40cb91a55b5eab2e) by [@kelset](https://github.com/kelset), [@mdvacca](https://github.com/mdvacca), [@gaearon](https://github.com/gaearon)) +- `react-native-community/cli` is now at v2.0.0 (by [@thymikee](https://github.com/thymikee)) +- `flow` is now at v0.98 ([0e1dfd4369](https://github.com/facebook/react-native/commit/0e1dfd436917a78a09da7b57a0b50397e6a0b6e1) by [@nmote](https://github.com/nmote)) +- `prettier` is now at v1.17.0 ([ff9f8f347d](https://github.com/facebook/react-native/commit/ff9f8f347d71630664dc3da1e8be0425799c0ce0)) +- `metro` packages are now at v0.54.1 ([7ff3874ec0](https://github.com/facebook/react-native/commit/7ff3874ec060bce568537a2238aea2c888e6e13f), [343f0a1d50](https://github.com/facebook/react-native/commit/343f0a1d50662aa37ef0b26d5436b2a0b40fbabb) by [@motiz88](https://github.com/motiz88)) +- Replace patched fetch polyfill with `whatwg-fetch@3.0` ([bccc92dfdd](https://github.com/facebook/react-native/commit/bccc92dfdd2d85933f2a9cb5c8d1773affb7acba) by [@janicduplessis](https://github.com/janicduplessis)) + +#### Android specific + +- Use class canonical name for `PARTIAL_WAKE_LOCK` tag ([88dbb4558c](https://github.com/facebook/react-native/commit/88dbb4558cd10f129f2c31e3b0b872924aba5416) by [@timwangdev](https://github.com/timwangdev)) + +#### iOS specific + +- _BREAKING_: Split React.podspec into separate podspecs for each Xcode project; your libraries will need to update for this change as well to avoid CocoaPods build errors ([2321b3fd7f](https://github.com/facebook/react-native/commit/2321b3fd7f666ce30f5dad4cd2673ddf22972056) by [@fson](https://github.com/fson)) +- Improve handling of native module exceptions; they are now propagated to crash reporting tools with the context and callstack ([629708beda](https://github.com/facebook/react-native/commit/629708bedae65a30e39d234da6b04d6fa101a779) by [@pvinis](https://github.com/pvinis)) +- Switch **Slider** `onSlidingComplete` event to a non-bubbling event on iOS to match Android ([7927437a6d](https://github.com/facebook/react-native/commit/7927437a6d5d63de2424d43d58085291c1067091) by [@rickhanlonii](https://github.com/rickhanlonii)) + +### Deprecated + +- **StatusBar** is no longer deprecated; thank you for the feedback ([a203ebe206](https://github.com/facebook/react-native/commit/a203ebe2062b3c12f85783f46030971f3aa5db1d) by [@cpojer](https://github.com/cpojer)) + +### Removed + +- **NetInfo** has been removed; its replacement is now available via the [react-native-community/netinfo](https://github.com/react-native-community/react-native-netinfo) package ([5a30c2a205](https://github.com/facebook/react-native/commit/5a30c2a2052ba76e88dbf71b5b5c92966591bf26) by [@cpojer](https://github.com/cpojer)) +- **WebView** has been removed; its replacement is now available via the [react-native-community/webview](https://github.com/react-native-community/react-native-webview) package ([](https://github.com/facebook/react-native/commit/6ca438a7f4bd7e6b317f0461aebbd5a7186151ed), [1ca9a95537](https://github.com/facebook/react-native/commit/1ca9a9553763a89c977f756b45486f8b9cedab80), and [954f715b25](https://github.com/facebook/react-native/commit/954f715b25d3c47c35b5a23ae23770a93bc58cee) by [@cpojer](https://github.com/cpojer) and [@thorbenprimke](https://github.com/thorbenprimke)) +- **Geolocation** has been removed; its replacement is now available via the [react-native-community/geolocation](https://github.com/react-native-community/react-native-geolocation) package ([17dbf98884](https://github.com/facebook/react-native/commit/17dbf988845bb7815dbb6182218c8c28d027fb91) and [9834c580af](https://github.com/facebook/react-native/commit/9834c580af654366bf0d38b78cd2694b0a0c477f) by [@cpojer](https://github.com/cpojer) and [@mmmulani](https://github.com/mmmulani)) + +### Fixed + +- Fix `Animated.Value` value after animation if component was re-mounted ([b3f7d53b87](https://github.com/facebook/react-native/commit/b3f7d53b87413abdf302c521114e4d77aa92e07f) by [@michalchudziak](https://github.com/michalchudziak)) +- Consistent reporting native module name on crash on native side ([fdd8fadea8](https://github.com/facebook/react-native/commit/fdd8fadea84f475714a16b6f0ec433f898d09558) and [b79d7db9db](https://github.com/facebook/react-native/commit/b79d7db9dbf588085b29274e507d34438e2e2595) by [@DimitryDushkin](https://github.com/DimitryDushkin)) +- Handle null filenames in symbolicated stack trace gracefully in **ExceptionsManager** ([2e8d39bed7](https://github.com/facebook/react-native/commit/2e8d39bed70e2e5eeddeb2dc98155bf70f9abebd) by [@motiz88](https://github.com/motiz88)) +- Fix HasteImpl platform name regex ([28e0de070d](https://github.com/facebook/react-native/commit/28e0de070d2dae9a486ab5915b6fd76723bd84ef) by [@CaptainNic](https://github.com/CaptainNic)) +- Fix a JS memory leak in Blob handling; this resolves multiple leaks around `fetch` ([05baf62721](https://github.com/facebook/react-native/commit/05baf6272143667694585a14fb59657fdc93c3b1) and [9ef5107d04](https://github.com/facebook/react-native/commit/9ef5107d04da374fc566d8b296572ddd992419f0) by [@janicduplessis](https://github.com/janicduplessis)) +- **SectionList**'s `scrollToLocation` now scrolls to the top of the sticky header as expected ([d376a444e3](https://github.com/facebook/react-native/commit/d376a444e318beabd8ebe9ccb41ffc300e12ea76) by [@danilobuerger](https://github.com/danilobuerger)) + +#### Android specific + +- Fix duplicate resource error in Android build ([962437fafd](https://github.com/facebook/react-native/commit/962437fafd02c936754d1e992479056577cafd05) and [eb534bca58](https://github.com/facebook/react-native/commit/eb534bca58a89ae306010626a8bdae92c23b8784) by [@mikehardy](https://github.com/mikehardy) and [@Dbroqua](https://github.com/Dbroqua)) +- Reorder operations of native view hierarchy ([5f027ec64d](https://github.com/facebook/react-native/commit/5f027ec64d6764fbbb9813fabb373194dec79db7) by [@lunaleaps](https://github.com/lunaleaps)) +- Fix performance regression from new custom fonts implementation ([fd6386a07e](https://github.com/facebook/react-native/commit/fd6386a07eb75a8ec16b1384a3e5827dea520b64) by [@dulmandakh](https://github.com/dulmandakh)) +- Fix internal test case around disabled state of buttons ([70e2ab2ec9](https://github.com/facebook/react-native/commit/70e2ab2ec9a1df60b39987946af18cac8621b3b0)) +- Fix extra call of **PickerAndroid**'s `onValueChange` on initialization; now it is only called when `selectedValue` changes ([82148da667](https://github.com/facebook/react-native/commit/82148da6672e613f34ffb48133cdefc235418dea) by [@a-c-sreedhar-reddy](https://github.com/a-c-sreedhar-reddy)) +- Fix **PickerAndroid** will reset selected value during items update ([310cc38a5a](https://github.com/facebook/react-native/commit/310cc38a5acb79ba0f1cda22913bd1d0cb296034) by [@Kudo](https://github.com/Kudo)) +- Fix unexpected PARTIAL_WAKE_LOCK when no headless tasks registered. ([bdb1d4377e](https://github.com/facebook/react-native/commit/bdb1d4377e47c6cd49ff619134d4860519a3cb0c) by [@timwangdev](https://github.com/timwangdev)) +- Fix calling **TextInput**'s `onKeyPress` method when the user types an emoji ([a5c57b4ed4](https://github.com/facebook/react-native/commit/a5c57b4ed4965ac4bb231399fd145da8095cece3)) +- Fix value of **TextInput**'s `onSelectionChange` start and end arguments by normalizing them ([2ad3bb2e2d](https://github.com/facebook/react-native/commit/2ad3bb2e2d62ffb780bab020f645626a16dd3b4a) by [@uqmessias](https://github.com/uqmessias)) +- In `Linking.getInitialURL` method, use the `InteractionManager` to wait for the current activity to finish initializing ([c802d0b757](https://github.com/facebook/react-native/commit/c802d0b757912358d703d4d8a114073377a905b9) by [@mu29](https://github.com/mu29)) +- Disable delta bundles on the first app run ([e4aff423ac](https://github.com/facebook/react-native/commit/e4aff423ac0421f4af7b9a111e5ad954f489da19) by [@wojteg1337](https://github.com/wojteg1337)) +- In **DatePickerAndroid**, work around Android Nougat bug displaying the wrong the spinner mode ([bb060d6cf8](https://github.com/facebook/react-native/commit/bb060d6cf89500778bba27d1da5925e2623c7a99) by [@luancurti](https://github.com/luancurti)) +- Fix crash in Animated Interpolation when inputMin === inputMax ([7abfd23b90](https://github.com/facebook/react-native/commit/7abfd23b90db08b426c3c91b0cb6d01d161a9b9e) by [@olegbl](https://github.com/olegbl)) +- Fix symbolication for **RedBox** and **YellowBox** when using delta bundling ([a05e9f8e09](https://github.com/facebook/react-native/commit/a05e9f8e094b25cc86ee297477cccafc3be5ef52) by [@motiz88](https://github.com/motiz88)) +- Fix **CameraRoll** crash on mime type guessing ([ebeb893b50](https://github.com/facebook/react-native/commit/ebeb893b50b4aa1ad77bdb203e4f8faed75db43a) by [@Sroka](https://github.com/Sroka)) + +#### iOS specific + +- Call designated initializer for SurfaceHostingProxyRootView ([3c125e867f](https://github.com/facebook/react-native/commit/3c125e867f52efd7f18b2bd8c9a21b246afcd788) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix **RedBox** JS symbolication when adding JS engine tag to the message ([920632cadb](https://github.com/facebook/react-native/commit/920632cadb108ceeacad93e9316e706608df2942) by [@motiz88](https://github.com/motiz88)) +- Fix **TextInput**'s `onSelectionChange` behavior in single line text inputs ([0c11d8d9b4](https://github.com/facebook/react-native/commit/0c11d8d9b4edf7830255f5b016d0ba7ef72ae827) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix accessibility problem with **TextInput** Clear Button ([4e37d37cbf](https://github.com/facebook/react-native/commit/4e37d37cbff27e61659440094a662e00eafd8fc4) by [@shergin](https://github.com/shergin)) +- Fix `renderingMode` not applied to GIF **Image**s ([75380aa329](https://github.com/facebook/react-native/commit/75380aa3296210777dc0be70a722701767276117) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix **ScrollView** `centerContent` not work in some cases ([2cdf9694b5](https://github.com/facebook/react-native/commit/2cdf9694b56e76477dde572eb3dc38be31361eab) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix crash on performance logger ([5d3d3987d8](https://github.com/facebook/react-native/commit/5d3d3987d8a81b84d43dc88808d7f50c7bf11d19) by [@zhigang1992](https://github.com/zhigang1992)) +- Do not run packager in Release mode ([4ea6204111](https://github.com/facebook/react-native/commit/4ea62041118fb031d7540726df2d29185c6b130d) by [@lucasbento](https://github.com/lucasbento)) +- Fix `code` and `reason` arguments being ignored when calling `WebSocket.close` ([0ac2171c54](https://github.com/facebook/react-native/commit/0ac2171c549b389228c4a37ae645eb0d9813b82d) by [@jeanregisser](https://github.com/jeanregisser)) +- Fix return value of `Linking.openURL()` ([4a5d0bdbd7](https://github.com/facebook/react-native/commit/4a5d0bdbd75c433d2f51f160657a0ad91e440272) by [@thib92](https://github.com/thib92)) +- When an accessibilityLabel can't be discerned, return `nil` instead of `@""` ([d4ff5ed258](https://github.com/facebook/react-native/commit/d4ff5ed258b75fe77c5d801af7b097b04fcd3690) by [@sammy-SC](https://github.com/sammy-SC)) +- Fix Xcode build when the project's path contains whitespace ([f0770b6b37](https://github.com/facebook/react-native/commit/f0770b6b370f483fdd729bdba04069cc783353dc)) +- Move accessibility props to UIView+React ([9261035c2b](https://github.com/facebook/react-native/commit/9261035c2bf2fe9522806fb1c535a1835e7acfa2) by [@janicduplessis](https://github.com/janicduplessis)) + +## v0.59.10 + +This is likely the last patch release for version 59 of React Native for the foreseeable future: it contains an important Android side update for the JavaScript Core, to prevent a great number of crashes mostly related to Samsung devices - thanks to [@Kudo](https://github.com/Kudo) for his work on fixing this via [557989a86f](https://github.com/facebook/react-native/commit/557989a86f8730113393ed229927d607a478e524)! + +Thanks everyone who participated in the [discussion](https://github.com/react-native-community/releases/issues/127). + +## v0.59.9 + +This is a patch fix release addressing a couple ScrollView regressions, and "future-proof" RN 59 from crashes caused by upgrading Gradle (now can support up to 5.4.1 & 3.4.0 for the plugin) and Xcode 11 Beta 1. You can upgrade to this version without upgrading your tooling. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/124) for cherry-picking commits. You can participate to the conversation for the next patch release in the dedicated [issue](https://github.com/react-native-community/react-native-releases/issues/127). + +### Changed + +- If `isInteraction` is not specified in the config, it would always default to `true` which would block interactions like VirtualizedList updates. This is generally not what you want with useNativeDriver since the animation won't be interrupted by JS. If something does end up interfering with an animation and causes frame drops, `isInteraction` can be set manually. ([8f186b84ae](https://github.com/facebook/react-native/commit/8f186b84aeeb2613bf6ae08f20a8547d40179007) by [@sahrens](https://github.com/sahrens)) + +- Update detox to match master ([c6a5c09e2b](https://github.com/facebook/react-native/commit/c6a5c09e2b330891242af5c0b3ed7875f32c189e) by [@kelset](https://github.com/kelset)) + +#### Android specific + +- Bump Gradle to 5.4.1 & Android Gradle plugin to 3.4.0 ([b4017a9923](https://github.com/facebook/react-native/commit/b4017a9923b09fed4b693a8e4cfadd30ce34c88d), [d9f5a9dc16](https://github.com/facebook/react-native/commit/d9f5a9dc16f68cecc995bf8ba64fb726e397fadf), [30348f7899](https://github.com/facebook/react-native/commit/30348f789946dc99f5ccd02c85c8decbdb9ac29b), [6976a93126](https://github.com/facebook/react-native/commit/6976a931266126f249458a099bfaf509f9d81a05) by [@dulmandakh](https://github.com/dulmandakh)) + +### Fixed + +- Fixes wrong time unit of scroll event throttle ([1148c03f6f](https://github.com/facebook/react-native/commit/1148c03f6f51329710e23fba99a6916fff3ba42c) by [@zhongwuzw](https://github.com/zhongwuzw)) + +#### Android specific + +- Fix indexed RAM bundle ([d8fa1206c3](https://github.com/facebook/react-native/commit/d8fa1206c3fecd494b0f6abb63c66488e6ced5e0) by [@dratwas](https://github.com/dratwas)) + +#### iOS specific + +- Fix Xcode 11 Beta 1 builds ([46c7ada535](https://github.com/facebook/react-native/commit/46c7ada535f8d87f325ccbd96c24993dd522165d) by [@ericlewis](https://github.com/ericlewis)) + +## v0.59.8 + +This is a patch fix release addressing regressions, crashes, and a few developer-experience pain points (in particular, check the `KeyboardAvoidingView` change). Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/118) for cherry-picking commits. + +### Fixed + +- Fix regexp on `hasteImpl` ([bcd1e2](https://github.com/facebook/react-native/commit/28e0de070d2dae9a486ab5915b6fd76723bd84ef) by [@CaptainNic](https://github.com/CaptainNic)) +- Fix sparse array handling in `EventEmitter#listeners()` ([f68dc8](https://github.com/facebook/react-native/commit/f68dc8) by [@ide](https://github.com/ide)) +- Fix **VirtualizedList** to call `_updateViewableItems` immediately ([08141e](https://github.com/facebook/react-native/commit/efe6a0f0b56191907e8f13be2aee28fe1dcdf555) by [@sahrens](https://github.com/sahrens)) +- Fix prop overrides of **TouchableWithoutFeedback** ([0c4206](https://github.com/facebook/react-native/commit/68825f9ca5a6c8c70390e8499d9663c5be475639) by [@aleclarson](https://github.com/aleclarson)) +- Fix resolve relative size rendering error in inspector ([4884ab](https://github.com/facebook/react-native/commit/972ee2edbd4e1c4201da1606bf5a4c5add9f0083) by [@gandreadis](https://github.com/gandreadis)) +- Fix **VirtualizedSectionList** by making sure to check array bounds ([54f91d](https://github.com/facebook/react-native/commit/929908f28728c217ab4a16c8596e0957295f4d67) by [@vonovak](https://github.com/vonovak)) +- Update `_scrollAnimatedValue` offset of **ScrollView** ([e0d1b3](https://github.com/facebook/react-native/commit/58c956768af75047b2acdca429a28945a6a8b8c0) by [@miyabi](https://github.com/miyabi)) +- Fix infinite `setState` in **VirtualizedList** ([c40a93](https://github.com/facebook/react-native/commit/88787b5e7a7f6dd9c3b258b9dfb60b93ca5a5cea) by [@sahrens](https://github.com/sahrens)) + +#### iOS specific + +- Fix incorrect opacity behavior for **Text** component ([f71357](https://github.com/facebook/react-native/commit/d99e657e3909ff14cd623d1df7d3d13056fdd851) by [@shergin](https://github.com/shergin)) +- Fix **Text** shadow displays when `text Offset` is `{0,0}` ([17a81b](https://github.com/facebook/react-native/commit/9b63b50ad562c8567336898c7511a9a5198a4d6b) by [@Woodpav](https://github.com/Woodpav)) +- Add convert compatible of **NSString** for bridge message data ([c37e9c](https://github.com/facebook/react-native/commit/ffa3b0d4d601fe6788319a7cfd4185b8e4bf462f) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix nullability warnings in **RCTExceptionsManager** ([2b7d79](https://github.com/facebook/react-native/commit/31850df116fdd1595dddcd7b37a21568e679ffa7) by [@jtreanor](https://github.com/jtreanor)) +- Fix app to reconnect with metro after the bundler is closed and reopened ([c28676](https://github.com/facebook/react-native/commit/62bac80f90cf5a4ab216488b4ede441f0e3f86ba) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Fix throttle below 16ms on **ScrollView** ([39776a](https://github.com/facebook/react-native/commit/c87de765f6a9ebf656c188fa2115a1ba01b7939c) by [@sahrens](https://github.com/sahrens)) + +#### Android specific + +- Fix JS errors during bundle load were reported as `UnknownCppException` ([84e263](https://github.com/facebook/react-native/commit/6f6696fa63dc5f7029cb121c7e0ee98f8d271602)) +- Add logic to catch `MissingWebViewPackageException` ([379874](https://github.com/facebook/react-native/commit/954f715b25d3c47c35b5a23ae23770a93bc58cee) by [@thorbenprimke](https://github.com/thorbenprimke)) +- Revert "[improve RTL](https://github.com/facebook/react-native/commit/b3c74967ca6b20d7bda84c690ae3a99dfe255843)" ([f3801d](https://github.com/facebook/react-native/commit/8d3e16831a93079fc5a855a7b0f8b4be508c6942) by [@thorbenprimke](https://github.com/thorbenprimke)) + +### Added + +- Add listener for non-value animated node ([4a82dc](https://github.com/facebook/react-native/commit/68a5ceef312c7e3ac74d616b960c1cfde46a109d) by [@osdnk](https://github.com/osdnk)) +- Set **ScrollView** throttle by default ([74d740](https://github.com/facebook/react-native/commit/b8c8562ffb424831cc34a18aeb25e5fec0954dd0) by [@sahrens](https://github.com/sahrens)) + +### Changed + +- Make **KeyboardAvoidingView** with `behavior="height"` resize on keyboard close ([7140a7](https://github.com/facebook/react-native/commit/3711ea69375ea420800bac97914aa0d24fc9b1a6) by [@WaldoJeffers](https://github.com/WaldoJeffers)) +- Update network inspector to have smarter scroll stickiness ([57dc37](https://github.com/facebook/react-native/commit/c06473ab464e07edbb4715f58cd13674273bb29b) by [@AlanFoster](https://github.com/AlanFoster)) + +## v0.59.7 + +This patch release was unpublished. + +## v0.59.6 + +This patch release was unpublished. + +## v0.59.5 + +This is a patch fix release addressing regressions, crashes, and a few developer-experience pain points. Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/113) for cherry-picking commits. + +### Fixed + +- Remove wrapper around **ListEmptyComponent** ([54af5b](https://github.com/facebook/react-native/commit/46276444508581bac7b9f27edd56ec0c8ec450bc) by [@AntoineDoubovetzky](https://github.com/AntoineDoubovetzky)) + +#### Android specific + +- Enforced thread safety on UIImplementation methods that mutate the shadowNodeRegistry ([f5a318](https://github.com/facebook/react-native/commit/f5a31801a03b61df3d7bc2fc86df7bad272082e2) by [@SudoPlz](https://github.com/sunnylqm)) +- Fixed a `NoSuchKeyException` when parsing JS stack frames without line numbers ([d7bd6c](https://github.com/facebook/react-native/commit/c953e0b4319da0976ece877c09b648a55bc57d9f) by [@Salakar](https://github.com/Salakar)) +- Fixed `mostRecentEventCount` is not updated ([b8aac0](https://github.com/facebook/react-native/commit/60c0a60c508346f7639d32fde0376fabded9f3f0) by [@jainkuniya](https://github.com/jainkuniya)) + +#### iOS specific + +- Pass back correct dimensions for application window in Dimensions module ([72b4cc](https://github.com/facebook/react-native/commit/33b55ccccad56e0b97af294749d728b67b03e658) by [@rdonnelly](https://github.com/rdonnelly)) +- Fixed warning: "RCTImagePickerManager requires main queue setup" ([effb02](https://github.com/facebook/react-native/commit/6508b88cfdccdb2da6bfde05faac4647436ce4e7) by [@scarlac](https://github.com/scarlac)) + +## v0.59.4 + +This is a patch fix release addressing regressions, crashes, and a few developer-experience pain points. Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/100) for cherry-picking commits. + +### Changed + +- Make Jest transform @react-native-community packages by default ([7e23c7c565](https://github.com/facebook/react-native/commit/7e23c7c5654818fa076eeb627b709d39130f57f6) by [@thymikee](https://github.com/thymikee)) + +#### iOS specific + +- Add `scrollToOverflowEnabled` prop to **ScrollView** ([6f4239b37c](https://github.com/facebook/react-native/commit/6f4239b37c3059d6cb1fdaf2dcd3b6c962dde471) by [@mysport12](https://github.com/mysport12)) + +### Fixed + +- Fix **Touchable** long-press ([59e50237bf](https://github.com/facebook/react-native/commit/59e50237bff9521d2b78d7576abf4e23d844ac1b) by [@Kida007](https://github.com/Kida007)) + +#### Android specific + +- Fix a crash when setting `underlineColorAndroid` in **TextInput** ([556aa93ed7](https://github.com/facebook/react-native/commit/556aa93ed72d9dc0f18a1c6d7dec3d9c182fee85) by [@sunnylqm](https://github.com/sunnylqm)) + +#### iOS specific + +- Fix universal links not working in iOS 12 / Xcode 10 ([56679ed359](https://github.com/facebook/react-native/commit/56679ed359834c2177c8837d744cc7bf2ceb6b0a) by [@IljaDaderko](https://github.com/IljaDaderko)) +- Fix triangle views ([7a6fe0cda0](https://github.com/facebook/react-native/commit/7a6fe0cda0a1089c1c82fdd5f7f2db940f70feae) by [@zhongwuzw](https://github.com/zhongwuzw)) + +## v0.59.3 + +This is a patch fix release addressing regressions, crashes, and a few developer-experience pain points. Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/100) for cherry-picking commits. + +### Changed + +#### Android specific + +- Improve RTL support ([b3c74967ca](https://github.com/facebook/react-native/commit/b3c74967ca6b20d7bda84c690ae3a99dfe255843) by [@dulmandakh](https://github.com/dulmandakh)) + +### Fixed + +- Fix **VirtualizedList**, **SectionList** and **FlatList** behavior on rendering list headers with inverted prop and zero items ([c13f5d48cf](https://github.com/facebook/react-native/commit/c13f5d48cfe3e7c0f6c6d0b745b50a089d6993ef) by [@michalchudziak](https://github.com/michalchudziak)) +- Fix **VirtualizedList** debug mode crash ([02e8e531dd](https://github.com/facebook/react-native/commit/02e8e531ddfd86e9abf7ef47fbf30445afeb37cf)) +- Fix running Metro on Windows ([43d3313788](https://github.com/facebook/react-native/commit/43d3313788a5f0a36abdbfadc000b06b2188fc06) and [9db347fabc](https://github.com/facebook/react-native/commit/9db347fabca19c66f669faf4054c81cc3624be03) by [@aliazizi](https://github.com/aliazizi) and [@nazreinkaram](https://github.com/nazreinkaram)) + +#### Android specific + +- Fix IllegalStateException when invalid URL or headers are passed ([aad4dbbbfe](https://github.com/facebook/react-native/commit/aad4dbbbfe937d1924e5359556979ab067198a58) by [@dryganets](https://github.com/dryganets)) +- Fix IllegalStateException when tapping next on Android Keyboard ([b943db418f](https://github.com/facebook/react-native/commit/b943db418f4f0b9d0865642aaca3e1a2f1529663) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- Show Perf Monitor after reloading JS ([15619c22e5](https://github.com/facebook/react-native/commit/15619c22e57f73dfbed7bbe5fd6d9b3d2a8c9225) by [@usrbowe](https://github.com/usrbowe)) +- Fix **TextInput**'s `maxLength` when inserting characters at begin ([17415938c7](https://github.com/facebook/react-native/commit/17415938c7180a95811db949122b8ad24a442866) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix runtime crash in Xcode 10.2 when using `RCT_EXTERN_MODULE` for swift classes ([ff66600224](https://github.com/facebook/react-native/commit/ff66600224e78fec5d0e902f8a035b78ed31a961)) + +## v0.59.2 + +This is a patch fix release addressing regressions, crashes, and a few developer-experience pain points. Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/100) for cherry-picking commits. + +### Fixed + +#### Android specific + +- Crash on pre-26 Android devices when setting **TextInput** content type ([d4aa1e7a52](https://github.com/facebook/react-native/commit/d4aa1e7a52b51fa5d7fc9ded132b7b50170f2190) by [@hramos](https://github.com/hramos)) +- Crash when scroll to index 0 in a **SectionList** ([8fa116cc0e](https://github.com/facebook/react-native/commit/8fa116cc0e1cadbb6cf0734cfde0e0b8060f6b59) by [@danilobuerger](https://github.com/danilobuerger)) +- **Switch**'s `trackColor` being reset when toggled ([7652e31d8c](https://github.com/facebook/react-native/commit/7652e31d8c233c1c831f6597c8a2f7ce3d9c0b6e) and [d6ee448e15](https://github.com/facebook/react-native/commit/d6ee448e15a25a7485482a4702aadb2e396445c7) by [@dulmandakh](https://github.com/dulmandakh) and [@ejanzer](https://github.com/ejanzer)) + +#### iOS specific + +- **ScrollView** offset out of content size ([9c1c5a7455](https://github.com/facebook/react-native/commit/9c1c5a7455d90ec837a9a6141c096de70b798e43) by [@zhongwuzw](https://github.com/zhongwuzw)) +- **RefreshControl** state's race condition ([95d399bc82](https://github.com/facebook/react-native/commit/95d399bc825c5471e08b83eff4b1b1b510e384a0) by [@rostislav-simonik](https://github.com/rostislav-simonik)) +- Start Metro packager from project root ([fe3aebf87b](https://github.com/facebook/react-native/commit/fe3aebf87b4123f8b16cdfcb9e2e774e6e0bf0b6) by [@MatthieuLemoine](https://github.com/MatthieuLemoine)) +- **TextInput**s that are single-line reverting to default text ([e38be82dfa](https://github.com/facebook/react-native/commit/e38be82dfa8b49385b990629318f027de26500cf) by [@PeteTheHeat](https://github.com/PeteTheHeat)) + +### Changed + +#### Android specific + +- Add TLS 1.3 support to all Android versions using Conscrypt; to use this, you must add `implementation('org.conscrypt:conscrypt-android:2.0.0')` to `build.gradle` ([75af15ede4](https://github.com/facebook/react-native/commit/75af15ede44135110e40de75a649d5b15430c590) by [@dulmandakh](https://github.com/dulmandakh)) +- Turn off Metro JS Deltas by default for Android ([845189c17d](https://github.com/facebook/react-native/commit/845189c17de621cc5aa373503220c1c12f649c3c) by [@PeteTheHeat](https://github.com/PeteTheHeat)) + +## v0.59.1 + +This is a small patch release that addresses two critical issues from the 0.59.0 release. + +### Fixed + +#### Android specific + +- Template build gradle error on x86_64 ([4b996da470](https://github.com/facebook/react-native/commit/4b996da470b43f97fd0426b54bda739d7717fb28) by [@grabbou](https://github.com/grabbou)) + +#### iOS specific + +- Build error warning of **Text** module ([d834197746](https://github.com/facebook/react-native/commit/d834197746371b203bd7d7aaabdc2bc581acc867) by [@zhongwuzw](https://github.com/zhongwuzw)) + +## v0.59.0 + +Welcome to release 0.59 of React Native! For highlights of this release, please view the dedicated [blog post](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059). Thanks to those who gave feedback during the [release candidate phase](https://github.com/react-native-community/react-native-releases/issues/79). If you're interested in helping evaluate our next release (0.60), subscribe to the dedicated issue [here](https://github.com/react-native-community/react-native-releases/issues/99). + +### Added + +- Add a Metro configuration to the template with inline require/import options; read more about it [in the blog post](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059) ([ae11993d0f](https://github.com/facebook/react-native/commit/ae11993d0f6c6de661867b5d032d844e91c83c6f) by [@cpojer](https://github.com/cpojer)) + +#### Android specific + +- **Text** and **TextInput** now has prop [maxFontSizeMultiplier](https://reactnative.dev/docs/text#maxfontsizemultiplier) ([4936d284df](https://github.com/facebook/react-native/commit/4936d284df36071047ce776d9e2486c0371f7b97) by [@rigdern](https://github.com/rigdern)) +- **TextInput** now has prop [autoComplete](https://reactnative.dev/docs/textinput#autocomplete) prop ([f15145639d](https://github.com/facebook/react-native/commit/f15145639dab1e8d7a1c79a127b7d45c91d025a8)) +- **CameraRoll**'s `getPhotos` now supports `assetType: "All"` to let users pick from video and photos simultaneously ([54534e79d7](https://github.com/facebook/react-native/commit/54534e79d724ff57572efc43f65100067f35d4c1) by [@kesha-antonov](https://github.com/kesha-antonov)) +- **Text** and **TextInput** now support `textAlign:justify` for android O+ (api level >=26) ([d2153fc58d](https://github.com/facebook/react-native/commit/d2153fc58d825006076a3fce12e0f7eb84479132) by [sunnylqm](https://github.com/sunnylqm)) + +#### iOS specific + +- **TextInput** now has prop `rejectResponderTermination` to enable TextInputs inside Swipeables to function properly ([11df0eae5f](https://github.com/facebook/react-native/commit/11df0eae5ff8f530bfaf56aaf2209ff48f3ed9ac) by [@cmcewen](https://github.com/cmcewen)) +- **ActionSheetIOS** has a new prop `destructiveButtonIndexes` for an `Array` of destructive indexes ([67e7f16944](https://github.com/facebook/react-native/commit/67e7f16944530aa0d1a4d375b0de5efd5c432865) by [@sdg9](https://github.com/sdg9)) +- Add `isEventFromThisApp` to `KeyboardEvent` notifications to disambiguate keyboard events when apps are running side-by-side ([05f35c296d](https://github.com/facebook/react-native/commit/05f35c296d91d946acf4edd94106fbdd0dd69a29) by [@nossbigg](https://github.com/nossbigg)) +- Allow changing the project path in `react-native-xcode.sh` using env var `PROJECT_ROOT` ([9ccde378b6](https://github.com/facebook/react-native/commit/9ccde378b6e6379df61f9d968be6346ca6be7ead) by [@janicduplessis](https://github.com/janicduplessis)) + +### Changed + +- `React` is now at `v16.8.3` ([ccefc700d0](https://github.com/facebook/react-native/commit/ccefc700d0120539eba73747d1d6b65effb0645d) and ([2af13b4477](https://github.com/facebook/react-native/commit/2af13b4477342d3498ab302ceb5297fcbc17e097) by [@cpojer](https://github.com/cpojer) and [@hramos](https://github.com/hramos)) +- `Flow` dependency is now at `v0.92.0` ([5ee738659b](https://github.com/facebook/react-native/commit/5ee738659b4ac7b0e73b9dba09a63091d4571ed9) by [@pakoito](https://github.com/pakoito)) +- `@react-native-community/cli` dependency is at `v1.2.1` ([a252aee2ea](https://github.com/facebook/react-native/commit/a252aee2eabd9eeffb279b9fcf1827093ef4039c) and [5e1504b0fc](https://github.com/facebook/react-native/commit/5e1504b0fca99cad3bfe2339ac0e7862b2315f9c) by [@grabbou](https://github.com/grabbou)) +- Enhance Flow types definitions for **ViewPropTypes** ([7ff9456f2e](https://github.com/facebook/react-native/commit/7ff9456f2e5fd72286f5be52598988707eaef69c) by [@danibonilha](https://github.com/danibonilha)) + +#### Android specific + +- Clarify error message to direct people to `react-native start` rather than `react-native bundle` ([46aaa02274](https://github.com/facebook/react-native/commit/46aaa02274a51ebe2aaa9fca2422dcebf9323475) by [@sunnylqm](https://github.com/sunnylqm)) +- **BREAKING** - removed `OkHttpClientProvider.replaceOkHttpClient` method; please use `OkHttpClientProvider.setOkHttpClientFactory` from 0.54+ ([7cbdd7b6ac](https://github.com/facebook/react-native/commit/7cbdd7b6ac7db2192f7d0193d22326041517a63e) by [@cdlewis](https://github.com/cdlewis)) +- **BREAKING** - remove `ViewHelper`, use `ViewCompat` instead; this may also require changing the `android:theme` to be from `Theme.AppCompat`; read more about it [in the blog post](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059) ([c493cfe708](https://github.com/facebook/react-native/commit/c493cfe7083a6b97b6ec9eb9cb59cf1fdec45458) by [@dulmandakh](https://github.com/dulmandakh)) +- Add nullable annotations to `ReadableMap`, `WritableMap`, `ReadableArray`, `Writable`, `ReactPackage`, and native module interfaces; this may impact Kotlin usage ([b640b6faf7](https://github.com/facebook/react-native/commit/b640b6faf77f7af955e64bd03ae630ce2fb09627), [c93cbdf1b2](https://github.com/facebook/react-native/commit/c93cbdf1b272cfd60124d9ddb4c52b58ca59d319), [7b33d6b0b9](https://github.com/facebook/react-native/commit/7b33d6b0b96578a548e9a7f973eb59ac9236697b), and [84f40da990](https://github.com/facebook/react-native/commit/84f40da990dfd21eb1c21e20f2be0f8b2c5a78e4) by [@dulmandakh](https://github.com/dulmandakh)) +- `Soloader` is now at `v0.6.0` ([07d1075f37](https://github.com/facebook/react-native/commit/07d1075f372bb864ddc62b9c8f613b03becfa568) by [@dulmandakh](https://github.com/dulmandakh)) +- Android Support Library is now at `v28.0.0` ([5bbed43854](https://github.com/facebook/react-native/commit/5bbed43854957a37c4b51f49f30669665a72e7f7) by [@dulmandakh](https://github.com/dulmandakh)) +- `targetSdkVersion` is now at `v28` ([57f444bd8a](https://github.com/facebook/react-native/commit/57f444bd8a175038c367fa1b7d93e2e8ba9de7ed) by [@dulmandakh](https://github.com/dulmandakh)) +- Android Plugin is now at `v3.3.1` ([da5b5d2fa1](https://github.com/facebook/react-native/commit/da5b5d2fa134aa09dda4a620be9fa4d3d419201f) by [@dulmandakh](https://github.com/dulmandakh)) +- Enable Java 8 support ([38eb2a70af](https://github.com/facebook/react-native/commit/38eb2a70afa87c49c1e62754f5ae3cd26e7f59c3) by [@dulmandakh](https://github.com/dulmandakh)) +- Suppress misleading missing permission warnings ([d53dbb0dfb](https://github.com/facebook/react-native/commit/d53dbb0dfb99bdee5cd7eeaaa6f4ae51dcca00c5) by [@dulmandakh](https://github.com/dulmandakh)) +- Add back `buildToolsVersion` to build.gradle ([cf52ab561d](https://github.com/facebook/react-native/commit/cf52ab561d9fa0e4d14de7a8f3324cbc2b25bf92) by [@dulmandakh](https://github.com/dulmandakh)) +- **TimePickerAndroid** has better Flow types definitions ([2ed1bb2e01](https://github.com/facebook/react-native/commit/2ed1bb2e01ab7360d9bf13e4f9e13cb9c9c9d32e) by [@yushimatenjin](https://github.com/yushimatenjin)) +- `ReactActivity`, `ReactSlider`, `ReactTextView`, and `ReactPicker` extends `AppCompatActivity`; updates to `TimePickerDialogModule` and `DatePickerDialogModule` as well ([dda2b82a0a](https://github.com/facebook/react-native/commit/dda2b82a0a49da52b43b50db5a2bda50a216c09b), [3b9604feda](https://github.com/facebook/react-native/commit/3b9604feda8f9e8fe3dd884912ec7d9be67d7f1d), [ba0c3ffd5b](https://github.com/facebook/react-native/commit/ba0c3ffd5b46963a8bb27b40eb396965535cd927), [833429dd63](https://github.com/facebook/react-native/commit/833429dd633b33fff71224a7ce663b60681a7f81), [adc1410572](https://github.com/facebook/react-native/commit/adc14105727f708c990b7a744a0ea270ff0fba13), [c6c5a173bc](https://github.com/facebook/react-native/commit/c6c5a173bce3d8c847931d26eddb295956285438), and [be361d0fc1](https://github.com/facebook/react-native/commit/be361d0fc1930b1679c4226e15c1a5b416b94105) by [@dulmandakh](https://github.com/dulmandakh)) +- Fix lint error/warnings that cause older Android crashes ([d2fc19f4aa](https://github.com/facebook/react-native/commit/d2fc19f4aa94888b7c3d3f4a5fb621bf96a1aff9) by [@dulmandakh](https://github.com/dulmandakh)) +- The error message on getting Android drawable folder suffix now gives more information ([a159a33c02](https://github.com/facebook/react-native/commit/a159a33c02e0c0d7aa245adfd540a066ec065362) by [@BrunoVillanova](https://github.com/BrunoVillanova)) +- `SYSTEM_ALERT_WINDOW` permissions available only in debug builds ([84a2fb0a4a](https://github.com/facebook/react-native/commit/84a2fb0a4a67cd9dc37cf4e5bab814f25181cfb7) by [@dulmandakh](https://github.com/dulmandakh)) +- Add talkback navigation support for links and header ([b9d3743cda](https://github.com/facebook/react-native/commit/b9d3743cda95d1f475dbec8f6d72935941519deb) by [@yangweigbh](https://github.com/yangweigbh)) +- **FlatList** has `removeClippedSubviews` default to `true` on Android ([1a499f43b2](https://github.com/facebook/react-native/commit/1a499f43b2d03cc27dd6c25c8f13a767862afba1) by [@fred2028](https://github.com/fred2028)) + +#### iOS specific + +- Moved iOS build cache directory from `~/.rncache` to `~/Library/Caches/com.facebook.ReactNativeBuild` ([1024dc251e](https://github.com/facebook/react-native/commit/1024dc251e1f4777052b7c41807ea314672bb13a) by [@sryze](https://github.com/sryze)) +- Keyboard API Event flow types have been improved ([7ee13cc84c](https://github.com/facebook/react-native/commit/7ee13cc84c342244d3aa9e485de0e759482287ea) by [@nossbigg](https://github.com/nossbigg)) +- Expose **AsyncLocalStorage** get/set methods to native code ([7b8235a95a](https://github.com/facebook/react-native/commit/7b8235a95ad9519e9735cc1555a8d3aa5bb7c0ee) by [@ejmartin504](https://github.com/ejmartin504)) +- Clear RCTBridge **launchOptions** when bridge is reloaded ([19d04a312b](https://github.com/facebook/react-native/commit/19d04a312bf4221cd26beff6d0da6dd296a28cd0) by [@venik](https://github.com/venik)) + +### Deprecated + +The following deprecations are part of our Lean Core initiative; read more about it [in the blog post](https://reactnative.dev/blog/2019/03/12/releasing-react-native-059). + +- Deprecated [MaskedViewIOS](https://reactnative.dev/docs/maskedviewios) as it has now been moved to [react-native-community/masked-view](https://github.com/react-native-community/react-native-masked-view) ([4ac65f5413](https://github.com/facebook/react-native/commit/4ac65f5413ee59f7546b88a2eae2c4ce6fa8826b) by [@FonDorn](https://github.com/FonDorn)) +- Deprecated [ViewPagerAndroid](https://reactnative.dev/docs/viewpagerandroid) as it has now been moved to [react-native-community/viewpager](https://github.com/react-native-community/react-native-viewpager) ([77300ca91c](https://github.com/facebook/react-native/commit/77300ca91c17d371f6ba04230b8c2e8f5cd99ab8) by [@ferrannp](https://github.com/ferrannp)) +- Deprecated [AsyncStorage](https://reactnative.dev/docs/asyncstorage) as it has now been moved to [react-native-community/asyncstorage](https://github.com/react-native-community/react-native-async-storage) ([ffe37487b2](https://github.com/facebook/react-native/commit/ffe37487b228b77a3697c32767e91f1dd68041d8) by [@Krizzu](https://github.com/Krizzu)) +- Deprecated [Slider](https://reactnative.dev/docs/slider) as it has now been moved to [react-native-community/slider](https://github.com/react-native-community/react-native-slider) ([bf888a7582](https://github.com/facebook/react-native/commit/bf888a7582763a593d8b36874d242653fc0a9575) by [@michalchudziak](https://github.com/michalchudziak)) +- Deprecated [NetInfo](https://reactnative.dev/docs/netinfo) as it has now been moved to [react-native-community/netinfo](https://github.com/react-native-community/react-native-netinfo) ([d9c0dfe353](https://github.com/facebook/react-native/commit/d9c0dfe353eceb91efcec774bab0f65b6792e4fa) by [@matt-oakes](https://github.com/matt-oakes)) +- Deprecated [ImageStore](https://reactnative.dev/docs/imagestore) and directed users to `expo-file-system` and `react-native-fs` ([62599fa8ff](https://github.com/facebook/react-native/commit/62599fa8ff7f308259fe178fa37b7bcf3c1a408c) by [@EvanBacon](https://github.com/EvanBacon)) + +#### iOS specific + +- Replace deprecated `stringByReplacingPercentEscapesUsingEncoding:` with `stringByAddingPercentEncodingWithAllowedCharacters:` ([61ca119650](https://github.com/facebook/react-native/commit/61ca11965046f75e7500e5152c5f2b60df2a2cd5) by [@pvinis](https://github.com/pvinis)) + +### Removed + +- `react-native-git-upgrade` is now officially dead; use `react-native upgrade` instead (which uses [rn-diff-purge](https://github.com/react-native-community/rn-diff-purge) under the covers) ([a6bdacb257](https://github.com/facebook/react-native/commit/a6bdacb2575dcc3be2acec95d8a6db6e2db909c4) by [@cpojer](https://github.com/cpojer)) + +#### iOS specific + +- Remove the previously deprecated **TabBarIOS** ([02697291ff](https://github.com/facebook/react-native/commit/02697291ff41ddfac5b85d886e9cafa0261c8b98) by [@axe-fb](https://github.com/axe-fb)) +- **AlertIOS** is now replaced with **Alert** ([e2bd7db732](https://github.com/facebook/react-native/commit/e2bd7db732602b2c477fe040f2946bd8293df297) by [@wellmonge](https://github.com/wellmonge)) + +### Fixed + +- **KeyboardAvoidingView** now shows the correct height after the keyboard is toggled ([745484c892](https://github.com/facebook/react-native/commit/745484c892e40cfe15ded128f5a589edb28d8f6b) by [@shauns](https://github.com/shauns)) +- Adds fixes for react-native-windows UWP ([dfcbf9729f](https://github.com/facebook/react-native/commit/dfcbf9729fab64c4bd8c00e1d092ec4e9bae717f) by [@rozele](https://github.com/rozele)) +- The `Map` and `Set` polyfills no longer reject non-extensible object keys; also fix hash collision scenario ([90850cace9](https://github.com/facebook/react-native/commit/90850cace9991ed0a02605586ea5c32ce099de65) by [@benjamn](https://github.com/benjamn)) +- Corrected StyleSheet's transformation perspective to match iOS's behavior, regardless of screen density ([4c10f9321c](https://github.com/facebook/react-native/commit/4c10f9321c9d01dbcac4808e7e6674cba12f3aa5) by [@syaau](https://github.com/syaau)) +- Fix `yarn test` in new projects ([5218932b13](https://github.com/facebook/react-native/commit/5218932b13ad0649ff2a57aaf1ec682fe278c47d) by [@Esemesek](https://github.com/Esemesek)) +- Fix issue with `getInspectorDataForViewTag` that caused red screen when toggling inspector ([46f3285a3f](https://github.com/facebook/react-native/commit/46f3285a3f240f9325a548e677a1927402d76bd7) by [@TranLuongTuanAnh](https://github.com/TranLuongTuanAnh)) +- Fix `displayName` for `Image`; this will make tests no longer mistake it as `Component` ([4989123f8c](https://github.com/facebook/react-native/commit/4989123f8cab37c95b020e23b9a925746a3f3677) by [@linnett](https://github.com/linnett)) +- Fix regression of **VirtualizedList** jumpy header ([e4fd9babe0](https://github.com/facebook/react-native/commit/e4fd9babe03d82fcf39ba6a46376f746a8a3e960) by [@danilobuerger](https://github.com/danilobuerger)) +- Set `wait_for_recheck=true` to work around crash in Flow ([ffc9908bef](https://github.com/facebook/react-native/commit/ffc9908bef535ba1392c370ca4e9e4e528c3c4c5) by [@gabelevi](https://github.com/gabelevi)) +- Fix flow typing of **Text** ([10c8352141](https://github.com/facebook/react-native/commit/10c835214160cc5a5726c8dd9f0d42a0275d198b) by [@sahrens](https://github.com/sahrens)) +- Fix `jest` and `jest-junit` to be only development dependencies ([c7b57f1986](https://github.com/facebook/react-native/commit/c7b57f19869f31474c8ee17f7a1ac1551bab1b6e) by [@vovkasm](https://github.com/vovkasm)) +- Fix layout issue with **SwipeableQuickActionButton** ([ad52f52624](https://github.com/facebook/react-native/commit/ad52f526247af6eebadd2ea436b86ff7eb874f27) by [@varungupta85](https://github.com/varungupta85)) + +#### Android specific + +- Fix textTransform when used with other text styles on Android (#22670) ([3a33e75183](https://github.com/facebook/react-native/commit/3a33e75183bf196d61b46e662b4c3f84a5f570bd) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix warnings related to updating to gradle 4.10.1 or higher ([5be50d4820](https://github.com/facebook/react-native/commit/5be50d482082917351b46ee2e339e56e7e34e111) by [@misaku](https://github.com/misaku)) +- Fix issue with use of Android API 28 by adding security config for metro access ([5747094532](https://github.com/facebook/react-native/commit/5747094532bace3fe6b1ebdf55235e53189baa71), [19492b730b](https://github.com/facebook/react-native/commit/19492b730b6779486f83d5ddbaeeb870cb3d5e9c), [3b0b7ce8c3](https://github.com/facebook/react-native/commit/3b0b7ce8c3c3679610c14ca72beb1a9dcf84d930), and [84572c4051](https://github.com/facebook/react-native/commit/84572c4051f11f68ddf0928d2c3df5850ae15491) by [@Salakar](https://github.com/Salakar) and [@dulmandakh](https://github.com/dulmandakh)) +- Fix Inverted Horizontal **ScrollView** ([32cb9ec49c](https://github.com/facebook/react-native/commit/32cb9ec49c801fcebe61486149134ab542d9364b) by [@dmainas](https://github.com/dmainas)) +- Fix crash on **CheckBox** on older Android versions ([58437cd10a](https://github.com/facebook/react-native/commit/58437cd10a667bbcbc16781df855fd7c3d73bf49) by [@vonovak](https://github.com/vonovak)) +- Fix undefined error description in **Image** `onError` callback ([7795a672d3](https://github.com/facebook/react-native/commit/7795a672d3a24a5b50df6ad6d30555d856b557cc) by [@Jyrno42](https://github.com/Jyrno42)) +- Fix Android crash on animating with `useNativeDriver` ([e405e84fc3](https://github.com/facebook/react-native/commit/e405e84fc35923888442df748757787698040010) by [@scisci](https://github.com/scisci)) +- Fix dev settings menu not appearing for certain codebases due to namespace conflicts ([9968d0c203](https://github.com/facebook/react-native/commit/9968d0c2030c1065979db34cc9a244bd52b7b2a5) by [@khaled-cliqz](https://github.com/khaled-cliqz)) +- Fix exception occurring while fading a **TextView** ([f83281e2ce](https://github.com/facebook/react-native/commit/f83281e2ce2aece44b1207844d8a5149d5d2e78d) by [@mdvacca](https://github.com/mdvacca)) +- Fix **StatusBar** overwriting previously set `SystemUiVisibility` flags ([8afa0378cd](https://github.com/facebook/react-native/commit/8afa0378cd09b8fa6c30d759539fc9a680e8cae2) by [@rogerkerse](https://github.com/rogerkerse)) +- Prevent `fetch()` POST requests from appending `charset=utf-8` to `Content-Type` header ([4a807761a4](https://github.com/facebook/react-native/commit/4a807761a4aca9e551ff2cee8ca18a2450fb11ca) and [0d5aebbd9a](https://github.com/facebook/react-native/commit/0d5aebbd9ac92a90ec7ab1426ed92dd22ae8c736) by [@nhunzaker](https://github.com/nhunzaker)) +- Fix issue with **Location** that led to exceptions in two cases ([f32dc63546](https://github.com/facebook/react-native/commit/f32dc635467a2e93371f0cf2e40b07a712349288) by [@mikelambert](https://github.com/mikelambert)) + +#### iOS specific + +- Fix **TextInput** mistakenly capitalizing I's after emojiis ([f307ac7c5e](https://github.com/facebook/react-native/commit/f307ac7c5e3adb9b4c0f8b2e4b8cdc2f980c7733) by [@dchersey](https://github.com/dchersey)) +- Fix **TextView**'s `setAttributedText` for CJK languages on single-line text fields ([e38be82dfa](https://github.com/facebook/react-native/commit/e38be82dfa8b49385b990629318f027de26500cf) by [@mandrigin](https://github.com/mandrigin)) +- Fix RCTImageLoader multi thread crash ([5ed31ce524](https://github.com/facebook/react-native/commit/5ed31ce5240a7392afdc522120edef182e0014ed)) +- Fix removing keys of large values from **AsyncStorage** ([27b4d21564](https://github.com/facebook/react-native/commit/27b4d215641f9397ef415cbb2acfc1275e6110ac) by [@esprehn](https://github.com/esprehn)) +- Fix overscroll behavior on virtualized lists; behavior is now consistent ([4d5f85ed42](https://github.com/facebook/react-native/commit/4d5f85ed426cfb43dc5e63f915e416a47d76b965)) +- Fix **Alert** to not block input focus and blur ([e4364faa3c](https://github.com/facebook/react-native/commit/e4364faa3cab150b82272819fc92086fb4da297e) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix broken JSIexecutor search path ([2aa2401766](https://github.com/facebook/react-native/commit/2aa24017667721ba17a859ca4e13d43e52d86bc5) by [@amccarri](https://github.com/amccarri)) +- Fix potential linker issues when using Xcode project ([9f72e6a5d0](https://github.com/facebook/react-native/commit/9f72e6a5d02d84fe8ed545e0c0904199b9cb3c7a) by [@tyrone-sudeium](https://github.com/tyrone-sudeium)) +- Fix crash when `scrollEnabled` used in singleline textinput ([9ff43abe65](https://github.com/facebook/react-native/commit/9ff43abe653ac5af0e591b369228f0809caad204) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix crash in gif image usage ([d0cd3cae13](https://github.com/facebook/react-native/commit/d0cd3cae13a1b1fff8a2e378b5228d3cdccd695f) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix **geolocation** to not constantly reset accuracy to default of 100 meters ([bbcb97a29a](https://github.com/facebook/react-native/commit/bbcb97a29adc2a3a05728b47d28e28fa78d84df2) by [@omnikron](https://github.com/omnikron)) +- Fix iOS build issue related to missing `DoubleConversion` and `glog` to `cxxreact`, `jsi` and `jsiexecutor` subspecs in `React.podspec` file ([00392ac46b](https://github.com/facebook/react-native/commit/00392ac46b6319dcff2b6df2e5f7bb4ee094612f) by [@alexruperez](https://github.com/alexruperez)) +- Fix "'folly/folly-config.h' file not found" build error when using React via CocoaPods ([5560a47c1d](https://github.com/facebook/react-native/commit/5560a47c1dbc7daab1e4f4aac0667080fdea836a) by [@Salakar](https://github.com/Salakar)) +- Fix image cache to follow [MDN strategy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#Freshness) ([fb8ba3fe95](https://github.com/facebook/react-native/commit/fb8ba3fe959fd2a0c4ef0025fd84f6deffd9d03b) and [fb8ba3fe95](https://github.com/facebook/react-native/commit/fb8ba3fe959fd2a0c4ef0025fd84f6deffd9d03b) by [@zhongwuzw](https://github.com/zhongwuzw)) +- Fix crash due to IllegalArgumentException when creating CookieManage ([cda8171af3](https://github.com/facebook/react-native/commit/cda8171af30815edfa331e07d1bbf605f0926303) by [@mdvacca](https://github.com/mdvacca)) +- Fix cursor placement after toggling `secureTextEntry` cursor spacing ([8ce3c1b43e](https://github.com/facebook/react-native/commit/8ce3c1b43edd47191c8e5ee8432c58f6e93dfca7) by [@ericlewis](https://github.com/facebook/react-native/commits?author=ericlewis)) + +## v0.58.6 + +This release is fairly small, as we approach stable status for [0.59](https://github.com/react-native-community/react-native-releases/issues/79). + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/95) for cherry-picking commits - you can participate in the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/97). + +### Fixed + +#### Android specific + +- Fix Inverted Horizontal ScrollView on Android (#23233) ([32cb9ec49c](https://github.com/facebook/react-native/commit/32cb9ec49c801fcebe61486149134ab542d9364b) by [@dmainas](https://github.com/dmainas)) + +#### iOS specific + +- Map TextInput textContentType strings to Objective-C constants (#22611) ([a89fe4165c](https://github.com/facebook/react-native/commit/a89fe4165c2a331a9d88636d89a5a48151ab8660) by [@levibuzolic](https://github.com/levibuzolic)) +- Don't reconnect inspector if connection refused (#22625) ([d9489c4e9c](https://github.com/facebook/react-native/commit/d9489c4e9c646b79025f07635b840e9974be8cd5) by [@msand](https://github.com/msand)) + +## v0.58.5 + +This release resolves a few bugs and includes a few improvements, listed below. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/86) for cherry-picking commits - you can participate in the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/95). + +### Removed + +- Remove fallback cache ([9d60c20cb3](https://github.com/facebook/react-native/commit/9d60c20cb35074e92a90b803d3d6f420f6671635) by [@grabbou](https://github.com/grabbou)) + +### Fixed + +- Fixes capitalized I's when emojis are present after the text being edited. (#21951) ([f307ac7c5e](https://github.com/facebook/react-native/commit/f307ac7c5e3adb9b4c0f8b2e4b8cdc2f980c7733) by [@dchersey](https://github.com/dchersey)) +- Fix broken jsiexecutor search path. (#23274) ([2aa2401766](https://github.com/facebook/react-native/commit/2aa24017667721ba17a859ca4e13d43e52d86bc5) by [@amccarri](https://github.com/amccarri)) +- Fix duplicate symbols linker error in xcodeproj (#23284) ([9f72e6a5d0](https://github.com/facebook/react-native/commit/9f72e6a5d02d84fe8ed545e0c0904199b9cb3c7a) by [@tyrone-sudeium](https://github.com/tyrone-sudeium)) +- apply Network Security Config file (fixes #22375) (part 2 of #23105) (#23135) ([84572c4051](https://github.com/facebook/react-native/commit/84572c4051f11f68ddf0928d2c3df5850ae15491) by [@Salakar](https://github.com/Salakar)) +- Fix crash for web socket in some race conditions (#22439) ([dd209bb789](https://github.com/facebook/react-native/commit/dd209bb7891ed5f05b96a9922c7b0e39bf3ac9e9) by [@zhongwuzw](https://github.com/zhongwuzw)) + +#### iOS specific + +- Don't attempt to load RCTDevLoadingView lazily ([a9dd828c68](https://github.com/facebook/react-native/commit/a9dd828c68338dbf0e55ffa1838bf8ff574f317d) by [@fkgozali](https://github.com/fkgozali)) + +### Security + +#### Android specific + +- improve Android Network Security config (#23429) ([5747094532](https://github.com/facebook/react-native/commit/5747094532bace3fe6b1ebdf55235e53189baa71) by [@dulmandakh](https://github.com/dulmandakh)) + +## v0.58.4 + +This release resolves a few bugs and includes a few improvements, listed below. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/81) for cherry-picking commits - you can participate in the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/86). + +### Added + +#### Android specific + +- Add error description to Image onError callback ([7795a672d3](https://github.com/facebook/react-native/commit/7795a672d3a24a5b50df6ad6d30555d856b557cc) by [@Jyrno42](https://github.com/Jyrno42)) + +### Changed + +#### Android specific + +- bump soloader to `0.6.0` ([07d1075f37](https://github.com/facebook/react-native/commit/07d1075f372bb864ddc62b9c8f613b03becfa568) by [@dulmandakh](https://github.com/dulmandakh)) + +### Removed + +- Remove jest and jest-junit from runtime dependencies (#23276) ([c7b57f1986](https://github.com/facebook/react-native/commit/c7b57f19869f31474c8ee17f7a1ac1551bab1b6e) by [@vovkasm](https://github.com/vovkasm)) + +### Fixed + +#### Android specific + +- Fixes Android crash on animated style with string rotation ([e405e84fc3](https://github.com/facebook/react-native/commit/e405e84fc35923888442df748757787698040010) by [@scisci](https://github.com/scisci)) + +#### iOS specific + +- fix incorrect type which makes animated gifs not loop forever on device (#22987) ([728a35fcf2](https://github.com/facebook/react-native/commit/728a35fcf2a2b0d695a4d7083b266eda486b1392) by [@chrisnojima](https://github.com/chrisnojima)) +- Fixes for running the simulator ([9a8c9596eb](https://github.com/facebook/react-native/commit/9a8c9596ebe41e27d37ba18d6bf09f1c931c1ff2) by [@osunnarvik](https://github.com/osunnarvik)), ([98bcfe00fb](https://github.com/facebook/react-native/commit/98bcfe00fbca066d6914a2680c3647b678caccc5) by [@cpojer](https://github.com/cpojer)) and ([8bddcb6cb0](https://github.com/facebook/react-native/commit/8bddcb6cb0914373a0aeb927f12a6d48ffc5bb84) by [@cpojer](https://github.com/cpojer)) + +## v0.58.3 + +This release resolves a regression in **StatusBar** using [these fixes](https://github.com/facebook/react-native/compare/v0.58.2...v0.58.3). + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/81) for cherry-picking commits - you can participate in the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/81). + +## v0.58.2 + +This release fixes an issue caused by a wrongly reverted merge commit, that caused a short timeframe of commits to not actually be in the original 0.58.0. Those commits have been added to the 0.58 changelog below, as many are intertwined with the original work. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/81) for cherry-picking commits - you can participate in the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/81). + +## v0.58.1 + +There were some regressions with developer tools that prevented `react-native run-ios` from working properly in 0.58.0; this patch fix addresses that. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/81) for cherry-picking commits - you can participate to the decision process for the next patch release [here](https://github.com/react-native-community/react-native-releases/issues/81). + +## v0.58.0 + +Welcome to first stable release of React Native of 2019! +There are a number of significant changes in this version, and we'd like to especially draw your attention to them: + +- [Modernizing](https://github.com/facebook/react-native/issues/21581) and [strengthening flow types](https://github.com/facebook/react-native/issues/22100) for core components +- Breaking changes to `ScrollView`, `CameraRollView`, and `SwipeableRow` that make it no longer bound to the component instance in certain methods +- Support for mutual TLS in WebKit +- Asset serving from directories besides `/assets` +- Numerous crash fixes and resolutions for unexpected behavior + +Aside from those: + +- if you are an iOS developer, you'll need to manually link `JavaScriptCore.framework` when upgrading; this can be done via Xcode, and following the steps shown [here](https://camo.githubusercontent.com/c09cd42747364b498efa7c82fcb73978ba076eae/687474703a2f2f646f63732e6f6e656d6f62696c6573646b2e616f6c2e636f6d2f696f732d61642d73646b2f616464696e672d6672616d65776f726b732e706e67). + +- Android developers, please note that Android's target SDK 27 is supported. Work is still underway to land target SDK 28 support, and it will come soon. + +Thanks to those who gave feedback during the [release candidate phase](https://github.com/react-native-community/react-native-releases/issues/41). If you're interested in helping evaluate our next release (0.59), subscribe to the dedicated issue [here](https://github.com/react-native-community/react-native-releases/issues/79). + +### Added + +- Add support for `publicPath` to enable serving static assets from different locations ([0b314960aa](https://github.com/facebook/react-native/commit/0b314960aa34c71fc731bac9c1f2b48f3223c5cb) by [@gdborton](https://github.com/gdborton)) +- Add **TouchableBounce** now has a simple `bounce()` function that can be used to trigger a bounce without callbacks ([7fe3f90156](https://github.com/facebook/react-native/commit/7fe3f90156e879fe53665efb5a90ba3a711475fa)) + +#### Android specific + +- Bundler server host can now be set using Android System Properties, making for easier debugging across multiple apps or app installs `adb shell setprop metro.host` ([e02a154787](https://github.com/facebook/react-native/commit/e02a154787274be1da3632cb1412554cbd53928b) by [@stepanhruda](https://github.com/stepanhruda)) +- Native Modules can now reject a promise with an additional `WritableMap` arg for extra properties (`userInfo`). See the interface defined in [`Promise.java`](https://github.com/facebook/react-native/blob/60b3942/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java) for available methods. This is accessible in JavaScript as `Error.userInfo`. This is to match iOS's existing `Error.userInfo` behavior. See PR for examples. (#20940 by @Salakar) +- Native Modules now expose a `nativeStackAndroid` property to promises rejected with an Exception/Throwable - making native error stacks available inside Javascript: `Error.nativeStackAndroid`. This is to match iOS's existing `Error.nativeStackIOS` support. See PR for examples. (#20940 by @Salakar) + +#### iOS specific + +- Add `moduleForName: lazilyLoadIfNecessary` to **RCTBridge.h** to lookup modules by name and force load them, plus various improvements to LazyLoading ([d7a0c44590](https://github.com/facebook/react-native/commit/d7a0c44590bcf3fb9d055aeae3391d5bcd7e21be), [6534718a18](https://github.com/facebook/react-native/commit/6534718a1898aa472e255d2aa9a0a6cae305619a), [d7865ebde8](https://github.com/facebook/react-native/commit/d7865ebde879983b355d6f6e64232e4bd264081d), [04ea9762e2](https://github.com/facebook/react-native/commit/04ea9762e2013dcebf9f8a51d8974fa799e41cd5), [1f394fa673](https://github.com/facebook/react-native/commit/1f394fa673a876753fdc9ac2cb86a4d4a58cd8cd), [80f92adf1f](https://github.com/facebook/react-native/commit/80f92adf1f35e74ee6db0b2f445cc851463059cf), and [81b74ec1ed](https://github.com/facebook/react-native/commit/81b74ec1ed3792c0b406c30b0a1c01219a2d6243) by [@dshahidehpour](https://github.com/dshahidehpour), [@fkgozali](https://github.com/fkgozali), and [@mmmulani](https://github.com/mmmulani)) +- Add ability for **WebView** to `setClientAuthenticationCredential` when `useWebKit={true}` for mutual TLS authentication ([8911353c47](https://github.com/facebook/react-native/commit/8911353c47af018f78c1cff59dfab05b975e39ed) and [8911353c47](https://github.com/facebook/react-native/commit/8911353c47af018f78c1cff59dfab05b975e39ed) by [@mjhu](https://github.com/mjhu)) + +### Changed + +- Major improvements to Flow types for Core Components ([499c195eba](https://github.com/facebook/react-native/commit/499c195ebab0f276e3a58baf1e6172c1ba046a9e), [fbc5a4f5e6](https://github.com/facebook/react-native/commit/fbc5a4f5e65e024c10ad43d84f2b2353c9e92461), [f9050e0908](https://github.com/facebook/react-native/commit/f9050e09084cf3700bfc1954f97adf0f60cd8d88), [6476151717](https://github.com/facebook/react-native/commit/6476151717f44d3a90679f0f5293bed62a4f420e), [c03fc4087f](https://github.com/facebook/react-native/commit/c03fc4087ff9ac3ccbd1ab2261a1af329b354d99), [69213eea95](https://github.com/facebook/react-native/commit/69213eea9512c81ed998d240a6f5a3be05346b48), [136dfc8312](https://github.com/facebook/react-native/commit/136dfc831230e5418db02d1202e60d23a95c17b6), [3c0211b61a](https://github.com/facebook/react-native/commit/3c0211b61a1e723c3aaeba42c61b60bc724a3729), [c127000a7d](https://github.com/facebook/react-native/commit/c127000a7d2bb54599c9d80503528c3e8d75fddc), [636e146c4a](https://github.com/facebook/react-native/commit/636e146c4a27990547c81c2d36411d36b2c8e788), [c3dea894bd](https://github.com/facebook/react-native/commit/c3dea894bdb07d0b7ec18ab0388626d0340e6b69), [35a65cd704](https://github.com/facebook/react-native/commit/35a65cd704f2da67cd759c4d91251f8d4964b251), [79274979b7](https://github.com/facebook/react-native/commit/79274979b775e89d5f54a557a34062f873134199), [45c51835d6](https://github.com/facebook/react-native/commit/45c51835d69e111b67b4fcf1af39a13f7df1ee48), [a97d104b44](https://github.com/facebook/react-native/commit/a97d104b44daa068fa3848cc6c3225356f9dc318), [fb4825a2c6](https://github.com/facebook/react-native/commit/fb4825a2c65fba3aa905f7defb7d0c125fff644d), [84c5416617](https://github.com/facebook/react-native/commit/84c541661729dd20ab260c7468e48abbbe82affb), [3649a503cf](https://github.com/facebook/react-native/commit/3649a503cf52feac0386b4a10aab5ef6c4ec5ca0) by [@mottox2](https://github.com/mottox2), [@saitoxu](https://github.com/saitoxu), [@RSNara](https://github.com/RSNara), [@watanabeyu](https://github.com/watanabeyu), [@Tnarita0000](https://github.com/Tnarita0000), [@exced](https://github.com/exced), [@nd-02110114](https://github.com/nd-02110114), [@flowkraD](https://github.com/flowkraD)) +- Many public components were converted to ES6 classes ([ScrollView](https://github.com/facebook/react-native/commit/010e3302b8101287f231254086f3a8788a5a2c3e) by [@thymikee](https://github.com/thymikee), [CameraRollView](https://github.com/facebook/react-native/pull/21619), [SwipeableRow](https://github.com/facebook/react-native/pull/21876/files) and [ProgressBarAndroid](https://github.com/facebook/react-native/pull/21874) by [@exceed](https://github.com/exceed), [ProgressViewIOS](https://github.com/facebook/react-native/pull/21588) by [@empyrical](https://github.com/empyrical), [SegmentedControlIOS](https://github.com/facebook/react-native/pull/21888/files), [ToolbarAndroid](https://github.com/facebook/react-native/pull/21893/files) by [@nd-02110114](https://github.com/nd-02110114) +- Flow dependency is now at `v0.85.0` ([adc8a33fcf](https://github.com/facebook/react-native/commit/adc8a33fcfeb8fc163f48ae4a4bc5aaac98bcb0d) by [@samwgoldman](https://github.com/samwgoldman)) +- metro dependency is now at `v0.49.1` ([f867db366a](https://github.com/facebook/react-native/commit/f867db366aa4f0ead5a20c0d3154ca58be43fc20), [88882951e1](https://github.com/facebook/react-native/commit/88882951e1607b0af6f1772ef13135e037f9c4e3), [31bb551e75](https://github.com/facebook/react-native/commit/31bb551e75bda155b4821381e5497dc423326e3c), [de60e8643a](https://github.com/facebook/react-native/commit/de60e8643ac4e13a7f92175351268dd3c3e768db), and [de60e8643a](https://github.com/facebook/react-native/commit/de60e8643ac4e13a7f92175351268dd3c3e768db) by [@alexkirsz](https://github.com/alexkirsz) and [@rafeca](https://github.com/rafeca)) +- jest dependency is now at `v24.0.0-alpha.6` ([1b4fd64325](https://github.com/facebook/react-native/commit/1b4fd643256817d29163b37101da612867a225a1), [66aba09251](https://github.com/facebook/react-native/commit/66aba092514abd2b278a4fb66c30abffbdd5d5ff), and [06c13b3e06](https://github.com/facebook/react-native/commit/06c13b3e066636b414f5dc19c919dcb138763c71) by [@rafeca](https://github.com/rafeca) and [@rubennorte](https://github.com/rubennorte)) +- fbjs-scripts dependency is now at `v1.0.0` (#21880) ([cdbf719307](https://github.com/facebook/react-native/commit/cdbf719307f41e94a62307ec22463bb562d1c8de) by [@jmheik](https://github.com/jmheik)) +- folly dependency is now at `v2018.10.22.00` ([a316dc6ec3](https://github.com/facebook/react-native/commit/a316dc6ec34655981c0f226186f4fb668e4a01e2), [287934dba9](https://github.com/facebook/react-native/commit/287934dba943cd954164bde8b06f9ba85940b45f), and [a70625abd7](https://github.com/facebook/react-native/commit/a70625abd7bf4fba3dafb8a969a73854b7ddcd42) by [@Kudo](https://github.com/Kudo) and [@radko93](https://github.com/radko93)) +- React is set to `16.6.3` now via sync for revisions 4773fdf...6bf5e85 ([0cb59b5c23](https://github.com/facebook/react-native/commit/0cb59b5c23b76771a30f59cdcedaa3c95c4dd280) and [073ad6a036](https://github.com/facebook/react-native/commit/073ad6a0367c3156e03680c0ab0648feed2bf89c) by [@yungsters](https://github.com/yungsters)) +- Clearer error messages when hot reloading ([c787866d64](https://github.com/facebook/react-native/commit/c787866d644be4c8d30bb17c237a50fdd6e1a82d) by [@alexkirsz](https://github.com/alexkirsz)) +- Allow CxxModules to implement functions which take two callbacks ([8826d8b233](https://github.com/facebook/react-native/commit/8826d8b233c1e3325a575d5012b713c4786e6062) by [@acoates-ms](https://github.com/acoates-ms)) + +#### Breaking Changes 💥 + +- Public methods of components converted to ES6 classes are no longer bound to their component instance. For `ScrollView`, the affected methods are `setNativeProps`, `getScrollResponder`, `getScrollableNode`, `getInnerViewNode`, `scrollTo`, `scrollToEnd`, `scrollWithoutAnimationTo`, and `flashScrollIndicators`. For `CameraRollView`, the affected methods are: `rendererChanged`. For `SwipeableRow`, the affected methods are: `close`. Therefore, it is no longer safe to pass these method by reference as callbacks to functions. Auto-binding methods to component instances was a behaviour of `createReactClass` that we decided to not preserve when switching over to ES6 classes. (you can refer to [this example](https://github.com/react-native-community/react-native-releases/issues/81#issuecomment-459252692)) +- Native Modules in Android now require `@ReactModule` annotations to access `.getNativeModule` method on the `ReactContext`. This is how your updated Native Module should look like: + + ```diff + // CustomModule.java + + // ... + + import com.facebook.react.module.annotations.ReactModule; + + + @ReactModule(name="CustomBridge") + public class CustomModule extends ReactContextBaseJavaModule { + // ... + + @Override + public String getName() { + return "CustomBridge"; + } + + // ... + } + ``` + +#### Android specific + +- Optimize `PlatformConstants.ServerHost`, `PlatformConstants.isTesting`, and `PlatformConstants.androidID` for performance ([2bf0d54f15](https://github.com/facebook/react-native/commit/2bf0d54f155c28244fa60230871b3eed60a20c6d), [339d9d3afb](https://github.com/facebook/react-native/commit/339d9d3afba45bb28073db59e365caea37258891), and [9f9390ddfc](https://github.com/facebook/react-native/commit/9f9390ddfccab706ff2d346fdbd408c1cfc1c312) by [@stepanhruda](https://github.com/stepanhruda), [@fkgozali](https://github.com/fkgozali), and [@axe-fb](https://github.com/axe-fb)) + +#### iOS specific + +- Suppress yellow box about missing export for native modules ([5431607c6d](https://github.com/facebook/react-native/commit/5431607c6d4983e0adccf0192dd4dc4f5dc85443) by [@fkgozali](https://github.com/fkgozali)) + +### Removed + +- Remove `UIManager.measureViewsInRect()` ([d6236796b2](https://github.com/facebook/react-native/commit/d6236796b285e6ad19c53c5308a0ad9c10792a05) by [@shergin](https://github.com/shergin)) + +### Fixed + +- Fix potential UI thread stalling scenario from Yoga JNI bindings ([2a8f6c3028](https://github.com/facebook/react-native/commit/2a8f6c3028feec7fc9a01cbdfad45955c4771bf8) by [@davidaurelio](https://github.com/davidaurelio)) +- Fix crash happening due to race condition around bridge cxx module registry ([188cbb04ad](https://github.com/facebook/react-native/commit/188cbb04ad264aea32ae235b85b61e626b767b83), [188cbb04ad](https://github.com/facebook/react-native/commit/188cbb04ad264aea32ae235b85b61e626b767b83), and [188cbb04ad](https://github.com/facebook/react-native/commit/188cbb04ad264aea32ae235b85b61e626b767b83) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fix **View** and **Text**'s displayName; show the specific name rather than generic "Component" ([7a914fcef4](https://github.com/facebook/react-native/commit/7a914fcef4ae035221e1f984c104ba20430d6fad) by [@rajivshah3](https://github.com/rajivshah3)) +- Fix `react-native init --help` so that it doesn't return `undefined` ([58732a88b6](https://github.com/facebook/react-native/commit/58732a88b629b40b2d223a76fac46ecee5ae7295) by [@ignacioola](https://github.com/ignacioola)) +- Fix `react-native --sourceExts` ([ce860803a4](https://github.com/facebook/react-native/commit/ce860803a4341c4121a0bb504dc669349ac0db35) by [@elyalvarado](https://github.com/elyalvarado)) +- Fix accidental showing of **Modal** when `visible` prop is undefined or null ([cc13a7367b](https://github.com/facebook/react-native/commit/cc13a7367b08bd766749ddbfaacc25ade1f33a8f) by [@MateusAndrade](https://github.com/MateusAndrade)) +- Fix crash during **VirtualizedList** pagination ([5803772017](https://github.com/facebook/react-native/commit/580377201793314ca643250c1bd7cf1c47d49920)) +- Fix scenario where removing a module with remote debugging and Delta bundles may cause incorrect stack traces ([bea57d871f](https://github.com/facebook/react-native/commit/bea57d871f6b5bed76d1625b3e3f483695bd13e9) by [@alexkirsz](https://github.com/alexkirsz)) +- Fix regression in **StyleSheet** `setStyleAttributePreprocessor` ([04085337bc](https://github.com/facebook/react-native/commit/04085337bc47392922c7911b95b8fdaea98800e8) by [@brentvatne](https://github.com/brentvatne)) +- Fix React Native AsyncMode and DevTools ([aacb06c594](https://github.com/facebook/react-native/commit/aacb06c594dcd4581918035f713a69cf73bf125b) by [@bvaughn](https://github.com/bvaughn)) + +#### Android specific + +- Fix crash when removing root nodes ([b649fa96a0](https://github.com/facebook/react-native/commit/b649fa96a088a9e8ccbf3f979ebfa4a5e28a066f) by [@ayc1](https://github.com/ayc1)) +- Fix various **ReactInstanceManager** deadlocks and race conditions ([df7e8c64ff](https://github.com/facebook/react-native/commit/df7e8c64ff8f5ff739fba2ba5ed6b0610567235e), [df7e8c64ff](https://github.com/facebook/react-native/commit/df7e8c64ff8f5ff739fba2ba5ed6b0610567235e), and [be282b5287](https://github.com/facebook/react-native/commit/be282b5287f7eecf8a3fd14b06ab36454dbba5fe) by [@ayc1](https://github.com/ayc1)) +- Fix IllegalArgumentException when dismissing ReactModalHostView and DialogManager ([e57ad4ee37](https://github.com/facebook/react-native/commit/e57ad4ee37b02cd4c9e10a97d7a1d2b799204d7d) and [38e01a20c3](https://github.com/facebook/react-native/commit/38e01a20c343e60d5f8cd92fb26454e9940565df) by [@mdvacca](https://github.com/mdvacca)) +- Fix incorrect merged asset path with flavor for Android Gradle Plugin 3.2 ([e90319e9fa](https://github.com/facebook/react-native/commit/e90319e9fa18661144ad29faf36efba3750edb32) by [@yatatsu](https://github.com/yatatsu)) +- Fix HTTP connection ontimeout callback ([a508134724](https://github.com/facebook/react-native/commit/a50813472450f51d2ef24b40be99a22beefec33c)) +- Fix websocket properly closing when remote server initiates close ([2e465bca15](https://github.com/facebook/react-native/commit/2e465bca158ae9cfa89448e2a3bb8cc009397ac8) by [@syaau](https://github.com/syaau)) +- Fix compatibility issue for Android 16 device ([5939d078a0](https://github.com/facebook/react-native/commit/5939d078a01edc9f83fce102317540ffbcac17c1), [f22473e9e9](https://github.com/facebook/react-native/commit/f22473e9e9f73605cd27c5e38298bd793478c84d), and [f22473e9e9](https://github.com/facebook/react-native/commit/f22473e9e9f73605cd27c5e38298bd793478c84d) by [@gengjiawen](https://github.com/gengjiawen)) +- Fix issue where `Image.resizeMode` isn't respected while source is loading, resulting in unexpected padding ([673ef39561](https://github.com/facebook/react-native/commit/673ef39561ce6640e447fa40299c263961e4f178) by [@dulmandakh](https://github.com/dulmandakh)) +- Fix Android 28's inverted **ScrollView** so that momentum is in the proper direction ([b971c5beb8](https://github.com/facebook/react-native/commit/b971c5beb8c7f90543ea037194790142f4f57c80) by [@mandrigin](https://github.com/mandrigin)) +- Fix HTTP connection timeout callback to be appropriately called ([a508134724](https://github.com/facebook/react-native/commit/a50813472450f51d2ef24b40be99a22beefec33c)) +- Fix compatibility issue with Android 16 device ([5939d078a0](https://github.com/facebook/react-native/commit/5939d078a01edc9f83fce102317540ffbcac17c1) by [@gengjiawen](https://github.com/gengjiawen)) +- Fix crash when releasing RN views and removing root nodes([83405ff316](https://github.com/facebook/react-native/commit/83405ff3167eaba6fa59ca52c54943221a05ee09) and [b649fa96a0](https://github.com/facebook/react-native/commit/b649fa96a088a9e8ccbf3f979ebfa4a5e28a066f) by [@ayc1](https://github.com/ayc1)) +- Close websocket properly when remote server initiates close ([2e465bca15](https://github.com/facebook/react-native/commit/2e465bca158ae9cfa89448e2a3bb8cc009397ac8) by [@syaau](https://github.com/syaau)) +- Workaround a wrong fling direction for inverted ScrollViews on Android P ([b971c5beb8](https://github.com/facebook/react-native/commit/b971c5beb8c7f90543ea037194790142f4f57c80) by [@mandrigin](https://github.com/mandrigin)) +- Fix **Image** to respect `resizeMode` for `defaultSource` images rather than showing padding while loading ([673ef39561](https://github.com/facebook/react-native/commit/673ef39561ce6640e447fa40299c263961e4f178) by [@dulmandakh](https://github.com/dulmandakh)) + +#### iOS specific + +- Fix case where content of inline views didn't get relaid out ([798517a267](https://github.com/facebook/react-native/commit/798517a267841675956cb52a1c0ae493a913962a) by [@rigdern](https://github.com/rigdern)) +- Fix issue with **ImagePickerIOS**'s inconsistent image when using the front-facing camera ([4aeea4d2dc](https://github.com/facebook/react-native/commit/4aeea4d2dc14cf02dc3766043e2938bf367c6170)) +- Fix race condition and crash around shutdown of the JSC for iOS 11 and earlier ([bf2500e38e](https://github.com/facebook/react-native/commit/bf2500e38ec06d2de501c5a3737e396fe43d1fae) by [@mhorowitz](https://github.com/mhorowitz)) +- Fix crash in **NetInfo**'s \_firstTimeReachability ([eebc8e230a](https://github.com/facebook/react-native/commit/eebc8e230a9f72c3dde34a5cfd59bbffba55e53d) by [@mmmulani](https://github.com/mmmulani)) +- Fix case where inline view is visible even though it should have been truncated ([70826dbafc](https://github.com/facebook/react-native/commit/70826dbafcb00c08e0038c5066e33848141be77b) by [@rigdern](https://github.com/rigdern)) +- Fix crash with **ScrollView** related to content offsets ([585f7b916d](https://github.com/facebook/react-native/commit/585f7b916d63b7b4d22a7347334765ffcd7945e9) by [@shergin](https://github.com/shergin)) +- Fix an issue where **CameraRoll** wasn't showing the front-facing camera consistently during capture and preview ([4aeea4d2dc](https://github.com/facebook/react-native/commit/4aeea4d2dc14cf02dc3766043e2938bf367c6170)) +- Fix case where inline view is visible even though it should have been truncated ([70826dbafc](https://github.com/facebook/react-native/commit/70826dbafcb00c08e0038c5066e33848141be77b) by [@rigdern](https://github.com/rigdern)) + +### Known issues + +It is possible that you'll face an AAPT error regarding missing resources, [here](https://github.com/infinitered/ignite-andross/issues/244) is an example of this error, in this case, you should try to update the build tools versions to `buildToolsVersion = "28.0.2"` in your android/build.gradle file. If you maintain a react-native library which uses native code you should avoid using hardcoded versions of the build tools and use the packaged version numbers, here is an example you can [follow](https://github.com/react-native-community/react-native-linear-gradient/blob/master/android/build.gradle) + +## v0.57.8 + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.3"`. + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/71) for cherry-picking commits - you can participate to the decision process for the next release [here](https://github.com/react-native-community/react-native-releases/issues/75). + +### Added + +- Fix: Add displayName to ActivityIndicator (#22417) ([53da585832](https://github.com/facebook/react-native/commit/53da5858326bbddd2df112f86b2c1e935adc3882)) + +### Changed + +- Switch: Improve Accessibility ([0c8db08f51](https://github.com/facebook/react-native/commit/0c8db08f519fdf5162dff1d9a18b58885c4c7d2f) by [@yungsters](https://github.com/yungsters)) +- React sync for revisions 3ff2c7c...6bf5e85 ([073ad6a036](https://github.com/facebook/react-native/commit/073ad6a0367c3156e03680c0ab0648feed2bf89c) by [@yungsters](https://github.com/yungsters)) + +#### iOS specific + +- Extend reason message for `RCTFatalException` (#22532) ([2831d9ef61](https://github.com/facebook/react-native/commit/2831d9ef614280d08699f3134eeaeda84c30234e) by [@zackzachariah](https://github.com/zackzachariah)) + +### Removed + +- Remove trailing slash from origin header if no port is specified (#22290) ([cbe7d41f3f](https://github.com/facebook/react-native/commit/cbe7d41f3f509aaa8b8b0819b0d8ad4996fd7296)) + +### Fixed + +- Fix text alpha bug ([fd78eee11b](https://github.com/facebook/react-native/commit/fd78eee11b71799aa7fa57bbd70d59c6c642c3b3) by [@necolas](https://github.com/necolas)) +- fix possible NPE in StatusBarModule ([0f3be77b7d](https://github.com/facebook/react-native/commit/0f3be77b7d4177c8f94d775bf8ef2a2b68f1e828) by [@mdvacca](https://github.com/mdvacca)) +- Fixes animated gifs incorrectly looping/not stopping on last frame (#21999) ([de759b949e](https://github.com/facebook/react-native/commit/de759b949e4aa4904fd60a2fcbb874a3c857b50c) by [@staufman](https://github.com/staufman)) +- Fix ListEmptyComponent is rendered upside down when using inverted flag. (#21496) ([198eb02697](https://github.com/facebook/react-native/commit/198eb0269781803cc16254916b0477916afbcb0e) by [@hyochans](https://github.com/hyochans)) +- Fix bug in comparison logic of object property (#22348) ([c3b3eb7f73](https://github.com/facebook/react-native/commit/c3b3eb7f73b0fb4035d4b2478bf9827caf746372) by [@vreality64](https://github.com/vreality64)) +- Fix dispatch of OnLayout event for first render ([844e11967d](https://github.com/facebook/react-native/commit/844e11967d9292bd5cfe423d0fd57e34388f2337) by [@mdvacca](https://github.com/mdvacca)) +- KeyboardAvoidingView: Duration cannot be less then 10ms (#21858) ([87b6533937](https://github.com/facebook/react-native/commit/87b65339379362f9db77ae3f5c9fa8934da34b25)) +- default hitSlop values to 0 (#22281) ([f6d3a61677](https://github.com/facebook/react-native/commit/f6d3a6167730cc9253b796b859d8f1f33f821687) by [@Taylor123](https://github.com/Taylor123)) + +#### iOS specific + +- Fixed for supporting mediaPlaybackRequiresUserAction under iOS 10. (#22208) ([c45d290b07](https://github.com/facebook/react-native/commit/c45d290b079f95466ad4054acf7b93c66cabc429) by [@ifsnow](https://github.com/ifsnow)) +- Use main.jsbundle in iOS template for production build (#22531) ([a2ef5b85d8](https://github.com/facebook/react-native/commit/a2ef5b85d8c46cefd5873bf5fc6dddabf1d51d13) by [@radeno](https://github.com/radeno)) +- Use relative path for SCRIPTDIR (#22598) ([0314fca63a](https://github.com/facebook/react-native/commit/0314fca63a035c95864e5b198e1fbd9a5ee1d2a7) by [@sunnylqm](https://github.com/sunnylqm)) +- Fix UIScrollView crash ([585f7b916d](https://github.com/facebook/react-native/commit/585f7b916d63b7b4d22a7347334765ffcd7945e9) by [@shergin](https://github.com/shergin)) +- Avoid using `-[UITextView setAttributedString:]` while user is typing (#19809) ([f77aa4eb45](https://github.com/facebook/react-native/commit/f77aa4eb459b9dcb7f0b558ad3f04e0c507955e9)) + +### Security + +- Bump ws package to 1.1.5 due to vulnerability issues (#21769) ([96ce6f9538](https://github.com/facebook/react-native/commit/96ce6f9538ed3559ffea6040a47b1d6a30546ab9) by [@prog1dev](https://github.com/prog1dev)) + +## v0.57.7 + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.1"`. + +This patch release fixes version 0.57.6 about losing focus in `TextInput` because of [ada7089066](https://github.com/facebook/react-native/commit/ada70890663503b65b42bb5f6f98d3df412ecdc4). + +Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/64) for cherry-picking commits. + +## v0.57.6 + +**INFO NOTE**: It's highly recommended that you skip this version and upgrade to 0.57.7. + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.1"`. +This patch release fixes a number of crashes, resolves build issues (both for iOS and Android). Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/64) for cherry-picking commits. + +### Added + +#### iOS specific + +- Add iOS 12 textContentType options (#21079) ([644fc57fad](https://github.com/facebook/react-native/commit/644fc57fad4b163e96c3b3d6ec441c7b566d2d43) by [@ultramiraculous](https://github.com/ultramiraculous)) + +### Removed + +- Remove useless additional blur call (#22156) ([ada7089066](https://github.com/facebook/react-native/commit/ada70890663503b65b42bb5f6f98d3df412ecdc4)) + +### Fixed + +- Improving Modal `visible` prop check to handle undefined and null (#22072) ([cc13a7367b](https://github.com/facebook/react-native/commit/cc13a7367b08bd766749ddbfaacc25ade1f33a8f) by [@MateusAndrade](https://github.com/MateusAndrade)) +- Fix crash in nativeInjectHMRUpdate (#22412) ([0b4fd621e3](https://github.com/facebook/react-native/commit/0b4fd621e3ab511510d6852af67183a3581d1aad) by [@vovkasm](https://github.com/vovkasm)) +- Fix IllegalArgumentException when dismissing ReactModalHostView ([e57ad4ee37](https://github.com/facebook/react-native/commit/e57ad4ee37b02cd4c9e10a97d7a1d2b799204d7d) by [@mdvacca](https://github.com/mdvacca)) +- Fix regression in StyleSheet.setStyleAttributePreprocessor (#22262) ([04085337bc](https://github.com/facebook/react-native/commit/04085337bc47392922c7911b95b8fdaea98800e8) by [@brentvatne](https://github.com/brentvatne)) +- Fix React Native AsyncMode and DevTools ([f41383fb6d](https://github.com/facebook/react-native/commit/f41383fb6d6d0858e1b09dda79a74632d7932d07) by [@bvaughn](https://github.com/bvaughn)) +- CxxReact: Silence 'unused lambda capture' warnings in open-source (#22240) ([0c0540965a](https://github.com/facebook/react-native/commit/0c0540965ad9e3cdd9af16f606e141eca8ab2193) by [@empyrical](https://github.com/empyrical)) + +#### Android specific + +- Fixed HTTP connection timeout on Android (#22164) ([a508134724](https://github.com/facebook/react-native/commit/a50813472450f51d2ef24b40be99a22beefec33c)) +- resizeMode applies to Image.defaultSource (#22216) ([673ef39561](https://github.com/facebook/react-native/commit/673ef39561ce6640e447fa40299c263961e4f178) by [@dulmandakh](https://github.com/dulmandakh)) +- Android: Close websocket properly when remote server initiates close (#22248) ([2e465bca15](https://github.com/facebook/react-native/commit/2e465bca158ae9cfa89448e2a3bb8cc009397ac8) by [@syaau](https://github.com/syaau)) +- Workaround a wrong fling direction for inverted ScrollViews on Android P (#21117) ([b971c5beb8](https://github.com/facebook/react-native/commit/b971c5beb8c7f90543ea037194790142f4f57c80) by [@mandrigin](https://github.com/mandrigin)) +- Fix crash when releasing RN views ([83405ff316](https://github.com/facebook/react-native/commit/83405ff3167eaba6fa59ca52c54943221a05ee09) by [@ayc1](https://github.com/ayc1)) + +#### iOS specific + +- iOS: Support inline view truncation (#21456) ([70826dbafc](https://github.com/facebook/react-native/commit/70826dbafcb00c08e0038c5066e33848141be77b) by [@rigdern](https://github.com/rigdern)) +- NetInfo: try to solve crash with releasing \_firstTimeReachability ([eebc8e230a](https://github.com/facebook/react-native/commit/eebc8e230a9f72c3dde34a5cfd59bbffba55e53d) by [@mmmulani](https://github.com/mmmulani)) +- Generate ip.txt before SKIP_BUNDLING check (#20554) ([9c1ea45d38](https://github.com/facebook/react-native/commit/9c1ea45d38a6ec731894443debe8879fa3876ab7) by [@keatongreve](https://github.com/keatongreve)) +- Revert [Performance improvement for loading cached images on iOS ] ([7eeb305933](https://github.com/facebook/react-native/commit/7eeb305933fca695c5a15d675bb10569c3385109) by [@kelset](https://github.com/kelset)) +- Fix inability to remove 'Disabled' state from AccessibilityStates ([5eaa2d29c0](https://github.com/facebook/react-native/commit/5eaa2d29c0c4c633a40f7241408737c754edea84)) + +## v0.57.5 + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.1"`. + +This patch release fixes a number of crashes, resolves build issues (both for iOS and Android), and brings React to v16.6.1. Thanks everyone who contributed code or participated in the [discussion](https://github.com/react-native-community/react-native-releases/issues/54) for cherry-picking commits. + +### Changed + +- React is now at v16.6.1 ([8325e09e5c](https://github.com/facebook/react-native/commit/8325e09e5cd8538fded1b5a1b4fff1854e17eb22) and [76c99f20e3](https://github.com/facebook/react-native/commit/76c99f20e39ef1b5fa93487bc8c82e7c6aede5dd) by [@yungsters](https://github.com/yungsters)) + +#### iOS specific + +- Performance improvement for loading cached images ([54f7eb3424](https://github.com/facebook/react-native/commit/54f7eb34243715a1d4bc821ccbadeec12486d22c) and [3a98318c91](https://github.com/facebook/react-native/commit/3a98318c91283a1bba35c0bca93b975d4a550330) by [@esamelson](https://github.com/esamelson) and others) + +### Fixed + +- Fix crash in **VirtualizedList** during pagination ([5803772017](https://github.com/facebook/react-native/commit/580377201793314ca643250c1bd7cf1c47d49920)) +- Fix polyfilling of **regeneratorRuntime** to avoid setting it to undefined in some situations ([2a7e02edf6](https://github.com/facebook/react-native/commit/2a7e02edf64c20410b2f95f35e313279545b40db) by [@rafeca](https://github.com/rafeca)) +- Fix **View**, **Text**, and **ActivityIndicator**'s `displayName` ([7a914fcef4](https://github.com/facebook/react-native/commit/7a914fcef4ae035221e1f984c104ba20430d6fad) and [53da585832](https://github.com/facebook/react-native/commit/53da5858326bbddd2df112f86b2c1e935adc3882) by [@rajivshah3](https://github.com/rajivshah3) and others) +- Fix crash that happens when a component throws an exception that contains a null message ([6debfdf6d6](https://github.com/facebook/react-native/commit/6debfdf6d6172cec2d87fd1e780c3b347d41dc1d) by [@mdvacca](https://github.com/mdvacca)) + +#### Android specific + +- Fix incorrect merged asset path with flavor for Android Gradle Plugin 3.2 ([e90319e9fa](https://github.com/facebook/react-native/commit/e90319e9fa18661144ad29faf36efba3750edb32) by [@yatatsu](https://github.com/yatatsu)) +- Fix crash in **ReadableNativeArray.getType** when size of ReadableNativeArray's length > 512 ([09c78fe968](https://github.com/facebook/react-native/commit/09c78fe968e1bb71108c4058e76ebf70178e5a8b) by [@dryganets](https://github.com/dryganets)) + +#### iOS specific + +- Fix crash in rapid use of **NetInfo.getCurrentConnectivity** ([67afaefa78](https://github.com/facebook/react-native/commit/67afaefa78c314b38249a7e2758e0af38c18f34a) by [@mmmulani](https://github.com/mmmulani)) +- Fix Xcode 10 errors relating to third-party ([277c19c93e](https://github.com/facebook/react-native/commit/277c19c93eacf3e3ce63f71236fd399214d6e6d0) by [@mmccartney](https://github.com/mmccartney)) +- Fix build errors when path to `$NODE_BINARY` contains spaces ([7d4e94edcc](https://github.com/facebook/react-native/commit/7d4e94edccfc2f642fcbd1d6aa00756f02e3a525) by [@sundbry](https://github.com/sundbry)) +- Fix case where content of inline views didn't get relaid out ([798517a267](https://github.com/facebook/react-native/commit/798517a267841675956cb52a1c0ae493a913962a) by [@rigdern](https://github.com/rigdern)) +- Fix **InputAccessoryView**'s safe area when not attached to a **TextInput** ([2191eecf54](https://github.com/facebook/react-native/commit/2191eecf54b5c4bf278dfaf23fec46d44ac9a1f0) by [@janicduplessis](https://github.com/janicduplessis)) + +## v0.57.4 + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.0-alpha.8af6728"` (next version, 0.57.5, will update to `16.6.0`, and it will come soon). Also, please check the _Known issues_ section below, especially if you are using Xcode 10. + +Thanks to everyone that contributed to the [discussion](https://github.com/react-native-community/react-native-releases/issues/48) for cherry-picking the commits that landed in this release, and the developers who submitted those commits! + +### Added: new features + +#### Android specific additions + +- Android textTransform style support ([22cf5dc566](https://github.com/facebook/react-native/commit/22cf5dc5660f19b16de3592ccae4c42cc16ace69) by Stephen Cook) + +### Changes: existing functionality that is now different + +- Add deprecation notice to SwipeableListView ([99471f87b9](https://github.com/facebook/react-native/commit/99471f87b944b26bbdaa0fb0881db91c1118b741) by [@TheSavior](https://github.com/TheSavior)) + +#### Android specific changes + +- Consolidate native dependencies versions ([ba608a2db7](https://github.com/facebook/react-native/commit/ba608a2db786a8e983a6e30b31662fac254286c0) by [@dulmandakh](https://github.com/dulmandakh)) +- bump okhttp3 to 3.11 ([10fc548809](https://github.com/facebook/react-native/commit/10fc548809cc08db209ae6696b723341925137d1) by [@dulmandakh](https://github.com/dulmandakh)) +- Android: Send `` metrics in onTextLayout events ([737f93705c](https://github.com/facebook/react-native/commit/737f93705ca8b5d3fdd207f870cf27adcf1e885b) by [@mmmulani](https://github.com/mmmulani)) +- Use TextLegend example in Android as well ([335927db44](https://github.com/facebook/react-native/commit/335927db44fe47e20db4503a1ab5fcf8d62144a8) by [@mmmulani](https://github.com/mmmulani)) + +#### iOS specific changes + +- Bump xcode@1.0.0 ([b9514995a2](https://github.com/facebook/react-native/commit/b9514995a26b4c3f6d555257740457dd4d6cfeae) by [@peat-psuwit](https://github.com/peat-psuwit)) +- Text: send metrics after rendering (iOS) ([737f93705c](https://github.com/facebook/react-native/commit/737f93705ca8b5d3fdd207f870cf27adcf1e885b) by [@mmmulani](https://github.com/mmmulani)) +- Allow specifying iOS version for run-ios with simulator option ([0fab27cbac](https://github.com/facebook/react-native/commit/0fab27cbaca57b90119ab36104af4d0b3052ae30) by [@elyalvarado](https://github.com/elyalvarado)) +- Relax the requirement that lazy module cannot be initialized on the main thread ([dbc864c9cd](https://github.com/facebook/react-native/commit/dbc864c9cd95f9df268d85a642742e84e2360db4) by [@spredolac](https://github.com/spredolac)) + +### Fixed: bugs that have been resolved + +- Fix crashes on invalid regex ([298f14da12](https://github.com/facebook/react-native/commit/298f14da1210460b3e25c6002e8d1aa5f7b4e0ef) by [@RSNara](https://github.com/RSNara)) +- Fix pull to refresh refresh component clipping on Android ([8a3a0ad2d0](https://github.com/facebook/react-native/commit/8a3a0ad2d0f894a3d8c1e403a9336dab17c2dde8) by Andy Huang) +- ListView requestAnimationFrame leak ([70b5eb3aa2](https://github.com/facebook/react-native/commit/70b5eb3aa27822fa11571c3d8d3628ecf03268ab) by [@exced](https://github.com/exced)) + +#### Android specific fixes + +- reverted [Update bad method](https://github.com/facebook/react-native/commit/1592a8d42411d1f91c8ceb738c0533c1cee73f71) +- Fix accessibility role crash ([1f96ff62cf](https://github.com/facebook/react-native/commit/1f96ff62cf786f93c91e6625bf2b819077902251) by Haseeb Saeed) +- Fix accessibilityRole value lookup ([1f96ff62cf](https://github.com/facebook/react-native/commit/1f96ff62cf786f93c91e6625bf2b819077902251) by [@ayc1](https://github.com/ayc1)) +- Fix DynamicFromMap object pool synchronization ([b0d68c0bb9](https://github.com/facebook/react-native/commit/b0d68c0bb971a44dfdf7722682933f1e96e1cd45) by [@haitaoli](https://github.com/haitaoli)) +- Back out "[react-native][pr] Rounded corner rendering fixed on Android N." ([bb407fa1ec](https://github.com/facebook/react-native/commit/bb407fa1ec0bd0367373961fdc0e840150840068) by Jonathan Lee) +- Fix onTextLayout metrics on Android when using alignText ([1c240ae898](https://github.com/facebook/react-native/commit/1c240ae898e26534b8d9a09a334dec02e96faa05) by [@mmmulani](https://github.com/mmmulani)) +- Cleaning up imports in ViewGroupManager ([082a869dae](https://github.com/facebook/react-native/commit/082a869daef3cf602a088d0418c185279052b8c3) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific fixes + +- Fix issue when inserting text at 0 when maxLength is set ([17415938c7](https://github.com/facebook/react-native/commit/17415938c7180a95811db949122b8ad24a442866) by [@ejanzer](https://github.com/ejanzer)) + +### Known issues + +There are a few issues that don't have a finalized solution (as it happens for 0.x projects). In particular: + +- when using Xcode 10 and `react-native init`, your build may fail due to third-party build steps ([#20774](https://github.com/facebook/react-native/issues/20774)). There is a [commit](https://github.com/facebook/react-native/commit/b44c5ae92eb08125d466cf151cb804dabfbbc690) we are planning to cherry pick in a future release that should help - in the meantime, you should be able to run these commands from the project folder to fix the issue (you should need to do it only once per project): + + ```bash + cd node_modules/react-native + scripts/ios-install-third-party.sh + cd third-party/glog-0.3.5/ + ../../scripts/ios-configure-glog.sh + ``` + +- React `16.6.0` works for the most part, aside from the Context API (check [this issue](https://github.com/facebook/react-native/issues/21975)) - and if you are eager to test the new React Hooks you will have to be patient, as they are not production ready and `16.7.alpha` is **not** yet [supported](https://github.com/facebook/react-native/issues/21967) by React Native. + +## v0.57.3 + +**NOTE WELL**: when you upgrade to this version you **NEED** to upgrade `react` and `react-test-renderer` to version `"16.6.0-alpha.8af6728"`. Also, please check the _Known issues_ section below, especially if you are using Xcode 10. + +Thanks to everyone that contributed to the [discussion](https://github.com/react-native-community/react-native-releases/issues/46) for cherry-picking the commits that landed in this release, and the developers who submitted those commits! + +### Added: new features + +- Expose enableBabelRuntime config param externally ([89a358f347](https://github.com/facebook/react-native/commit/89a358f34786198c8a9a2d379588efd57b6a0eec) by [@rafeca](https://github.com/rafeca)) + +#### Android specific additions + +- Add test for InterpolatorType ([b7526b2095](https://github.com/facebook/react-native/commit/b7526b2095e4a5c8641e8264786d1622d6390029) by [@ejanzer](https://github.com/ejanzer)) + +### Changes: existing functionality that is now different + +- React sync for revisions ade5e69...d836010 ([fa6035bda6](https://github.com/facebook/react-native/commit/7142e9b1c5f95e82ceb04798b166318385004147) by [@yungsters](https://github.com/yungsters)) +- React: Bump Canary Version ([8258b6a280](https://github.com/facebook/react-native/commit/8258b6a2801121bebb25272bfcc5d3da1fb5ae39) by [@yungsters](https://github.com/yungsters)) +- FBJS: Upgrade to ^1.0.0 ([ee034596fe](https://github.com/facebook/react-native/commit/ee034596fecfb47ff9e6e1fc30cefb0e970e7d80) by [@yungsters](https://github.com/yungsters)) +- Bump metro@0.48.1 ([bf47589b8b](https://github.com/facebook/react-native/commit/bf47589b8be145750e954d09684370463a616779) by [@rafeca](https://github.com/rafeca)) +- Update to Detox 9 ([15c05988e9](https://github.com/facebook/react-native/commit/15c05988e980118151bdf41ed82ebb8c8e30a0f3) by [@kelset](https://github.com/kelset)) +- Add Deprecation Warning to ListView ([e90f5fa263](https://github.com/facebook/react-native/commit/e90f5fa2630f8a89e15fa57c70ada83e75a20642) by [@TheSavior](https://github.com/TheSavior)) +- Deprecate legacyImplementation of FlatList ([3aa8f09b44](https://github.com/facebook/react-native/commit/3aa8f09b4437eab8b91429b7325f8a6173ffa49a) by [@TheSavior](https://github.com/TheSavior)) + +#### Android specific changes + +- bump Android NDK to r17c ([436cf154bb](https://github.com/facebook/react-native/commit/436cf154bb9cf4fc0bcafd7115d33544ce36b759) by [@dulmandakh](https://github.com/dulmandakh)) +- Resolve protocol http, https when not in lowercase ([d00bdb9bb8](https://github.com/facebook/react-native/commit/d00bdb9bb8b9b11bce900689c7e28cebd2eb0807) by [@hyunjongL](https://github.com/hyunjongL)) +- Normalize scheme for URL on Android ([4b6f02ea75](https://github.com/facebook/react-native/commit/4b6f02ea758a9ab5853a29ebfc054eaa98e6dc53) by [@radeno](https://github.com/radeno)) + +#### iOS specific changes + +- Bump up the buffer size and show a warning if the trace might be truncated ([1fc8a46570](https://github.com/facebook/react-native/commit/1fc8a46570561a32657ffccb0f5a12c6f4d6a3dd) by [@alexeylang](https://github.com/alexeylang)) + +### Fixed: bugs that have been resolved + +- Fix deprecation warning message in Switch ([997f382adc](https://github.com/facebook/react-native/commit/997f382adcc7f82fccd97ac671d13e86aef7171e) by [@radko93](https://github.com/radko93)) + +#### Android specific fixes + +- Fix default accessibility delegate ([fa6035bda6](https://github.com/facebook/react-native/commit/fa6035bda6c606868977179534cb941f26fbdb92) by [@ayc1](https://github.com/ayc1)) +- Fix accessibility role/label ([fa6035bda6](https://github.com/facebook/react-native/commit/fa6035bda6c606868977179534cb941f26fbdb92) by [@ayc1](https://github.com/ayc1)) +- When converting arguments JS->Java, handle integers correctly ([bb9b9a8b9d](https://github.com/facebook/react-native/commit/bb9b9a8b9d5868c7ab5034117b785943496f6405) by [@mhorowitz](https://github.com/mhorowitz)) +- Fix CameraRoll.getPhotos() crash on Android if device has a problematic video asset ([4768bea0cf](https://github.com/facebook/react-native/commit/4768bea0cf288cf9c8097fc498b896610728c645) by [@naxel](https://github.com/naxel)) +- Android ScrollView fix for snapToInterval not snapping to end ([6eeff75849](https://github.com/facebook/react-native/commit/6eeff75849c9b8bf91592c1b7906b4dab8fba518) by [@olegbl](https://github.com/olegbl)) +- Fix for InterpolatorType crash ([01a1004808](https://github.com/facebook/react-native/commit/01a1004808928e29a6d6c698b3b18312fed17a02) by [@ejanzer](https://github.com/ejanzer)) +- Update bad method ([1592a8d424](https://github.com/facebook/react-native/commit/1592a8d42411d1f91c8ceb738c0533c1cee73f71) by [@grabbou](https://github.com/grabbou)) + +#### iOS specific fixes + +- Dealloc first time RCTNetInfo reachability callback ([e7e63fd409](https://github.com/facebook/react-native/commit/e7e63fd4092a81beec482fc48d05f1a048801037) by [@mmmulani](https://github.com/mmmulani)) +- iOS: fix the baseline issue when displaying a mixture of different-language characters ([c1561ab441](https://github.com/facebook/react-native/commit/c1561ab4411854bef96b5d268d38002a013d6d3e) by [@BingBingL](https://github.com/BingBingL)) +- Fix artifacting on RN-drawn borders with asymmetric radii ([9e6522374b](https://github.com/facebook/react-native/commit/9e6522374bc605bb1a92ff02842878ace35e9f3d) by [@jamesreggio](https://github.com/jamesreggio)) +- check isAvailable key on simulator object ([1031872784](https://github.com/facebook/react-native/commit/1031872784e9373082797e5bf5c815816af2105b) by [@antonychan](https://github.com/antonychan)) +- ios-simulator: change default iphone version ([6d09df5b72](https://github.com/facebook/react-native/commit/6d09df5b726ac951417b87a49bc345ebc9142951) by Vitor Capretz) + +### Known issues + +There are a few issues that don't have a finalized solution. In particular, when using Xcode 10 and `react-native init`, your build may fail due to third-party build steps ([#20774](https://github.com/facebook/react-native/issues/20774)). There is an open pull request which we are testing and hope to land soon ([#21458](https://github.com/facebook/react-native/pull/21458)). In the meantime, you can find a workaround here: [https://github.com/facebook/react-native/issues/20774](https://github.com/facebook/react-native/issues/20774). + +## v0.57.2 + +Thanks to everyone that contributed to the [discussion](https://github.com/react-native-community/react-native-releases/issues/45) for cherry-picking the commits that landed in this release, and the developers who submitted those commits! + +### Added: new features + +#### Android specific additions + +- Android subpixel text ([65e4e674fc](https://github.com/facebook/react-native/commit/65e4e674fca7127fd7800ae011cab449561f475b) by [@kevinresol](https://github.com/kevinresol)) + +### Changes: existing functionality that is now different + +- ReactScrollView should account for `overflow: scroll` ([201f2f189f](https://github.com/facebook/react-native/commit/201f2f189f2c41092397e5457eda83b0764ee4cd) by [@mcolotto](https://github.com/mcolotto)) +- bump metro 0.47.0 ([4750f52b34](https://github.com/facebook/react-native/commit/4750f52b34f524cae8ca08fcacec063c0725e4de) by [@rafeca](https://github.com/rafeca)) +- Use babel runtime instead of relying on global babelHelpers and regenerator ([36033bd0ed](https://github.com/facebook/react-native/commit/36033bd0edecd20fe2ae5edd56408bcc134378e7) by [@janicduplessis](https://github.com/janicduplessis)) +- React: Upgrade to react-devtools@^3.4.0 ([25119f95c8](https://github.com/facebook/react-native/commit/25119f95c81039761dd505c216c1e499003c6294) by [@yungsters](https://github.com/yungsters)) +- Change new Date() to Date.now() to save on date allocations ([bbb2d9a5b3](https://github.com/facebook/react-native/commit/bbb2d9a5b358bc0c150fe6ff74c45594c987e949) by [@dulinriley](https://github.com/dulinriley)) +- Make config object read-only ([2c1057062e](https://github.com/facebook/react-native/commit/2c1057062e81f8b43d3f942a35371fb3db841bed) by [@rafeca](https://github.com/rafeca)) +- Cleanup the transformer flow types ([28dedfb6d6](https://github.com/facebook/react-native/commit/28dedfb6d61e64a50d78aa06ee4f744665a54c2a) by [@rafeca](https://github.com/rafeca)) +- bump metro 0.47.1 ([12ab08a5aa](https://github.com/facebook/react-native/commit/12ab08a5aab3e14c9b2fb35454b16708b8ce093d) by [@rafeca](https://github.com/rafeca)) + +#### Android specific changes + +- Android ScrollView support for `overflow: visible` ([4af4da9089](https://github.com/facebook/react-native/commit/4af4da9089e20aa84bc5660bfb37763556442a4e) by [@olegbl](https://github.com/olegbl)) +- Expose a getter for overflow setting in ReactViewGroup ([02ad56f541](https://github.com/facebook/react-native/commit/02ad56f5419675572d684c3cc8a28644f29afffa) by [@kmagiera](https://github.com/kmagiera)) +- Add workaround for Android Gradle Plugin 3.2 change to asset dir ([ff084a4e80](https://github.com/facebook/react-native/commit/ff084a4e8071adb4ff6198b32aa8a7e8e29cca1c) by [@edilaic](https://github.com/edilaic)) + +### Fixed: bugs that have been resolved + +- Fix HEAD request failing with `Invalid response for blob` ([7e9c3f77cc](https://github.com/facebook/react-native/commit/7e9c3f77cce881dbb47af266993da5a2b6e98b5b) by [@anthonyhumphreys](https://github.com/anthonyhumphreys)) +- Check if child view != null before dropping ([af181fb192](https://github.com/facebook/react-native/commit/af181fb192c83e1dd0575c24e38d8814bbf187d6) by [@chrusart](https://github.com/chrusart)) + +#### Android specific fixes + +- Fix event handlers for DPad arrows on Android TV ([4d71b1525d](https://github.com/facebook/react-native/commit/4d71b1525d357a61a1740d6de5c1b97b6527f986) by [@krzysztofciombor](https://github.com/krzysztofciombor)) +- Rounded corner rendering fixed on Android N ([748cf82c97](https://github.com/facebook/react-native/commit/748cf82c974d6cf5d5df64b6e6013211c870530c) by [@dryganets](https://github.com/dryganets)) +- Android: fix cookies lost on Android 5.0 and above ([ea53727e16](https://github.com/facebook/react-native/commit/ea53727e16223d412fcbba49df79cc68b39f5d93) by chenwenyu) +- allow zero offset when shadow radius is nonzero ([97f0e43710](https://github.com/facebook/react-native/commit/97f0e43710a990c30e14d66bf67c7d612377d3f2) by Timothy Kukulski) +- Android ScrollView fix for pagingEnabled ([e0170a9445](https://github.com/facebook/react-native/commit/e0170a944501bb487e899b92363bf5aa64b29299) by [@olegbl](https://github.com/olegbl)) + +### Removed: features that have been removed; these are breaking + +- Remove global babelHelpers and regenerator ([458d56c0a1](https://github.com/facebook/react-native/commit/458d56c0a1ac73c088660830a8bf2db65de7d9a2) by [@janicduplessis](https://github.com/janicduplessis)) +- Remove overflow hidden killswitch ([59aada873e](https://github.com/facebook/react-native/commit/59aada873e13bf0b1f5e3a10cfe9a5a45c28f9fb) by [@ayc1](https://github.com/ayc1)) +- Remove absolute path parameter from transformers ([2e0d5c87e9](https://github.com/facebook/react-native/commit/2e0d5c87e93bb970ef1c8864ca44b47b36d6ae2e) by [@rafeca](https://github.com/rafeca)) + +## v0.57.1 + +We are trying, for 0.57, to approach it as a version with a longer "support", while waiting for some features to land that will allow for [0.58 to be cut](https://github.com/react-native-community/react-native-releases/issues/41). + +Thanks to everyone that contributed to the [discussion](https://github.com/react-native-community/react-native-releases/issues/34) for cherry-picking the commits that landed in this release, and the developers who submitted those commits! + +### Added: new features + +- Expose AllowFileAccess property in WebView ([0c576ef84a](https://github.com/facebook/react-native/commit/0c576ef84a1c7f79b228f205cc687ab1b945bda1) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific additions + +- Expose scrollEnabled as iOS prop for TextInput ([b9c28c236b](https://github.com/facebook/react-native/commit/b9c28c236bc971a5fbc51a3bda09c3980d547b96) by Chun Chen) + +### Changes: existing functionality that is now different + +- Give RNPM the ability to look for plugins in `@Scoped` modules ([4b106be477](https://github.com/facebook/react-native/commit/4b106be47765dd391f7a4cc6cf0e705ae977b90a) by [empyrical](https://github.com/empyrical)) +- Upgrade babel-eslint to 9.0.0 ([44dc283bcd](https://github.com/facebook/react-native/commit/44dc283bcd0a75826d9be86cdc727e32d5697ef2) by [@rafeca](https://github.com/rafeca)) +- bump metro 0.45.6 ([7bac0565e8](https://github.com/facebook/react-native/commit/7bac0565e82981d4a6e2b500d376ba9fa8aba721) by [@rafeca](https://github.com/rafeca)) + +#### iOS specific changes + +- Making RCTIsIPhoneX() return true for the R and Max models ([5e7c3ca005](https://github.com/facebook/react-native/commit/5e7c3ca0057f6084d692e30ae4db863fb20968ff) by [@shergin](https://github.com/shergin)) +- Way to register RCT_MODULE in Plugin2.0 instead of +load ([5c160e5ded](https://github.com/facebook/react-native/commit/5c160e5dedae713c686d88d4b9d4308b596e68a7) by Jeff Thomas) +- Update RCTLinkingManager.h to explicitly state the 'nullability' of parameters ([2271d1f912](https://github.com/facebook/react-native/commit/2271d1f912435eba7da2414ea4665ba8e56c7ad7) by Warren Knox) + +### Fixed: bugs that have been resolved + +- Pass the maxWorkers config param correctly to Metro ([7a69f1aa27](https://github.com/facebook/react-native/commit/7a69f1aa272a9b71755033a80f6f4aa5e9dcbaf6) by [@rafeca](https://github.com/rafeca)) +- Fix ignored --projectRoot/watchFolders arguments (([9fca769e76](https://github.com/facebook/react-native/commit/9fca769e7666df696299b422c140d6509e726ec6) by [@oblador](https://github.com/oblador)) +- Debug only code were leaking into release builds on iOS. (([d1ff0b0cc5](https://github.com/facebook/react-native/commit/d1ff0b0cc51c31cae89689b2ad2f4b35f29531d8) by [@dryganets](https://github.com/dryganets)) + +#### iOS specific fixes + +- Fix RCTNetInfo first time connection status ([e7e63fd409](https://github.com/facebook/react-native/commit/e7e63fd4092a81beec482fc48d05f1a048801037) by [@karanjthakkar](https://github.com/karanjthakkar)) + +### Removed: features that have been removed; these are breaking + +#### iOS specific removals + +- Removing development team from Xcode project ([8103c431c8](https://github.com/facebook/react-native/commit/8103c431c897c02d47cfad1e71bb2e6ddaabbdc0) by [@hramos](https://github.com/hramos)) + +## v0.57.0 + +Welcome to the 0.57 release of React Native! This release addresses a number of issues and has some exciting improvements. We again skipped a monthly release, focused on quality by extending the release candidate phase, and let some upstream packages reach stable for inclusion. + +This release includes [599 commits by 73 different contributors](https://github.com/facebook/react-native/compare/0.56-stable...0.57-stable)! In response to feedback, we've prepared a changelog that contains only user-impacting changes. Please share your input and let us know how we can make this even more useful, and as always [let us know](https://github.com/react-native-community/react-native-releases/issues/34) if you have any feedback on this process. + +### Highlights + +#### New features + +- Accessibility APIs now support accessibility hints, inverted colors, and easier usage of defining the element's role and states; read more at [@ziqichen6's excellent blog post](https://reactnative.dev/blog/2018/08/13/react-native-accessibility-updates) +- On iOS, `WKWebView` can now be used within the `WebView` component; read more at [@rsnara's awesome blog post](https://reactnative.dev/blog/2018/08/27/wkwebview) +- Better support for out-of-tree platforms. For details, please refer to [the discussion](https://github.com/react-native-community/discussions-and-proposals/issues/21) that the community used to get this up and running (there will be a new page in the docs dedicated to it too) - huge props to @empyrical for working on this! + +#### Tooling updates + +- Android tooling has been updated to match newer configuration requirements (SDK 27, gradle 4.4, and support library 27); building with Android plugin 3.2 doesn't work due to the gradle scripts, so **please** stay on Android Studio 3.1 for now +- Support Babel 7 stable landed! Be sure to read [here](https://blogs.msdn.microsoft.com/typescript/2018/08/27/typescript-and-babel-7/) about using TypeScript and check out the [Babel 7 migration guide](https://babeljs.io/docs/en/next/v7-migration) for help migrating. +- Metro has been upgraded (with Babel 7 and better transformer support), and in the next major release we plan on having two new features (ram bundles and inline requires) optional for you all to use - you can read how it will happen [here](https://github.com/react-native-community/discussions-and-proposals/blob/master/core-meetings/2018-09-metro-meeting.md); moreover, if you have a custom packager config, we recommend you read also the "updating to this version" section. +- Flow, React, and related packages have also been updated; this includes [working support](https://github.com/facebook/react-native/commit/5491c3f942430982ce9cb6140ed1733879ed3d1d) for the [React Profiler](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html). + +#### The Slimmening is happening + +As mentioned a few times in the past, the core team is reviewing the repository to trim it to the base React Native features in order to make the whole ecosystem more maintainable (by using a _divide-et-impera_ approach, the community will move faster and enable pull requests to be reviewed and merged quicker). This change requires extracting some components into their own separate repos and removing old, unused code ([details here](https://github.com/react-native-community/discussions-and-proposals/issues/6)). + +0.57 is **not** directly affected by any changes, but we want you to know that: + +- `WebView` will be moved to its own repo at [react-native-community/react-native-webview](https://github.com/react-native-community/react-native-webview). There is already a base implementation there. Help us out by giving that a try, and expect that `WebView` will be deprecated soon +- `NavigatorIOS` will be **fully** removed from the main codebase starting 0.58.0 (via [this commit](https://github.com/facebook/react-native/commit/0df92afc1caf96100013935d50bdde359b688658)); it is now deprecated + +### Updating to this version + +1. Upgrade the version of React Native in the `package.json` from `0.56.0` to `0.57.0`, and the React version to `16.5` +2. Change the babel-preset dependency from `"babel-preset-react-native": "^5",` to `"metro-react-native-babel-preset": "^0.45.0",`, then change the `.babelrc` configuration to: + + ```JSON + { + "presets": ["module:metro-react-native-babel-preset"] + } + ``` + +3. Ensure that you have all the babel dependencies to version `^7.0.0` (you may also need to add `"babel-core": "7.0.0-bridge.0"` as a yarn resolution to ensure retro-compatibility). The Babel team has released a tool, [babel-upgrade](https://github.com/babel/babel-upgrade), that should help you in this migration. +4. Upgrading android gradle version to 4.4 + 1. In your project's `android/gradle/wrapper/gradle-wrapper.properties` file, change the `distributionUrl` to `https\://services.gradle.org/distributions/gradle-4.4-all.zip` + 2. In `android/build.gradle` file add `google()` right above `jcenter()` in both `buildscript` and `allprojects` repositories. Then change Android build tools to version 3.1.4 `classpath 'com.android.tools.build:gradle:3.1.4'` + 3. In `android/app/build.gradle` file update all your `compile` statements to be `implementation`, e.g. `implementation 'com.facebook.fresco:animated-gif:1.10.0'` + 4. Do note that when running your app from within Android Studio, you may encounter `Missing Byte Code` errors. This is due to [a known issue](https://issuetracker.google.com/issues/72811718) with version 3.1.x of the android tools plugin. You'll need to disable Instant Run to get past this error. +5. If you have a custom packager configuration via `rn-cli.config.js`, you probably need to update it to work with the updated Metro configuration structure (for full detail refer to Metro's [documentation](https://facebook.github.io/metro/docs/en/configuration)); here are some commonly encountered changes to `rn-cli.config.js`: + + ```diff + -const blacklist = require('metro/src/blacklist') + +const blacklist = require('metro-config/src/defaults/blacklist') + + // ... + + module.exports = { + + watchFolders: alternateRoots, + + resolver: { + + blacklistRE: blacklist + + }, + + transformer: { + + babelTransformerPath: require.resolve('./scripts/transformer.js'), + + }, + - getProjectRoots() { + - return [ + - path.resolve(__dirname), + - ].concat(alternateRoots) + - }, + - getBlacklistRE() { + - return blacklist; + - }, + - transformModulePath: require.resolve('./scripts/transformer.js'), + } + ``` + +6. Run `yarn` to ensure that all the new dependencies have been installed + +### Added: new features + +- Add .nvmrc and ensure node version required is compatible with ESLint 5 ([30b9d81087](https://github.com/facebook/react-native/commit/30b9d81087cb86f5fb272d00bfee63a0632009f5) by [@slorber](https://github.com/slorber)) +- Major improvements to accessibility features ([48b3d1322b](https://github.com/facebook/react-native/commit/48b3d1322b884f62eb5aeb36136bcd76c502e42d), [b5b704dc19](https://github.com/facebook/react-native/commit/b5b704dc19b80a1909d66adcd617220a98c7aace), [c36e8b3307](https://github.com/facebook/react-native/commit/c36e8b3307295690cddf74e3a41ca0ac11ac4c6b), [40f6998b67](https://github.com/facebook/react-native/commit/40f6998b6766e8aa3c038a1416e5c62cbafca109), [c1d0ccde0f](https://github.com/facebook/react-native/commit/c1d0ccde0f6f8615fce077ef7ee0867a14ca0fb7), [5eaa2d29c0](https://github.com/facebook/react-native/commit/5eaa2d29c0c4c633a40f7241408737c754edea84), [10b603fdd3](https://github.com/facebook/react-native/commit/10b603fdd34919f72304720c25d1420668a6213a), [d9eeae91a0](https://github.com/facebook/react-native/commit/d9eeae91a08123c3a458704869acd6f637fc4c53), [3cfa7ae698](https://github.com/facebook/react-native/commit/3cfa7ae69847cc3b687930346016b248f2427864), [5acb7211bb](https://github.com/facebook/react-native/commit/5acb7211bb211e0ef48334630ddccbb3f0ffa2da), [5741f77156](https://github.com/facebook/react-native/commit/5741f771562962110e105114a2c65def4baa805b), [d0b86ecb4f](https://github.com/facebook/react-native/commit/d0b86ecb4f33d6b10a99062f050a4d659db4ddfc), [e739143809](https://github.com/facebook/react-native/commit/e7391438093cd5dd5033204cdce62e66509e66e1), [c27b495a89](https://github.com/facebook/react-native/commit/c27b495a89e71ff13959eb4c34605a527514fa1e), [5aa040dfb7](https://github.com/facebook/react-native/commit/5aa040dfb780c09a6efa5d3072232dea775d432f), [03036f79f7](https://github.com/facebook/react-native/commit/03036f79f7b062ae11015b33cca3dd7e4e67dda6), [3bedc78a35](https://github.com/facebook/react-native/commit/3bedc78a35b9efc259299744f4134ac0e880d1ea), [ca01290d8e](https://github.com/facebook/react-native/commit/ca01290d8e8fe73494f317ed9f81d339e65fdea0), [121e2e5ca6](https://github.com/facebook/react-native/commit/121e2e5ca6cdb17051c6d8072072f7f480ac2015), [1bc52267f5](https://github.com/facebook/react-native/commit/1bc52267f504eb02c8744c380fa2de878b0ab79f), [48b3d1322b](https://github.com/facebook/react-native/commit/48b3d1322b884f62eb5aeb36136bcd76c502e42d), [ef3d8b23c3](https://github.com/facebook/react-native/commit/ef3d8b23c35246d4e088d532c41723e06b688f1b), [ef3d8b23c3](https://github.com/facebook/react-native/commit/ef3d8b23c35246d4e088d532c41723e06b688f1b), [50e400128e](https://github.com/facebook/react-native/commit/50e400128eba554af5de4ca267430524e3eff107), and [f39d0923c7](https://github.com/facebook/react-native/commit/f39d0923c78686118a5d268c0e659d2608d28df0) by [@ziqichen6](https://github.com/ziqichen6)) +- Add `YogaNodeProperties` implementation based on `ByteBuffer` ([0c97e75dfe](https://github.com/facebook/react-native/commit/0c97e75dfeec831abb6cb39889309d8299cdce9f) and [23657ccf5b](https://github.com/facebook/react-native/commit/23657ccf5bcab6c511903660b3c617c3b8248f20) by [@davidaurelio](https://github.com/davidaurelio)) +- Add `FlatList` and `SectionList` to Animated exports ([daa7c78055](https://github.com/facebook/react-native/commit/daa7c78055857cd2d9ea650de0c4b0f72d3f2acf) by [@yunyu](https://github.com/yunyu)) +- Adding new styling props to `FlatList`/`VirtualizedList` for `ListHeaderComponent` and `ListFooterComponent` ([a2675ced4e](https://github.com/facebook/react-native/commit/a2675ced4efe0df7745bf38908efa41d4d7a9841)) +- Added more info to Module Registry systraces ([c7fdd2701f](https://github.com/facebook/react-native/commit/c7fdd2701f7edc1a771a04c890da4d742dca6ffb) by [@axe-fb](https://github.com/axe-fb)) +- Added support for out-of-tree platform plugins via a new `haste` field in `package.json`; read more in the [docs entry](https://reactnative.dev/docs/out-of-tree-platforms) ([03476a225e](https://github.com/facebook/react-native/commit/03476a225e012a0285650780430d64fc79674f0f) by [@empyrical](https://github.com/empyrical)) +- Added `snapToOffsets` to `ScrollView` and made a number of fixes to `snapToInterval` as well ([fd744dd56c](https://github.com/facebook/react-native/commit/fd744dd56ca183933a67e8398e1d20da14a31aab) by [@olegbl](https://github.com/olegbl)) + +#### Android specific additions + +- Allow registering custom packager command handlers ([b3ef1c3a56](https://github.com/facebook/react-native/commit/b3ef1c3a5645793ef42d25bb16ef023a743a1f9f) by [@fkgozali](https://github.com/fkgozali)) +- Implement `AccessibilityInfo.setAccessibilityFocus` for Android ([be715ec705](https://github.com/facebook/react-native/commit/be715ec7053a77fa6c9087990a493d17c1155de2) by [@draperunner](https://github.com/draperunner)) +- Add Support for `overflow` style property ([b81c8b51fc](https://github.com/facebook/react-native/commit/b81c8b51fc6fe3c2dece72e3fe500e175613c5d4) and [bbdc12eda7](https://github.com/facebook/react-native/commit/bbdc12eda7dec135799b7f4c41fe678180970dd2)by [@yungsters](https://github.com/yungsters)) + +#### iOS specific additions + +- `WebView` can now use `WKWebView` internally if you pass `useWebKit={true}` ([7062e5bdb5](https://github.com/facebook/react-native/commit/7062e5bdb5582bb21d1ef890965f08cc20d292b7), [1442c265da](https://github.com/facebook/react-native/commit/1442c265da36601114ce184cd5bc322f45dc1b44), [3703927e7e](https://github.com/facebook/react-native/commit/3703927e7e12ffc8922644ea251cd6f7a384570c), [7a6dd9807c](https://github.com/facebook/react-native/commit/7a6dd9807cda45c2d60641864f2d6c8d401e8ae3), [e5f95aba9b](https://github.com/facebook/react-native/commit/e5f95aba9b23376de498456282ad17113ef44cd9), [1741fe9571](https://github.com/facebook/react-native/commit/1741fe95710556f30dc2442aaaae23e31dad4cc0), [90e85a4adc](https://github.com/facebook/react-native/commit/90e85a4adc749666f81034119f281ac54840e7df), [0022354525](https://github.com/facebook/react-native/commit/0022354525eae0a368704da65c9d0f85f33ba5fb), [03b57d9db6](https://github.com/facebook/react-native/commit/03b57d9db6509fa3e715f23c8270caf6ca091acd), [1584108805](https://github.com/facebook/react-native/commit/1584108805ca6c8eff7a77e15c8553028665b53f), [a997c0ac16](https://github.com/facebook/react-native/commit/a997c0ac16d8863333d057269a8b5e28994b84eb), [4ca949b46e](https://github.com/facebook/react-native/commit/4ca949b46ec8fd72b5305daa06fac3ef58a8fa5f), [721763020a](https://github.com/facebook/react-native/commit/721763020a4a7b4b3cad1a9c074ec2e51a8d840b), [1af17f1648](https://github.com/facebook/react-native/commit/1af17f164828b6d6fa0450af46baf945745363e7), [215fa14efc](https://github.com/facebook/react-native/commit/215fa14efc2a817c7e038075163491c8d21526fd), [bacfd92976](https://github.com/facebook/react-native/commit/bacfd9297657569006bab2b1f024ad1f289b1b27), [95801f1eda](https://github.com/facebook/react-native/commit/95801f1eda2d723d9b87760d88fa9f1a1bb33ab1), [b18fddadfe](https://github.com/facebook/react-native/commit/b18fddadfeae5512690a0a059a4fa80c864f43a3), [28b058c341](https://github.com/facebook/react-native/commit/28b058c341690bd35e1d59885762ec29614a3d45), and [78fcf7c559](https://github.com/facebook/react-native/commit/78fcf7c5598ce7f5d0d62110eb34fe5a4b962e71) by [@rsnara](https://github.com/rsnara)) +- Add `accessibilityHint` for iOS ([253b29dbd8](https://github.com/facebook/react-native/commit/253b29dbd8ddb11824866e423c00a4a68bb856f3) by [@draperunner](https://github.com/draperunner)) + +### Changes: existing functionality that is now different + +- _[BREAKING]_ In the CLI, `unbundle` is now `ram-bundle` ([ebf5aeab28](https://github.com/facebook/react-native/commit/ebf5aeab280f2ebc439ec39d25c48fdf1980cf73) by [@jeanlauliac](https://github.com/jeanlauliac)) +- Bump minimum Node version to 8.3 (#20236) ([e64e13fce3](https://github.com/facebook/react-native/commit/e64e13fce394332ce609f0def35fa573f30138e9) by [@hramos](https://github.com/hramos)) +- Updated React ([70913a4623](https://github.com/facebook/react-native/commit/70913a4623c53db8a0db578eec30cad8671f8319), [b7bb25fe4c](https://github.com/facebook/react-native/commit/b7bb25fe4c1bfbedb5b8c75725721cf901dc54b0), and [672528ffde](https://github.com/facebook/react-native/commit/672528ffde3b467ccdfd6b1ce0352f150b20c922) by [@acdlite](https://github.com/acdlite), [@hramos](https://github.com/hramos), and [@yungsters](https://github.com/yungsters)) +- Upgrade Flow to v0.76.0 ([eac34e3021](https://github.com/facebook/react-native/commit/eac34e30215d88b5fe9056f9678275b894032636) by [@gabelevi](https://github.com/gabelevi)) +- Upgrade jest to 23.4.1 ([51cf9eb3e8](https://github.com/facebook/react-native/commit/51cf9eb3e823a13304570b352b81734f069c18c3) by [@rafeca](https://github.com/rafeca)) +- Upgrade babel-eslint to v9.0.0-beta.2 with better support for Flow ([abf1188de2](https://github.com/facebook/react-native/commit/abf1188de225e4b7d36ecbad316af92ca29c85c2) by [@rubennorte](https://github.com/rubennorte)) +- Upgrade ESLint to 5.1.0 ([0f2f0cad41](https://github.com/facebook/react-native/commit/0f2f0cad41f632d1dbb0c676d5edea5db62eb01c) by [@rubennorte](https://github.com/rubennorte)) +- Upgrade Babel to v7.0.0 ([b9d1c832b0](https://github.com/facebook/react-native/commit/b9d1c832b0bb7161bcec48d655e878af609b8350), [724c7498b6](https://github.com/facebook/react-native/commit/724c7498b6f10f6fd03eb217160508001fb1c5b3) by Peter van der Zee, and [bf8e1b4ffa](https://github.com/facebook/react-native/commit/bf8e1b4ffab4958587efdf3ce97e4ebdd887a20c) by [@rubennorte](https://github.com/rubennorte) and [@rafeca](https://github.com/rafeca)) +- Metro is now at v0.45.0 ([169d6839bb](https://github.com/facebook/react-native/commit/169d6839bb32d0149036ab1641d13318c0eb6f9d), [bda84a32d0](https://github.com/facebook/react-native/commit/bda84a32d08d6de3849d6afac4cbbf309837b676), [877212e18c](https://github.com/facebook/react-native/commit/877212e18c86905feda9faa1b2508c0c39396227), [169812f9ce](https://github.com/facebook/react-native/commit/169812f9ce60317dd7320384007879be16278678), [cfeb60c19b](https://github.com/facebook/react-native/commit/cfeb60c19bd23e683f1809f6535439c81e8ed166) by [@CompuIves](https://github.com/CompuIves) and [@rafeca](https://github.com/rafeca)) +- Hide pre-bundled notification when not on dev mode ([edf71005b5](https://github.com/facebook/react-native/commit/edf71005b5a4d7cfb09eae14f5765d30b9c5704e) by [@yancouto](https://github.com/yancouto)) +- Refined `StyleSheet.compose` Flow Type ([50a481d23a](https://github.com/facebook/react-native/commit/50a481d23ae72a434849d2c85007e411b0c2bb1f) by [@yungsters](https://github.com/yungsters)) +- Catch JS bundle load failure and prevent calls to JS after that ([201ba8c69d](https://github.com/facebook/react-native/commit/201ba8c69d2defc284a04acadcd13df001028ada) by [@fkgozali](https://github.com/fkgozali)) +- Use new Metro configuration in react-native cli ([a32620dc3b](https://github.com/facebook/react-native/commit/a32620dc3b7a0ebd53feeaf7794051705d80f49e) and [aaf797ad67](https://github.com/facebook/react-native/commit/aaf797ad67b965f64450b199c554c65ad8dad351) by [@CompuIves](https://github.com/CompuIves)) +- Whitelist `react-native-dom` in haste/cli config defaults ([c4bcca6685](https://github.com/facebook/react-native/commit/c4bcca66853cd231486de61f11cbcec42427b7b2) by [@vincentriemer](https://github.com/vincentriemer)) +- In the CLI, don't override `metro.config.js` settings ([c5297c75cb](https://github.com/facebook/react-native/commit/c5297c75cb6da58a241c8f91b0d2fefbc5835a46) by [@rozele](https://github.com/rozele)) + +#### Breaking Changes + +- Public methods of Image (`blur`, `focus`, `measure`, `measureInWindow`, `measureLayout`, `setNativeProps`) are no longer bound to the image component instance. Therefore, it is unsafe to pass these methods by reference (i.e: as callbacks) to functions. So, things like `setTimeout(this._imgRef.focus, 1000)` will no longer work. Please instead do: `setTimout(() => this._imgRef.focus(), 1000)`. + +#### Android specific changes + +- `Image` source without a uri now returns null ([28c7ccf785](https://github.com/facebook/react-native/commit/28c7ccf785132458fce32c234ce777a6fe475c93) by [@himabindugadupudi](https://github.com/himabindugadupudi)) +- `targetSdkVersion` is 26 ([bfb68c09ee](https://github.com/facebook/react-native/commit/bfb68c09ee88c6e1d91d3b54c01746f9a98c7c6c) by [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade NDK to r17b ([6117a6c720](https://github.com/facebook/react-native/commit/6117a6c7205c969f93d39ba02e0583881572d5fa) by [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade NDK toolchain to 4.9 ([ccdd450b12](https://github.com/facebook/react-native/commit/ccdd450b1284b73bee80a9709c864816cbfc1108) by [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade Android Support Library to version 27.1.1 and set compileSdkVersion to 27; buildToolsVersion comes along for the ride, too ([874cca1ac2](https://github.com/facebook/react-native/commit/874cca1ac258ec224bade999722d7a34c307def0) and [044b399e65](https://github.com/facebook/react-native/commit/044b399e6590d84065a9b186750f77bc9d851aac) by [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade Android gradle plugin to 3.1.4, Gradle wrapper to 4.4 ([6e356895e7](https://github.com/facebook/react-native/commit/6e356895e79fb92640295a14483af1a430732247) and [33d20da41b](https://github.com/facebook/react-native/commit/33d20da41b814a2fb9ba02cbab8b61a819cad95b) by [@gengjiawen](https://github.com/gengjiawen) and [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade to soloader 0.5.1 ([b6f2aad9c0](https://github.com/facebook/react-native/commit/b6f2aad9c0119d11e52978ff3fa9c6f269f04a14) by [@gengjiawen](https://github.com/gengjiawen)) +- Upgrade mockito to 2.19.1 ([3ea803a814](https://github.com/facebook/react-native/commit/3ea803a814f43edb3ec256dd85d778c652ca99d1) by [@dulmandakh](https://github.com/dulmandakh)) +- Upgrade glog to 0.3.5 ([b5fca80605](https://github.com/facebook/react-native/commit/b5fca806059e628edb504cb1bacf62e89ee6f102) by [@dulmandakh](https://github.com/dulmandakh)) + +### Fixed: bugs that have been resolved + +- Fixed builds on Windows machines ([3ac86c366c](https://github.com/facebook/react-native/commit/3ac86c366c91f8d62f0128057019b94a783b4249) by [@rafeca](https://github.com/rafeca)) +- Fixed building tvOS ([1f1ddd0261](https://github.com/facebook/react-native/commit/1f1ddd0261762bdeff3e747250400b208b18839b)) +- Fixed `TextInputState`'s `currentlyFocusedField()` ([b4b594cec1](https://github.com/facebook/react-native/commit/b4b594cec1d91c38faac11a90a787ae692e35296) by [@janicduplessis](https://github.com/janicduplessis)) +- `` fix for jumpy content when `initialScrollIndex` specified ([e0c73633cf](https://github.com/facebook/react-native/commit/e0c73633cfc0a62df9d39991b0df65fa5875609a) by [@rbrosboel](https://github.com/rbrosboel)) +- Fix local-cli assetRegistryPath and middlewares ([f05943de0a](https://github.com/facebook/react-native/commit/f05943de0abfc16da41163c6b91a04ecc8de8e67) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix issue with when all are `flexGrow` and `flexShrink` set to 0 except for one ([90a408ea6f](https://github.com/facebook/react-native/commit/90a408ea6ff7833e33b4058f490073e04460d00b) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Fix react-native CLI's debugger UI path and metro host/port arg usage ([5067540487](https://github.com/facebook/react-native/commit/50675404873c1ffac0deedc51fe745168051148b) by [@Kureev](https://github.com/Kureev)) +- Hotfix to include `react-native-windows` in hasteImpl accepted paths ([54942746d4](https://github.com/facebook/react-native/commit/54942746d4037e1153e14fcfc95e4edc772d296a) by [@rubennorte](https://github.com/rubennorte)) +- Fix some classes of incorrect Flow errors for `Animated` ([db2159d0b3](https://github.com/facebook/react-native/commit/db2159d0b3fd57556383eff68d32d32246dd9081) by [@yunyu](https://github.com/yunyu)) +- Fixed a typo in DockerTests.md ([c1831d50cf](https://github.com/facebook/react-native/commit/c1831d50cfd35b7a7393e50bc71d8389b36021ce) by [@kant](https://github.com/kant)) +- Fix invalid use of destructuring in jest preprocessor ([9d5bd50737](https://github.com/facebook/react-native/commit/9d5bd507372c7b63e59a94383c3e3091d96409de) by [@umairda](https://github.com/umairda)) +- Fixed a CLI crash when using old versions of node ([e61176d650](https://github.com/facebook/react-native/commit/e61176d650e2b5fe51dd6cd4c429ff47a1a9b1dc) by [@keksipurkki](https://github.com/keksipurkki)) + +#### Android specific fixes + +- Fix issue with AsyncStorage not behaving properly on Android 7+ ([1b09bd7fba](https://github.com/facebook/react-native/commit/1b09bd7fba92431d63d2cecb83565491e91db396)) +- Fixed extreme `` slowness ([5017b86b52](https://github.com/facebook/react-native/commit/5017b86b525e3ef6023f0f8a88e6fd1cf98024e0) by [@gnprice](https://github.com/gnprice)) +- Fixed `` placeholder not being completely visible ([84022321c4](https://github.com/facebook/react-native/commit/84022321c437e597660ecd8a773e51bdf8855f4e) and [86f24ccf71](https://github.com/facebook/react-native/commit/86f24ccf71f4c41904838c8c7e13268c300fd745) by [@jainkuniya](https://github.com/jainkuniya)) +- Fix Horizontal ``'s scroll position during layout changes with RTL content ([de573277bf](https://github.com/facebook/react-native/commit/de573277bf64703aefdeb52db2c2524b2c241bab)) +- Fix Horizontal `` overflow issue ([d5465a9a0a](https://github.com/facebook/react-native/commit/d5465a9a0a840f7e759bb8fb6679b01017eb3d05)) +- Fixing crash on SDK 15 on ReactTextInputLocalData ([1bb2bead8b](https://github.com/facebook/react-native/commit/1bb2bead8bef850037c8b72209cd72a442572821)) +- Fix Drawing Rect for ReactScrollView ([6a16bec882](https://github.com/facebook/react-native/commit/6a16bec882cba809bdf9027367b76f6543b6617d) by [@yungsters](https://github.com/yungsters)) +- Fixed NoSuchKeyException Thrown From ReadableNativeMap bysafely unwrapping ReadableMap by defaulting to 0 if key not present ([1a6666a116](https://github.com/facebook/react-native/commit/1a6666a116fd8b9e8637956de2b41a1c315dd470) by [@Bhavik-P](https://github.com/Bhavik-P)) +- Fixed runAndroid to enable the use of a package on port <> 8081 for Windows ([3cd0737fe2](https://github.com/facebook/react-native/commit/3cd0737fe2dce9df29822854bfbfaf2f22346c69) by [@ihenshaw](https://github.com/ihenshaw)) +- Don't crash on upload retry when trying to fetch on a varying quality network ([79fe925f1d](https://github.com/facebook/react-native/commit/79fe925f1daa053d5a5d92a228e5c7beff565ab4) by [@dryganets](https://github.com/dryganets)) + +#### iOS specific fixes + +- Fix `TextInput.clear()` and `TextInput.setNativeProps({text: ''})` to work ([2307ea60d0](https://github.com/facebook/react-native/commit/2307ea60d03edd234511bfe32474c453f30c1693) by [@magicien](https://github.com/magicien)) +- Correct fishhook import in RCTReconnectingWebSocket ([75a0273de2](https://github.com/facebook/react-native/commit/75a0273de21948b0b959263100f09111f738ec35)) +- Change in RCTImagePickerManager to handle crashes if height/width is nil ([82af7c989b](https://github.com/facebook/react-native/commit/82af7c989be42a516f438f162d21f699be297e79) by [@abhi06276](https://github.com/abhi06276)) +- Fix controlled `` on iOS when inputting in Chinese/Japanese ([892212bad2](https://github.com/facebook/react-native/commit/892212bad2daadd373f4be241e4cd9889b0a1005) by [@mmmulani](https://github.com/mmmulani)) +- Fixed `` bug encountered with brownfield apps ([fab5fffbb3](https://github.com/facebook/react-native/commit/fab5fffbb3eb8668c9202dec5e770330d49880b0) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- Fixed missing selection indicator lines on `` ([e592d6f8c7](https://github.com/facebook/react-native/commit/e592d6f8c7b0409ab6f0a2dbf6ebe3cea28c3e79) by [@VSchlattinger](https://github.com/VSchlattinger)) +- Fix crash in RCTImagePicker on iOS ([934c50fbe0](https://github.com/facebook/react-native/commit/934c50fbe07e49391ba27c3469f99bec65e48d39) by [@mmmulani](https://github.com/mmmulani)) +- Fix `undefined_arch` error received when building in Xcode 10 beta ([e131fffb37](https://github.com/facebook/react-native/commit/e131fffb37a545363daf11735a0243165b57f63f) by [@futuun](https://github.com/futuun)) +- Add support for connecting to the Packager when running the iOS app on device when using custom Debug configuration ([079bf3f206](https://github.com/facebook/react-native/commit/079bf3f2067cb268b60e75cd9e1bc51a9c85359c)) +- Fixed RCTAnimation import for integrating with cocoapods ([eef8d47a37](https://github.com/facebook/react-native/commit/eef8d47a37211bf7d4978db75df1fedd9cacbde8) by [@LukeDurrant](https://github.com/LukeDurrant)) + +### Removed: features that have been removed; these are breaking + +- _[BREAKING]_ Removed `ScrollView.propTypes`; use flow or typescript for verifying correct prop usage instead ([5b6ff01764](https://github.com/facebook/react-native/commit/5b6ff01764502c88848867c7e04cab969da384a2) by [@sahrens](https://github.com/sahrens)) + +#### Android specific removals + +- `ReactInstancePackage` is now deprecated; use `@link ReactPackage` or `@link LazyReactPackage` ([b938cd524a](https://github.com/facebook/react-native/commit/b938cd524a20c239a5d67e4a1150cd19e00e45ba) by [@axe-fb](https://github.com/axe-fb)) + +## v0.56.0 + +Welcome to the June 2018 release of React Native! +Over 60 contributors made [821 commits](https://github.com/facebook/react-native/compare/0.55-stable...0.56-stable) since March - and we are extremely grateful to every single one of you. + +As you'll see in a second, this new version has some important **breaking changes** that required a lot of extra efforts to bring to a stable 0.56. This was the main reason behind skipping April and May from the monthly release cycle, but looking forward we are planning on going back to do a rollout every month. + +### Highlights + +#### React Native now uses **Babel 7** + +When upgrading to 0.56, make sure to bump your `babel-preset-react-native` `package.json` dependency to `5.0.2` or newer (but still as _fixed_ value). + +React Native library authors will need to update their libraries to make use of the updated Babel preset as Babel 7 is **not** backwards compatible. + +If you have issues upgrading to Babel 7, please double check the [related documentation](https://new.babeljs.io/docs/en/next/v7-migration.html#versioning-dependencies-blog-2017-12-27-nearing-the-70-releasehtml-peer-dependencies-integrations), in particular the sections related to _Package Renames_ and _Scoped Packages_. + +The [`babel-bridge`](https://github.com/babel/babel-bridge) library may be used if you need to use libraries that have not yet upgraded to Babel 7. You may also enforce the Babel 7 dependency via tools like [yarn resolutions](https://yarnpkg.com/lang/en/docs/selective-version-resolutions/). Overall, you need to ensure all the `@babel/*` deps are fixed at version `7.0.0-beta.47`. + +#### **Node 8** is now the minimum required version + +Trailing commas are now allowed. + +#### **iOS 9** is now the minimum required version + +Any device that can run iOS 8, can upgrade to iOS 9. Developers who support iOS 8 in their apps may continue doing so as this is a Xcode-level setting (`IPHONEOS_DEPLOYMENT_TARGET`). + +#### **Xcode 9** is now the minimum required version + +We recommend using Xcode 9.4 as that is what we use to run our tests. + +#### **Android** projects are now compiled using the _Android 26 SDK_ + +The target API level is left unchanged in this release. + +Starting August 2018, new apps submitted to the Play Store will need to target API 26 as a minimum. You can now opt your project in to use API 26 (or newer) as the target. Please let us know about any issues, as we'd like to finalize support for Android API 26 by the time `0.57.0` is released. + +#### `WebView` will only load http(s) URLs by default + +Geolocation is disabled by default. + +#### Consistently Throw for `` + +Removes a pitfall that people may run into when releasing an app for Android if the bulk of the testing has been performed on iOS only. Nesting a `` within a `` component (e.g. ``) is unsupported on Android, but using this pattern on iOS has not thrown errors in the past. With this release, nesting a `` inside a `` will now throw an error on iOS in order to reduce the parity gap between the platforms. + +#### Flow improvements, migrating away from PropTypes + +Added Flow types for several components. + +We're migrating away from PropTypes and runtime checks and instead relying on **Flow**. You'll notice many improvements related to Flow in this release. + +- Fix project settings warnings on newer Xcode versions, remove unnecessary console logging. +- Modernized `YellowBox`. + Sort warnings by recency, group warnings by format string, present stack traces, show status of loading source maps, support inspecting each occurrence of a warning, and bug fixes. +- Prettier files! +- Lots of bug fixes. + +#### State of React Native + +Heads-up: the Facebook internal team is [currently working on a rewrite of some core architecture pieces](https://reactnative.dev/blog/2018/06/14/state-of-react-native-2018). This is a **work in progress** and we do not expect it to be ready for use in open source quite yet, but we felt the need to let you know what those commits mentioning Fabric are about. + +--- + +### Added: new features + +- Update `babelHelpers` with Babel 7 support ([fbd1beaf66](https://github.com/facebook/react-native/commit/fbd1beaf666be9c09a380784f8c0cd34ba083a6b)) +- `FlatList` is now Strict Mode compliant ([a90d0e3614](https://github.com/facebook/react-native/commit/a90d0e3614c467c33cf85bcbe65be71903d5aecc)) +- Enable `?.` optional chaining operator plugins ([aa6f394c42](https://github.com/facebook/react-native/commit/aa6f394c4236e5a4998c3be8ed61ec1bab950775)) +- Support `flexWrap: 'wrap-reverse'` ([d69e55060f](https://github.com/facebook/react-native/commit/d69e55060fd76d91eccc45905d250a9fce4b2c49)) +- Add prop type `accessibilityTraits` to `Text` ([654435d1ed](https://github.com/facebook/react-native/commit/654435d1ed9e584e65fff601e1fa50591e042664)) +- Add devDependencies support for templates ([c4ab03a18e](https://github.com/facebook/react-native/commit/c4ab03a18e75e6ed55444b5d86f3ceee435b9a78)) +- Add support for springDamping in `SpringInterpolator` ([1dde989919](https://github.com/facebook/react-native/commit/1dde989919d2c272ca7fcaa5c4b2d9ee02c490a0)) + +#### Android specific additions + +- Add support for build.gradle with CRLF for use with `react-native link` ([843cfc3b20](https://github.com/facebook/react-native/commit/843cfc3b202433aad9a236b1b623da7c45e1ac15)) +- add decimal pad to android ([75e49a0637](https://github.com/facebook/react-native/commit/75e49a0637eaa3bd3bb7e445648f084a42d9c8af)) +- Add a way to dismiss PopupMenu elements ([353c070be9](https://github.com/facebook/react-native/commit/353c070be9e9a5528d2098db4df3f0dc02d758a9)) +- Implement `Image.defaultSource` ([b0fa3228a7](https://github.com/facebook/react-native/commit/b0fa3228a77d89d6736da6fcae5dd32f74f3052c)) +- Support Image resizeMode=repeat ([0459e4ffaa](https://github.com/facebook/react-native/commit/0459e4ffaadb161598ce1a5b14c08d49a9257c9c)) +- Yoga: Add back deprecated `getParent` methods for non-breaking API change ([c3c5c3cbce](https://github.com/facebook/react-native/commit/c3c5c3cbce24a31f73ae6339e377ee76ca6401ad)) + +#### iOS specific additions + +- Run tests using Xcode 9.4 and iOS 11.4 ([c55bcd6ea7](https://github.com/facebook/react-native/commit/c55bcd6ea729cdf57fc14a5478b7c2e3f6b2a94d)) +- Add support for Homebrew-installed Node ([0964135a17](https://github.com/facebook/react-native/commit/0964135a178b459e06b44a49a4ecb0dd6c5bec9b)) +- Add textTransform style support ([8621d4b797](https://github.com/facebook/react-native/commit/8621d4b79731e13a0c6e397abd93c193c6219000)) +- Add docs for Swift usage to `RCTBridgeModule.h` ([ca898f4367](https://github.com/facebook/react-native/commit/ca898f4367083e0943603521a41c48dec403e6c9)) + +--- + +### Changes: existing functionality that is now different + +- Upgrade React Native to Babel 7 ([f8d6b97140](https://github.com/facebook/react-native/commit/f8d6b97140cffe8d18b2558f94570c8d1b410d5c)) +- New projects created using `react-native init` will use Babel 7 ([e315ec9891](https://github.com/facebook/react-native/commit/e315ec9891eb0bcb51afb0e797dbd49aa8f9ac71)) +- Restrict `WebView` to only http(s) URLs: ([634e7e11e3](https://github.com/facebook/react-native/commit/634e7e11e3ad39e0b13bf20cc7722c0cfd3c3e28), [23f8f7aecb](https://github.com/facebook/react-native/commit/23f8f7aecb1f21f4f5e44fb9e4a7456ea97935c9)) +- Node 8 is now the minimum required version ([c1e6f27823](https://github.com/facebook/react-native/commit/c1e6f278237e84c8ed26d3d2eb45035f250e2d40)) +- Upgrade React to v16.4.1, sync React Renderer to revision ae14317 ([c749d951ad](https://github.com/facebook/react-native/commit/c749d951ada829c6f6fb76f35e68142e61054433)) +- Update new project template's Flow config to fix `Cannot resolve module X` isse due to removal of `@providesModule` ([843a433e87](https://github.com/facebook/react-native/commit/843a433e87b0ccaa64ab70d07e22bffbabad8045)) +- Upgrade Flow to v0.75 ([3bed272a62](https://github.com/facebook/react-native/commit/3bed272a620ac806a6142327013265ea8138641a), [bc2f12c68c](https://github.com/facebook/react-native/commit/bc2f12c68cf8cfdf8c060354e84392fd9a3645d8), [6264b6932a](https://github.com/facebook/react-native/commit/6264b6932a08e1cefd83c4536ff7839d91938730)) +- Upgrade Flow definitions ([f8b4850425](https://github.com/facebook/react-native/commit/f8b4850425f115c8a23dead7ec0716b61663aed6)) +- Upgrade Prettier to v1.13.6 ([29fb2a8e90](https://github.com/facebook/react-native/commit/29fb2a8e90fa3811f9485d4b89d9dbcfffea93a6), [bc2f12c68c](https://github.com/facebook/react-native/commit/bc2f12c68cf8cfdf8c060354e84392fd9a3645d8)) +- Upgrade Jest to v23.2.0 ([536c937269](https://github.com/facebook/react-native/commit/536c9372692712b12317e657fc3e4263ecc70164), [bc2f12c68c](https://github.com/facebook/react-native/commit/bc2f12c68cf8cfdf8c060354e84392fd9a3645d8)) +- Upgrade Metro to v0.38 ([d081f83a04](https://github.com/facebook/react-native/commit/d081f83a0487ffbc7d19f8edc7532611b359dfc6)) +- Modernized `YellowBox` ([d0219a0301](https://github.com/facebook/react-native/commit/d0219a0301e59e8b0ef75dbd786318d4b4619f4c)) +- Disallow requiring from invariant/warning ([521fb6d041](https://github.com/facebook/react-native/commit/521fb6d041167ec8a8d0e98ac606db1f27f0c5c8)) +- Remove native prop type validation ([8dc3ba0444](https://github.com/facebook/react-native/commit/8dc3ba0444c94d9bbb66295b5af885bff9b9cd34)) +- Add `$FlowFixMe` to invalid prop accesses where Flow wasn't complaining before ([f19ee28e7d](https://github.com/facebook/react-native/commit/f19ee28e7d896aaacf26c6f850230019bdef0d3d)) +- Create Flow props for `Image` ([8bac869f5d](https://github.com/facebook/react-native/commit/8bac869f5d1f2ef42e707d0ec817afc6ac98b3b2)) +- Flow type for `SegmentedControlIOS` ([113f009698](https://github.com/facebook/react-native/commit/113f009698dbd8f1b4c1048d77ff1eb373021083)) +- Flow type for `ProgressViewIOS` ([c87701ba05](https://github.com/facebook/react-native/commit/c87701ba05a8524756e87c089eb92c8f3c81823e)) +- Flow type for `PickerIOS` ([1c66cdc7e8](https://github.com/facebook/react-native/commit/1c66cdc7e8ce8190dfbef76629601497446b2b0a)) +- Flow type for `Switch` ([06052a2330](https://github.com/facebook/react-native/commit/06052a2330fc9c1dd0d56c6bbe5a17703f80c6b9)) +- Flow type for `Slider` ([cbe045a95f](https://github.com/facebook/react-native/commit/cbe045a95f1ca53d99ae521742a93299a53d6136)) +- Flow type for `RefreshControl` ([891dfc3da4](https://github.com/facebook/react-native/commit/891dfc3da4b5825097aedf73ff04e8982c00aeff)) +- Flow type for `ListView` ([4b1ecb6204](https://github.com/facebook/react-native/commit/4b1ecb62045fbb78764d1f51030f2253be705c5c)) +- Flow type for `TextInput` ([c8bcda8150](https://github.com/facebook/react-native/commit/c8bcda8150278fde07331ca6958976b2b3395688)) +- Flow type for `TouchableBounce` ([8454a36b0b](https://github.com/facebook/react-native/commit/8454a36b0bc54cb1e267bc264657cc693607da71)) +- Flow type for `TouchableOpacity` ([44743c07ad](https://github.com/facebook/react-native/commit/44743c07ad672e39668f92a801578906ec92996a)) +- Flow type for `TouchableHighlight` ([f0c18dc820](https://github.com/facebook/react-native/commit/f0c18dc820537892dcc33d5aebbf4f52cf299b95)) +- Flow type for `TouchableWithoutFeedback` ([0b79d1faa2](https://github.com/facebook/react-native/commit/0b79d1faa21eb3c29aeeba08ee0fb2ed62e6cc54)) +- Flow type for `ScrollView` ([b127662279](https://github.com/facebook/react-native/commit/b1276622791d5dbe4199bb075f473908c3e62b31)) +- Flow type for `DatePickerIOS` ([97e572ea6d](https://github.com/facebook/react-native/commit/97e572ea6d7b1fd829ca20f5d5c8ff970d88e68b)) +- Flow type for `KeyboardAvoidingView` ([188b118b60](https://github.com/facebook/react-native/commit/188b118b6075be1614c553596b85d430767f2dbc)) +- Flow type for `ActivityIndicator` ([0b71d1ddb0](https://github.com/facebook/react-native/commit/0b71d1ddb03c036ed118574c105b0af505da19fc)) +- Remove `$FlowFixMe` in `TouchableBounce` ([ffda017850](https://github.com/facebook/react-native/commit/ffda0178509ed92396f15db37a41d3d668ade4e6)) +- Remove `$FlowFixMe` in `ScrollView` ([af6e2eb02d](https://github.com/facebook/react-native/commit/af6e2eb02d3651f869b5436e68e61ef3ab3405a0)) +- Remove `$FlowFixMe` in `ListView` ([af6e2eb02d](https://github.com/facebook/react-native/commit/af6e2eb02d3651f869b5436e68e61ef3ab3405a0)) +- Remove `$FlowFixMe` in `Text` ([6042592cf4](https://github.com/facebook/react-native/commit/6042592cf46787f089e76b661376705380607207)) +- Remove `$FlowFixMe` in `RTLExample` ([206ef54aa4](https://github.com/facebook/react-native/commit/206ef54aa415e3e2bb0d48111104dfc372b97e0f)) +- Remove `$FlowFixMe` in `AppContainer` ([a956551af7](https://github.com/facebook/react-native/commit/a956551af73cf785ee4345e92e71fd5b17c5644e)) +- Remove `$FlowFixMe` in `Slider` ([1615f9d161](https://github.com/facebook/react-native/commit/1615f9d16149c7082ce0e1485aa04a6f2108f7ba)) +- `StyleSheet`: Support animated values for border dimensions ([3e3b10f404](https://github.com/facebook/react-native/commit/3e3b10f4044ada7b523d363afb614720468c217f)) +- Update `react-devtools-core` and `plist` to include security fixes reported by `npm audit` ([3a1d949906](https://github.com/facebook/react-native/commit/3a1d949906acb0c3b44d125d54d0c99305bbbb56)) +- Update `Switch` to ES6 Class ([970caa4552](https://github.com/facebook/react-native/commit/970caa4552d4ba87c1a954391535ff42b00832e7)) +- Update `Slider` to ES6 Class ([5259450c14](https://github.com/facebook/react-native/commit/5259450c143f71c65e157d6b7d3f0e1655eb7aa1)) +- Update `ActivityIndicator` to ES6 Class ([edd7acbb1e](https://github.com/facebook/react-native/commit/edd7acbb1e6fe185600a19cc1cbb38feb16c85ad)) +- Update `RefreshControl` to ES6 Class ([a35a238317](https://github.com/facebook/react-native/commit/a35a23831789030e17f766f72d307ae315be107d)) +- Update `KeyboardAvoidingView` to ES6 Class ([c017dcb0f2](https://github.com/facebook/react-native/commit/c017dcb0f2903b49b2f21cc150226aeb7f5026ee)) +- Update `DatePickerIOS` to ES6 Class ([f8c8231706](https://github.com/facebook/react-native/commit/f8c8231706492b588331354d45b833aa21434e13)) +- Update `Text` to ES6 Class ([ab92c00245](https://github.com/facebook/react-native/commit/ab92c00245c0ce717819ddb0ab8b9204d4c13c34)) +- Replace `context.isInAParentText` w/ `React.createContext` ([e1339bc183](https://github.com/facebook/react-native/commit/e1339bc18303ca5394cd0c9dc97cededb2261581)) +- Cleanup `Text` implementation ([06c05e744d](https://github.com/facebook/react-native/commit/06c05e744d8af9582bde348210f254d76dae48b9)) +- Switch `Text` to `React.forwardRef` ([e708010d18](https://github.com/facebook/react-native/commit/e708010d18f938e2d6b6424cfc9485d8e5dd2800)) +- Switch `View` to `React.forwardRef` ([3e534b9aab](https://github.com/facebook/react-native/commit/3e534b9aab5156adac67762877b2457408fe8934)) +- Update uses of `genMockFunction` and `genMockFn` to `fn` in tests ([390ded871c](https://github.com/facebook/react-native/commit/390ded871cb905d149e9c1f4a082e67a7ec7addb)) +- Make `ViewProps` exact ([65c336f38f](https://github.com/facebook/react-native/commit/65c336f38f4afd43c8b5f81745abf38bd9b8ddbf)) +- Spread `TVViewProps` into `ViewProps` instead of intersection ([bc658d3c44](https://github.com/facebook/react-native/commit/bc658d3c4405676643d952a126295dbc7fc26217)) +- Allow trailing commas ([1e2de71290](https://github.com/facebook/react-native/commit/1e2de712907e5fe0d17648f0ff5c81d4384ca85b)) +- Use `let`/`const` ([8f5ebe5952](https://github.com/facebook/react-native/commit/8f5ebe5952d0675b463137103a82f3fb0c26ae0d)) +- Refactor `MockNativeMethods` in Jest ([5d4c542c58](https://github.com/facebook/react-native/commit/5d4c542c58d84bbe05f76bf01d9efdd9d438572c)) +- Use app name from `app.json` after ejecting ([57774a4a98](https://github.com/facebook/react-native/commit/57774a4a981e2f12cfe9b029447e34f203221b18)) +- Suggest `git apply --reject` for failed upgrades ([4fbd244b9a](https://github.com/facebook/react-native/commit/4fbd244b9a6b62e0efe1b4b5a7ec3de468f020f6)) +- Moved `TouchHistoryMath` from React to React Native ([06085d3836](https://github.com/facebook/react-native/commit/06085d38366373f3135074dc14e2c9871ca4fe29)) +- Refactor `RCTInputAccessoryView` ([c136c54ff0](https://github.com/facebook/react-native/commit/c136c54ff0211e2bf149fab600cd6e295f9d19dd)) +- Don't wrap `ListEmptyComponent` in an extra view ([db061ea8c7](https://github.com/facebook/react-native/commit/db061ea8c7b78d7e9df4a450c9e7a24d9b2382b4)) +- Move `Text` PropTypes to its own file ([cd8128b2ec](https://github.com/facebook/react-native/commit/cd8128b2eccf6898cdf798a1e1be1f7a5762a0d4)) +- Mock `ReactNative.NativeComponent` native methods in Jest ([3e9a371ace](https://github.com/facebook/react-native/commit/3e9a371ace5f25b2eb7a0d30177251f8a0c10ed9)) +- Tightening types for `View` and `VirtualizedList` ([5035af80ec](https://github.com/facebook/react-native/commit/5035af80ecddb44e2a8444780f25f336b760bf32)) +- Make values optional in `ViewPropTypes` ([f1316cab6c](https://github.com/facebook/react-native/commit/f1316cab6c351852ef1da9939d4c8f0244fb8a6f)) +- propTypes are optional for native components ([dbdf43b428](https://github.com/facebook/react-native/commit/dbdf43b428da19a9eba012753904bcf33339ea9a)) +- Rename `Style` to `DangerouslyImpreciseStyle` ([4895c645ea](https://github.com/facebook/react-native/commit/4895c645ea17ff939811f3d5ec6218cd4e31c5fb)) +- _[BREAKING]_ `requireNativeComponent`'s signature has been simplified to only take extraOptions ([820673e707](https://github.com/facebook/react-native/commit/820673e7076b5906ba50e09e40fb9a32cf500c1b), [b549e364e0](https://github.com/facebook/react-native/commit/b549e364e0025e0e1b4005f04a9de2d767006da1), [28d37781c6](https://github.com/facebook/react-native/commit/28d37781c6589574de1113bd12077f6d54053ffb), [1c90a2b47b](https://github.com/facebook/react-native/commit/1c90a2b47b420a4b6aa16a55a344cc08f0eacbe3), and [1ab7d49c2d](https://github.com/facebook/react-native/commit/1ab7d49c2df5673dd214eb8a9b7fd3defb0ff857) by [@yungsters](https://github.com/yungsters)) + +#### Breaking Changes + +- Public methods of Text (`blur`, `focus`, `measure`, `measureInWindow`, `measureLayout`, `setNativeProps`) are no longer bound to the text component instance. It is therefore unsafe to pass these methods by reference (i.e: as callbacks) to functions. So, things like `setTimeout(this._txtRef.focus, 1000)` will no longer work. Please instead do: `setTimeout(() => this._txtRef.focus(), 1000)`. + +### iOS specific changes + +- _[BREAKING]_ WebViews now can only use https; do not use it for `file://` ([634e7e11e3](https://github.com/facebook/react-native/commit/634e7e11e3ad39e0b13bf20cc7722c0cfd3c3e28) by [@mmmulani](https://github.com/mmmulani)) +- iOS 9 is now the minimum required version ([f50df4f5ec](https://github.com/facebook/react-native/commit/f50df4f5eca4b4324ff18a49dcf8be3694482b51)) +- Update podspecs to target iOS 9 ([092103e752](https://github.com/facebook/react-native/commit/092103e7525e58e04346e0a1a16a67ca4f31c2e9)) +- Xcode 9.4 is now used to run tests ([c55bcd6ea7](https://github.com/facebook/react-native/commit/c55bcd6ea729cdf57fc14a5478b7c2e3f6b2a94d)) +- Prevent console logging on iOS 11.3+ within WebSocket ([8125be942b](https://github.com/facebook/react-native/commit/8125be942bd5fd8fe851bad04ae6b9bcb0af4727)) +- Expose `RCTFont` size overrides ([6611fefef7](https://github.com/facebook/react-native/commit/6611fefef7559c4cd3d1824235d263bff210d5e2)) + +### Android specific changes + +- Projects are now compiled using Android SDK 26 ([065c5b6590](https://github.com/facebook/react-native/commit/065c5b6590de18281a8c592a04240751c655c03c)) +- Use Google Maven repo in new Android projects ([6d56a234e3](https://github.com/facebook/react-native/commit/6d56a234e3cf5984335ff2713236260fac977f5f)) +- Upgrade Buck to v2018.03.26.01 ([1324e7b558](https://github.com/facebook/react-native/commit/1324e7b5580db815471172cf6dd140124bd2f11a)) +- Upgrade gradle-plugin to 2.3.3, gradle to 3.5.1, gradle-download-task to 3.4.3 ([699e5eebe8](https://github.com/facebook/react-native/commit/699e5eebe807d1ced660d2d2f39b5679d26925da)) +- Bump NDK APP_PLATFORM to android-16 ([b5dc45420a](https://github.com/facebook/react-native/commit/b5dc45420a0d3aa54d2d2075d7f14ff1835df78a)) +- Bump glog to 0.3.5 (added libc++ support) ([b5fca80605](https://github.com/facebook/react-native/commit/b5fca806059e628edb504cb1bacf62e89ee6f102)) +- `ReactFragmentActivity` deprecated as it's not necessary when targeting API level 14 and above ([77a02c0d83](https://github.com/facebook/react-native/commit/77a02c0d83dbfcd9a5397cf63e1ab2e6c94cfdde)) +- Touchables now play a sound on press ([722f88ca90](https://github.com/facebook/react-native/commit/722f88ca9058c5d902c416b826a7a7ab347326b8)) +- Default `underlineColorAndroid` to transparent ([a3a98eb1c7](https://github.com/facebook/react-native/commit/a3a98eb1c7fa0054a236d45421393874ce8ce558)) +- Disable `WebView` geolocation by default ([23d61b35fb](https://github.com/facebook/react-native/commit/23d61b35fb6fdbfb84f77b6d99ff155a0ff868e6)) +- Ensure cookies with illegal characters are not sent to okhttp ([04028bf216](https://github.com/facebook/react-native/commit/04028bf2169b01f79bd86ecd6b0d8aa5f99599f1)) +- Update app icons to match recent Android releases ([94393f8652](https://github.com/facebook/react-native/commit/94393f8652c414806fc861c214ad36e9ac1b6114)) +- Better error messages for `ReadableNativeMap` ([30d06b4286](https://github.com/facebook/react-native/commit/30d06b42862fc5e8704e109db652d62f86f8eabc)) +- Update Fresco to v1.9.0, okhttp3 to v3.10.0 ([6b07602915](https://github.com/facebook/react-native/commit/6b07602915157f54c39adbf0f9746ac056ad2d13)) +- Add tint color to inline icons ([e8e2a6e410](https://github.com/facebook/react-native/commit/e8e2a6e4102c1ba0ee3d068769e47fa61c160524)) +- Fix antialiasing rounded background ([e4f88c66e3](https://github.com/facebook/react-native/commit/e4f88c66e300505d3c86329dacd84d84e8109837)) +- `react-native link` will now replace '/' by '\_' when linking projects. If you previously linked scoped packages, they will get linked again. ([dbd47592a1](https://github.com/facebook/react-native/commit/dbd47592a18ed09ee6e94c79bed16d63be625af6)) +- New project template now uses project-wide properties ([0a3055d98a](https://github.com/facebook/react-native/commit/0a3055d98a36e49746144e883edc7e20afec4fcb)) + +--- + +### Fixed: bugs that have been resolved + +- `VirtualizedList` now accounts for `ListHeaderComponent` length when calculating offset ([604bcfa4a8](https://github.com/facebook/react-native/commit/604bcfa4a83396c402ba8beaa13f40d05d6e9f5c)) +- Prevent showing a hidden status bar when opening modals ([076b1cea35](https://github.com/facebook/react-native/commit/076b1cea3563cae30e11d63cc100ceaed9082692)) +- Fix crash when reloading while Perf Monitor is enabled ([4fcd9970bd](https://github.com/facebook/react-native/commit/4fcd9970bd2dfb24890bc87e9c82e16dab71ec09)) +- Fixed concurrency issue in remote debugger ([578b0b2a51](https://github.com/facebook/react-native/commit/578b0b2a51fc0c2aba5d27cdd5335396d5351463)) +- Fix `Modal` + `FlatList` scrolling ([45b0907f61](https://github.com/facebook/react-native/commit/45b0907f619f455825f459838615a5a7cc59a204)) +- Fix bug in `RCTNetworking` where not all tasks/handlers were being cleared during invalidation ([b805172034](https://github.com/facebook/react-native/commit/b8051720344f3716e964eaf7cfdd2a91dc703602)) +- Fix keyboard handling with `keyboardShouldPersistTaps: never` ([ffe6c110f7](https://github.com/facebook/react-native/commit/ffe6c110f7ce33460fe0399ccbda16a6adbe90ca)) +- Fix Responder Logic in `Text` ([e2ce22b823](https://github.com/facebook/react-native/commit/e2ce22b823661a7dcf6b70a825921a2910383bd1)) +- Fix `VirtualizedSectionList` lint warnings ([26a1eba1ce](https://github.com/facebook/react-native/commit/26a1eba1cef853b0dab7aad5731699c06d36b781)) +- Fix `VirtualizedSectionList:ItemWithSeparators` ([488a4c7e1c](https://github.com/facebook/react-native/commit/488a4c7e1c86ac5900ff9194106511fbf5a8e3cb)) +- Fix `TextInput`'s initial layout measurements ([c6b4f9f2ce](https://github.com/facebook/react-native/commit/c6b4f9f2ce59bc757d9e211f46294faa03df55c6)) +- Fix `requireNativeComponent` check ([1c90a2b47b](https://github.com/facebook/react-native/commit/1c90a2b47b420a4b6aa16a55a344cc08f0eacbe3)) +- Fix `TextInput` autocapitalization bug ([ff70ecf868](https://github.com/facebook/react-native/commit/ff70ecf868cf12fc66b45dc1496391d0a1e9011f)) +- Add missing events to `ViewPropTypes` ([41a940392c](https://github.com/facebook/react-native/commit/41a940392cea497bc5eb627b24083d0211d1eb89)) +- Add missing Jest mock in `StatusBarManager` ([4a2c560768](https://github.com/facebook/react-native/commit/4a2c560768abb2d8407900fdb2fbe4971ae00a1c)) +- Add Flow declaration for Metro module ([1853e15190](https://github.com/facebook/react-native/commit/1853e1519030caaeeb7f31017d98823aa5696daf)) +- Fix type for `ReactNative.NativeComponent` (1/2) ([de11ba2a5e](https://github.com/facebook/react-native/commit/de11ba2a5ee90929dbc67d914de59bdd2ebc29ca)) +- Fix type for `ReactNative.NativeComponent` (2/2) ([752863629d](https://github.com/facebook/react-native/commit/752863629d63bca6d96a101bfeccc4e7ad3e953e)) +- Move Image PropTypes to new file ([67656991b3](https://github.com/facebook/react-native/commit/67656991b32075e8b4a99c6409b0a131206c6941)) +- Tests: Fix JUnit report location when running Jest ([85fc98d437](https://github.com/facebook/react-native/commit/85fc98d437c08cdec883a73161e120478737ba72)) +- Tests: Fix ReactImagePropertyTest SoLoader failures (#19607) ([a52d84d7e1](https://github.com/facebook/react-native/commit/a52d84d7e1cdb287f2877c4d85f2e9866c248d43)) +- Tests: Fix jest snapshot testing on Windows ([216bce3163](https://github.com/facebook/react-native/commit/216bce31632480ce70cc03b1b2a57ec12440afd7)) +- Fixes "Cannot resolve module" errors in new `react-native init` projects ([843a433e87](https://github.com/facebook/react-native/commit/843a433e87b0ccaa64ab70d07e22bffbabad8045)) +- Haste hotfix for `react-native-windows` ([54942746d4](https://github.com/facebook/react-native/commit/54942746d4037e1153e14fcfc95e4edc772d296a)) + +#### iOS specific fixes + +- Fix undefined_arch error in Xcode 10 beta - e131fff +- Make `react-native run-ios` command play nicely with multiple Xcode versions ([a130239257](https://github.com/facebook/react-native/commit/a1302392577789faab79dad0cb39b147464e0e42)) +- Correct fishhook import ([75a0273de2](https://github.com/facebook/react-native/commit/75a0273de21948b0b959263100f09111f738ec35)) +- Fix bug where a Backspace event was emitted when entering characters after clearing a text in `TextInput` by an empty string ([1ffb2b63be](https://github.com/facebook/react-native/commit/1ffb2b63be4c4af331fece0b4286e5c92b1e575d)) +- Expose `InputAccessoryView` so it can be imported ([80fc415cf1](https://github.com/facebook/react-native/commit/80fc415cf179ffe26d020bc8d6e4451352da94fd)) +- Fix `InputAccessoryView` safe area conformance ([490f22ae72](https://github.com/facebook/react-native/commit/490f22ae72ba43fa9364ce0f6c238744c07ac830)) +- Fix use of C++ syntax in header file ([bfcfe7961d](https://github.com/facebook/react-native/commit/bfcfe7961db0970e2575eafe2f3c9c668bd8940d)) +- Fix install step when running `run-ios` ([0934c1778f](https://github.com/facebook/react-native/commit/0934c1778f0e3c0b691e1a3ca2df1d486eb905dd)) +- Fix `run-ios` not turning on Simulator ([9736ddc061](https://github.com/facebook/react-native/commit/9736ddc061e9c4291df8a3185c7f9d6f73e435c7)) +- Use correct library reference for Fishhook. This fixes the build for the new Xcode build system, on both Xcode 9 and Xcode 10 ([a8b74576da](https://github.com/facebook/react-native/commit/a8b74576da6f1a42fde4e39f97e88c8f45a3a51d)) +- Add missing `onChange` event definition to `DatePickerIOS` ([3b53091869](https://github.com/facebook/react-native/commit/3b53091869b673ea33a4af34242e2227ca944768)) +- Fix crash during Archive phase on Xcode 9.3 ([344c205070](https://github.com/facebook/react-native/commit/344c205070d5ad670c97984dd86ec9ac13c73f81)) +- `RNTesterPods`: Add missing folly include ([128c9343c4](https://github.com/facebook/react-native/commit/128c9343c464f3e7898d6e245f135f8bdf6caa6a)) +- `RNTesterPods`: folly::Optional's `has_value()` to `hasValue()` until folly is upgraded ([128c9343c4](https://github.com/facebook/react-native/commit/128c9343c464f3e7898d6e245f135f8bdf6caa6a)) +- `RNTesterPods`: Fix import for `RCTTestAttributes.h` ([128c9343c4](https://github.com/facebook/react-native/commit/128c9343c464f3e7898d6e245f135f8bdf6caa6a)) +- `RNTesterPods`: Fix `conversions.h` to use namespaced includes ([128c9343c4](https://github.com/facebook/react-native/commit/128c9343c464f3e7898d6e245f135f8bdf6caa6a)) +- Fix or mark enum conversions surfaced by `-Wenum-conversion` ([b8f30db0ae](https://github.com/facebook/react-native/commit/b8f30db0ae21d5f96547702abbf50aefa93b1094)) +- Fix CocoaPods integration without DevSupport subspec ([c09d509c2b](https://github.com/facebook/react-native/commit/c09d509c2b8a5a02701829e1f0ace8081ce64277)) +- Update Yoga to handle being in a Xcode framework project ([cf036dbc7a](https://github.com/facebook/react-native/commit/cf036dbc7af16a8453c115372694dc51e8086fcf)) +- Fix Blob memory leak ([122b3791ed](https://github.com/facebook/react-native/commit/122b3791ede095345f44666691aa9ce5aa7f725a)) +- Avoid double reload event when reloading JS ([7b9b1559a7](https://github.com/facebook/react-native/commit/7b9b1559a7f6719c3c9ad8e894fcdd99ed109afe)) +- Suppress spurious warning about RCTCxxModule ([569061dd83](https://github.com/facebook/react-native/commit/569061dd8384a86cd27719b8b068360d8379f4c3)) + +#### Android specific fixes + +- Fix extreme `TextInput` slowness on Android ([5017b86b52](https://github.com/facebook/react-native/commit/5017b86b525e3ef6023f0f8a88e6fd1cf98024e0)) +- Correct draw path dimensions while doing even border, fixes blurred borders ([c5ca26a0e5](https://github.com/facebook/react-native/commit/c5ca26a0e5c0660196300ee34d6007c63879611f)) +- Don't pass additional arguments to `requireNativeComponent` in `.android.js` files ([a51e8b19cc](https://github.com/facebook/react-native/commit/a51e8b19cc4dc36dee42ac95278b883c06b2e40f)) +- Prevent `RefreshControl` from getting stuck when a parent is scrolled horizontally ([33ffa79a51](https://github.com/facebook/react-native/commit/33ffa79a51d4db9ba69148861f2da304646175cd)) +- Prevent crash due to unsupported ellipsize mode ([85e33aaf90](https://github.com/facebook/react-native/commit/85e33aaf908996e99220bff4a2bdbbdf7c0d12b0)) +- Fix okhttp3 response handling in `DevServerHelper` ([56d48bd9ec](https://github.com/facebook/react-native/commit/56d48bd9ecd2d0f08625259121312531064a09f2)) +- Fix `ReactInstanceManager` unmountApplication to support `ReactRootView` recycling ([4a9b2a7302](https://github.com/facebook/react-native/commit/4a9b2a73021fb547febe1fa193c3effb7ff8da4e)) +- Fix `NullPointerException` when emitting event using `UIManagerModule` ([291c01f4ff](https://github.com/facebook/react-native/commit/291c01f4ffe614760852e36b05d78b42cb4271df)) +- Fix link to Android build guide ([57e7556b8d](https://github.com/facebook/react-native/commit/57e7556b8db61e5fcc3ccea56c1b163b82a091a6)) +- Fix Android open source test failures ([3e0ebc7663](https://github.com/facebook/react-native/commit/3e0ebc76632238f21c60caa92c7a2b5ee8102b71)) +- Fix view indices with LayoutAnimation ([05b75b9ebf](https://github.com/facebook/react-native/commit/05b75b9ebfa3ce6d67b2a3aee446ff0cd515311b)) +- Fix originalNode memory leak ([8102e35271](https://github.com/facebook/react-native/commit/8102e35271ab68e0525a9c60d86a855bbeef9c1a)) +- Fix `ScrollView` with a `TextInput` ([2f1421dec7](https://github.com/facebook/react-native/commit/2f1421dec7cd3a35779caceac108e872033c7d72)) +- Disable onKeyPRess logic when handler not defined ([41975f75d9](https://github.com/facebook/react-native/commit/41975f75d96ef4b606b4618461bf24d5db063b77)) +- fix permission requests on pre-M android ([4e1abdd74d](https://github.com/facebook/react-native/commit/4e1abdd74dc4127a86d62e7750d01d39bb781c08)) + +--- + +### Removed: features that have been removed; these are breaking + +- Deprecate `focusTextInput` and `blurTextInput` ([ce3b7b8204](https://github.com/facebook/react-native/commit/ce3b7b8204dad0fd62a76a0ce66472eca4b25bc8)) +- _[BREAKING]_ `ImageResizeMode` on `Image` is no longer exposed; check your usage of `resizeMode`; the same resize modes exist, but pass them as strings instead ([870775ee73](https://github.com/facebook/react-native/commit/870775ee738e9405c6545500f9a637df9b513a02) by [@TheSavior](https://github.com/TheSavior)) + +#### Android specific removals + +- Remove native extensions ([7c5845a5a2](https://github.com/facebook/react-native/commit/7c5845a5a26592598c9380df078766a680a23f06)) +- Remove Fresco ProGuard rules ([07df36557c](https://github.com/facebook/react-native/commit/07df36557c8cbbaee5e870460162aa725a606ff4)) + +#### iOS specific removals + +- Disallow nesting of `` within `` (e.g. ``) ([6a1b41643a](https://github.com/facebook/react-native/commit/6a1b41643a5f5035c61a96263220d11d3462e8f2) +- Removed deprecated `UIActionSheetDelegate` methods ([5863b564f8](https://github.com/facebook/react-native/commit/5863b564f84b9fe97b256f8cde0f7f2e1db9b641)) + +--- + +### Known issues + +During the RC testing of this version, a few issues that have been opened don't have yet a finalized solution ( [19827](https://github.com/facebook/react-native/issues/19827), [19763](https://github.com/facebook/react-native/issues/19763), [19859](https://github.com/facebook/react-native/issues/19859), [19955](https://github.com/facebook/react-native/issues/19955) ). We are aware of them and we hope that by releasing 0.56.0 the surface of developers interacting to find solutions to them will allow for faster resolution and an even better 0.56.1 release. So please check the already opened issues before submitting new ones. + +If you are using Windows to develop React Native apps, we suggest you keep an eye on [this issue in particular](https://github.com/facebook/react-native/issues/19953) since there have been many reports of issues related to Win 10 and RN 0.56. + +## v0.55.0 + +Welcome to the March 2018 release of React Native ! Over 81 contributors made 247 commits since February. Thanks for another exciting release. + +Here are a few highlights: + +- React Native is now using the MIT license +- Android TV device support + +[![RNAndroidTVDemo](http://img.youtube.com/vi/EzIQErHhY20/0.jpg)](http://www.youtube.com/watch?v=EzIQErHhY20) + +- Animated tracking with native driver - check out the [silky smooth framerate](https://t.co/dE1KST1i3g) +- Lots of Flow improvements +- Bugfixes + +### Added: new features + +- Added support for animated tracking to native driver. Now you can use `useNativeDriver` flag with animations that track other `Animated.Values` ([b48f7e5605](https://github.com/facebook/react-native/commit/b48f7e560545d53db7c906ced51a91c4cce6ee94) by [@kmagiera](https://github.com/kmagiera)) +- There's a new UTFSequence module in the library for common Unicode sequences (Emoji!) ([54870e0c6c](https://github.com/facebook/react-native/commit/54870e0c6ca8611fed775e5ba12a0d6d9b1cdbd7) and [4761d5a83e](https://github.com/facebook/react-native/commit/4761d5a83e707e0ed651f02a9e02fc5d66b1869a) by [@sahrens](https://github.com/sahrens)) +- Added `contextMenuHidden` property for **TextInput** ([2dd2529b3a](https://github.com/facebook/react-native/commit/2dd2529b3ab3ace39136a6e24c09f80ae421a17e) by [@amhinson](https://github.com/amhinson)) +- Add `testOnly_pressed` to **TouchableHighlight** for snapshot tests ([3756d41de1](https://github.com/facebook/react-native/commit/3756d41de1feb167482f01b26f9a5f2563ef8bff) by [@sahrens](https://github.com/sahrens)) + +#### Android specific additions + +- Added support for Android TV devices ([b7bb2e5745](https://github.com/facebook/react-native/commit/b7bb2e5745f2bdbfeeccef8d97d469730942e01c) by [@krzysztofciombor](https://github.com/krzysztofciombor)) +- Implemented style `letterSpacing` for **Text** and **TextInput** ([5898817fc1](https://github.com/facebook/react-native/commit/5898817fc1a66bd317d65ce96520159df2f96045) by [@motiz88](https://github.com/motiz88)) +- Bundle download progress is now shown [d06e143420](https://github.com/facebook/react-native/commit/d06e143420462344ea6fc21c0446db972f747404) by [@janicduplessis](https://github.com/janicduplessis)) +- **AndroidInfoModule** now also returns Android ID ([216c8ec04b](https://github.com/facebook/react-native/commit/216c8ec04b22704f722ecaac4718157af4434a0c) by [@L33tcodex0r](https://github.com/L33tcodex0r)) + +#### iOS specific additions + +- Introducing **InputAccessoryView**, "a component which enables customization of the keyboard input accessory view" ([38197c8230](https://github.com/facebook/react-native/commit/38197c8230657d567170cdaf8ff4bbb4aee732b8), [84ef7bc372](https://github.com/facebook/react-native/commit/84ef7bc372ad870127b3e1fb8c13399fe09ecd4d), and [6d9fe455dc](https://github.com/facebook/react-native/commit/6d9fe455dc815cdce86c00f81c71c9ca0c724964) by [@PeteTheHeat](https://github.com/PeteTheHeat)) +- `base-line` metric exposure for **Text** and **TextInput** ([51b3529f6c](https://github.com/facebook/react-native/commit/51b3529f6c2ca354800c0cf6ecb8eb3115eaa36e), [0dbe18375e](https://github.com/facebook/react-native/commit/0dbe18375ebb712be8bebd3b6592170f90f8b7bc), and [7630a614e4](https://github.com/facebook/react-native/commit/7630a614e4bd56c1a3ac728e1dfafd114340f2b7) by [@shergin](https://github.com/shergin)) +- **DatePickerIOS** now has `initialDate` prop ([446ce49e9b](https://github.com/facebook/react-native/commit/446ce49e9b097d2a5e95b0f17aa23756733c27ec)) +- Expose version via `RCTVersion.h`'s `RCTGetReactNativeVersion()` ([30469ed001](https://github.com/facebook/react-native/commit/30469ed00170a74743d2ba5aadce61aae742715c) by [@LeoNatan](https://github.com/LeoNatan)) +- Allow running multiple simulators simultaneously with `react-native run-ios --simulator ...` ([2ad34075f1](https://github.com/facebook/react-native/commit/2ad34075f1d048bebb08ef30799ac0d081073150) by [@koenpunt](https://github.com/koenpunt)) +- Introduced **RCTSurfaceHostingProxyRootView** for migration to **RCTSurfaceHostingView** ([34b8876ac6](https://github.com/facebook/react-native/commit/34b8876ac6510398e03a03c94f4ffb9aaa7519d3) by [@fkgozali](https://github.com/fkgozali)) +- New UIManager API allowing intercept/delay mounting process ([402ae2f01f](https://github.com/facebook/react-native/commit/402ae2f01fd91051be5b717b0578e18b863854af) and [b90c1cf6c3](https://github.com/facebook/react-native/commit/b90c1cf6c30454859579278be18ac650c66f516b) by [@shergin](https://github.com/shergin)) + +### Changes: existing functionality that is now different + +- React Native has now adopted the MIT license ([1490ab12ef](https://github.com/facebook/react-native/commit/1490ab12ef156bf3201882eeabfcac18a1210352) and [26684cf3ad](https://github.com/facebook/react-native/commit/26684cf3adf4094eb6c405d345a75bf8c7c0bf88) by [@sophiebits](https://github.com/sophiebits)) +- The HelloWorld template now exclude `*.jsbundle` files from Git ([21231084db](https://github.com/facebook/react-native/commit/21231084dbccc8abe7823d4444a7e772c08e3e72) by [@aneophyte](https://github.com/aneophyte)) +- `react-native-git-upgrade` now shows files merged with conflicts in red ([e53a8f7097](https://github.com/facebook/react-native/commit/e53a8f7097965f38d87eade1407661bc63adc68e) by [@alvinthen](https://github.com/alvinthen)) +- `ResolvedAssetSource` type to have all read-only members ([4d0ee37293](https://github.com/facebook/react-native/commit/4d0ee37293b5e21fc3c7a8c6edd72c9ff899df7d) by [@sahrens](https://github.com/sahrens)) +- Flow types improvements ([b6c7e551a9](https://github.com/facebook/react-native/commit/b6c7e551a91c406884cbbe8ee37c0038a1b7f0be), [b98bf1e097](https://github.com/facebook/react-native/commit/b98bf1e09739860d82e37225f1635bba3bc817b3), [80c18395e2](https://github.com/facebook/react-native/commit/80c18395e24760cd12b69592a10037f071255437), [70a3ececc3](https://github.com/facebook/react-native/commit/70a3ececc368a8d0fe4b57b13ac956ad99a637c7), [f7343576fc](https://github.com/facebook/react-native/commit/f7343576fc2ca941b03145d9e97208bcbc8c345b), [a817c64043](https://github.com/facebook/react-native/commit/a817c6404338b7b15aaeac5693ae3635a0a3dde0), [3fd82d3c89](https://github.com/facebook/react-native/commit/3fd82d3c89f2d7e5103b024b54250f2ded970d88), [cd8128b2ec](https://github.com/facebook/react-native/commit/cd8128b2eccf6898cdf798a1e1be1f7a5762a0d4), [5035af80ec](https://github.com/facebook/react-native/commit/5035af80ecddb44e2a8444780f25f336b760bf32), [26734a8473](https://github.com/facebook/react-native/commit/26734a8473ac2f5715f2b7a016f0cc8a15c6f073), [321ba067a8](https://github.com/facebook/react-native/commit/321ba067a8323c80262e51c94a931199d5ff5cd7), [b6b80f6a70](https://github.com/facebook/react-native/commit/b6b80f6a70c6d790c52b58453fefc2cea6cd06fe), [f1316cab6c](https://github.com/facebook/react-native/commit/f1316cab6c351852ef1da9939d4c8f0244fb8a6f), [2520c645f8](https://github.com/facebook/react-native/commit/2520c645f863c299e8dccb844bac3dc6a9d553e0), [214da52fe7](https://github.com/facebook/react-native/commit/214da52fe76c1688d0c1a402b3e6c4d0fc19d882), [dbdf43b428](https://github.com/facebook/react-native/commit/dbdf43b428da19a9eba012753904bcf33339ea9a), [49396aa78d](https://github.com/facebook/react-native/commit/49396aa78d218625c1933fa864acd70853faa9f9), [4895c645ea](https://github.com/facebook/react-native/commit/4895c645ea17ff939811f3d5ec6218cd4e31c5fb), [a3c07c95ef](https://github.com/facebook/react-native/commit/a3c07c95effd891c2bd5f3257efe5b24d85862be), [49ffc9fada](https://github.com/facebook/react-native/commit/49ffc9fada4266c3ba9751c5e8e4c475174c7e6c), and [c129457d3a](https://github.com/facebook/react-native/commit/c129457d3a6622d7c28e8b27829ffc2b0a03c5eb) by [@TheSavior](https://github.com/TheSavior), [@yungsters](https://github.com/yungsters), and [@alex288ms](https://github.com/alex288ms)) +- Better enable cross-platform support of WebSocket.js ([b9be28915c](https://github.com/facebook/react-native/commit/b9be28915cf323eb36f1d7c77821cdf994954074) by [@rozele](https://github.com/rozele)) +- Better error handling in the CLI around making directories ([d2817f48a1](https://github.com/facebook/react-native/commit/d2817f48a1146b469d544ee78015251551d358c3) by [@BridgeAR](https://github.com/BridgeAR)) +- Verify that the component passed to createAnimatedComponent is not functional ([10b642a7af](https://github.com/facebook/react-native/commit/10b642a7af097bd508dab7b5d4723ccb4339d35f) by [@janicduplessis](https://github.com/janicduplessis)) +- Don't truncate in the middle of an emoji ([9c8c597000](https://github.com/facebook/react-native/commit/9c8c5970002d048e8b18088f7c63b39431def50b) by [@Vince0613](https://github.com/Vince0613)) +- Loosen Platform check to allow better code sharing for out-of-tree platforms ([84affbd6a3](https://github.com/facebook/react-native/commit/84affbd6a371dd865a3550b1fde1ebabee921341)) +- In CLI, fix issue with `isInstalled` check for Android and references to unregister ([ec884890b1](https://github.com/facebook/react-native/commit/ec884890b1f40da42e84202e082b4cef2506bbfc) by [@rozele](https://github.com/rozele)) + +#### iOS specific changes + +- tvOS `onPress` magnification animation now works via the `tvParallaxProperties` prop object taking `pressMagnification`, `pressDuration`, and `pressDelay` ([6c353fd7e9](https://github.com/facebook/react-native/commit/6c353fd7e9fd324717951ad62754d817537d7339) by [@JulienKode](https://github.com/JulienKode)) + +### Fixed: bugs that have been resolved + +- In **TouchableOpacity**, trigger animation on `opacity` upon change in `disabled` prop ([9366ce416f](https://github.com/facebook/react-native/commit/9366ce416fbf015e4795987d39a65199b1b335c2) by [@maxkomarychev](https://github.com/maxkomarychev)) +- Fixed an issue encountered when using `react-native-vector-icons` ([a759a44358](https://github.com/facebook/react-native/commit/a759a44358711180b37cf4ad25f28af47e3de298) and [54dc11a5fb](https://github.com/facebook/react-native/commit/54dc11a5fbafaccc9c0a781f1151225909717597) by [@jeanlauliac](https://github.com/jeanlauliac) and [@t4deu](https://github.com/t4deu))) +- Add missing mock for Jest for `removeEventListener` method ([59c7b2cfac](https://github.com/facebook/react-native/commit/59c7b2cfac534a79ff2461af5fd2034b280812a3) by [@MoOx](https://github.com/MoOx)) +- Fix main size calculation from the aspect ratio ([f751c3460e](https://github.com/facebook/react-native/commit/f751c3460e5dc48c1f1a2d72a56173285899de21)) +- Fix crash in Subscribable due to uglify-es ([b57a78c3de](https://github.com/facebook/react-native/commit/b57a78c3def50eda11e57542be0e5233a62d173b) by [@iMagdy](https://github.com/iMagdy)) +- Update `node-notifier` dependency to fix memory leak ([860fcd458a](https://github.com/facebook/react-native/commit/860fcd458a1873ebcf977be01670be5912ae7104) by [@rickhanlonii](https://github.com/rickhanlonii)) +- Fix issues with pollParams and link ([ca8ce83cc3](https://github.com/facebook/react-native/commit/ca8ce83cc3c38751604afce5a3e2f0473d9cba91) by [@grabbou](https://github.com/grabbou)) + +#### iOS specific fixes + +- DevLoadingView now supports the iPhone X screen shape ([47b36d3ff0](https://github.com/facebook/react-native/commit/47b36d3ff0dbb99fd3fc98f6e981a38084dd4d2c) by [@mrtnrst](https://github.com/mrtnrst)) +- Added bounds check to prevent ScrollView from scrolling to an offset which is out of bounds of the ScrollView ([16c9e5b715](https://github.com/facebook/react-native/commit/16c9e5b71500135a631480035af6cd2de3dafdc9) by [@siddhantsoni](https://github.com/siddhantsoni)) +- **NetInfo** `isConnected` works again ([dbafc29e60](https://github.com/facebook/react-native/commit/dbafc29e60aba1d5b24c2b0d321834c40e0b9bca) by [@alburdette619](https://github.com/alburdette619)) +- In **AlertIOS**, fix duplicate var name declaration ([6893a26bfb](https://github.com/facebook/react-native/commit/6893a26bfb06a2d8ad9d23a572f4d9143305d905)) +- Permit `react-native run-ios --device [id]` by passing port when running on device ([f8fee0a631](https://github.com/facebook/react-native/commit/f8fee0a631d77313d7cb5e65a3dd04a5a8ba3d03) by [@jozan](https://github.com/jozan)) +- Fixed issue with `run-ios` where `Entry, ":CFBundleIdentifier", Does Not Exist` was being received ([5447ca6707](https://github.com/facebook/react-native/commit/5447ca67076a110e2b0df03b014f53d1df4646ab) by [@blackneck](https://github.com/blackneck)) +- Fixed problem in Text measurement on iOS ([a534672e13](https://github.com/facebook/react-native/commit/a534672e132136e7bbd17c94a7f4e67149bcc67a) by [@shergin](https://github.com/shergin)) +- Fix crash when reloading in tvOS ([3a3d884df2](https://github.com/facebook/react-native/commit/3a3d884df253dbc1c02ffef33e99c4a91ea8751b) by [@dlowder-salesforce](https://github.com/dlowder-salesforce)) +- Fixed a bug with positioning of nested views inside **Text** ([7d20de412b](https://github.com/facebook/react-native/commit/7d20de412b37a35951e615d98509573dc1a24bcb) by [@shergin](https://github.com/shergin)) +- Fix blob response parsing for empty body ([f5207ba9c7](https://github.com/facebook/react-native/commit/f5207ba9c764f33ef83fa897f6014d67193be0e2) by [@janicduplessis](https://github.com/janicduplessis)) +- Fix tvOS react-native init release build ([3002c4eb98](https://github.com/facebook/react-native/commit/3002c4eb981d439f0ea304556d8dbd4ffd62a80b) by [@dlowder-salesforce](https://github.com/dlowder-salesforce) +- Fix RedBox from bridge reload due is not re-registering its root view ([2e51fa5f5d](https://github.com/facebook/react-native/commit/2e51fa5f5d4f229329ae457ab1a77ba5bcea0448) by [@fkgozali](https://github.com/fkgozali)) + +#### Android specific fixes + +- Fix: incorrect line-height calculation ([74e54cbcc4](https://github.com/facebook/react-native/commit/74e54cbcc408a8bbdd70f47cc8728d30cdc0d299) by [@strindhaug](https://github.com/strindhaug)) +- Fix crashes with TextInput introduced in 0.53 ([b60a727adb](https://github.com/facebook/react-native/commit/b60a727adbcfa785e3d1b13bf069b766216e60f8) by [@joshyhargreaves](https://github.com/joshyhargreaves)) +- Update ReactAndroid build script to support gradle 2.3.0 ([d8bb990abc](https://github.com/facebook/react-native/commit/d8bb990abc226e778e2f32c2de3c6661c0aa64e5)) +- Allow "unexpected URL" exception to be caught on Android when using fetch ([da84eba318](https://github.com/facebook/react-native/commit/da84eba318ae69fea28f40418178bdeb35c4a99b) by [@jcurtis](https://github.com/jcurtis)) +- Fix `onLayout` prop for **TextInput** ([8a073c1d8b](https://github.com/facebook/react-native/commit/8a073c1d8b89305a9a2561a7c33740919730f408) by [@rozele](https://github.com/rozele)) +- Fix ViewPager when using native navigation ([a1295e1707](https://github.com/facebook/react-native/commit/a1295e1707a856b9cd5c3129320d386aa9166310) by [@ruiaraujo](https://github.com/ruiaraujo)) +- Fix localization crash in **DevSettingsActivity** ([427e464bb9](https://github.com/facebook/react-native/commit/427e464bb95e4e0ecc7455e71b5d477014618200) by [@ayc1](https://github.com/ayc1)) +- Fix pinch crash in touch-responsive views ([67c3ad4e6a](https://github.com/facebook/react-native/commit/67c3ad4e6a1847cbac43115b01f72cc5c8932a61) by [@tobycox](https://github.com/tobycox)) +- Fix IllegalStateException thrown in looped timing native animation ([ef9d1fba23](https://github.com/facebook/react-native/commit/ef9d1fba237c08a158c8f32e823f229921e7c052) by [@kmagiera](https://github.com/kmagiera)) +- Workaround android-only js module resolution issue ([c20e0f94fe](https://github.com/facebook/react-native/commit/c20e0f94feb42a71633212114b42c62494fd4ff0) by [@fkgozali](https://github.com/fkgozali)) +- Fix ReadableNativeMap.toHashMap() for nested maps and arrays ([15fa2250fd](https://github.com/facebook/react-native/commit/15fa2250fdd0865ce1d0c6ac13b817e7b2c7757a) by [@esamelson](https://github.com/esamelson)) +- Fix Android Sanity Buck version check ([e0573225d5](https://github.com/facebook/react-native/commit/e0573225d5fe28e5ad61690eda3060289bdbf3a4) by [@hramos](https://github.com/hramos)) +- Fixes the connection to Firestore by following whatwg.org's XMLHttpRequest send() method ([d52569c4a1](https://github.com/facebook/react-native/commit/d52569c4a1b6bd19792e4bda23e3a8c3ac4ad8df) by [@samsafay](https://github.com/samsafay)) +- `invertStickyHeaders` can now be set from **SectionList** or **FlatList** ([dd479a9377](https://github.com/facebook/react-native/commit/dd479a93772c3a52561fc32ee84b25ce822a30fa) by [@dannycochran](https://github.com/dannycochran)) + +### Removed: features that have been removed; these are breaking + +- Removed various types ([b58e377961](https://github.com/facebook/react-native/commit/b58e377961ddd278bfa36df0e15953f976875de6), [ee26d9bcb0](https://github.com/facebook/react-native/commit/ee26d9bcb0719246efa51af404aa7805404675cc), [d89517d60a](https://github.com/facebook/react-native/commit/d89517d60a8a6cabc9013b603fa3f63a1face6a2), [852084ad45](https://github.com/facebook/react-native/commit/852084ad454565bb856e85f09e098f1a4a0771a6) by [@TheSavior](https://github.com/TheSavior)) +- Deleted `Systrace.swizzleJSON()` ([3e141cb6c9](https://github.com/facebook/react-native/commit/3e141cb6c957143e998bf2926b8fe1aabccbce2d) by [@yungsters](https://github.com/yungsters)) + +#### Android specific removals + +- `ReactInstanceManager#registerAdditionalPackages` has been removed; Create UIManager interface and extract common classes in uimanager/common ([6b45fb2cb1](https://github.com/facebook/react-native/commit/6b45fb2cb1ca44fa7375bc7696bf90a68a85df3c) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific removals + +- Remove callFunctionSync experimental APIs ([19a4a7d3cb](https://github.com/facebook/react-native/commit/19a4a7d3cb6d00780ccbbbd7b0062896f64ab24d) by [@danzimm](https://github.com/danzimm)) + +## v0.54.0 + +Welcome to the February 2018 release of React Native! This release includes work done by the React Native team and the community in January, and there are some big changes here after the holidays. Thanks for 270 commits from 87 contributors, you all are great! Here are a few highlights from the release: + +- Long awaited **Blob** changes: upload, download, fetch locally, and more +- Sticky headers now work on inverted Lists +- Update to the newest React, which deprecated some lifecycle methods and added new ones – expect Yellowbox until React Native is updated +- `Space-evenly` is now there (sorry for the confusion with 0.52's release notes) +- A lot of under-the-covers work on Yoga, iOS's **Text** and **TextInput**, and a ton of other areas +- Multiple crash fixes + +The changelog is arranged by the customary added, removed, changed, and fixed plus internal; the changes are also organized by platform. + +### Added + +- ✨ **Blob**s now can be: made from Strings, loaded by File using a FileReader API, uploaded and downloaded via `XMLHttpRequest#fetch`, and fetched on files to a local blob consistently ([be56a3efee](https://github.com/facebook/react-native/commit/be56a3efeefefa6dca816ca5149a3dabfa5164e2) and [854c2330eb](https://github.com/facebook/react-native/commit/854c2330ebe748eb0508bb788685232b6cff0022) by [@satya164](https://github.com/satya164) and [@fkgozali](https://github.com/fkgozali)) +- Dynamic node_module dependencies are now supported ([b5e19adc02](https://github.com/facebook/react-native/commit/b5e19adc02a3293cd3fdbe54cc45adc78f94d325) by [@jeanlauliac](https://github.com/jeanlauliac)) +- Support sticky headers for inverted Lists with `invertStickyHeaders` ([ecaca80d42](https://github.com/facebook/react-native/commit/ecaca80d42b686e4cf91aa4bb0c8fce69eba18bb) by [@janicduplessis](https://github.com/janicduplessis)) +- `space-evenly` is now supported (sorry for the confusion with 0.52 notes) ([b1cdb7d553](https://github.com/facebook/react-native/commit/b1cdb7d553146160f99319f9dbe4083b18db60e4) by [@gedeagas](https://github.com/gedeagas)) +- Platform plugins can participate in RNConfig, `link`, and `unlink` – keep an eye on [react-native-window's use of it](https://github.com/Microsoft/react-native-windows/pull/1601)! ([a40bfa730e](https://github.com/facebook/react-native/commit/a40bfa730e05c68da49e6f217ae0f161dcc7ba98) by [@rozele](https://github.com/rozele)) +- Add `minify` flag to react-native bundle command ([3f969cb1db](https://github.com/facebook/react-native/commit/3f969cb1db3a39dd8a4fd622abbb7e4270a84216) by [@tomduncalf](https://github.com/tomduncalf)) + +#### VR Specific Additions + +- Added **ScrollView** support ([6fa039dab0](https://github.com/facebook/react-native/commit/6fa039dab0b9f738a3cb464aeca378c6210a5747) by [@MartinSherburn](https://github.com/MartinSherburn)) + +#### Android Specific Additions + +- Bundle download progress is now shown like iOS ([d06e143420](https://github.com/facebook/react-native/commit/d06e143420462344ea6fc21c0446db972f747404) by [@janicduplessis](https://github.com/janicduplessis)) +- Add back ability to customise OkHttp client ([22efd95be1](https://github.com/facebook/react-native/commit/22efd95be1f0b236eeaaa8a8e6d01e89771c9543) by [@cdlewis](https://github.com/cdlewis)) + +#### iOS specific additions + +- **ScrollView** now supports smooth bi-directional content loading and takes new prop `maintainVisibleContentPosition` ([cae7179c94](https://github.com/facebook/react-native/commit/cae7179c9459f12b1cb5e1a1d998a9dc45f927dc) and [65184ec6b0](https://github.com/facebook/react-native/commit/65184ec6b0ef2d136c0db239d65e0624efac8a2d) by [@sahrens](https://github.com/sahrens)) +- Allow substituting a default font handler ([a9c684a0ff](https://github.com/facebook/react-native/commit/a9c684a0ff45852087310d5218062acfdab673f7) by [@mmmulani](https://github.com/mmmulani)) +- Add `accessibilityElementsHidden` prop ([31288161e1](https://github.com/facebook/react-native/commit/31288161e188723456fdb336c494f3c8a3f5b0a8) by [@aputinski](https://github.com/aputinski)) +- Add EXTRA_PACKAGER_ARGS extensibility point on `scripts/react-native-xcode.sh` (PR rev [0d4ff1b7ea](https://github.com/facebook/react-native/commit/0d4ff1b7ea768cceca0405c665e322c0d6b5ba20) by [@brunolemos](https://github.com/brunolemos) with landing assists [b8c86b8dec](https://github.com/facebook/react-native/commit/b8c86b8deced01027b609959576ffcf5d2d0f520) and [0d4ff1b7ea](https://github.com/facebook/react-native/commit/0d4ff1b7ea768cceca0405c665e322c0d6b5ba20)) + +### Removed + +- Remove internal `utf8` utility - use the **utf8** package now instead ([431670f908](https://github.com/facebook/react-native/commit/431670f90860936c24260d36fc73e0c5fbf4e02a) by [@mathiasbynens](https://github.com/mathiasbynens)) + +#### iOS specific removals + +- Removed outdated assertion in RCTShadowView related to breaking change in Yoga ([e3ff3cf6cb](https://github.com/facebook/react-native/commit/e3ff3cf6cbc137e315eff6ac8aed43954b3668eb) by [@shergin](https://github.com/shergin)) + +#### Android specific removals + +- Fix an issue when swapping to and from the `visible-password` or `phone-pad` keyboard types. ([164f6b6afd](https://github.com/facebook/react-native/commit/164f6b6afd7e0050d63134fcdc65ec6969ab03a0) by [@BrandonWilliamsCS](https://github.com/BrandonWilliamsCS)) +- Remove redundant config in AndroidManifest.xml ([d7a9ca2893](https://github.com/facebook/react-native/commit/d7a9ca2893fb240c25d1cd1e0778f6b93b1e3ded) by [@gengjiawen](https://github.com/gengjiawen)) + +#### iOS specific removals + +- Delete RCTBatchedBridge ([816d417189](https://github.com/facebook/react-native/commit/816d41718998868f276d83b0c21e17d11ad392a2) by [@mhorowitz](https://github.com/mhorowitz)) + +### Changed + +- Docs clarifications ([7abffc3f8c](https://github.com/facebook/react-native/commit/7abffc3f8ce69fab5bbb4147f9b8bcb85a7d2c38) by [@IgorGanapolsky](https://github.com/IgorGanapolsky)) + +#### iOS Specific Changes + +- ⚡️ **Text** and **TextInput** have been re-implemented from the ground up for performance, flexibility, and reduced technical debt ([2716f53220](https://github.com/facebook/react-native/commit/2716f53220f947c690d5f627286aad51313256a0), [74963eb945](https://github.com/facebook/react-native/commit/74963eb945438a6fd269b5764a6cb251c86deda8), [d7fa81f181](https://github.com/facebook/react-native/commit/d7fa81f18110f0dc0f310a5c066d9a30020ca830), [74963eb945](https://github.com/facebook/react-native/commit/74963eb945438a6fd269b5764a6cb251c86deda8), [6c4ef287ad](https://github.com/facebook/react-native/commit/6c4ef287ad95eb14475a9f512487e5d05949309a), [ebc98840e9](https://github.com/facebook/react-native/commit/ebc98840e93c336e8c9e4a93c78e6ca03591f0ec), [d7fa81f181](https://github.com/facebook/react-native/commit/d7fa81f18110f0dc0f310a5c066d9a30020ca830), [7d1ec7a3dc](https://github.com/facebook/react-native/commit/7d1ec7a3dc66654b13a8e9cb3ddf912e92506f55), [52648326e6](https://github.com/facebook/react-native/commit/52648326e6ac4470eeffc0a56d91bc487bc1eae4), [6bb8617f3a](https://github.com/facebook/react-native/commit/6bb8617f3a2f3f80f89eb00595a621aec35aca83), [5dbb3c586c](https://github.com/facebook/react-native/commit/5dbb3c586c9e8483aa7e6f1edd35ffb12bd4305d), [7e7d00aebe](https://github.com/facebook/react-native/commit/7e7d00aebefd2416f948066c65c739581c6e3f54), [46fd864348](https://github.com/facebook/react-native/commit/46fd8643485b21147c780d22ee8cf751b2dc8750), [9dfa2e7f3c](https://github.com/facebook/react-native/commit/9dfa2e7f3cfa5009f6c54382e90681d99a9c3cb8), [8a882fe6d6](https://github.com/facebook/react-native/commit/8a882fe6d6bb35776551eb8b0cd6892f41cab492), and [0f9fc4b295](https://github.com/facebook/react-native/commit/0f9fc4b2953d52fa1754e786dc5c74bfecbeaaca) by [@shergin](https://github.com/shergin) and [@hovox](https://github.com/hovox)) +- **Image**'s `resizeMode="center"` is now documented and has an example present ([be7037fd8e](https://github.com/facebook/react-native/commit/be7037fd8e1c4b92646caf7a70b9d6d28ef2c30a) by [@motiz88](https://github.com/motiz88)) +- Geolocation API no longer timeouts when `skipPermissionRequests: true` ([5c17db8352](https://github.com/facebook/react-native/commit/5c17db8352abfd94f094deb9b550284ec17f1fcd) by [@ngandhy](https://github.com/ngandhy)) +- Rounding pixels is now done with an algorithm from Yoga rather than React Native, reducing debt and improving performance ([ceb1d1ca5b](https://github.com/facebook/react-native/commit/ceb1d1ca5bc7c04b9d9ad16dcd9583f05b0ef498) and [114c258045](https://github.com/facebook/react-native/commit/114c258045ccca3a4433de206c7983b42d14c03b) by [@shergin](https://github.com/shergin)) + +#### Android specific changes + +- Numerous refactors around bundle handling and the `DevServerHelper` ([644123aa6f](https://github.com/facebook/react-native/commit/644123aa6fc6132125f56b485e5ab3b16f28f666), [e756251413](https://github.com/facebook/react-native/commit/e7562514130f614a9f138c0b855bfe4516150add), [6e44356c9b](https://github.com/facebook/react-native/commit/6e44356c9bb364195280aafc69aae48cdcb2ab84), [1019bda930](https://github.com/facebook/react-native/commit/1019bda930fa4c26fc0006efa023ee2c586705c6), [06d8f96a64](https://github.com/facebook/react-native/commit/06d8f96a64f00a003e34b0c1e93033893173ccc8), [f88c9d6382](https://github.com/facebook/react-native/commit/f88c9d63828e975a9792969e27accd851ead3e86), and [108f9664bf](https://github.com/facebook/react-native/commit/108f9664bffd1a4e0a7b2c2da3dc3810f1b29de2) by [@davidaurelio](https://github.com/davidaurelio)) + +### Fixed + +- Fix JS debugger issues related to CORS ([29f8354c19](https://github.com/facebook/react-native/commit/29f8354c1946a6325e9020b9ef5ee4ccdf0fa51f) by [@njbmartin](https://github.com/njbmartin)) +- Keep the `.gitignore`d files during the `react-native-git-upgrade` process ([7492860ffb](https://github.com/facebook/react-native/commit/7492860ffb3a010ff2273abf45c7414c098bdc37) by [@ncuillery](https://github.com/ncuillery)) +- Fix re-render case on SwipeableRow ([a580a44b0d](https://github.com/facebook/react-native/commit/a580a44b0d51ca7f33a4394b0a22d1c7d2234190)) +- Fix display of syntax error messages when HMR is enabled ([2b80cdf1bb](https://github.com/facebook/react-native/commit/2b80cdf1bba3b756915117139474440c203cbd8d) by [@ide](https://github.com/ide)) +- Add fixtures to metro blacklist in order to let build succeed ([54dc11a5fb](https://github.com/facebook/react-native/commit/54dc11a5fbafaccc9c0a781f1151225909717597) by [@t4deu](https://github.com/t4deu)) + +#### Android specific fixes + +- Don't crash when using decimal `Animated.modulo` values with `useNativeDriver: true` ([6c38972327](https://github.com/facebook/react-native/commit/6c389723274712bc52d6642cc6c1907b5523726d) by [@motiz88](https://github.com/motiz88)) +- Don't crash when receiving unknown websocket IDs ([1a790f8703](https://github.com/facebook/react-native/commit/1a790f8703d44c2322000dbf40a55678ca8a436a) by [@sunweiyang](https://github.com/sunweiyang)) +- Dont crash when `NativeModules.UIManager.showPopupMenu` method calls error callback ([0c18ec5b9c](https://github.com/facebook/react-native/commit/0c18ec5b9c64613dbdcd4be9f80e470e9532483d) by [@dryganets](https://github.com/dryganets)) +- Maintain cursor position when **TextInput**'s `secureTextEntry` changes ([09b43e479e](https://github.com/facebook/react-native/commit/09b43e479e97dfe31910503190b5d081c78e4ea2) by [@jainkuniya](https://github.com/jainkuniya)) +- Race condition fix in Dialogs module ([d5e3f081c6](https://github.com/facebook/react-native/commit/d5e3f081c6b41697533775d378969fcf554c7290) by [@dryganets](https://github.com/dryganets)) +- Fix NPE in Android Switch during measure ([7b1915e74d](https://github.com/facebook/react-native/commit/7b1915e74daa82d0a94e90ff266e9271bc43f4d8) by [@4ndroidev](https://github.com/4ndroidev)) +- Fix initialScrollIndex ([ef596dec49](https://github.com/facebook/react-native/commit/ef596dec49975dd4f8860ad8adcd29dd23e04c14) by [@olegbl](https://github.com/olegbl)) +- Fix redbox style ([f363dfe766](https://github.com/facebook/react-native/commit/f363dfe766244c8fc10eab3d2c4acdd8fc4b576b) by [@ayc1](https://github.com/ayc1)) +- Fix crash due to mishandling of UTF-8 in progressive download. ([9024f56bda](https://github.com/facebook/react-native/commit/9024f56bda4186fbade7bbd1e61f8e0252585c02) by [@dryganets](https://github.com/dryganets)) +- Fix crash because ClassCastException fix: getText() returns CharSequence not Spanned ([46cc4907e3](https://github.com/facebook/react-native/commit/46cc4907e3e49f5c7b1ac0a1088866f2958f43a1) by [@dryganets](https://github.com/dryganets)) +- Fix and re-enable "view flattening" optimizations ([877f1cde2e](https://github.com/facebook/react-native/commit/877f1cde2ebe8f304d6fd0855fc4a874d1d5ee27) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific fixes + +- Fix Crash when **CameraRoll** is getting assets from iCloud and no filename is provided ([2ae24361c5](https://github.com/facebook/react-native/commit/2ae24361c5e0fc4aed9a321123bba8ca416a35ff) by [@pentarex](https://github.com/pentarex)) +- Fix Xcode Archive task failing if project path contains whitespace ([8aa568e867](https://github.com/facebook/react-native/commit/8aa568e867bbbe7e23ded3651f23581ff2753323) by [@jevakallio](https://github.com/jevakallio)) +- `react-native link` has been fixed to correctly link iOS and tvOS targets ([a63fd378a4](https://github.com/facebook/react-native/commit/a63fd378a47173cc9f750e9980f18dc12dd7ea51) by [@dlowder-salesforce](https://github.com/dlowder-salesforce)) +- `GLog` fix on case sensitive APFS macOS ([2fef1bafc8](https://github.com/facebook/react-native/commit/2fef1bafc8bee33432486212caf4fef5c659dd37) by [@hovox](https://github.com/hovox)) +- Fixed issue where you cannot launch tvOS app on Apple TV simulator ([afd988f85a](https://github.com/facebook/react-native/commit/afd988f85a8cf0980b5844cb88c1803e41502d03)) + +### Internal work + +- A **massive** amount of Yoga optimizations, cleanups, refactors, and test fixes ([62d01006a1](https://github.com/facebook/react-native/commit/62d01006a125517c8991fa93979aaec6ccc18823), [1475fc4856](https://github.com/facebook/react-native/commit/1475fc4856d366f8ec2027374971ed5aefcdeafa), [9daa17458a](https://github.com/facebook/react-native/commit/9daa17458a5f4ab8ead4d7c29de331f08b1a4a46), [d4517ddb9f](https://github.com/facebook/react-native/commit/d4517ddb9f2ad6d6175cbe6a8be2b819e4aa2c29), [ca91f0e3ac](https://github.com/facebook/react-native/commit/ca91f0e3ac55cb1e7a0fa2399d594a47de80a100), [34b7ec82b5](https://github.com/facebook/react-native/commit/34b7ec82b5d22efbdaa8b74b930d3c4da87414ec), [fda861a889](https://github.com/facebook/react-native/commit/fda861a88914a008b94c12078c9e579a99929643), [9f7cedbe14](https://github.com/facebook/react-native/commit/9f7cedbe14321d24b7aee1ba969b3d23d5c9d204), [ac1c8c265e](https://github.com/facebook/react-native/commit/ac1c8c265e6030c52434f99e882639c67c8c175d), [fcf2c7cf61](https://github.com/facebook/react-native/commit/fcf2c7cf61ca454f10d398d57b78b5b29ed05ae2), [2b27f1aa19](https://github.com/facebook/react-native/commit/2b27f1aa1964eba749876100be1f3ac4c085fa8f), [210ae5b95a](https://github.com/facebook/react-native/commit/210ae5b95a9c94194e95a21fdb93f88ddfdc5ce2), [82088580ab](https://github.com/facebook/react-native/commit/82088580ab17417a51386722f98b83d6cad0a6a0), [7f94bff89a](https://github.com/facebook/react-native/commit/7f94bff89a09547e76b50ae4c96ec59de73a153a), [bd7bf94af9](https://github.com/facebook/react-native/commit/bd7bf94af9ddfc9221ac3f6f62821b7e53e9b0ea), [2fe65b032e](https://github.com/facebook/react-native/commit/2fe65b032e9ec3faf3cef31290372b9face2d3f1), [9658d9f82b](https://github.com/facebook/react-native/commit/9658d9f82ba536c2f39937d61b3954e3dcc6a54e), [ee5c91c031](https://github.com/facebook/react-native/commit/ee5c91c0317b0defbb8da21f7e6d8d3ac8967381), [64d530ba07](https://github.com/facebook/react-native/commit/64d530ba0785af21555d48ddc9e7d561af37db4c), [400a29e151](https://github.com/facebook/react-native/commit/400a29e15134f5264cc55b239bd2a18a107911dd), [f75e21f1ca](https://github.com/facebook/react-native/commit/f75e21f1caf9117ae3eda31c23e286116ebf586c), [528bbacf6b](https://github.com/facebook/react-native/commit/528bbacf6b8a5a62faf4db5bfc8dfe063f0b82a3), [be8e7c6e65](https://github.com/facebook/react-native/commit/be8e7c6e65724d4915862098238506172dbe9657), [d0f7d4d107](https://github.com/facebook/react-native/commit/d0f7d4d107a90fdfbf795d842f4bd4a81290ec62), [4b4959a21c](https://github.com/facebook/react-native/commit/4b4959a21cb1e9e356eab51bfba0f16b76e8ec7f), [fdef3784f0](https://github.com/facebook/react-native/commit/fdef3784f00e8c3233a30aa2e35aaaadaa867489), [831a1bb4b1](https://github.com/facebook/react-native/commit/831a1bb4b1cc201b53685874a9dbdd6c3c1615ad), [2a22d998f8](https://github.com/facebook/react-native/commit/2a22d998f8a7f896db6c0708ba92ed9c9082ee7c), [9f57dedc17](https://github.com/facebook/react-native/commit/9f57dedc1712733ce4a490121138a97917fd3a52), and [ff2658c3de](https://github.com/facebook/react-native/commit/ff2658c3de111ef745d9582c6863ab0d6c90f960) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar), [@passy](https://github.com/passy), [@ryu2](https://github.com/ryu2), and others) +- 🚧 Lifecycle methods were renamed to be consistent with [React RFC6](https://github.com/reactjs/rfcs/blob/master/text/0006-static-lifecycle-methods.md) – note that there are Yellowbox warnings right now because of this, it's work-in-progress ([6f007e8957](https://github.com/facebook/react-native/commit/6f007e8957c9bf5652b0184cba65f385050a8236) by [@bvaughn](https://github.com/bvaughn)) +- Some autogenerated mystery string files were added ([c7846c4bfb](https://github.com/facebook/react-native/commit/c7846c4bfb5b944714d95382210f83c83da1ac52), [bb6fceac27](https://github.com/facebook/react-native/commit/bb6fceac274422102b347ec7aedb36efd9b701cd), [8bd00a2361](https://github.com/facebook/react-native/commit/8bd00a2361bb39f1bda58a260b7ffd278a05d79d), [faa9519021](https://github.com/facebook/react-native/commit/faa951902161201846f20a4dc55950e8f96cb0ff), [f49f7932d5](https://github.com/facebook/react-native/commit/f49f7932d581fe1f9569fb460196801528cfb591)) +- Improvements to the cli's implementation ([1673c570f9](https://github.com/facebook/react-native/commit/1673c570f984d86e88a3b6b44eb78f4848eb0515), [752427b7b8](https://github.com/facebook/react-native/commit/752427b7b8221bbb8304a158b2dad12b26afd7a5), and [619a8c9f29](https://github.com/facebook/react-native/commit/619a8c9f298356db68f8cd7e5d25e5bcf48bab05) by [@arcanis](https://github.com/arcanis), [@voideanvalue](https://github.com/voideanvalue), and [@rozele](https://github.com/rozele)) +- Measure touch events from nearest "root view" ([a70fdac5bd](https://github.com/facebook/react-native/commit/a70fdac5bdd4500b4ca3074dac26d414bd931fb9) by [@mmmulani](https://github.com/mmmulani)) +- Allow to attach the HMR server to an external http server ([8c6b816caa](https://github.com/facebook/react-native/commit/8c6b816caa908845471460f453f9d761bfba3f3d) by [@rafeca](https://github.com/rafeca)) +- Split folly/Memory out from headers-only targets in Buck ([b8e79a7e8b](https://github.com/facebook/react-native/commit/b8e79a7e8be1f3db1482a849352fda6e23c1c78a) by [@mzlee](https://github.com/mzlee)) +- Code cleanup of **ReactHorizontalScrollView** in Android ([71ec85f24c](https://github.com/facebook/react-native/commit/71ec85f24c3a1007a9e1f036a140cce43b38019f) by [@mdvacca](https://github.com/mdvacca)) +- Always create a debugger websocket connection when in iOS dev builds ([fa334ce464](https://github.com/facebook/react-native/commit/fa334ce464da39625f4e4fbfee259e9dcea31abc) by [@bnham](https://github.com/bnham)) +- Make the React Native HMR client extend from the generic metro HMR client ([9a19867798](https://github.com/facebook/react-native/commit/9a198677989930971912b98487ec68d162636411) by [@rafeca](https://github.com/rafeca)) +- Removed use of xip.io ([40a8434bde](https://github.com/facebook/react-native/commit/40a8434bde855ecae42408ec1240622152432de7) by [@jvranish](https://github.com/jvranish)) +- Fix Buck dependencies ([cec2e80fc2](https://github.com/facebook/react-native/commit/cec2e80fc251e4ea45ce1e446323716a3792390d), [4f6c157250](https://github.com/facebook/react-native/commit/4f6c157250676f07619af2a935bddd8301b50caa) by [@swolchok](https://github.com/swolchok)) +- Fix permissions on test script ([42c410ac84](https://github.com/facebook/react-native/commit/42c410ac84619a3d12a4619e59a0a526a3ebdca9) by [@mzlee](https://github.com/mzlee)) +- Better handling exception in loadScript() ([3fbf7856d9](https://github.com/facebook/react-native/commit/3fbf7856d9acb0909357d6b315388471a6b5a69c)) +- Fix ESLint upgrade "parsing error" ([9d214967d2](https://github.com/facebook/react-native/commit/9d214967d2c8184ce26addec150e392e3b519fcd) by [@zertosh](https://github.com/zertosh)) +- Fixing 🤡 in RCTSurfaceRootShadowView ([5fba82deff](https://github.com/facebook/react-native/commit/5fba82deffde731176e3e118193c212f5d2c2bca) by [@shergin](https://github.com/shergin)) +- Handle invalidation error in RCTObjcExecutor ([493f3e8da5](https://github.com/facebook/react-native/commit/493f3e8da5a112e1b33bfb3e9f51e7a2bd7edc7a) by [@fromcelticpark](https://github.com/fromcelticpark)) +- Check for nullptr when accessing isInspectable method ([70d23e82ad](https://github.com/facebook/react-native/commit/70d23e82ad21a4cfde1ce7c3b1c00fe7c7d5adbd) by [@fromcelticpark](https://github.com/fromcelticpark)) +- Introduce new Fabric API in RNAndroid ([2d35bde101](https://github.com/facebook/react-native/commit/2d35bde10130167018791c1b2fe4fece27cefddc) by [@mdvacca](https://github.com/mdvacca)) +- Fixing Prepack model for latest global.nativeExtensions changes. ([01a58d182a](https://github.com/facebook/react-native/commit/01a58d182abd19c9e089ec38b08ffd4b45e2076c) by [@NTillmann](https://github.com/NTillmann)) +- General code cleanup: unused code and configurations ([e233646d09](https://github.com/facebook/react-native/commit/e233646d095a272091b07c29fa87b206831ad6e3) and [e7010348d8](https://github.com/facebook/react-native/commit/e7010348d8b2f703fcc057c2914bd45ca6238f98) by [@bvaughn](https://github.com/bvaughn) and others) +- Add support for finding multiple views with NativeIds using a single listener to Android ([f5efc460ad](https://github.com/facebook/react-native/commit/f5efc460ad30cc60a62edd540c3b0f45c67bcda3) by [@axe-fb](https://github.com/axe-fb)) +- Add CountingOutputStream ([a5e135aed6](https://github.com/facebook/react-native/commit/a5e135aed6941772c663adffd67729f7a5026d08) by [@hramos](https://github.com/hramos)) +- Changes from Prettier ([b815eb59be](https://github.com/facebook/react-native/commit/b815eb59bef7bed9825027adc676b8d09db463c6), [e758cb7f39](https://github.com/facebook/react-native/commit/e758cb7f397b37b5621a4e0afcabc1c74443bc06), [bf9cabb03c](https://github.com/facebook/react-native/commit/bf9cabb03c7245930c270a19816545eae1b9007d), and [a5af841d25](https://github.com/facebook/react-native/commit/a5af841d259b6b29d95a9fb346a0ffce9c6efbfe) by [@shergin](https://github.com/shergin)) +- Typos in code ([8ffc16c6e7](https://github.com/facebook/react-native/commit/8ffc16c6e7d25dd434ca3fc7f9ffd6d5917f7bcd) by [@ss18](https://github.com/ss18)) +- Support for inherited events in view managers ([2afe7d4765](https://github.com/facebook/react-native/commit/2afe7d4765ffc0d0c71d233211edd1d21972040e) by [@shergin](https://github.com/shergin)) +- Flow types changes ([3fc33bb54f](https://github.com/facebook/react-native/commit/3fc33bb54fc5dcf7ef696fe245addc320f85a269), [e485cde187](https://github.com/facebook/react-native/commit/e485cde187e4cd92bc821e58047b149a789dd713), [83ed9d170b](https://github.com/facebook/react-native/commit/83ed9d170b8fd750a345fc608ec69db2fe3ca9b2), [52ffa5d13e](https://github.com/facebook/react-native/commit/52ffa5d13ef6fe2752bc8f838dc1c2dfe651bb64), [d37cdd97ae](https://github.com/facebook/react-native/commit/d37cdd97aee4c1bac864cb28b686f2d1a128128e), [6e7fb01c02](https://github.com/facebook/react-native/commit/6e7fb01c02f3e91777c8292389c09a15d24cf800), [d99ba70c49](https://github.com/facebook/react-native/commit/d99ba70c492d3cd15ef6aded3f8712976d251f88), [bcfbdf4fbe](https://github.com/facebook/react-native/commit/bcfbdf4fbec1a05da151a2255f44a87b651965d6), and [a1c479fb3b](https://github.com/facebook/react-native/commit/a1c479fb3be674511131b46f856bc9b197a38cda) by [@alexeylang](https://github.com/alexeylang), [@sahrens](https://github.com/sahrens), [@yungsters](https://github.com/yungsters), and [@zjj010104](https://github.com/zjj010104)) +- Give IInspector a virtual destructor for correct InspectorImpl destruction ([2a3c37f424](https://github.com/facebook/react-native/commit/2a3c37f424a4d1b9f4c5a2960a1cbe3517eac007) by [@toulouse](https://github.com/toulouse)) +- Migrated `SourceCode` and `DeviceInfoModule` out of Native Modules ([47fe52380a](https://github.com/facebook/react-native/commit/47fe52380a232a1c364e21f71e2644a5a3348366) and [429fcc8cab](https://github.com/facebook/react-native/commit/429fcc8cab3ca877275d7deb1040fdff17a414c7)) +- Jest config change as part of bringing back support for the `assetPlugin` option in Metro ([af6450c660](https://github.com/facebook/react-native/commit/af6450c660d3055d9c5c70d200471541a1ce7e12) by [@ide](https://github.com/ide)) +- Nested virtualized lists should receive recordInteration events ([ae2d5b1e68](https://github.com/facebook/react-native/commit/ae2d5b1e68a2207c27ef2f1b533f86c86d6d849b)) +- Upgrade connect dependency ([709ede799c](https://github.com/facebook/react-native/commit/709ede799cc9820acadaf22aa84f0fe6dd2be319) by [@rafeca](https://github.com/rafeca)) +- xplat/js: asyncRequire: redirect async modules to control modules ([5e11b8870a](https://github.com/facebook/react-native/commit/5e11b8870aa855a56cfafa6575aed5e33b272065) by [@jeanlauliac](https://github.com/jeanlauliac)) +- More progress towards split bundle support ([1a1a956831](https://github.com/facebook/react-native/commit/1a1a956831aec93a4fe2c6e2f63f558271fb466b) and [9e34cbda9d](https://github.com/facebook/react-native/commit/9e34cbda9de8f7350cfb02c884fbef2da18e0e3a) by [@fromcelticpark](https://github.com/fromcelticpark)) +- Implement bundle sync status ([88980f2ef7](https://github.com/facebook/react-native/commit/88980f2ef7331aa630ff19e54427cdc3b7510869)) +- Various improvements to RCTSurface and RCTShadowView ([7d9e902d72](https://github.com/facebook/react-native/commit/7d9e902d72e240f54ea01225cc3272698ff70014), [06ebaf2205](https://github.com/facebook/react-native/commit/06ebaf2205f979b6e6595ec7985447a07d25c4d4), [6882132421](https://github.com/facebook/react-native/commit/688213242130536c5d4db8b9aa17dc418372aadf), and [193a2bd4cd](https://github.com/facebook/react-native/commit/193a2bd4cdffbbc79b69c067b31420663dc9b03a) by [@shergin](https://github.com/shergin)) +- Progress towards experimental ReactFabric and FabricUIManager ([b1e5c01483](https://github.com/facebook/react-native/commit/b1e5c01483a69b181c75d242231077cb2f96e846), [fa0ac92b2c](https://github.com/facebook/react-native/commit/fa0ac92b2c9cfc302314ff18325a96354bb1f432), [94dac23583](https://github.com/facebook/react-native/commit/94dac23583dc6b693475769c196c4b51954e74f1) by [@fkgozali](https://github.com/fkgozali)) +- (almost) kill fbjsc ([702b7e877e](https://github.com/facebook/react-native/commit/702b7e877e09afede0dcdc7f8c680be63e942153) by [@michalgr](https://github.com/michalgr)) +- Refactored bridge ReadableNativeMap and ReadableNativeArray to add centralized accesses ([7891805d22](https://github.com/facebook/react-native/commit/7891805d22e3fdc821961ff0ccc5c450c3d625c8), [28be33ac34](https://github.com/facebook/react-native/commit/28be33ac34d9214ffd452f88a4d19468683a6a0d), and [5649aed6d3](https://github.com/facebook/react-native/commit/5649aed6d3223ec49c42416f242249eb0c4fd890)) +- Removed unused core from Image.android.js ([ce3146a6f3](https://github.com/facebook/react-native/commit/ce3146a6f3162141c7dc06d2c91ec291d3756a92) by [@shergin](https://github.com/shergin)) +- Capture StackOverflowExceptions triggered when drawing a ReactViewGroup or ReactRootView and log more debugging information for it ([1aac962378](https://github.com/facebook/react-native/commit/1aac9623789e3d2a428b51ae699d4c340b3afb99) and [4d3519cc6a](https://github.com/facebook/react-native/commit/4d3519cc6af5bb33c6f21d9392b82379780d79dc) by [@mdvacca](https://github.com/mdvacca)) +- `babel-preset-react-native`: only require plugins once ([df6c48cf36](https://github.com/facebook/react-native/commit/df6c48cf36d39a75a6196462d661ce75c6aef104) by [@davidaurelio](https://github.com/davidaurelio)) +- Report module id as string and as double, in case of invalid values are passed to nativeRequire ([8f358a2088](https://github.com/facebook/react-native/commit/8f358a20881b61cf3256fa1e404b86d5f104932d) by [@fromcelticpark](https://github.com/fromcelticpark)) +- More work moving build configurations to Skylark ([d3db764f38](https://github.com/facebook/react-native/commit/d3db764f383fc588a87e0d1e4267b310d6188bc8), [869866cc5c](https://github.com/facebook/react-native/commit/869866cc5c639d8c0257c776368381987a7f7159), [a8c95d2417](https://github.com/facebook/react-native/commit/a8c95d241757fefaa06ff49193975f7c103a6418), and [79a63d040f](https://github.com/facebook/react-native/commit/79a63d040f1346a0e320104fb35da405502aae19) by [@mzlee](https://github.com/mzlee), [@ttsugriy](https://github.com/ttsugriy), and others) +- `[RCTShadowView isHidden]` was removed ([c19bc79688](https://github.com/facebook/react-native/commit/c19bc7968855e85758df9e1a47dc2a52e69668ed) by [@shergin](https://github.com/shergin)) +- Remove unused `packagerInstance` option and rename it to `server` ([bbbc18c4ee](https://github.com/facebook/react-native/commit/bbbc18c4ee9b13a5aeca10edcb29694db3f15769)) +- The blog has moved to [react-native-website](https://github.com/facebook/react-native-website/tree/master/website/blog) ([e16d67340e](https://github.com/facebook/react-native/commit/e16d67340e0ad1724afeee78f9d820177abbd8b6) by [@hramos](https://github.com/hramos)) +- Remove SoLoaderShim, use SoLoader ([fc6dd78935](https://github.com/facebook/react-native/commit/fc6dd78935a41106aa6a44058c1abb7d0ba0fa24) by [@foghina](https://github.com/foghina)) +- Removed broken link for 'Getting Help' in the README ([b3a306a667](https://github.com/facebook/react-native/commit/b3a306a66709a0ab0ff29151a38eaa3f8f845c1f) by [@rickydam](https://github.com/rickydam)) +- Changed to use boost-for-react-native cocoapod, which speeds up `pod install` a ton; this was in 0.53 originally but had to be re-added ([d40db3a715](https://github.com/facebook/react-native/commit/d40db3a715afaf1cde4a5e231e96e46b2808bbef) by [@CFKevinRef](https://github.com/CFKevinRef)) +- Remove fbobjc's RN copy ([af0c863570](https://github.com/facebook/react-native/commit/af0c8635709b8014c68ce88ebb1e9e94ec56768a)) +- Measure time to create **ReactInstanceManager** ([6224ef5301](https://github.com/facebook/react-native/commit/6224ef5301d67266b28c77e5e46816f319122f38) by [@alexeylang](https://github.com/alexeylang)) +- Upgrade create-react-class to v15.6.3 ([74f386633d](https://github.com/facebook/react-native/commit/74f386633d5e123b2ea73b4773d0ee7d83832fb5) by [@bvaughn](https://github.com/bvaughn)) +- Upgrade react-devtools to v3.1.0 ([8235a49a33](https://github.com/facebook/react-native/commit/8235a49a33cc8e84cd4ba1cc15bc09eed6712b4c) by [@bvaughn](https://github.com/bvaughn)) +- Upgrade flow to v0.65.0 ([7aba456b04](https://github.com/facebook/react-native/commit/7aba456b04ff6a4e4721bcf1064f22a8a87f90c7) and [298f3bb69a](https://github.com/facebook/react-native/commit/298f3bb69abecdcd83adb64e043a2974bd52b1ab) by [@avikchaudhuri](https://github.com/avikchaudhuri) and [@mroch](https://github.com/mroch)) +- Upgrade Jest to v22.2.1 ([46f4d3e1bc](https://github.com/facebook/react-native/commit/46f4d3e1bc9340009c53f366ebd98600c485c993) and [24e521c063](https://github.com/facebook/react-native/commit/24e521c063035e470587bb279976a955ff03717a) by [@mjesun](https://github.com/mjesun)) +- Upgrade ESLint to v4.17.0 (plus update related deps) ([bba19e846e](https://github.com/facebook/react-native/commit/bba19e846e377241826475906f642264409a3990) by [@zertosh](https://github.com/zertosh)) +- Upgrade React to v16.3.0-alpha.1 ([03d7b2aa0e](https://github.com/facebook/react-native/commit/03d7b2aa0e7f239c78b6fe3a96c0ddf3de00a58b) and [5e80d95e03](https://github.com/facebook/react-native/commit/5e80d95e034259af8c41b50756a623756cc81a77) by [@grabbou](https://github.com/grabbou) and [@hramos](https://github.com/hramos)) +- Synced React and ReactFabric render ([c7ed03a95c](https://github.com/facebook/react-native/commit/c7ed03a95c8c372c7631c11e0778cf9753afdabc), [13829751b1](https://github.com/facebook/react-native/commit/13829751b11330f8e1400c5c70c59c49ac2f091f), and [d676746f14](https://github.com/facebook/react-native/commit/d676746f14fb6d714d2576871655b13c005480e7) by [@bvaughn](https://github.com/bvaughn)) +- Upgrade metro to v0.26.0 ([9e6f3b8aff](https://github.com/facebook/react-native/commit/9e6f3b8aff7572f5e9008a2641c70846da0817bb), [ce50f25d22](https://github.com/facebook/react-native/commit/ce50f25d22d56f24bdb7d80a3f9a279863d5dc2a), [e9b83e608e](https://github.com/facebook/react-native/commit/e9b83e608e8487ef8fcbfc956a52bfa7ee1d727f), [2fe7483c36](https://github.com/facebook/react-native/commit/2fe7483c36b10146f737f0a84799c2eb01aaba79), [0f96ebd93b](https://github.com/facebook/react-native/commit/0f96ebd93b634ec3fb0e6036a4960bb4af167e7b), [0de470ec19](https://github.com/facebook/react-native/commit/0de470ec19f2b9f3f4f3ab3dd4322c0f0ece2868), [e8893a021f](https://github.com/facebook/react-native/commit/e8893a021f60ffeea27443998b1716e9a1963d64), and [b1d8af48ae](https://github.com/facebook/react-native/commit/b1d8af48ae251f57bdcd55f89d8fc62aa9eca872) by [@rafeca](https://github.com/rafeca) and [@grabbou](https://github.com/grabbou)) +- Add Context to Redbox report api ([e3c27f585a](https://github.com/facebook/react-native/commit/e3c27f585aaeb685e86250f45fc790c06932af0d) by [@ayc1](https://github.com/ayc1)) +- GitHub bot commands have been disabled in the short term ([b973fe45bd](https://github.com/facebook/react-native/commit/b973fe45bdbc84e12fd0a3afbd6fdd327bcb9d02) by [@hramos](https://github.com/hramos)) +- Various CI configuration changes ([17bd6c8e84](https://github.com/facebook/react-native/commit/17bd6c8e84d9f5d42767a6f42a9a2cf812aa778b), [51b6749c07](https://github.com/facebook/react-native/commit/51b6749c075bb87a340096a0dc06cd6cf9a19907), [a2f3ba864e](https://github.com/facebook/react-native/commit/a2f3ba864ed17ca32e71f15724a8ebf2b1e640c1), [2ef9b7f2da](https://github.com/facebook/react-native/commit/2ef9b7f2da5b875ac1a4fee0ade3cc16ad96772a), [40b17926bb](https://github.com/facebook/react-native/commit/40b17926bb2c724f1580b2eb0c30a37f5d48030a), [613afbab7f](https://github.com/facebook/react-native/commit/613afbab7f30748ba767b055f23d0d294562805f), [da8bec9f8b](https://github.com/facebook/react-native/commit/da8bec9f8b62b46e58e0e98413aa915ece05b71b), [fa11faecb6](https://github.com/facebook/react-native/commit/fa11faecb69f385a5c0aba60f4039612e46b87f3), [f50af7f8a4](https://github.com/facebook/react-native/commit/f50af7f8a48e9cae2cb512962870d5692da5aaf2), [9227ba73ab](https://github.com/facebook/react-native/commit/9227ba73ab8c2b8b8ce4086b5f4667a8a55cdcfa), [365a4d4b43](https://github.com/facebook/react-native/commit/365a4d4b4315d4ca7a0e1236012b763d7e5bb1fd), [b58d848d9c](https://github.com/facebook/react-native/commit/b58d848d9cf78d755fe38392e26826ed481175cd), [c8e98bbaf5](https://github.com/facebook/react-native/commit/c8e98bbaf58b7a7f866e831982355b78dfa43b9d), [f5975a97ad](https://github.com/facebook/react-native/commit/f5975a97adcf3ae9c2988d7e267947a84ab60cee), and [605a6e4031](https://github.com/facebook/react-native/commit/605a6e4031fc9b63edbb9120ffacf7b045dc9db8) by [@hramos](https://github.com/hramos), [@grabbou](https://github.com/grabbou), and [@dryganets](https://github.com/dryganets)) +- Restore copyright header ([4f883bd0bc](https://github.com/facebook/react-native/commit/4f883bd0bcdc015e2cf70bcc29bbe05fd5b8a40b) by [@hramos](https://github.com/hramos)) +- Trim docs that are already present in the open source docs site ([28d60b68ad](https://github.com/facebook/react-native/commit/28d60b68ad7bc5b7ebda6b720981feacd3bde337) by [@hramos](https://github.com/hramos)) +- Fix obsolete instructions about editing docs ([2f46712074](https://github.com/facebook/react-native/commit/2f46712074d187f5456723499e6885bf0941cfbc) by [@ExplodingCabbage](https://github.com/ExplodingCabbage)) +- Fix links to beginner friendly issues ([c355a34de1](https://github.com/facebook/react-native/commit/c355a34de107befd26bc495272b91c11957f3fd0) by [@hotchemi](https://github.com/hotchemi)) +- Typos in comments and log messages ([d2c569795c](https://github.com/facebook/react-native/commit/d2c569795ca07b6b7c0227cfc6d0b3bf5dd23b99) by [@ss18](https://github.com/ss18)) +- Don't run the Danger CI tool through Flow ([1ea3065feb](https://github.com/facebook/react-native/commit/1ea3065feb265bef738bd53e835567d595266725) by [@hramos](https://github.com/hramos)) +- Namespace custom ESLint rules through eslint-plugin-lint ([488b6825c5](https://github.com/facebook/react-native/commit/488b6825c5fb4ec68a8b7315559c4d34e012de12) by [@zertosh](https://github.com/zertosh)) + +- ... and now we're at 0.54 🎉 ([67e67ec83c](https://github.com/facebook/react-native/commit/67e67ec83ce83d4a1a38bc29dd52bf5c28723752), [21dd3dd296](https://github.com/facebook/react-native/commit/21dd3dd296989f4de2d4e9b1ba0df88ea2d32413), [49e35bd939](https://github.com/facebook/react-native/commit/49e35bd9399716a2734e824bab14faf1683cdfdd), [829f675b8b](https://github.com/facebook/react-native/commit/829f675b8b4abae696151e404552af515a2da1ce), and [294d95a236](https://github.com/facebook/react-native/commit/294d95a23687b2e3155fe4ae1bc5e4a649e6b014) by [@grabbou](https://github.com/grabbou) and [@hramos](https://github.com/hramos)) + +## v0.53.0 + +Welcome to the January 2018 release of React Native. The CLI now supports `--port` for both platforms, a few components were made to support consistent props across both platforms, and various fixes were made. There was a lot of under-the-cover work done with more test improvements and dependency updates. 118 commits were made by 43 contributors 🎉. + +### Added + +- ✨ **Keyboard** events now include `easing` and `duration` ([4d33080f0f](https://github.com/facebook/react-native/commit/4d33080f0fa7b2eb7b0e9ff7bbd50c222f461786) by [@sahrens](https://github.com/sahrens)) + +#### iOS exclusive additions + +- `react-native run-ios` now supports the `--port` argument for metro ([33d710e8c5](https://github.com/facebook/react-native/commit/33d710e8c58ef1dc69816a59ac1cf390894e7cb9)) + +#### Android exclusive additions + +- On Android, **ScrollView** now takes `snapToInterval` like iOS ([ddd65f1ba9](https://github.com/facebook/react-native/commit/ddd65f1ba9cca945313d116c1dcf75f3a0556099) and [b2848a54b0](https://github.com/facebook/react-native/commit/b2848a54b05470b3e258c935dd33b8c11a31b3c3) ) +- On Android, **TextInput** now takes `onKeyPress` like iOS ([c9ff0bc212](https://github.com/facebook/react-native/commit/c9ff0bc212b680232f7379fba7b9332927075c3c) by [@joshyhargreaves](https://github.com/joshyhargreaves)) + +### Changed + +- ⬆️ Metro to v0.24.2 ([2e008bc464](https://github.com/facebook/react-native/commit/2e008bc464f94df013794d3da6e9d4e4722151a0) and [0b5e8b4852](https://github.com/facebook/react-native/commit/0b5e8b485229957086d416c307f07c75a4139ffa) by [@rafeca](https://github.com/rafeca)) +- ⬆️ Flow to v0.63 ([6b95c4fb14](https://github.com/facebook/react-native/commit/6b95c4fb142a7015b2afca50cc19eec0b8913d8c) by [@gabelevi](https://github.com/gabelevi)) +- ⬆️ Danger to v2.0 ([b750e3b21b](https://github.com/facebook/react-native/commit/b750e3b21bc5c135773e8de53c5663bdf6266951) by [@hramos](https://github.com/hramos)) +- ⬆️ Jest to v22.0.0 ([4803419dc8](https://github.com/facebook/react-native/commit/4803419dc8406b6892f20cdfb448a01c16ad4338) by [@mjesun](https://github.com/mjesun)) +- 💄 Bundler is now called Metro Bundler in the terminal ([654d7595fe](https://github.com/facebook/react-native/commit/654d7595fe5766667c015307129e75d8986482e1) by [@edcs](https://github.com/edcs)) +- 📝 Update getting started url on Android CLI ([6661633390](https://github.com/facebook/react-native/commit/6661633390276f707faa60509b2805a83929e747)) +- 🐳 Dockerfile uses newest Android SDK, Buck, and new Docker tags have been pushed ([4fbfbe6bb0](https://github.com/facebook/react-native/commit/4fbfbe6bb0e0eaaf12ec713888bf2c6a347f0f96) and [c547f783c4](https://github.com/facebook/react-native/commit/c547f783c440019a4a87ba55b668b3af5ff8fc91) by [@hramos](https://github.com/hramos)) +- 📝 Update repo docs to use HTTPS ([33a2e533b7](https://github.com/facebook/react-native/commit/33a2e533b76d35c1b9fb32e926f7c2c27cb616e9) by [@him2him2](https://github.com/him2him2)) +- 🎨 Make **ScrollResponder** follow code style ([45e6fcdba0](https://github.com/facebook/react-native/commit/45e6fcdba089900555faa63fe8e37b4bd4a7700a) by [@TheSavior](https://github.com/TheSavior)) +- **VirtualizedList** now requires a windowSize greater than 0 ([3559e42c55](https://github.com/facebook/react-native/commit/3559e42c55366bacd9bb5178ecab64f95e9a8ea7)) +- react-devtools works with emulator and real devices now without needing to tweak the devServer value ([fa574c6092](https://github.com/facebook/react-native/commit/fa574c60920588e29d7b642e547e240ac8655e66) by [@jhen0409](https://github.com/jhen0409)) +- 📝 Clarify use of Flow props types in react-native-cli's template project ([9b147a53d1](https://github.com/facebook/react-native/commit/9b147a53d1ab1e14d7ef5b436f1e140a28a5d6a3) by [@hramos](https://github.com/hramos)) +- 📝 Add docs for `isInspectable` ([59c7967627](https://github.com/facebook/react-native/commit/59c79676277abaaaf61388309429c77164c3de4b) by [@bnham](https://github.com/bnham)) +- ✅ More Flow improvements (**Text**, **SectionList**, and others) ([f71f4e7906](https://github.com/facebook/react-native/commit/f71f4e7906648766e1a5b630abbad8935daef955), [632f1202ab](https://github.com/facebook/react-native/commit/632f1202ab3f9dd300a53f096bc15325e0d8f6c1), and [a8391bde7d](https://github.com/facebook/react-native/commit/a8391bde7d757d01521a6d12170fb9090c17a6a0) by [@yungsters](https://github.com/yungsters), [@samwgoldman](https://github.com/samwgoldman), and others) +- Various code cleanup to satisfy linting errors and standards ([b0319f3293](https://github.com/facebook/react-native/commit/b0319f3293b553c105b813dd12bff7d55940e60b), [dd4611721d](https://github.com/facebook/react-native/commit/dd4611721d0ad49ceaf4514df1b47cf2753b9ae6), and [7f58189605](https://github.com/facebook/react-native/commit/7f5818960596a2a18b7d427fd23d46396c05bee5) by [@ayc1](https://github.com/ayc1), [@grabbou](https://github.com/grabbou), and [@ide](https://github.com/ide)) + +#### iOS exclusive changes + +- 🔥⚡️ iOS UI Manager cleanup and optimizations ([0ec1017660](https://github.com/facebook/react-native/commit/0ec1017660602d6b3ae84b4d7a444cbd49ba0d53), [0ae4c47daa](https://github.com/facebook/react-native/commit/0ae4c47daa6280d2931d8bbf89612b2f1cb106d4), [2679f3efb6](https://github.com/facebook/react-native/commit/2679f3efb69bc7b0db1840b4ea59ebe791a54dd2),and [d9e5b313bb](https://github.com/facebook/react-native/commit/d9e5b313bb63a1ec0d402a96539c6df29bc72c42) by [@shergin](https://github.com/shergin)) +- If the inspector tries to handle a wrapped event but there is no connection, log a warning rather than a Redbox ([30da2622e2](https://github.com/facebook/react-native/commit/30da2622e222c338421508ce6e5663155fc874ef) by [@bnham](https://github.com/bnham)) +- Various under-the-covers changes around the bridge, RCTShadowView, RCTSurface, and a few others ([c3139d798a](https://github.com/facebook/react-native/commit/c3139d798af633bb81bf0da6df05b3c0beb0ced4), [2789ba016b](https://github.com/facebook/react-native/commit/2789ba016bfddace1407473769e933795333cfab), [b8e60a3ca3](https://github.com/facebook/react-native/commit/b8e60a3ca3314d79e9c38ee8c7995df81a27624c), [099b28006b](https://github.com/facebook/react-native/commit/099b28006b59abb11eae1f27424566b280860b0d), [b263560c73](https://github.com/facebook/react-native/commit/b263560c73af5559fdc3bba411f16c588abbffef), [19a9c5e41d](https://github.com/facebook/react-native/commit/19a9c5e41da0aa6ee28a54772edcb92daa498561), [d3b41e0da3](https://github.com/facebook/react-native/commit/d3b41e0da37c08ab0637d9f70d612e50b6f5e63c), [b2a251948f](https://github.com/facebook/react-native/commit/b2a251948f3309d2b1d0d533fb2fa74d2f8a10b8), [870bc4807a](https://github.com/facebook/react-native/commit/870bc4807a8c3f90498cf4c2ed3c030cb7b43ef9), [176a578238](https://github.com/facebook/react-native/commit/176a578238566ad857c0911e127669f1ee82107d), [c491b22233](https://github.com/facebook/react-native/commit/c491b2223313676bd4900de7a8c70a10051fa9f0), [c75612219e](https://github.com/facebook/react-native/commit/c75612219ef0c99d1ddca0aadf354f20de27608c), and[c01a171ed8](https://github.com/facebook/react-native/commit/c01a171ed881fb91a972ed475011f85697a29341) by [@shergin](https://github.com/shergin)) +- Changed to use _boost-for-react-native_ cocoapod, which speeds up `pod install` a ton ([d40db3a715](https://github.com/facebook/react-native/commit/d40db3a715afaf1cde4a5e231e96e46b2808bbef) by [@CFKevinRef](https://github.com/CFKevinRef)) + +#### Android exclusive changes + +- Include scroll momentum info when there are scroll events from Android ([c49d249fd7](https://github.com/facebook/react-native/commit/c49d249fd7c274f02e6018892992bcd273d6a465) by [@wwalser](https://github.com/wwalser)) +- Yoga's mkfile for Android now uses wildcard instead of manual file addition ([d89901fa60](https://github.com/facebook/react-native/commit/d89901fa6002802dc9d744bfe3e5e712d6a411a1) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) + +### Removed + +- **TextInput** no longer has the `autoGrow` prop, since this is platform-default behavior now ([dabb78b127](https://github.com/facebook/react-native/commit/dabb78b1278d922e18b2a84059460689da12578b) by [@shergin](https://github.com/shergin)) + +#### iOS exclusive removals + +- Updates to the bridge in order to enable future rendering optimizations ([d2dc451407](https://github.com/facebook/react-native/commit/d2dc4514077ae868f85d45ccdcc7c69df7b7648b) by [@shergin](https://github.com/shergin)) + +### Fixed + +- Do not set `minify=true` when calculating the list of dependencies for the CLI ([4a1bb8fe8d](https://github.com/facebook/react-native/commit/4a1bb8fe8dfd36ea207c0683d683bb8b22a282a5) by [@rafeca](https://github.com/rafeca)) +- 👷 Update CODEOWNERS now that the docs are in a separate repository ([85ff264445](https://github.com/facebook/react-native/commit/85ff264445aa4b9cf0b91aaca5764bb56caba997) by [@hramos](https://github.com/hramos)) +- Fixed a broken link in react-native-git-upgrade's readme ([bbedf2da9a](https://github.com/facebook/react-native/commit/bbedf2da9a3a091eeb687d43029f7d2450cf2612) by [@Taym95](https://github.com/Taym95)) +- 🤡 Do not use Node 8.x specific Stream.final for FS mocks ([4216cdef13](https://github.com/facebook/react-native/commit/4216cdef13c9ed47b9c746b39a0ddfdaf846d495) by [@hramos](https://github.com/hramos)) +- Fix virtualized cell keys for list headers and footers ([a010a0cebd](https://github.com/facebook/react-native/commit/a010a0cebd4afc0d88336c2c265a5d9dbb19918f)) +- Fix warnings of casting and null pointer handling in Yoga ([a8d4666651](https://github.com/facebook/react-native/commit/a8d46666518944a178e85c179da8047234c2d8fb) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Fix broken buck failures on master ([4e767013ed](https://github.com/facebook/react-native/commit/4e767013ed73fb89f69f2e59626d6dcf3bb77684) by [@hramos](https://github.com/hramos)) +- **RefreshControl** appears correctly when expected on initial render of a **FlatList** again ([ed5872e2cc](https://github.com/facebook/react-native/commit/ed5872e2cca955ee407e87e37e13c7fed182199a) by [@vonovak](https://github.com/vonovak)) +- Fixed JS debugger CORS issue ([29f8354c19](https://github.com/facebook/react-native/commit/29f8354c1946a6325e9020b9ef5ee4ccdf0fa51f) by [@njbmartin](https://github.com/njbmartin)) + +#### Android exclusive fixes + +- Fix position of dev loading view on Android API < 20 ([7ff6657985](https://github.com/facebook/react-native/commit/7ff6657985a09bd2572615d16403fba3af709859) by [@kmagiera](https://github.com/kmagiera)) +- Fix Modal not disappearing when navigating from inside a Modal to another activity ([e5c2a66897](https://github.com/facebook/react-native/commit/e5c2a66897b9c562c549e63adcf70783ea34c418) + +#### iOS exclusive fixes + +- Fix regression introduced where layout wouldn't occur in some situations ([46be5bf71c](https://github.com/facebook/react-native/commit/46be5bf71c78d33cda5cb496c475dcfb8e229346) by [@shergin](https://github.com/shergin)) +- Fixed double initial prop applying for newly created views ([0ec1017660](https://github.com/facebook/react-native/commit/0ec1017660602d6b3ae84b4d7a444cbd49ba0d53) by [@shergin](https://github.com/shergin)) +- tvOS build now works again ([3bd89867d6](https://github.com/facebook/react-native/commit/3bd89867d6f23547f07b9b3a569d5a62971004f6) by [@dlowder-salesforce](https://github.com/dlowder-salesforce)) + +### Other + +Below is a list of the remaining, low-level changes that made it into this release of React Native. + +- Remove "prepareReact" call from the bridge ([80f9e1f7de](https://github.com/facebook/react-native/commit/80f9e1f7de407ea417cecb04b3ba20b05696b478) and [56a42e57d0](https://github.com/facebook/react-native/commit/56a42e57d05ff609e8fce35dcb5e9db7938db801) by [@fromcelticpark](https://github.com/fromcelticpark)) +- Add explicit componentControllerClass to CKComponent for RCTSurface ([ab972708a8](https://github.com/facebook/react-native/commit/ab972708a8dcc9b37c19843f2fe134928a7c7a3f)) +- Changes to RCTShadowView to increase RCTSurface performance ([f96f9c5fd6](https://github.com/facebook/react-native/commit/f96f9c5fd692000f561e87cba68642ef7daf43e7) by [@shergin](https://github.com/shergin)) +- Designated methods to control dirty propagation ([af226ef949](https://github.com/facebook/react-native/commit/af226ef949f3a21ef68a6e6b9fbd4cc06fa05152) by [@shergin](https://github.com/shergin)) +- Add missing tvOS header ([49cbca7464](https://github.com/facebook/react-native/commit/49cbca7464e27c34105122459ae29cc3b1247513) by [@grabbou](https://github.com/grabbou)) +- On Android, seperate logic to initialize JS from starting the app ([4996b9aeb4](https://github.com/facebook/react-native/commit/4996b9aeb4127892b7579b45927dec14833b8b4d) by [@axe-fb](https://github.com/axe-fb)) +- ♻️ JS linting was cleaned up: removed unused libs, strengthened the rules, removed unneeded rules, prevent disabled tests, and more ([2815ada238](https://github.com/facebook/react-native/commit/2815ada23872fc28dc8dd5a9833962cb73f452eb), [183c316f4c](https://github.com/facebook/react-native/commit/183c316f4c869804b88cffe40614c84ac0a472d0), [9c67e749d8](https://github.com/facebook/react-native/commit/9c67e749d8f63cf14ece201ec19eee4676f96a85), [79902f99b8](https://github.com/facebook/react-native/commit/79902f99b81f538042b38a857182c2e5adbfd006), [9a36872f0c](https://github.com/facebook/react-native/commit/9a36872f0c7ba03a92fabf65e4d659d6861ea786), [67a3c42d1a](https://github.com/facebook/react-native/commit/67a3c42d1a29b6fa1375f7445d1c9b4429939bae), [b826596700](https://github.com/facebook/react-native/commit/b82659670041d0e472f68c0a14b3ef5b962db09b), [a1a0a69546](https://github.com/facebook/react-native/commit/a1a0a6954635141ce6c167816b67674aa5c31062), and [11a495cb32](https://github.com/facebook/react-native/commit/11a495cb3235ebbc2ad890e92ec612fd5316bffb) by [@TheSavior](https://github.com/TheSavior)) +- 👷 Separate JS lint and flow checks from tests ([5ea5683d01](https://github.com/facebook/react-native/commit/5ea5683d01487b49c814fca6e11a818b9a777239) by [@hramos](https://github.com/hramos)) +- 👷 Fix Buck in build config to enable CI ([796122d8f3](https://github.com/facebook/react-native/commit/796122d8f3b825c0bf0c138c662f3477f8bab123), [7c3a61f3b6](https://github.com/facebook/react-native/commit/7c3a61f3b610e219fd798eccd330deb9a2471253), [82b123e744](https://github.com/facebook/react-native/commit/82b123e744b87cc59c96b4e82af9ed03981b4f42) by [@grabbou](https://github.com/grabbou)) +- ♻️ Various refactoring within the YGNode implementation ([28968e2c0b](https://github.com/facebook/react-native/commit/28968e2c0ba23db9af12b47681f165d29d0f132d), [0a9e652bdd](https://github.com/facebook/react-native/commit/0a9e652bdd031d53d712e2e9610fb608bfa54ff1), [6627d7723c](https://github.com/facebook/react-native/commit/6627d7723c2df244ccc8a462bd98499cc11da2e2), and [d85da86dc7](https://github.com/facebook/react-native/commit/d85da86dc7c7833e71099c6a621547bc3cec8d4f), [a163f70f87](https://github.com/facebook/react-native/commit/a163f70f875dff4428eebd989bfaf28dda6551bf) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Fix ReactLegacy and delete RCTViewControllerProtocol ([a0ff8c7706](https://github.com/facebook/react-native/commit/a0ff8c7706fc37bdce78c9ec51da320f78d16a24) by [@javache](https://github.com/javache)) +- Define internal FB macro for OSS builds; remove some unused definitions ([077c3ab349](https://github.com/facebook/react-native/commit/077c3ab34952f4b442abdd7a47ab54ca4bd0ba2e) and ([a6a66c5b39](https://github.com/facebook/react-native/commit/a6a66c5b3943443e4016f32407e4a4f8d707e387) by [@ttsugriy](https://github.com/ttsugriy)) +- RNTester: Relax Bridge Release Check ([e3c6f38773](https://github.com/facebook/react-native/commit/e3c6f38773d0b578794726033d4fabbfa48d1c7b) by [@yungsters](https://github.com/yungsters)) +- Remove embeddedBundleURL from the asset resolver ([489b98bf10](https://github.com/facebook/react-native/commit/489b98bf1006818ba985e93478a088c0e1e1aae7)) +- Do not set FB_ASSERTION_ENABLED ([4cdbb77c33](https://github.com/facebook/react-native/commit/4cdbb77c3363e120877ff66f39cdcf51d668df7d) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- JSBigString to MAP_PRIVATE not MAP_SHARED ([f9f40cd3e4](https://github.com/facebook/react-native/commit/f9f40cd3e486314cd75bda8722147f2f0f5b8fe1)) +- Fixed black ARTSurfaceView ([5c8481e836](https://github.com/facebook/react-native/commit/5c8481e83646b9cae482a803c878fb007f370035) by [@shergin](https://github.com/shergin)) +- Kill orphaned marker end in JSCExecutor ([6ad1f0957a](https://github.com/facebook/react-native/commit/6ad1f0957a020cb57b177ab015c17aa883e1f0ad) by [@michalgr](https://github.com/michalgr)) +- Make YGNode as c++ struct with properties exposed through accessors ([f1055bcac8](https://github.com/facebook/react-native/commit/f1055bcac8b580c40f9646687e7a950fb6864c74) by [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- 🔖 ...and now we're at 0.53.0-rc.0 🎁 ([0b996577e3](https://github.com/facebook/react-native/commit/0b996577e321d439aa6ede4c7400ea76efb7816a) by [@grabbou](https://github.com/grabbou)) + +## v0.52.0 + +> This changelog has been prepared by Ryan Turner (@turnrye) - thank you for +> your time and making such a detailed changelog 🔥🔥 + +This release had a lot of work around the bundler and packager, a ton of +bugfixes, and updates to many of React Native's dependencies. Lots of +under-the-hood work was done as well to improve the layout engine. Happy new +year! + +> If you would like to help us with the next release changelog, please contact +> @grabbou + +### Added + +- Prettier has a config and an npm script; try it out with `npm run prettier` + ([164591218f](https://github.com/facebook/react-native/commit/164591218f5fab7d386e057e0d51b9c1fe30b0a9) by + [@janicduplessis](https://github.com/janicduplessis)) +- **Debug JS in Nuclide** is now an option in the dev menu 🐜 + ([7c7108a1e8](https://github.com/facebook/react-native/commit/7c7108a1e8e9354d8aeb2f0ff712561d8aa19408) and + [de424cc291](https://github.com/facebook/react-native/commit/de424cc291523a8f4e3d33059b725d5b85f31611)) +- Introducing **PlatformOS** – it looks a lot like **Platform**, but with a + simplified API + ([5ee27ff755](https://github.com/facebook/react-native/commit/5ee27ff7552a5707a6e6bdb3f23e7378f978a2f7) by + [@brishin](https://github.com/brishin)) +- New experimental _RCTSurface_: measure and layout a UI in a thread-safe and + synchronous manner + ([be6976d6fa](https://github.com/facebook/react-native/commit/be6976d6faa333311405bd6184300bbd8da6cbca), + [7df58e23a3](https://github.com/facebook/react-native/commit/7df58e23a3a265b0df0edc753cc79a153fec90d8), + [e75bd87a76](https://github.com/facebook/react-native/commit/e75bd87a76726a9b075061ef76156705b3c1e872), + [aa83b5a0ca](https://github.com/facebook/react-native/commit/aa83b5a0ca30736b2800833bcc6149dcbe8436fa), + [081f7d14ad](https://github.com/facebook/react-native/commit/081f7d14aded077a7627404e5e2d48163a5707ad), + [da17b237e1](https://github.com/facebook/react-native/commit/da17b237e15e20adf20d6b917349d6389efb17fd), + [e9e0cd7ab8](https://github.com/facebook/react-native/commit/e9e0cd7ab86c98bcd3201e306e96532685d8de1d), + [43b2509320](https://github.com/facebook/react-native/commit/43b25093202472c5af07d4f393d831e4d1484b07), + [ba6075120a](https://github.com/facebook/react-native/commit/ba6075120af9c0086b449aafa7420913fa58f746), + [d71d28f094](https://github.com/facebook/react-native/commit/d71d28f09495a1e2802db169e2d0cd1ec5bd5973), + [4d37cf0fbc](https://github.com/facebook/react-native/commit/4d37cf0fbcc529ad75eb4a04a185036a887e42c2), and + [d021dd25da](https://github.com/facebook/react-native/commit/d021dd25da92d84c62c9a77049bb798e3b891447) by + [@maicki](https://github.com/maicki) and + [@shergin](https://github.com/shergin)) +- Experimental **SwipeableRow**'s datasource now has a `getLastRowID` method + ([d79e245d19](https://github.com/facebook/react-native/commit/d79e245d19f7f246322bc657b407198b15cb1b98)) +- [React Native monthly + #5](https://reactnative.dev/blog/2017/11/06/react-native-monthly-5.html) + was added ([3c5a55ddc2](https://github.com/facebook/react-native/commit/3c5a55ddc21197cfa013dc6222e398138759b5d3) + by [@tenodi](https://github.com/tenodi)) + +#### iOS Specific + +- **DatePickerIOS** now takes **locale** 🌍 + ([fd9c3618fc](https://github.com/facebook/react-native/commit/fd9c3618fcd3dc80f9abf3da779627704c7350e4) by + [@RobertPaul01](https://github.com/RobertPaul01)) +- **CameraRoll** can now **deletePhotos** 📸 + ([554e873f58](https://github.com/facebook/react-native/commit/554e873f585ea05ce1e9fe4ff71173c47b3c259c) by + [@fxfactorial](https://github.com/fxfactorial)) +- There's now an API to specify a different directory for iOS image assets + ([8f9b291224](https://github.com/facebook/react-native/commit/8f9b291224d1ca57a5f90200ec4687abb4706f4b)) +- Support for [custom accessibility + actions](https://developer.apple.com/documentation/uikit/uiaccessibilitycustomaction) + on iOS ([36ad813899](https://github.com/facebook/react-native/commit/36ad8138997c195b8728906ceb51bd31338b6a24) by + [@ericdavmsft](https://github.com/ericdavmsft)) + +### Deprecated + +- Ignore YellowBox warnings with `YellowBox.ignoreWarnings([...])` rather than + `console.ignoredYellowBox = [...]` + ([26038f50bb](https://github.com/facebook/react-native/commit/26038f50bb003eec3770e2b66d428fbb739f1ce2) by + [@wli](https://github.com/wli)) + +### Changed + +- Metro-bundler is now metro, and it's v0.24.1; there were some performance + increases at the cost of a few breaking changes; improved tests of the bundler + too ([0bbd9f042a](https://github.com/facebook/react-native/commit/0bbd9f042a8ad7c7be094bd7636ca767c6f7b231), + [a2fd3fcef8](https://github.com/facebook/react-native/commit/a2fd3fcef84e916b36ef7753dff69b7c1b307890), + [503b4521a6](https://github.com/facebook/react-native/commit/503b4521a6ce6bb2282631df616440fa71416d25), + [654fed46f4](https://github.com/facebook/react-native/commit/654fed46f49b5002096ff55c2e8523af48b22c24), + [0091496891](https://github.com/facebook/react-native/commit/009149689119e180415f8138b2827366768fc1d3), + [aba148f733](https://github.com/facebook/react-native/commit/aba148f733e85a88d4be07b3b8ca37c43cf6a51e), + [3d5dc872a4](https://github.com/facebook/react-native/commit/3d5dc872a4eefa1557554b3b338227959d166370), + [48019a0c2a](https://github.com/facebook/react-native/commit/48019a0c2a9a45d51a4faab1d5bef52949f4b5c5), + [ecec4319c4](https://github.com/facebook/react-native/commit/ecec4319c4fda9bebc90216d5340442b2a5725df), + [f4d627c8fa](https://github.com/facebook/react-native/commit/f4d627c8faeb034eac8053fd253f17e42b85f2f2), + [f871d25eb4](https://github.com/facebook/react-native/commit/f871d25eb48dca224bb3796aa9cb5d714292662f), + [a7b231a327](https://github.com/facebook/react-native/commit/a7b231a3278e4fc2d7e4269ce106f22712f2e5a8), + [830b431453](https://github.com/facebook/react-native/commit/830b43145381e6e322569450affddba73f7dc2d1), + [29dafa1a86](https://github.com/facebook/react-native/commit/29dafa1a8644f7a537499074df334bab8da4ad00), + [7a5d5a4035](https://github.com/facebook/react-native/commit/7a5d5a40357bedfb24a01ff1160481656fda9554), + [4cd685a1e0](https://github.com/facebook/react-native/commit/4cd685a1e0a2ae07df02702dbf4702e407773df5), + [d326c86051](https://github.com/facebook/react-native/commit/d326c860519c5165c6e86fb4f3ce378ed055157c), + [231c7a0304](https://github.com/facebook/react-native/commit/231c7a03043b9fb3c4bf81251ad099bab0ba05c2), + [7d969a05de](https://github.com/facebook/react-native/commit/7d969a05de6a45543bc31a3b982828865bc57cdb), + [ae517307e7](https://github.com/facebook/react-native/commit/ae517307e76d977f813e5b880f3b7f42a20f461d), + [f587f8d51d](https://github.com/facebook/react-native/commit/f587f8d51dc57e6b9eb66edfbe58ee520a6cc568), + [fbf0aed3ac](https://github.com/facebook/react-native/commit/fbf0aed3acc056b5fd069af75fcae14246439a48), + [e9393f694d](https://github.com/facebook/react-native/commit/e9393f694d8f0d0190a3576fd65a65f747f8cce2), and + [968c88d141](https://github.com/facebook/react-native/commit/968c88d1410eac5b793345bacce6052f518fda8f) by + [@cpojer](https://github.com/cpojer), [@hramos](https://github.com/hramos), + [@jeanlauliac](https://github.com/jeanlauliac), and + [@rafeca](https://github.com/rafeca) + ) +- React is now v16.2.0, and it took react-test-renderer along with it; [now with + more + fragments!](https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html) + 🎉 ([c7f37074ac](https://github.com/facebook/react-native/commit/c7f37074ac89f7e568ca26a6bad3bdb02812c39f) and + [cd938d731c](https://github.com/facebook/react-native/commit/cd938d731c7531a683c050cd829a543d145e3dc1) by + [@bvaughn](https://github.com/bvaughn)) +- Jest is now v21.3.0-beta.13 + ([16bbd908e7](https://github.com/facebook/react-native/commit/16bbd908e72577e7d109397916323a0eeffce8d4) and + [ec2ea58e57](https://github.com/facebook/react-native/commit/ec2ea58e57872bfa077d9c9a5e1e8b253c6b37b3) by + [@mjesun](https://github.com/mjesun)) +- Flow is now v0.61.0, and there were a ton of Flow fixes/coverage improvements + made ([914ae93336](https://github.com/facebook/react-native/commit/914ae9333678df4888e3c72898991c8430625cce), + [eb0d6470e5](https://github.com/facebook/react-native/commit/eb0d6470e54663538610a70ab0bae9847eb33673), + [c8e72bb8b8](https://github.com/facebook/react-native/commit/c8e72bb8b8317bcdcb4fe2ff85978c7db70f4461), + [2d4bedba0f](https://github.com/facebook/react-native/commit/2d4bedba0f6a8f2cfe597a1044822eb635d5e243), + [e0202e459f](https://github.com/facebook/react-native/commit/e0202e459fd0181db551d0025ef562d7998186b0), + [2be3ae1ff2](https://github.com/facebook/react-native/commit/2be3ae1ff2441c0ee3f2b9255c23dc49ada852fe), + [22a1419900](https://github.com/facebook/react-native/commit/22a14199000ea994f24f6fe387ea26647af3c128), + [6ae0b344e5](https://github.com/facebook/react-native/commit/6ae0b344e5c221657287d1fc1511be520a6f6e58), + [76a2ca4c9c](https://github.com/facebook/react-native/commit/76a2ca4c9c09c9bdf922154c28040138a44ae672), + [3259353fce](https://github.com/facebook/react-native/commit/3259353fcec0dd9ea66de750a694c226f99f483d), + [e6c1fb7212](https://github.com/facebook/react-native/commit/e6c1fb72128fb13436028c2df9cdccf6ccfccb67), + [61d046be3c](https://github.com/facebook/react-native/commit/61d046be3c9b00f6a4d4f492d558a961a6d4210f), + [820cfa1f3b](https://github.com/facebook/react-native/commit/820cfa1f3b79406e47cb873773cadafefe0effb1), + [240039c6f2](https://github.com/facebook/react-native/commit/240039c6f2d8db700ebc15404b0acc2a49068249), + [343c5a97a0](https://github.com/facebook/react-native/commit/343c5a97a013669745cf3938728539001d3076e6), + [5f8d8e90c2](https://github.com/facebook/react-native/commit/5f8d8e90c2c43127b8a5d2fc5d18f16185c7a67e), and + [da047966e4](https://github.com/facebook/react-native/commit/da047966e4c2064a48e02ff74830c99808d8194b) by + [@Ashoat](https://github.com/Ashoat), + [@calebmer](https://github.com/calebmer), + [@cdlewis](https://github.com/cdlewis), + [@deecewan](https://github.com/deecewan), + [@grabbou](https://github.com/grabbou), + [@jamesisaac](https://github.com/jamesisaac), + [@mroch](https://github.com/mroch), [@nmn](https://github.com/nmn), + [@nmote](https://github.com/nmote), [@sahrens](https://github.com/sahrens), + [@samwgoldman](https://github.com/samwgoldman), + [@TheSavior](https://github.com/TheSavior), and others) +- react-devtools-core is now v3.0.0 + ([a7d46ea970](https://github.com/facebook/react-native/commit/a7d46ea97012bdcc644e3397bbf60bd3cb37e9eb) by + [@rsnara](https://github.com/rsnara)) +- Split out docs to [their own + repo](https://github.com/facebook/react-native-website/tree/master/docs) (and + a few formatting fixes along the journey) 👋 + ([2d86618e7e](https://github.com/facebook/react-native/commit/2d86618e7ef477cdfc2ed510c7bc05d41dbfe972), + [64d80b13db](https://github.com/facebook/react-native/commit/64d80b13db3dd28bb5d93cbedf511ae8f91e2127), + [3362da421c](https://github.com/facebook/react-native/commit/3362da421ce919fc6f4f1bda29b59e2e3bfe02de), + [75123c614b](https://github.com/facebook/react-native/commit/75123c614bb5a61c383cdc247230b3a76c76e14d), and + [79e24ede40](https://github.com/facebook/react-native/commit/79e24ede40b2508aaa77b8ff3876d3dbf4cfe6d8) by + [@hramos](https://github.com/hramos)). +- **TouchableHighlight** now has a default delayPressOut value of 100; it was + also refactored a bit for style + ([9a31fa5fd6](https://github.com/facebook/react-native/commit/9a31fa5fd62fa101b2a76c69b56248d7f5ba9876) by + [@sahrens](https://github.com/sahrens)) +- When in a dev build, more robustly validate arguments for native methods + ([ea2e2c54cb](https://github.com/facebook/react-native/commit/ea2e2c54cb4d1a99b41aaa98eaf51696d34770dd) by + [@mhorowitz](https://github.com/mhorowitz)) +- On integration tests, report _all_ errors + ([3bcb912786](https://github.com/facebook/react-native/commit/3bcb9127866ef60b3697553e98a3ae279d02290a) by + [@sahrens](https://github.com/sahrens)) +- Yoga has less technical debt, thanks to replacing _YGNodeList_ with vectors + ([b08a912f11](https://github.com/facebook/react-native/commit/b08a912f11c729f3fe76700c6614f9e6165ae1a1) by + [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Yoga is now cpp, compiled as _c++1y_ + ([d7ab9496bc](https://github.com/facebook/react-native/commit/d7ab9496bc95f7b720fd6db1ed503af1c461e84d) by + [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar)) +- Bundle segments are handled better and used more + ([681278947e](https://github.com/facebook/react-native/commit/681278947eb4039a1d7a65f1edfeef25ae055a4f), + [a47431ed74](https://github.com/facebook/react-native/commit/a47431ed74f0b7b2a03ca48e84f2243d08ef3bdd), + [963c61d4d5](https://github.com/facebook/react-native/commit/963c61d4d546c94b689281ca1f5105ad050e10ff), + [b9f21dc2be](https://github.com/facebook/react-native/commit/b9f21dc2be14cd51543e6b2d1e63a861e5f433d1), + [f1258181ee](https://github.com/facebook/react-native/commit/f1258181eec84f73651d2fabd0d23764b8990ff8), and + [1988ba1d79](https://github.com/facebook/react-native/commit/1988ba1d7967dca04376e7e631e8910e5e79a6a7) by + [@fromcelticpark](https://github.com/fromcelticpark) and + [@jeanlauliac](https://github.com/jeanlauliac)) +- _packager-worker-for-buck_ has better tests + ([7fd5aa84a1](https://github.com/facebook/react-native/commit/7fd5aa84a1ef1744d01e7e877183b1f004216d00) by + [@jeanlauliac](https://github.com/jeanlauliac)) +- _RCTUIManager_ has less technical debt + ([46be5bf71c](https://github.com/facebook/react-native/commit/46be5bf71c78d33cda5cb496c475dcfb8e229346), + [60dc9bed00](https://github.com/facebook/react-native/commit/60dc9bed00cc13652752bf84f83c920ce66d5e39), and + [21714fe197](https://github.com/facebook/react-native/commit/21714fe1979ccbd62d665f383625f4ece8cf888e) by + [@shergin](https://github.com/shergin)) +- Numerous bridge changes, especially around URL resolution + ([e7bd0f056b](https://github.com/facebook/react-native/commit/e7bd0f056bf4edca1f0529d6eed03bbaaaca586a), + [260e6d2355](https://github.com/facebook/react-native/commit/260e6d23554a8e7f1743263894c9ca9a0cfbf01e), + [4894ac430d](https://github.com/facebook/react-native/commit/4894ac430d6df1118ce48f644fd8cf5bfce6344f), + [b983de9c54](https://github.com/facebook/react-native/commit/b983de9c5460e24c95a9a67f02695cd1c5f31bc5), + [b0193b098c](https://github.com/facebook/react-native/commit/b0193b098cdbd915bba90e1ab0b695ba44346f44), + [ae5ef653cb](https://github.com/facebook/react-native/commit/ae5ef653cbc4c03fe5edb5d4b0002e38cbb6f458), and + [1d6ce2311f](https://github.com/facebook/react-native/commit/1d6ce2311f6a51821b33c5473414d70c8bd34425) by + [@fromcelticpark](https://github.com/fromcelticpark) and others) +- Various cleanup and refactoring + ([053776338e](https://github.com/facebook/react-native/commit/053776338ea44c31f3671cb4502853da0c88e55a), + [0984f29a32](https://github.com/facebook/react-native/commit/0984f29a320ce7e40e8bc2a6c78b080908fa1384), + [6c70975689](https://github.com/facebook/react-native/commit/6c70975689d0f0839e6c2db9a9a25c3023f5be7b), + [d950dc6a21](https://github.com/facebook/react-native/commit/d950dc6a21c5cc1e736993b2ecc16abae086389e), + [70c359000a](https://github.com/facebook/react-native/commit/70c359000a2df091c3939f4c19db6024af992d43), + [cfa2bbf2f6](https://github.com/facebook/react-native/commit/cfa2bbf2f692d0bc5600d7e369a9a91272930ca6), and + [850efa8650](https://github.com/facebook/react-native/commit/850efa86508b19d800ff8cbdc725402c006db1a2) by + [@bnham](https://github.com/bnham), + [@priteshrnandgaonkar](https://github.com/priteshrnandgaonkar), and others) +- Jest preprocessing now uses the AST from metro + ([2ae255a6ea](https://github.com/facebook/react-native/commit/2ae255a6eaf820992bdf19799bb4403f3bbdcd5b) and + [d5b59517c2](https://github.com/facebook/react-native/commit/d5b59517c274874d7ce21e5c26d28b42ae389723) by + [@rafeca](https://github.com/rafeca)) +- `renderApplication()` now supports async initial render + ([1b22d49ae8](https://github.com/facebook/react-native/commit/1b22d49ae8945680dee4fd01e3fbb78b1e443e01) by + [@bvaughn](https://github.com/bvaughn)) +- Welcome [@lwinkyawmyat](https://github.com/lwinkyawmyat) to the React Native + GitHub Issue Task Force + ([4ebe76d559](https://github.com/facebook/react-native/commit/4ebe76d5598621160ffcf3ea8bc87c3ad1c1a2f8) by + [@lwinkyawmyat](https://github.com/lwinkyawmyat)) + +#### Android exclusive changes + +- Native components on Android register lazily rather than via ViewManager + ([1b71e03932](https://github.com/facebook/react-native/commit/1b71e03932f44e212b297b2c1e02100b6de74b93)) +- Android debug overlays (like **RedBox**, dev menu, loading) are no longer are + system overlays; they're now part of the _currentActivity_ + ([d19afc73f5](https://github.com/facebook/react-native/commit/d19afc73f5048f81656d0b4424232ce6d69a6368) by + [@kmagiera](https://github.com/kmagiera)) + +#### iOS exclusive changes + +- Improve iOS's _accessibilityLabel_ performance by up to 20% 📈 + ([19b0a65c5e](https://github.com/facebook/react-native/commit/19b0a65c5ecc4f41fea98a1e752785d6dbb6ea05) by + [@chendo](https://github.com/chendo)) + +### Fixed + +- Fix `backgroundColor` on **TouchableHighlight** + ([5a1171ebfa](https://github.com/facebook/react-native/commit/5a1171ebfaaedd9c7d5f1bfcf306049c3671a733) by + [@sahrens](https://github.com/sahrens)) +- Various corrections in messages, comments, and docblocks + ([58c3bc4901](https://github.com/facebook/react-native/commit/58c3bc490143b8d7831a00289e2565f49f5389ef), + [354e1cb508](https://github.com/facebook/react-native/commit/354e1cb5088a43fd4116504a34a65ca53c4de71b), + [58edf024a1](https://github.com/facebook/react-native/commit/58edf024a1ed3a71ef04f124546ee97496b6502f), + [b9e7006cc6](https://github.com/facebook/react-native/commit/b9e7006cc6dc2b0801ea0c776ba00cdea2204151), + [d2f0abdf4e](https://github.com/facebook/react-native/commit/d2f0abdf4ea94fbb3e2a5c7fb53ff5d1cf6abede), + [94cd9f5591](https://github.com/facebook/react-native/commit/94cd9f55915973355cdb63276b71f90df10281db), + [8547b7e111](https://github.com/facebook/react-native/commit/8547b7e11163d545b7b99d4bdd063ef71129d62c), + [44c16499fd](https://github.com/facebook/react-native/commit/44c16499fdc4665298f6c88b9ffee626fa1fc969), + [c91d87213e](https://github.com/facebook/react-native/commit/c91d87213e6862019b9ef7df7c38551bd6d659fd), + [85503a0612](https://github.com/facebook/react-native/commit/85503a0612b0c74b4d204e8748e9ed7010d838e4), and + [5b83dbe25a](https://github.com/facebook/react-native/commit/5b83dbe25af151d183009006b1fe323b2658d025) by + [@behrends](https://github.com/behrends), + [@bvaughn](https://github.com/bvaughn), + [@charpeni](https://github.com/charpeni), + [@dsandmark](https://github.com/dsandmark), + [@gusgard](https://github.com/gusgard), + [@nkabrown](https://github.com/nkabrown), + [@petterh](https://github.com/petterh), [@solon](https://github.com/solon), + [@swashcap](https://github.com/swashcap), and others) +- Various dev doc and project doc fixes for correctness and completeness + ([92c0980540](https://github.com/facebook/react-native/commit/92c0980540dde0053bad05fae6414cf8275a71b1), + [3c9092acf3](https://github.com/facebook/react-native/commit/3c9092acf39ecdb7c137a3cb0d4282694e95cbf5), + [e906525e84](https://github.com/facebook/react-native/commit/e906525e84f69a98de4d06ed1ec4c43d8589e350), + [60828566a7](https://github.com/facebook/react-native/commit/60828566a759dc579dbae1d76a8426e1e479166e), + [c49b97c4ef](https://github.com/facebook/react-native/commit/c49b97c4ef65a6351af437ef28033cb31ea0446f), + [45ed142596](https://github.com/facebook/react-native/commit/45ed14259677cff4cbd133e705ec4f0ec84bc216), + [cb6ec7c321](https://github.com/facebook/react-native/commit/cb6ec7c32141ef5bdde837d7f9d71b7adb83b751), + [9ec9567390](https://github.com/facebook/react-native/commit/9ec95673909beac7798f589e0e9821b4225f8fa9), + [e5a4ea97d9](https://github.com/facebook/react-native/commit/e5a4ea97d9e1b13509a3f36d0b469a6a88a90dc4), + [c544c0d2dc](https://github.com/facebook/react-native/commit/c544c0d2dca1d0e9f0b2a5565e03676ad71a36f5), + [33d5e5bd5a](https://github.com/facebook/react-native/commit/33d5e5bd5a5365ab712a2b9d33f33cbec305e128), + [95dac8db60](https://github.com/facebook/react-native/commit/95dac8db601ba48fe03da52e1adbdef0cac23546), + [6e1db1f1ee](https://github.com/facebook/react-native/commit/6e1db1f1ee263c3a8b68c3e1584e79ae86d5be86), + [e11d496e9d](https://github.com/facebook/react-native/commit/e11d496e9d907abb5bf58a8300c5a8f85aa03bbb), + [6da897945f](https://github.com/facebook/react-native/commit/6da897945f823728dbc82dd9f01c672354b1e76d), + [0ff576081b](https://github.com/facebook/react-native/commit/0ff576081b156ea26c4e7886f7266f3e4d8e3d5e), + [1ee64ccb8a](https://github.com/facebook/react-native/commit/1ee64ccb8a257210be3a74fb9b0adc83f2a8bb2b), + [3aa38564f7](https://github.com/facebook/react-native/commit/3aa38564f7b91c8588c8484140bc4221d50d55e0), + [6b26971a56](https://github.com/facebook/react-native/commit/6b26971a56fdd919d11cc338893d0b7a3f7a45ba), and + [de3976a486](https://github.com/facebook/react-native/commit/de3976a48655a248a2417fcf2d3a511be02e1996) by + [@adrianomelo](https://github.com/adrianomelo), + [@blargity](https://github.com/blargity), + [@charpeni](https://github.com/charpeni), + [@garlic-rice-with-butter](https://github.com/garlic-rice-with-butter), + [@gwmccull](https://github.com/gwmccull), + [@harikrishnanp](https://github.com/harikrishnanp), + [@hramos](https://github.com/hramos), + [@johnthewilson](https://github.com/johnthewilson), + [@jsdario](https://github.com/jsdario), [@kelset](https://github.com/kelset), + [@patrickkempff](https://github.com/patrickkempff), + [@ryanml](https://github.com/ryanml), + [@tiagomoraismorgado88](https://github.com/tiagomoraismorgado88), + [@timwangdev](https://github.com/timwangdev), and others) +- Stop `RCTRefreshControl` from jumping around + ([2e1707d0e6](https://github.com/facebook/react-native/commit/2e1707d0e600a30057511390dd87c18c00f19a59) by + [@sophiebits](https://github.com/sophiebits)) +- Fix a race condition in the animation module + ([515eb0e801](https://github.com/facebook/react-native/commit/515eb0e8012a7a8f085a8e410c6c694011fd8c1d) by + [@mhorowitz](https://github.com/mhorowitz)) +- Fix Windows local-cli's to not wrongfully identify as globally installed + ([ca106043fc](https://github.com/facebook/react-native/commit/ca106043fc655a1c51332aedf9b001a512269550) by + [@sballew](https://github.com/sballew)) +- Fix Jest mocks for **NetInfo**, **Dimensions**, and **ScrollView** modules + ([7fb3a9229d](https://github.com/facebook/react-native/commit/7fb3a9229df52bd45076470d059f245a8147cd2a), + [11a2a35c63](https://github.com/facebook/react-native/commit/11a2a35c63ae68de46482f5cd25271f8b0fb5ad4), and + [0c8a3e4f79](https://github.com/facebook/react-native/commit/0c8a3e4f797563c99e988ec2f42ec2a618a8b196) by + [@alvaromb](https://github.com/alvaromb), + [@timwangdev](https://github.com/timwangdev), and + [@uk-ar](https://github.com/uk-ar)) +- packager-worker-for-buck: `transformCommand`: add missing test + ([73a01be9bc](https://github.com/facebook/react-native/commit/73a01be9bcd9059f3172987fd30d8b6dc0125759) by + [@jeanlauliac](https://github.com/jeanlauliac)) +- Fixed issue where CLI wasn't following the config value for postMinifyProcess + when its running with `dev=false` + ([6d92046c56](https://github.com/facebook/react-native/commit/6d92046c56794a6a62bc07598545a23a7b53cdc0) by + [@rafeca](https://github.com/rafeca)) +- Fix asset resolver url handling + ([28d5d6baf1](https://github.com/facebook/react-native/commit/28d5d6baf1e6ac52e8672a653f56c3898e4e11d2) by + [@fkgozali](https://github.com/fkgozali)) +- Fix crash when destroying catalyst + ([f1015664e9](https://github.com/facebook/react-native/commit/f1015664e92f02c33417a591a2438db7c0cd3811)) +- You can now `justifyContent` while you're `minWidth`ing and `marginLeft`ing; + before the justification wasn't honored + ([f5becebc07](https://github.com/facebook/react-native/commit/f5becebc0710d5bb875bb9c0a2d3809a00f62605) by + [@woehrl01](https://github.com/woehrl01)) +- `marginLeft: auto` and `alignItem: stretch` now play nicely together; before + the width and height ended up incorrect + ([5f99b1a55f](https://github.com/facebook/react-native/commit/5f99b1a55f4002c105a7005cabf720aad422b628) by + [@woehrl01](https://github.com/woehrl01)) +- Fix assertion preventing YGNodeLayoutGet\* with YGEdgeEnd + ([a383b8ca05](https://github.com/facebook/react-native/commit/a383b8ca0545ba3704a51a78972107119f5683c0) by + [@justjake](https://github.com/justjake)) +- Fix shrinking in non-strech alignments + ([1d62848535](https://github.com/facebook/react-native/commit/1d6284853514be4da2b68d45732386eb81cc4253) by + [@woehrl01](https://github.com/woehrl01)) +- Correctly calculate min/max percentage constraints + ([4fdaf2de98](https://github.com/facebook/react-native/commit/4fdaf2de989c039a62681cc1f7a8407ec32b593e) by + [@woehrl01](https://github.com/woehrl01)) +- When running `react-native-git-upgrade`, don't execute git's hooks + ([0182086350](https://github.com/facebook/react-native/commit/018208635069311c1a7c7776c6f359f7ded45362) by + [@adrienthiery](https://github.com/adrienthiery)) +- When running `react-native-git-upgrade` and failing with a signal, return that + to the terminal + ([b9a5862f67](https://github.com/facebook/react-native/commit/b9a5862f670f52d48f1d3789c3f08ec139368da4) by + [@mateusz-](https://github.com/mateusz-)) +- In **KeyboardAvoidingView**, don't mistakenly try to layout when a hardware + keyboard changes + ([ad4450ac13](https://github.com/facebook/react-native/commit/ad4450ac1364710f052a927ceda7ae353440f682) by + [@koenpunt](https://github.com/koenpunt)) +- Don't endlessly collect websockets when not connected to the packager (dev + memory leak) + ([1e1e491246](https://github.com/facebook/react-native/commit/1e1e49124678f447d980bb22891d25db60fa83b3) by + [@mmmulani](https://github.com/mmmulani)) +- Fixed a bug in the sample project random `selection` prop that made it + not-so-random + ([766f020e68](https://github.com/facebook/react-native/commit/766f020e68abfc121ea6a9f92e0640368d69dae7) by + [@rozele](https://github.com/rozele)) + +#### Android exclusive fixes + +- Explicitly `#define isnan __builtin_isnan` for Android _clang-5_ to mimic + **gcc**'s default behavior + ([f8fe6b0c70](https://github.com/facebook/react-native/commit/f8fe6b0c70d1b7b626d05d9675c16b2f89339e8c)) +- Correctly update **NetInfo** on Android even if connection types change while + the app is in the background + ([e6f542d620](https://github.com/facebook/react-native/commit/e6f542d62037e9830c0ae5749a32874c44cf2334) by + [@berickson1](https://github.com/berickson1)) +- Direction-aware borders now work with Android APIs >= 17 + ([7170543e80](https://github.com/facebook/react-native/commit/7170543e8012250b7643a960b54cce7fd6d3a1e9) by + [@rsnara](https://github.com/rsnara)) +- Don't throw _BadTokenException_ and _IllegalArgmentException_ when showing or + dismissing Modal on Android + ([e57a43b97a](https://github.com/facebook/react-native/commit/e57a43b97ad24dc5b993753a45aa575b2a757b4f)) +- Fix Android crash when blurRadius is between 0 and 1 + ([dc01eff72d](https://github.com/facebook/react-native/commit/dc01eff72d23e1dd3f7ecf30859992ee3bf7c664) by + [@jamesreggio](https://github.com/jamesreggio)) +- Fix `borderRadius` with Android API level < 18 + ([5aa1fb3ff3](https://github.com/facebook/react-native/commit/5aa1fb3ff326a429e33a443576da866f2a63c20c) and + [ca7fe72c31](https://github.com/facebook/react-native/commit/ca7fe72c31fd7c7cbe4734118019f5808235560e) by + [@rsnara](https://github.com/rsnara)) +- Make Android `lineHeight` behavior match iOS's 📏 + ([3f1b021506](https://github.com/facebook/react-native/commit/3f1b0215060e4c27c286359cc90f3b2189956c4e)) +- Fixed autoscroll to cursor on Android **TextInput** + ([0bef872f3f](https://github.com/facebook/react-native/commit/0bef872f3fc8b1cd78c574d03eacc886bef4e239) by + [@shergin](https://github.com/shergin)) +- Fix logging unpacking time on Android when it happens concurrently with eager + unpacking ([028b64bcd3](https://github.com/facebook/react-native/commit/028b64bcd36c1c8dd76c0de95eeff80cf660aa23) + by [@alexeylang](https://github.com/alexeylang)) +- Prevent an Android crash when **TextInput** has `selectionColor` defined but + there is no drawable cursor + ([1e18d907bf](https://github.com/facebook/react-native/commit/1e18d907bfb8cc5f4f2e1a1ede0dd98aec40ab11) by + [@gpeal](https://github.com/gpeal)) + +#### iOS exclusive fixes + +- Don't have Xcode warnings for _YGDefaultLog_ in newly created projects + ([72e762d4bc](https://github.com/facebook/react-native/commit/72e762d4bca8d00cc2c73c390a654ae6143731bd) by + [@woehrl01](https://github.com/woehrl01)) +- iOS _RCTEventEmitter_ uses a `double` for count, not _NSInteger_ + ([eaa84997ce](https://github.com/facebook/react-native/commit/eaa84997cedc8dc4d46308e2217d2b094a51ed02)) +- Fix `isNuclideDebuggingAvailable` on iOS + ([59c3e33f63](https://github.com/facebook/react-native/commit/59c3e33f637d11e33204e8a912e98459ffad7fab)) +- iOS ScrollView is now rendered correctly with RefreshControl + ([75d62bf0a8](https://github.com/facebook/react-native/commit/75d62bf0a802b91a979d03ef497e84c3179e7767) by + [@vonovak](https://github.com/vonovak)) +- Fix a crash when keyboard is visible and bridge reload happens on iOS + ([d9c658566a](https://github.com/facebook/react-native/commit/d9c658566a14ce8767d87435264997aa18dd08e4) by + [@fromcelticpark](https://github.com/fromcelticpark)) +- **RedBox** now appears beneath the status bar on iOS + ([33cefc1760](https://github.com/facebook/react-native/commit/33cefc176096e03a4b3c3130a70abfabe9d40f38) by + [@adamjernst](https://github.com/adamjernst)) +- Fractional border widths on iOS are now the right size, honoring insets + ([15179f1798](https://github.com/facebook/react-native/commit/15179f1798b277c1836441fcf7f3b7f0bd5a4636) by + [@Nikita2k](https://github.com/Nikita2k)) +- Implement `requiresMainQueueSetup` in _RCTTVNavigationEventEmitter_ to satisfy + Xcode warning + ([ee3532b5c2](https://github.com/facebook/react-native/commit/ee3532b5c266d5ee7fb12920cb611a41b1daf750) by + [@charpeni](https://github.com/charpeni)) +- Support the iPhone X in the sample project's header + ([ad4b124aa1](https://github.com/facebook/react-native/commit/ad4b124aa117483b4a0ec9dfa145b8e9a17f06c6) by + [@vincentriemer](https://github.com/vincentriemer)) +- `testID` works on **TabBarItem** on iOS + ([e19d9dec9b](https://github.com/facebook/react-native/commit/e19d9dec9b3b257b5db3dc77ed8b95b93570f1e3)) +- On iOS, don't error on the first live-reload of large codebases because of too + little wait time + ([b6f1a6085f](https://github.com/facebook/react-native/commit/b6f1a6085f7470c16ae8850e7da8f4f9ae5c23ee) by + [@lelandrichardson](https://github.com/lelandrichardson)) +- Prevent iOS crash on receiving bad unicode in _XMLHTTPRequest_ + ([1c04ceeb4b](https://github.com/facebook/react-native/commit/1c04ceeb4ba954eee7ab34fc5b6c660d9772d9f6) by + [@cdlewis](https://github.com/cdlewis)) +- Define `pod_target_xcconfig` for PrivateDatabase + ([38b96cd7bb](https://github.com/facebook/react-native/commit/38b96cd7bb391f64066a6c91daa4173db1f33445) by + [@ide](https://github.com/ide)) +- Fixed podspec include/excludes around tvOS + ([ba1d7e92e4](https://github.com/facebook/react-native/commit/ba1d7e92e4f251b90a96be192214b5015cf6244e) by + [@yygene](https://github.com/yygene)) +- Don't spam the logs for iOS when receiving `ECONNREFUSED` on connecting to + packager ([b1701ccaef](https://github.com/facebook/react-native/commit/b1701ccaefa0c8cbb6d820b2ad07e0d911027d7c) + and [ff3dc2ed19](https://github.com/facebook/react-native/commit/ff3dc2ed19cdd4137ae8092599b16c09d4e2c711) by + [@adamjernst](https://github.com/adamjernst)) +- Don't crash Systrace when debugging JS remotely on iOS + ([e8eec24706](https://github.com/facebook/react-native/commit/e8eec24706e792314ee574bbf7f7c0066c4f3a7a) by + [@alexeylang](https://github.com/alexeylang)) + +### Removed + +- Removing `reactBridgeDidFinishTransaction` from _RCTScrollView_ + ([a255204e3e](https://github.com/facebook/react-native/commit/a255204e3e7fddefd2d7b0de224101768757ca7a) by + [@shergin](https://github.com/shergin)) +- Removing inherited background color optimization from _RCTText_ to reduce code + complexity – please give feedback if you find performance differences! + ([8c8944c10f](https://github.com/facebook/react-native/commit/8c8944c10fb7dc30ea99657225f25ea438cf6e14) by + [@shergin](https://github.com/shergin)) + +### Other + +Below is a list of the remaining, low-level changes that made it into this +release of React Native. + +- Foundational work for a new justifyContent value **space-evenly** + ([1050e0b476](https://github.com/facebook/react-native/commit/1050e0b47611602b758f73d99f51a1dd5ceabade) by + [@woehrl01](https://github.com/woehrl01)) +- Add Systrace-based telemetry to Hermes GC + ([05e862d48d](https://github.com/facebook/react-native/commit/05e862d48d363a8af765b2f0283569419dbd2e5c)) +- Unify Systrace native hook argument passing + ([52e3ae9063](https://github.com/facebook/react-native/commit/52e3ae9063705bac53bad99ffe23976c29c8f1b2) by + [@amnn](https://github.com/amnn)) +- Use different symbols for SystraceSection depending on `WITH_FBYSTRACE` + ([03956c4ecf](https://github.com/facebook/react-native/commit/03956c4ecfda381396336f725ea1c12d913df17d)) +- Don't set global.performance to undefined if it was initialized already + ([dfebcb70a5](https://github.com/facebook/react-native/commit/dfebcb70a5c948db94d1cd580bbcaa0aaa702349) by + [@alexeylang](https://github.com/alexeylang)) +- Autofixes for migrating to Buck's source-only ABI feature + ([801cbdb047](https://github.com/facebook/react-native/commit/801cbdb04788403cee022dec688136540da36fc5) by + [@jkeljo](https://github.com/jkeljo)) +- Add remote API to uninstall the global error handler in RN + ([1d16923063](https://github.com/facebook/react-native/commit/1d16923063940606dda89de94a83cbdf5f98e1f1)) +- Add _RCTLibraryPathForURL_ in _RCTUtil_ + ([2fecbf6171](https://github.com/facebook/react-native/commit/2fecbf61711f610124fc2453a79120932024f613)) +- Fix sections that come from React Fiber + ([1f40c95076](https://github.com/facebook/react-native/commit/1f40c95076297258a4194fd9c1b5af7002187c99) by + [@alexeylang](https://github.com/alexeylang)) +- Fix boolean conversion in sync RN method calls. + ([dd888d3346](https://github.com/facebook/react-native/commit/dd888d3346ef9477eae2cd2d29cef867467cb503)) +- Fix `embeddedBundleURL` update situation + ([d1fc8ef3a3](https://github.com/facebook/react-native/commit/d1fc8ef3a3cb3590b9cff4d1b3cc5d440b52ec8c)) +- Remove `android_react_native_perf.use_separate_ui_bg_thread` experiment. + ([4f886a29a1](https://github.com/facebook/react-native/commit/4f886a29a1234c967deae2354bbc5092e0e6595e)) +- ScrollView related files were moved to dedicated folder + ([098a63a1ce](https://github.com/facebook/react-native/commit/098a63a1cee1196a2f3eb5135eeb8fe59e7e8272) by + [@shergin](https://github.com/shergin)) +- move page registration logic in to jsinspector + ([bef7967f9a](https://github.com/facebook/react-native/commit/bef7967f9a485dc136d2cb32f552b2199ae3e2b8) by + [@bnham](https://github.com/bnham)) +- Type global hooks as function pointers + ([eca51eb46a](https://github.com/facebook/react-native/commit/eca51eb46a47112c8933d0a3b932f59008cadc78) by + [@johnislarry](https://github.com/johnislarry)) +- `std::string` to `const char*` + ([b952365ba6](https://github.com/facebook/react-native/commit/b952365ba6bd86f0e80a24aedec1f447cb3ec566) by + [@johnislarry](https://github.com/johnislarry)) +- Allow extending props supported by native animations + ([71751e9cc7](https://github.com/facebook/react-native/commit/71751e9cc7c67306ca038c5b254e6e81fe0aff1b) by + [@andrewimm](https://github.com/andrewimm)) +- Meyers singleton jsc error extractor + ([434f432d5d](https://github.com/facebook/react-native/commit/434f432d5d5ea2756c1adac8b1c36e82e60a2b13) by + [@johnislarry](https://github.com/johnislarry)) +- Do not overwrite the same text in **TextInput** + ([29f3f55298](https://github.com/facebook/react-native/commit/29f3f5529827579101f0d8bd6afe72f1cb0caeca)) +- Renaming _uiManagerWillFlushUIBlocks_ -> _uiManagerWillPerformMounting_ + ([0a8721c340](https://github.com/facebook/react-native/commit/0a8721c340480a972bb597cacdbddd9eb2015716) by + [@shergin](https://github.com/shergin)) +- Skylarkify flags macros. + ([ed2bfcb35a](https://github.com/facebook/react-native/commit/ed2bfcb35a2756eb700882ab8e21b6b273efa80a) by + [@ttsugriy](https://github.com/ttsugriy)) +- Skylarkify `config_utils_defs` macros. + ([88f6f69152](https://github.com/facebook/react-native/commit/88f6f69152e4b68609f28e80ee70705969529af8) by + [@ttsugriy](https://github.com/ttsugriy)) +- Round size geometry for Button and RichText components. + ([4034febb7e](https://github.com/facebook/react-native/commit/4034febb7ef9d9daa894a75b038226af74026163) by + [@iaroslav-pavlov](https://github.com/iaroslav-pavlov) +- Temporarily patched Map/Set non-extensible check into RN dev renderer + ([a99f0d6100](https://github.com/facebook/react-native/commit/a99f0d6100c9779f5f6df6008af54c06113355f6) by + [@bvaughn](https://github.com/bvaughn)) +- Run buildifier over all BUCK files + ([d674d48a7b](https://github.com/facebook/react-native/commit/d674d48a7b9b71169af59ceb886529371c26a2e5) by + [@zertosh](https://github.com/zertosh)) +- Pass `scriptURL` to _RCTTestRunner_ + ([266ab7a006](https://github.com/facebook/react-native/commit/266ab7a0061c11c4da7ccde9e0d461c0d7331563)) +- Make _RCTNativeModule::invokeInner_ explicitely return `folly::none` in case + of error ([0ac5a5230c](https://github.com/facebook/react-native/commit/0ac5a5230c4b5dd44db6a8dd7bb7752aff64d71c) + by [@fromcelticpark](https://github.com/fromcelticpark)) +- Make _RCTPackagerConnection_ a singleton + ([9180d4eb82](https://github.com/facebook/react-native/commit/9180d4eb82fb70a0fd396b15660c2ac6770183c9) by + [@adamjernst](https://github.com/adamjernst)) +- Register split segment paths with _RAMBundleRegistry_ + ([cff0d8e0e5](https://github.com/facebook/react-native/commit/cff0d8e0e599d1ab21b36779b41fbb26512874aa) by + [@fromcelticpark](https://github.com/fromcelticpark)) +- check if listener is still in the set before calling `onHostResume` + ([ad89ea7b50](https://github.com/facebook/react-native/commit/ad89ea7b5046c2cf9ca1cba88c387eb1db8dc042)) +- export _SeparatorsObj_ type for re-use in **ListItem**s etc. + ([c6fe101cdc](https://github.com/facebook/react-native/commit/c6fe101cdcc0b8d640a86108d8a76f7292b5f799) by + [@sahrens](https://github.com/sahrens)) +- Do not mark node as dirty if, new and old values are undefined + ([41da6e3128](https://github.com/facebook/react-native/commit/41da6e31284d46bb1dd2053c3c3100c075ace019) by + [@woehrl01](https://github.com/woehrl01)) +- Remove _RAMBundleRegistry_ subclasses + ([6ecae73fe5](https://github.com/facebook/react-native/commit/6ecae73fe5915863c27ac7e407f5b151fd0c5fc3) by + [@fromcelticpark](https://github.com/fromcelticpark)) +- Fix `minimumViewTime` in _ViewabilityHelper_ + ([d19d137cc1](https://github.com/facebook/react-native/commit/d19d137cc18f10957b5ac64ac727d15fde57f018)) + +[0.56]: https://github.com/facebook/react-native/compare/0.55-stable...0.56-stable +[0.55]: https://github.com/facebook/react-native/compare/0.54-stable...0.55-stable +[0.54]: https://github.com/facebook/react-native/compare/0.53-stable...0.54-stable +[0.53]: https://github.com/facebook/react-native/compare/0.52-stable...0.53-stable +[0.52.0]: https://github.com/facebook/react-native/compare/0.51-stable...0.52-stable diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 062c7ca1df5c2d..3a44dc2513d1b9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,43 +1,77 @@ -# [Open Source Code of Conduct](https://code.facebook.com/codeofconduct) +# Code of Conduct -This code of conduct outlines our expectations for participants within the **Facebook Open Source** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. +## Our Pledge -Our open source community strives to: +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. -* **Be friendly and patient.** -* **Be welcoming:** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. -* **Be considerate:** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language. -* **Be respectful:** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. -* **Be careful in the words that you choose:** we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to: - * Violent threats or language directed against another person. - * Discriminatory jokes and language. - * Posting sexually explicit or violent material. - * Posting (or threatening to post) other people’s personally identifying information (“doxing”). - * Personal insults, especially those using racist or sexist terms. - * Unwelcome sexual attention. - * Advocating for, or encouraging, any of the above behavior. - * Repeated harassment of others. In general, if someone asks you to stop, then stop. -* **When we disagree, try to understand why:** Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. -* **Remember that we’re different.** The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. +## Our Standards -This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter. +Examples of behavior that contributes to creating a positive environment +include: -## Diversity Statement +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members -We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. +Examples of unacceptable behavior by participants include: -Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities. +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting -## Reporting Issues +## Our Responsibilities -If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via opensource@fb.com. All reports will be handled with discretion. In your report please include: +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. -* Your contact information. -* Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. -* Any additional information that may be helpful. +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. -After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse. +## Scope -Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning. +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, +available at https://www.contributor-covenant.org/version/2/1/code_of_conduct/ + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq -_This Code Of Conduct follows the [template](http://todogroup.org/opencodeofconduct/) established by the [TODO Group](http://todogroup.org/)._ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc151d02603d62..786600c1a62205 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,212 +1,7 @@ # Contributing to React Native - -React Native is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody using Facebook's mobile apps. If you're interested in contributing to React Native, hopefully this document makes the process for contributing clear. +Thank you for your interest in contributing to React Native! From commenting on and triaging issues, to reviewing and sending Pull Requests, all contributions are welcome. +We aim to build a vibrant and inclusive [ecosystem of partners, core contributors, and community](ECOSYSTEM.md) that goes beyond the main React Native GitHub repository. -## [Code of Conduct](https://code.facebook.com/codeofconduct) - -Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. - -## Get involved - -There are many ways to contribute to React Native, and many of them do not involve writing any code. Here's a few ideas to get started: - -* Simply start using React Native. Go through the [Getting Started](https://facebook.github.io/react-native/docs/getting-started.html) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](https://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues). -* Look through the [open issues](https://github.com/facebook/react-native/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](https://facebook.github.io/react-native/docs/contributing.html#triaging-issues-and-pull-requests). -* If you find an issue you would like to fix, [open a pull request](https://facebook.github.io/react-native/docs/contributing.html#your-first-pull-request). Issues tagged as [_Good first issue_](https://github.com/facebook/react-native/labels/Good%20first%20issue) are a good place to get started. -* Read through the [React Native docs](https://facebook.github.io/react-native/docs/getting-started.html). If you find anything that is confusing or can be improved, you can make edits by clicking the "EDIT" button in the top-right corner of most docs. -* Browse [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) and answer questions. This will help you get familiarized with common pitfalls or misunderstandings, which can be useful when contributing updates to the documentation. -* Take a look at the [features requested](https://react-native.canny.io/feature-requests) by others in the community and consider opening a pull request if you see something you want to work on. - -Contributions are very welcome. If you think you need help planning your contribution, please hop into [#react-native](https://discord.gg/0ZcbPKXt5bZjGY5n) and let people know you're looking for a mentor. - -Core contributors to React Native meet monthly and post their meeting notes on the [React Native blog](https://facebook.github.io/react-native/blog). You can also find ad hoc discussions in the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook group. - -### Triaging issues and pull requests - -One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in. - -* Ask for more information if the issue does not provide all the details required by the template. -* Suggest [labels](https://github.com/facebook/react-native/labels) that can help categorize issues. -* Flag issues that are stale or that should be closed. -* Ask for test plans and review code. - -You can learn more about handling issues in the [maintainer's guide](docs/maintainers.html#handling-issues). - -## Our development process - -Some of the core team will be working directly on [GitHub](https://github.com/facebook/react-native). These changes will be public from the beginning. Other changesets will come via a bridge with Facebook's internal source control. This is a necessity as it allows engineers at Facebook outside of the core team to move fast and contribute from an environment they are comfortable in. - -When a change made on GitHub is approved, it will first be imported into Facebook's internal source control. The change will eventually sync back to GitHub as a single commit once it has passed all internal tests. - -### Branch organization - -We will do our best to keep `master` in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to [communicate these changes](https://github.com/facebook/react-native/releases) and version appropriately so you can lock into a specific version if need be. - -To see what changes are coming and provide better feedback to React Native contributors, use the [latest release candidate](https://facebook.github.io/react-native/versions.html) when possible. By the time a release candidate is released, the changes it contains will have been shipped in production Facebook apps for over two weeks. - -## Bugs - -We use [GitHub Issues](https://github.com/facebook/react-native/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you a are certain this is a new, unreported bug, you can submit a [bug report](https://facebook.github.io/react-native/docs/contributing.html#reporting-new-issues). - -If you have questions about using React Native, the [Community page](https://facebook.github.io/react-native/help.html) list various resources that should help you get started. - -We also have a [place where you can request features or enhancements](https://react-native.canny.io/feature-requests). If you see anything you'd like to be implemented, vote it up and explain your use case. - -## Reporting new issues - -When [opening a new issue](https://github.com/facebook/react-native/issues/new), always make sure to fill out the [issue template](https://raw.githubusercontent.com/facebook/react-native/master/.github/ISSUE_TEMPLATE.md). **This step is very important!** Not doing so may result in your issue getting closed. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template. - -* **One issue, one bug:** Please report a single bug per issue. -* **Provide a Snack:** The best way to get attention on your issue is to provide a reduced test case. You can use [Snack](https://snack.expo.io/) to demonstrate the issue. -* **Provide reproduction steps:** List all the steps necessary to reproduce the issue. Provide a Snack or upload a sample project to GitHub. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort. -* **Try out the latest version:** Verify that the issue can be reproduced locally by updating your project to use [React Native from `master`](https://facebook.github.io/react-native/versions.html). The bug may have already been fixed! - -We're not able to provide support through GitHub Issues. If you're looking for help with your code, consider asking on [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native) or reaching out to the community through [other channels](https://facebook.github.io/react-native/support.html). - -### Security bugs - -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues; go through the process outlined on that page. - -## Pull requests - -### Your first pull request - -So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at. - -Working on your first Pull Request? You can learn how from this free video series: - -[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) - -We have a list of [beginner friendly issues](https://github.com/facebook/react-native/labels/Good%20first%20issue) to help you get your feet wet in the React Native codebase and familiar with our contribution process. This is a great place to get started. - -### Proposing a change - -If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, we have a [place to track feature requests](https://react-native.canny.io/feature-requests). - -If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend [filing an issue](https://github.com/facebook/react-native/issues/new?title=%5BProposal%5D) that includes `[Proposal]` in the title. This lets us reach an agreement on your proposal before you put significant effort into it. These types of issues should be rare. If you have been contributing to the project long enough, you will probably already have access to the [React Native Core Contributors](https://www.facebook.com/groups/reactnativeoss/) Facebook Group, where this sort of discussion is usually held. - -If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. - -### Sending a pull request - -Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. - -Please make sure the following is done when submitting a pull request: - -1. Fork [the repository](https://github.com/facebook/react-native) and create your branch from `master`. -2. Add the copyright notice to the top of any new files you've added. -3. Describe your [**test plan**](https://facebook.github.io/react-native/docs/contributing.html#test-plan) in your pull request description. Make sure to [test your changes](https://facebook.github.io/react-native/docs/testing.html)! -4. Make sure your code lints (`npm run lint`). -5. If you haven't already, [sign the CLA](https://code.facebook.com/cla). - -All pull requests should be opened against the `master` branch. After opening your pull request, ensure [**all tests pass**](https://facebook.github.io/react-native/docs/contributing.html#contrinuous-integration-tests) on Circle CI. If a test fails and you believe it is unrelated to your change, leave a comment on the pull request explaining why. - -> **Note:** It is not necessary to keep clicking `Merge master to your branch` on the PR page. You would want to merge master if there are conflicts or tests are failing. The Facebook-GitHub-Bot ultimately squashes all commits to a single one before merging your PR. - -#### Test plan - -A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI. - -* If you've added code that should be tested, add tests! -* If you've changed APIs, update the documentation. - -See [What is a Test Plan?](https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9) to learn more. - -#### Continuous integration tests - -Make sure all **tests pass** on [Circle CI][circle]. PRs that break tests are unlikely to be merged. Learn more about [testing your changes here](https://facebook.github.io/react-native/docs/testing.html). - -[circle]: https://circleci.com/gh/facebook/react-native - -#### Breaking changes - -When adding a new breaking change, follow this template in your pull request: - -``` -### New breaking change here - -* **Who does this affect**: -* **How to migrate**: -* **Why make this breaking change**: -* **Severity (number of people affected x effort)**: -``` - -If your pull request is merged, a core contributor will update the [list of breaking changes](https://github.com/facebook/react-native/wiki/Breaking-Changes) which is then used to populate the release notes. - -#### Copyright Notice for files - -Copy and paste this to the top of your new file(s): - -```JS -/** - * 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. - */ -``` - -If you've added a new module, add a `@providesModule ` at the end of the comment. This will allow the haste package manager to find it. - -#### Contributor License Agreement (CLA) - -In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla). - -### What happens next? - -The core team will be monitoring for pull requests. Read [what to expect from maintainers](https://facebook.github.io/react-native/docs/maintainers.html#handling-pull-requests) to understand what may happen after you open a pull request. - -## Style Guide - -Our linter will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`. - -However, there are still some styles that the linter cannot pick up. - -### Code Conventions - -#### General - -* **Most important: Look around.** Match the style you see used in the rest of the project. This includes formatting, naming things in code, naming things in documentation. -* Add trailing commas, -* 2 spaces for indentation (no tabs) -* "Attractive" - -#### JavaScript - -* Use semicolons; -* ES6 standards -* Prefer `'` over `"` -* Do not use the optional parameters of `setTimeout` and `setInterval` -* 80 character line length - -#### JSX - -* Prefer `"` over `'` for string literal props -* When wrapping opening tags over multiple lines, place one prop per line -* `{}` of props should hug their values (no spaces) -* Place the closing `>` of opening tags on the same line as the last prop -* Place the closing `/>` of self-closing tags on their own line and left-align them with the opening `<` - -#### Objective-C - -* Space after `@property` declarations -* Brackets on *every* `if`, on the *same* line -* `- method`, `@interface`, and `@implementation` brackets on the following line -* *Try* to keep it around 80 characters line length (sometimes it's just not possible...) -* `*` operator goes with the variable name (e.g. `NSObject *variableName;`) - -#### Java - -* If a method call spans multiple lines closing bracket is on the same line as the last argument. -* If a method header doesn't fit on one line each argument goes on a separate line. -* 100 character line length - -### Documentation - -* Do not wrap lines at 80 characters - configure your editor to soft-wrap when editing documentation. - -## License - -By contributing to React Native, you agree that your contributions will be licensed under its MIT license. - +To learn more about how to contribute check out the Contributing section on the React Native website: +* https://reactnative.dev/contributing/overview diff --git a/ContainerShip/Dockerfile.android b/ContainerShip/Dockerfile.android deleted file mode 100644 index adb361ab58f79c..00000000000000 --- a/ContainerShip/Dockerfile.android +++ /dev/null @@ -1,50 +0,0 @@ -FROM hramos/android-base:latest - -# set default environment variables -ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\"" -ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8" - -# add ReactAndroid directory -ADD .buckconfig /app/.buckconfig -ADD .buckjavaargs /app/.buckjavaargs -ADD ReactAndroid /app/ReactAndroid -ADD ReactCommon /app/ReactCommon -ADD keystores /app/keystores - -# set workdir -WORKDIR /app - -# run buck fetches -RUN buck fetch ReactAndroid/src/test/java/com/facebook/react/modules -RUN buck fetch ReactAndroid/src/main/java/com/facebook/react -RUN buck fetch ReactAndroid/src/main/java/com/facebook/react/shell -RUN buck fetch ReactAndroid/src/test/... -RUN buck fetch ReactAndroid/src/androidTest/... - -# build app -RUN buck build ReactAndroid/src/main/java/com/facebook/react -RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell - -ADD gradle /app/gradle -ADD gradlew /app/gradlew -ADD settings.gradle /app/settings.gradle -ADD build.gradle /app/build.gradle -ADD react.gradle /app/react.gradle - -# run gradle downloads -RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSCHeaders - -# compile native libs with Gradle script, we need bridge for unit and integration tests -RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1 -Pcom.android.build.threadPoolSize=1 - -# add all react-native code -ADD . /app -WORKDIR /app - -# https://github.com/npm/npm/issues/13306 -RUN cd $(npm root -g)/npm && npm install fs-extra && sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js - -# build node dependencies -RUN npm install - -WORKDIR /app diff --git a/ContainerShip/Dockerfile.android-base b/ContainerShip/Dockerfile.android-base deleted file mode 100644 index 05caa480f3c827..00000000000000 --- a/ContainerShip/Dockerfile.android-base +++ /dev/null @@ -1,77 +0,0 @@ -FROM library/ubuntu:16.04 - -# set default build arguments -ARG ANDROID_TOOLS_VERSION=25.2.5 -ARG BUCK_VERSION=v2017.11.16.01 -ARG NDK_VERSION=10e -ARG NODE_VERSION=6.2.0 -ARG WATCHMAN_VERSION=4.7.0 - -# set default environment variables -ENV ADB_INSTALL_TIMEOUT=10 -ENV PATH=${PATH}:/opt/buck/bin/ -ENV ANDROID_HOME=/opt/android -ENV ANDROID_SDK_HOME=${ANDROID_HOME} -ENV PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools -ENV ANDROID_NDK=/opt/ndk/android-ndk-r$NDK_VERSION -ENV PATH=${PATH}:${ANDROID_NDK} - -# install system dependencies -RUN apt-get update && apt-get install ant autoconf automake curl g++ gcc git libqt5widgets5 lib32z1 lib32stdc++6 make maven npm openjdk-8* python-dev python3-dev qml-module-qtquick-controls qtdeclarative5-dev unzip -y - -# configure npm -RUN npm config set spin=false -RUN npm config set progress=false - -# install node -RUN npm install n -g -RUN n $NODE_VERSION - -# download buck -RUN git clone https://github.com/facebook/buck.git /opt/buck --branch $BUCK_VERSION --depth=1 -WORKDIR /opt/buck - -# build buck -RUN ant - -# download watchman -RUN git clone https://github.com/facebook/watchman.git /opt/watchman -WORKDIR /opt/watchman -RUN git checkout v$WATCHMAN_VERSION - -# build watchman -RUN ./autogen.sh -RUN ./configure -RUN make -RUN make install - -# Full reference at https://dl.google.com/android/repository/repository2-1.xml -# download and unpack android -RUN mkdir /opt/android -WORKDIR /opt/android -RUN curl --silent https://dl.google.com/android/repository/tools_r$ANDROID_TOOLS_VERSION-linux.zip > android.zip -RUN unzip android.zip -RUN rm android.zip - -# download and unpack NDK -RUN mkdir /opt/ndk -WORKDIR /opt/ndk -RUN curl --silent https://dl.google.com/android/repository/android-ndk-r$NDK_VERSION-linux-x86_64.zip > ndk.zip -RUN unzip ndk.zip - -# cleanup NDK -RUN rm ndk.zip - -# Add android SDK tools -RUN echo "y" | sdkmanager "system-images;android-19;google_apis;armeabi-v7a" -RUN echo "y" | sdkmanager "platforms;android-23" -RUN echo "y" | sdkmanager "platforms;android-19" -RUN echo "y" | sdkmanager "build-tools;23.0.1" -RUN echo "y" | sdkmanager "add-ons;addon-google_apis-google-23" -RUN echo "y" | sdkmanager "extras;android;m2repository" - -# Link adb executable -RUN ln -s /opt/android/platform-tools/adb /usr/bin/adb - -# clean up unnecessary directories -RUN rm -rf /opt/android/system-images/android-19/default/x86 diff --git a/ContainerShip/Dockerfile.javascript b/ContainerShip/Dockerfile.javascript deleted file mode 100644 index 7c1e14bb9b845b..00000000000000 --- a/ContainerShip/Dockerfile.javascript +++ /dev/null @@ -1,19 +0,0 @@ -FROM library/node:6.9.2 - -ENV YARN_VERSION=0.27.5 - -# install dependencies -RUN apt-get update && apt-get install ocaml libelf-dev -y -RUN npm install yarn@$YARN_VERSION -g - -# add code -RUN mkdir /app -ADD . /app - -WORKDIR /app -RUN yarn install --ignore-engines - -WORKDIR website -RUN yarn install --ignore-engines --ignore-platform - -WORKDIR /app diff --git a/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh b/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh deleted file mode 100644 index c7806c7f055aae..00000000000000 --- a/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -# for buck gen -mount -o remount,exec /dev/shm - -AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1) - -# create virtual device -echo no | android create avd -n $AVD_UUID -f -t android-19 --abi default/armeabi-v7a - -# emulator setup -emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim & -bootanim="" -until [[ "$bootanim" =~ "stopped" ]]; do - sleep 5 - bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1) - echo "boot animation status=$bootanim" -done - -set -x - -# solve issue with max user watches limit -echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches -watchman shutdown-server - -# integration tests -# build JS bundle for instrumentation tests -node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js - -# build test APK -source ./scripts/circle-ci-android-setup.sh && NO_BUCKD=1 retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1 - -# run installed apk with tests -node ./ContainerShip/scripts/run-android-ci-instrumentation-tests.js $* -exit $? diff --git a/ContainerShip/scripts/run-android-docker-unit-tests.sh b/ContainerShip/scripts/run-android-docker-unit-tests.sh deleted file mode 100644 index 5a58bb352e5421..00000000000000 --- a/ContainerShip/scripts/run-android-docker-unit-tests.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# set default environment variables -UNIT_TESTS_BUILD_THREADS="${UNIT_TESTS_BUILD_THREADS:-1}" - -# for buck gen -mount -o remount,exec /dev/shm - -set -x - -# run unit tests -buck test ReactAndroid/src/test/... --config build.threads=$UNIT_TESTS_BUILD_THREADS diff --git a/ContainerShip/scripts/run-ci-e2e-tests.sh b/ContainerShip/scripts/run-ci-e2e-tests.sh deleted file mode 100755 index de2b97edb5bb7a..00000000000000 --- a/ContainerShip/scripts/run-ci-e2e-tests.sh +++ /dev/null @@ -1,253 +0,0 @@ -#!/bin/bash - -set -ex - -# set default environment variables -ROOT=$(pwd) -SCRIPTS=$(pwd)/scripts - -RUN_ANDROID=0 -RUN_CLI_INSTALL=1 -RUN_IOS=0 -RUN_JS=0 - -RETRY_COUNT=${RETRY_COUNT:-2} -AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1) - -ANDROID_NPM_DEPS="appium@1.5.1 mocha@2.4.5 wd@0.3.11 colors@1.0.3 pretty-data2@0.40.1" -CLI_PACKAGE=$ROOT/react-native-cli/react-native-cli-*.tgz -PACKAGE=$ROOT/react-native-*.tgz - -# solve issue with max user watches limit -echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches -watchman shutdown-server - -# retries command on failure -# $1 -- max attempts -# $2 -- command to run -function retry() { - local -r -i max_attempts="$1"; shift - local -r cmd="$@" - local -i attempt_num=1 - - until $cmd; do - if (( attempt_num == max_attempts )); then - echo "Execution of '$cmd' failed; no more attempts left" - return 1 - else - (( attempt_num++ )) - echo "Execution of '$cmd' failed; retrying for attempt number $attempt_num..." - fi - done -} - -# parse command line args & flags -while :; do - case "$1" in - --android) - RUN_ANDROID=1 - shift - ;; - - --ios) - RUN_IOS=1 - shift - ;; - - --js) - RUN_JS=1 - shift - ;; - - --skip-cli-install) - RUN_CLI_INSTALL=0 - shift - ;; - - --tvos) - RUN_IOS=1 - shift - ;; - - *) - break - esac -done - -function e2e_suite() { - cd $ROOT - - if [ $RUN_ANDROID -eq 0 ] && [ $RUN_IOS -eq 0 ] && [ $RUN_JS -eq 0 ]; then - echo "No e2e tests specified!" - return 0 - fi - - # create temp dir - TEMP_DIR=$(mktemp -d /tmp/react-native-XXXXXXXX) - - # To make sure we actually installed the local version - # of react-native, we will create a temp file inside the template - # and check that it exists after `react-native init - IOS_MARKER=$(mktemp $ROOT/local-cli/templates/HelloWorld/ios/HelloWorld/XXXXXXXX) - ANDROID_MARKER=$(mktemp ${ROOT}/local-cli/templates/HelloWorld/android/XXXXXXXX) - - # install CLI - cd react-native-cli - npm pack - cd .. - - # can skip cli install for non sudo mode - if [ $RUN_CLI_INSTALL -ne 0 ]; then - npm install -g $CLI_PACKAGE - if [ $? -ne 0 ]; then - echo "Could not install react-native-cli globally, please run in su mode" - echo "Or with --skip-cli-install to skip this step" - return 1 - fi - fi - - if [ $RUN_ANDROID -ne 0 ]; then - set +ex - - # create virtual device - if ! android list avd | grep "$AVD_UUID" > /dev/null; then - echo no | android create avd -n $AVD_UUID -f -t android-19 --abi default/armeabi-v7a - fi - - # newline at end of adb devices call and first line is headers - DEVICE_COUNT=$(adb devices | wc -l) - ((DEVICE_COUNT -= 2)) - - # will always kill an existing emulator if one exists for fresh setup - if [[ $DEVICE_COUNT -ge 1 ]]; then - adb emu kill - fi - - # emulator setup - emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim & - - bootanim="" - until [[ "$bootanim" =~ "stopped" ]]; do - sleep 5 - bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1) - echo "boot animation status=$bootanim" - done - - set -ex - - ./gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError" - if [ $? -ne 0 ]; then - echo "Failed to compile Android binaries" - return 1 - fi - fi - - npm pack - if [ $? -ne 0 ]; then - echo "Failed to pack react-native" - return 1 - fi - - cd $TEMP_DIR - - retry $RETRY_COUNT react-native init EndToEndTest --version $PACKAGE --npm - if [ $? -ne 0 ]; then - echo "Failed to execute react-native init" - echo "Most common reason is npm registry connectivity, try again" - return 1 - fi - - cd EndToEndTest - - # android tests - if [ $RUN_ANDROID -ne 0 ]; then - echo "Running an Android e2e test" - echo "Installing e2e framework" - - retry $RETRY_COUNT npm install --save-dev $ANDROID_NPM_DEPS --silent >> /dev/null - if [ $? -ne 0 ]; then - echo "Failed to install appium" - echo "Most common reason is npm registry connectivity, try again" - return 1 - fi - - cp $SCRIPTS/android-e2e-test.js android-e2e-test.js - - cd android - echo "Downloading Maven deps" - ./gradlew :app:copyDownloadableDepsToLibs - - cd .. - keytool -genkey -v -keystore android/keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" - - node ./node_modules/.bin/appium >> /dev/null & - APPIUM_PID=$! - echo "Starting appium server $APPIUM_PID" - - echo "Building app" - buck build android/app - - # hack to get node unhung (kill buckd) - kill -9 $(pgrep java) - - if [ $? -ne 0 ]; then - echo "could not execute Buck build, is it installed and in PATH?" - return 1 - fi - - echo "Starting packager server" - npm start >> /dev/null & - SERVER_PID=$! - sleep 15 - - echo "Executing android e2e test" - retry $RETRY_COUNT node node_modules/.bin/_mocha android-e2e-test.js - if [ $? -ne 0 ]; then - echo "Failed to run Android e2e tests" - echo "Most likely the code is broken" - return 1 - fi - - # kill packager process - if kill -0 $SERVER_PID; then - echo "Killing packager $SERVER_PID" - kill -9 $SERVER_PID - fi - - # kill appium process - if kill -0 $APPIUM_PID; then - echo "Killing appium $APPIUM_PID" - kill -9 $APPIUM_PID - fi - - fi - - # ios tests - if [ $RUN_IOS -ne 0 ]; then - echo "Running ios e2e tests not yet implemented for docker!" - fi - - # js tests - if [ $RUN_JS -ne 0 ]; then - # Check the packager produces a bundle (doesn't throw an error) - react-native bundle --max-workers 1 --platform android --dev true --entry-file index.js --bundle-output android-bundle.js - if [ $? -ne 0 ]; then - echo "Could not build android bundle" - return 1 - fi - - react-native bundle --max-workers 1 --platform ios --dev true --entry-file index.js --bundle-output ios-bundle.js - if [ $? -ne 0 ]; then - echo "Could not build iOS bundle" - return 1 - fi - fi - - # directory cleanup - rm $IOS_MARKER - rm $ANDROID_MARKER - - return 0 -} - -retry $RETRY_COUNT e2e_suite diff --git a/DockerTests.md b/DockerTests.md deleted file mode 100644 index 790d1952786954..00000000000000 --- a/DockerTests.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dockerfile Tests - -This is a high level overview of the test configuration using docker. It explains how to run the tests locally -and how they integrate with the Jenkins Pipeline script to run the automated tests on ContainerShip . - -## Docker Installation - -It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles. -See for more information on how to install. - -## Convenience NPM Run Scripts - -We have added a number of default run scripts to the `package.json` file to simplify building and running your tests. - -`npm run test-android-setup` - Pulls down the base android docker image used for running the tests - -`npm run test-android-build` - Builds the docker image used to run the tests - -`npm run test-android-run-unit` - Runs all the unit tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail) - -`npm run test-android-run-instrumentation` - Runs all the instrumentation tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail). You can also pass additional flags to filter which tests instrumentation tests are run. Ex: `npm run test-android-run-instrumentation -- --filter=TestIdTestCase` to only run the TestIdTestCase instrumentation test. See below for more information -on the instrumentation test flags. - -`npm run test-android-run-e2e` - Runs all the end to end tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail) - -`npm run test-android-unit` - Builds and runs the android unit tests. - -`npm run test-android-instrumentation` - Builds and runs the android instrumentation tests. - -`npm run test-android-e2e` - Builds and runs the android end to end tests. - -## Detailed Android Setup - -There are two Dockerfiles for use with the Android codebase. - -The `Dockerfile.android-base` contains all the necessary prerequisites required to run the React Android tests. It is -separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite -a beastly image since it contains all the Android depedencies for running android and the emulators (~9GB). - -The good news is you should rarely have to build or pull down the base image! All iterative code updates happen as -part of the `Dockerfile.android` image build. - -So step one... - -`docker pull containership/android-base:latest` - -This will take quite some time depending on your connection and you need to ensure you have ~10GB of free disk space. - -Once this is done, you can run tests locally by executing two simple commands: - -1. `docker build -t react/android -f ./ContainerShip/Dockerfile.android .` -2. `docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-unit-tests.sh` - -> Note: `--cap-add=SYS_ADMIN` flag is required for the `ContainerShip/scripts/run-android-docker-unit-tests.sh` and -`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` in order to allow the remounting of `/dev/shm` as writeable -so the `buck` build system may write temporary output to that location - -Every time you make any modifications to the codebase, you should re-run the `docker build ...` command in order for your -updates to be included in your local docker image. - -The following shell scripts have been provided for android testing: - -`ContainerShip/scripts/run-android-docker-unit-tests.sh` - Runs the standard android unit tests - -`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` - Runs the android instrumentation tests on the emulator. *Note* that these -tests take quite some time to run so there are various flags you can pass in order to filter which tests are run (see below) - -`ContainerShip/scripts/run-ci-e2e-tests.sh` - Runs the android end to end tests - -#### ContainerShip/scripts/run-android-docker-instrumentation-tests.sh - -The instrumentation test script accepts the following flags in order to customize the execution of the tests: - -`--filter` - A regex that filters which instrumentation tests will be run. (Defaults to .*) - -`--package` - Name of the java package containing the instrumentation tests (Defaults to com.facebook.react.tests) - -`--path` - Path to the directory containing the instrumentation tests. (Defaults to ./ReactAndroid/src/androidTest/java/com/facebook/react/tests) - -`--retries` - Number of times to retry a failed test before declaring a failure (Defaults to 2) - -For example, if locally you only wanted to run the InitialPropsTestCase, you could do the following: - -`docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --filter="InitialPropsTestCase"` - -# Javascript Setup - -There is a single Dockerfile for use with the javascript codebase. - -The `Dockerfile.javascript` base requires all the necessary dependencies for running Javascript tests. - -Any time you make an update to the codebase, you can build and run the javascript tests with the following three commands: - -1. `docker build -t react/js -f ./ContainerShip/Dockerfile.javascript .` -2. `docker run -it react/js yarn test --maxWorkers=4` -3. `docker run -it react/js yarn run flow -- check` diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md new file mode 100644 index 00000000000000..2505baabf0cc29 --- /dev/null +++ b/ECOSYSTEM.md @@ -0,0 +1,59 @@ +# The React Native Ecosystem + +We aim to build a vibrant and inclusive ecosystem of partners, core contributors, and community that goes beyond the main React Native GitHub repository. This document explains the roles and responsibilities of various stakeholders and provides guidelines for the community organization. The structure outlined in this document has been in place for a while but hadn't been written down before. + +There are three types of stakeholders: + +* **Partners:** Companies that are significantly invested in React Native and have been for years. +* **Core Contributors:** Individual people who contribute to the React Native project. +* **Community Contributors:** Individuals who support projects in the [react-native-community](https://github.com/react-native-community) organization. + +## Partners + +Partners are companies that are significantly invested in React Native and have been for years. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Partners think of React Native as a product: they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications. + +React Native's current set of partners include Callstack, Expo, Infinite Red, Meta, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include: + +* **[Callstack](https://callstack.com/):** Manages releases, maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and organizes [React Native EU](https://react-native.eu/) +* **[Expo](https://expo.dev/):** Builds [expo](https://github.com/expo/expo) and [EAS](https://expo.dev/eas) on top of React Native to simplify app development +* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/) +* **[Meta](https://opensource.fb.com/):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/) +* **[Microsoft](http://aka.ms/reactnative):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) and [React Native macOS](https://github.com/microsoft/react-native-macos) for building apps that target Windows and macOS +* **[Software Mansion](https://swmansion.com/):** Maintain core infrastructure including JSC, Animated, and other popular third-party plugins and organizes [App.js Conf](https://appjs.co/) + +In terms of open source work, pull requests from partners are commonly prioritized. When you are contributing to React Native, you'll most likely meet somebody who works at one of the partner companies and who is a core contributor: + +## Core Contributors + +Core contributors are individuals who contribute to the React Native project. A core contributor is somebody who displayed a lasting commitment to the evolution and maintenance of React Native. The work done by core contributors includes responsibilities mentioned in the “Partners” section above, and concretely means that they: + +* Consistently contribute high quality changes, fixes and improvements +* Actively review changes and provide quality feedback to contributors +* Manage the release process of React Native by maintaining release branches, communicating changes to users and publishing releases +* Love to help out other users with issues on GitHub +* Mentor and encourage first time contributors +* Identify React Native community members who could be effective core contributors +* Help build an inclusive community with people from all backgrounds +* Are great at communicating with other contributors and the community in general + +These are behaviors we have observed in our existing core contributors. They aren't strict rules but rather outline their usual responsibilities. We do not expect every core contributor to do all of the above things all the time. Most importantly, we want to create a supportive and friendly environment that fosters collaboration. Above all else, **we are always polite and friendly.** + +Core contributor status is attained after consistently contributing and taking on the responsibilities outlined above and granted by other core contributors. Similarly, after a long period of inactivity, a core contributor may be removed. + +We aim to make contributing to React Native as easy and transparent as possible. All important topics are handled through a [discussion or RFC process on GitHub](https://github.com/react-native-community/discussions-and-proposals). We are always looking for active, enthusiastic members of the React Native community to become core contributors. + +## Community Contributors + +Community contributors are individuals who support projects in the [react-native-community](https://github.com/react-native-community) organization. This organization exists as an incubator for high quality components that extend the capabilities of React Native with functionality that many but not all applications require. Meta engineers will provide guidance to help build a vibrant community of people and components that make React Native better. + +This structure has multiple benefits: + +* Keep the core of React Native small, which improves performance and reduces the surface area +* Provide visibility to projects through shared representation, for example on the React Native website or on Twitter +* Ensure a consistent and high standard for code, documentation, user experience, stability and contributions for third-party components +* Upgrade the most important components right away when we make breaking changes and move the ecosystem forward at a fast pace +* Find new maintainers for projects that are important but were abandoned by previous owners + +Additionally, some companies may choose to sponsor the development of one or many of the packages that are part of the community organization. They will commit to maintain projects, triage issues, fix bugs and develop features. In turn, they will be able to gain visibility for their work, for example through a mention of active maintainers in the README of individual projects after a consistent period of contributions. Such a mention may be removed if maintainers abandon the project. + +If you are working on a popular component and would like to move it to the React Native community, please create an issue on the [discussions-and-proposals repository](https://github.com/react-native-community/discussions-and-proposals). diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000000000..5efda89f452582 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby '2.7.5' + +gem 'cocoapods', '~> 1.11', '>= 1.11.2' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000000000..848c9871f3f6ca --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,100 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + activesupport (6.1.7) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.1.0) + cocoapods (1.11.3) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.11.3) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-core (1.11.3) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.6.3) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.1.10) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.15.5) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + json (2.6.2) + minitest (5.16.3) + molinillo (0.8.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + public_suffix (4.0.7) + rexml (3.2.5) + ruby-macho (2.5.1) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.5) + concurrent-ruby (~> 1.0) + xcodeproj (1.22.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + zeitwerk (2.6.1) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (~> 1.11, >= 1.11.2) + +RUBY VERSION + ruby 2.7.5p203 + +BUNDLED WITH + 2.3.11 diff --git a/IntegrationTests/AccessibilityManagerTest.js b/IntegrationTests/AccessibilityManagerTest.js index 74dbe9ba19edab..f23972f25da1bd 100644 --- a/IntegrationTests/AccessibilityManagerTest.js +++ b/IntegrationTests/AccessibilityManagerTest.js @@ -1,41 +1,42 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow - * @providesModule AccessibilityManagerTest + * @format + * @flow strict-local */ -'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const { View } = ReactNative; -const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -const { - TestModule, - AccessibilityManager, -} = ReactNative.NativeModules; +import invariant from 'invariant'; +import NativeAccessibilityManager from 'react-native/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager'; +import {DeviceEventEmitter, NativeModules, View} from 'react-native'; +import * as React from 'react'; +const {TestModule} = NativeModules; -class AccessibilityManagerTest extends React.Component<{}> { +class AccessibilityManagerTest extends React.Component<{...}> { componentDidMount() { - AccessibilityManager.setAccessibilityContentSizeMultipliers({ - 'extraSmall': 1.0, - 'small': 2.0, - 'medium': 3.0, - 'large': 4.0, - 'extraLarge': 5.0, - 'extraExtraLarge': 6.0, - 'extraExtraExtraLarge': 7.0, - 'accessibilityMedium': 8.0, - 'accessibilityLarge': 9.0, - 'accessibilityExtraLarge': 10.0, - 'accessibilityExtraExtraLarge': 11.0, - 'accessibilityExtraExtraExtraLarge': 12.0, + invariant( + NativeAccessibilityManager, + "NativeAccessibilityManager doesn't exist", + ); + + NativeAccessibilityManager.setAccessibilityContentSizeMultipliers({ + extraSmall: 1.0, + small: 2.0, + medium: 3.0, + large: 4.0, + extraLarge: 5.0, + extraExtraLarge: 6.0, + extraExtraExtraLarge: 7.0, + accessibilityMedium: 8.0, + accessibilityLarge: 9.0, + accessibilityExtraLarge: 10.0, + accessibilityExtraExtraLarge: 11.0, + accessibilityExtraExtraExtraLarge: 12.0, }); - RCTDeviceEventEmitter.addListener('didUpdateDimensions', update => { + DeviceEventEmitter.addListener('didUpdateDimensions', update => { TestModule.markTestPassed(update.window.fontScale === 4.0); }); } diff --git a/IntegrationTests/AppEventsTest.js b/IntegrationTests/AppEventsTest.js index ae061e167b86d8..5ff829b20c5a5e 100644 --- a/IntegrationTests/AppEventsTest.js +++ b/IntegrationTests/AppEventsTest.js @@ -1,61 +1,61 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AppEventsTest + * @format * @flow */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - NativeAppEventEmitter, - StyleSheet, - Text, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {NativeAppEventEmitter, StyleSheet, Text, View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; -var deepDiffer = require('deepDiffer'); +const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer'); -var TEST_PAYLOAD = {foo: 'bar'}; +const TEST_PAYLOAD = {foo: 'bar'}; -type AppEvent = { data: Object, ts: number, }; +type AppEvent = { + data: Object, + ts: number, + ... +}; type State = { sent: 'none' | AppEvent, received: 'none' | AppEvent, elapsed?: string, + ... }; -class AppEventsTest extends React.Component<{}, State> { +class AppEventsTest extends React.Component<{...}, State> { state: State = {sent: 'none', received: 'none'}; componentDidMount() { NativeAppEventEmitter.addListener('testEvent', this.receiveEvent); - var event = {data: TEST_PAYLOAD, ts: Date.now()}; + const event = {data: TEST_PAYLOAD, ts: Date.now()}; TestModule.sendAppEvent('testEvent', event); + // eslint-disable-next-line react/no-did-mount-set-state this.setState({sent: event}); } - receiveEvent = (event: any) => { + receiveEvent: (event: any) => void = (event: any) => { if (deepDiffer(event.data, TEST_PAYLOAD)) { throw new Error('Received wrong event: ' + JSON.stringify(event)); } - var elapsed = (Date.now() - event.ts) + 'ms'; + const elapsed = Date.now() - event.ts + 'ms'; this.setState({received: event, elapsed}, () => { TestModule.markTestCompleted(); }); }; - render() { + render(): React.Node { return ( - - {JSON.stringify(this.state, null, ' ')} - + {JSON.stringify(this.state, null, ' ')} ); } @@ -63,7 +63,7 @@ class AppEventsTest extends React.Component<{}, State> { AppEventsTest.displayName = 'AppEventsTest'; -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { margin: 40, }, diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js index 3ce0265dd8ceb0..42bdbd62965458 100644 --- a/IntegrationTests/AsyncStorageTest.js +++ b/IntegrationTests/AsyncStorageTest.js @@ -1,74 +1,111 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule AsyncStorageTest */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - AsyncStorage, - Text, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; - -var deepDiffer = require('deepDiffer'); - -var DEBUG = false; - -var KEY_1 = 'key_1'; -var VAL_1 = 'val_1'; -var KEY_2 = 'key_2'; -var VAL_2 = 'val_2'; -var KEY_MERGE = 'key_merge'; -var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}}; -var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}}; -var VAL_MERGE_EXPECT = - {'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}}; +const React = require('react'); +const ReactNative = require('react-native'); +const {AsyncStorage, Text, View, StyleSheet} = ReactNative; +const {TestModule} = ReactNative.NativeModules; + +const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer'); +const nullthrows = require('nullthrows'); + +const DEBUG = false; + +const KEY_1 = 'key_1'; +const VAL_1 = 'val_1'; +const KEY_2 = 'key_2'; +const VAL_2 = 'val_2'; +const KEY_MERGE = 'key_merge'; +const VAL_MERGE_1 = {foo: 1, bar: {hoo: 1, boo: 1}, moo: {a: 3}}; +const VAL_MERGE_2 = {bar: {hoo: 2}, baz: 2, moo: {a: 3}}; +const VAL_MERGE_EXPECT = {foo: 1, bar: {hoo: 2, boo: 1}, baz: 2, moo: {a: 3}}; // setup in componentDidMount -var done = (result : ?boolean) => {}; -var updateMessage = (message : string ) => {}; +let done = (result: ?boolean) => {}; +let updateMessage = (message: string) => {}; -function runTestCase(description : string, fn) { +function runTestCase(description: string, fn: () => void) { updateMessage(description); fn(); } -function expectTrue(condition : boolean, message : string) { +function expectTrue(condition: boolean, message: string) { if (!condition) { throw new Error(message); } } -function expectEqual(lhs, rhs, testname : string) { +// Type-safe wrapper around JSON.stringify +function stringify( + value: + | void + | null + | string + | number + | boolean + | {...} + | $ReadOnlyArray, +): string { + if (typeof value === 'undefined') { + return 'undefined'; + } + return JSON.stringify(value); +} + +function expectEqual( + lhs: ?(any | string | Array>), + rhs: + | null + | string + | { + bar: {boo: number, hoo: number}, + baz: number, + foo: number, + moo: {a: number}, + } + | Array>, + testname: string, +) { expectTrue( !deepDiffer(lhs, rhs), - 'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) + - '\ngot\n' + JSON.stringify(lhs) + 'Error in test ' + + testname + + ': expected\n' + + stringify(rhs) + + '\ngot\n' + + stringify(lhs), ); } -function expectAsyncNoError(place, err) { +function expectAsyncNoError( + place: string, + err: ?(Error | string | Array), +) { if (err instanceof Error) { err = err.message; } - expectTrue(err === null, 'Unexpected error in ' + place + ': ' + JSON.stringify(err)); + expectTrue( + err === null, + 'Unexpected error in ' + place + ': ' + stringify(err), + ); } function testSetAndGet() { - AsyncStorage.setItem(KEY_1, VAL_1, (err1) => { + AsyncStorage.setItem(KEY_1, VAL_1, err1 => { expectAsyncNoError('testSetAndGet/setItem', err1); AsyncStorage.getItem(KEY_1, (err2, result) => { expectAsyncNoError('testSetAndGet/getItem', err2); expectEqual(result, VAL_1, 'testSetAndGet setItem'); - updateMessage('get(key_1) correctly returned ' + result); + updateMessage('get(key_1) correctly returned ' + String(result)); runTestCase('should get null for missing key', testMissingGet); }); }); @@ -78,14 +115,14 @@ function testMissingGet() { AsyncStorage.getItem(KEY_2, (err, result) => { expectAsyncNoError('testMissingGet/setItem', err); expectEqual(result, null, 'testMissingGet'); - updateMessage('missing get(key_2) correctly returned ' + result); + updateMessage('missing get(key_2) correctly returned ' + String(result)); runTestCase('check set twice results in a single key', testSetTwice); }); } function testSetTwice() { - AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ - AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ + AsyncStorage.setItem(KEY_1, VAL_1, () => { + AsyncStorage.setItem(KEY_1, VAL_1, () => { AsyncStorage.getItem(KEY_1, (err, result) => { expectAsyncNoError('testSetTwice/setItem', err); expectEqual(result, VAL_1, 'testSetTwice'); @@ -97,16 +134,17 @@ function testSetTwice() { } function testRemoveItem() { - AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ - AsyncStorage.setItem(KEY_2, VAL_2, ()=>{ + AsyncStorage.setItem(KEY_1, VAL_1, () => { + AsyncStorage.setItem(KEY_2, VAL_2, () => { AsyncStorage.getAllKeys((err, result) => { expectAsyncNoError('testRemoveItem/getAllKeys', err); expectTrue( - result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0, - 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')' + nullthrows(result).indexOf(KEY_1) >= 0 && + nullthrows(result).indexOf(KEY_2) >= 0, + 'Missing KEY_1 or KEY_2 in ' + '(' + nullthrows(result).join() + ')', ); updateMessage('testRemoveItem - add two items'); - AsyncStorage.removeItem(KEY_1, (err2) => { + AsyncStorage.removeItem(KEY_1, err2 => { expectAsyncNoError('testRemoveItem/removeItem', err2); updateMessage('delete successful '); AsyncStorage.getItem(KEY_1, (err3, result2) => { @@ -114,17 +152,17 @@ function testRemoveItem() { expectEqual( result2, null, - 'testRemoveItem: key_1 present after delete' + 'testRemoveItem: key_1 present after delete', ); updateMessage('key properly removed '); AsyncStorage.getAllKeys((err4, result3) => { - expectAsyncNoError('testRemoveItem/getAllKeys', err4); - expectTrue( - result3.indexOf(KEY_1) === -1, - 'Unexpected: KEY_1 present in ' + result3 - ); - updateMessage('proper length returned.'); - runTestCase('should merge values', testMerge); + expectAsyncNoError('testRemoveItem/getAllKeys', err4); + expectTrue( + nullthrows(result3).indexOf(KEY_1) === -1, + 'Unexpected: KEY_1 present in ' + nullthrows(result3).join(), + ); + updateMessage('proper length returned.'); + runTestCase('should merge values', testMerge); }); }); }); @@ -134,13 +172,17 @@ function testRemoveItem() { } function testMerge() { - AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => { + AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => { expectAsyncNoError('testMerge/setItem', err1); - AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => { + AsyncStorage.mergeItem(KEY_MERGE, stringify(VAL_MERGE_2), err2 => { expectAsyncNoError('testMerge/mergeItem', err2); AsyncStorage.getItem(KEY_MERGE, (err3, result) => { expectAsyncNoError('testMerge/setItem', err3); - expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge'); + expectEqual( + JSON.parse(nullthrows(result)), + VAL_MERGE_EXPECT, + 'testMerge', + ); updateMessage('objects deeply merged\nDone!'); runTestCase('multi set and get', testOptimizedMultiGet); }); @@ -149,49 +191,56 @@ function testMerge() { } function testOptimizedMultiGet() { - let batch = [[KEY_1, VAL_1], [KEY_2, VAL_2]]; + let batch = [ + [KEY_1, VAL_1], + [KEY_2, VAL_2], + ]; let keys = batch.map(([key, value]) => key); - AsyncStorage.multiSet(batch, (err1) => { + AsyncStorage.multiSet(batch, err1 => { // yes, twice on purpose - [1, 2].forEach((i) => { + [1, 2].forEach(i => { expectAsyncNoError(`${i} testOptimizedMultiGet/multiSet`, err1); AsyncStorage.multiGet(keys, (err2, result) => { expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2); expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`); - updateMessage('multiGet([key_1, key_2]) correctly returned ' + JSON.stringify(result)); + updateMessage( + 'multiGet([key_1, key_2]) correctly returned ' + stringify(result), + ); done(); }); }); }); } - -class AsyncStorageTest extends React.Component<{}, $FlowFixMeState> { - state = { +class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> { + state: any | {|done: boolean, messages: string|} = { messages: 'Initializing...', done: false, }; componentDidMount() { - done = () => this.setState({done: true}, () => { - TestModule.markTestCompleted(); - }); - updateMessage = (msg) => { + done = () => + this.setState({done: true}, () => { + TestModule.markTestCompleted(); + }); + updateMessage = (msg: string) => { this.setState({messages: this.state.messages.concat('\n' + msg)}); DEBUG && console.log(msg); }; AsyncStorage.clear(testSetAndGet); } - render() { + render(): React.Node { return ( - + { - /* $FlowFixMe(>=0.54.0 site=react_native_fb,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. */ - this.constructor.displayName + ': '} + /* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,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. + */ + this.constructor.displayName + ': ' + } {this.state.done ? 'Done' : 'Testing...'} {'\n\n' + this.state.messages} @@ -200,6 +249,13 @@ class AsyncStorageTest extends React.Component<{}, $FlowFixMeState> { } } +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + padding: 40, + }, +}); + AsyncStorageTest.displayName = 'AsyncStorageTest'; module.exports = AsyncStorageTest; diff --git a/IntegrationTests/BUCK b/IntegrationTests/BUCK new file mode 100644 index 00000000000000..fa6fe51a3c538a --- /dev/null +++ b/IntegrationTests/BUCK @@ -0,0 +1,33 @@ +load("@fbsource//tools/build_defs:js_library_glob.bzl", "js_library_glob") +load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library") + +# This file was generated by running +# js1 build buckfiles + +rn_library( + name = "IntegrationTests", + srcs = js_library_glob( + [ + "**/*", + ], + excludes = [ + "**/__*__/**", + "**/*.command", + "**/*.md", + "websocket_integration_test_server.js", + ], + ), + labels = [ + "pfh:ReactNative_CommonInfrastructurePlaceholder", + "supermodule:xplat/default/public.react_native.tests", + ], + skip_processors = True, + visibility = ["PUBLIC"], + deps = [ + "//xplat/js:node_modules__invariant", + "//xplat/js:node_modules__nullthrows", + "//xplat/js/RKJSModules/vendor/react:react", + "//xplat/js/react-native-github:react-native", + "//xplat/js/react-native-github/packages/assets:assets", + ], +) diff --git a/IntegrationTests/GlobalEvalWithSourceUrlTest.js b/IntegrationTests/GlobalEvalWithSourceUrlTest.js new file mode 100644 index 00000000000000..dd3206f3c9a34d --- /dev/null +++ b/IntegrationTests/GlobalEvalWithSourceUrlTest.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +'use strict'; + +import type {ExtendedError} from 'react-native/Libraries/Core/ExtendedError'; + +const React = require('react'); +const ReactNative = require('react-native'); +const parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack'); +const {View} = ReactNative; + +const {TestModule} = ReactNative.NativeModules; + +class GlobalEvalWithSourceUrlTest extends React.Component<{...}> { + componentDidMount() { + if (typeof global.globalEvalWithSourceUrl !== 'function') { + throw new Error( + 'Expected to find globalEvalWithSourceUrl function on global object but found ' + + typeof global.globalEvalWithSourceUrl, + ); + } + const value = global.globalEvalWithSourceUrl('42'); + if (value !== 42) { + throw new Error( + 'Expected globalEvalWithSourceUrl(expression) to return a value', + ); + } + let syntaxError: ?ExtendedError; + try { + global.globalEvalWithSourceUrl('{'); + } catch (e) { + syntaxError = e; + } + if (!syntaxError) { + throw new Error( + 'Expected globalEvalWithSourceUrl to throw on a syntax error', + ); + } + // Hermes throws an Error instead of a SyntaxError + // https://github.com/facebook/hermes/issues/400 + if ( + syntaxError.jsEngine !== 'hermes' && + !(syntaxError instanceof SyntaxError) + ) { + throw new Error( + 'Expected globalEvalWithSourceUrl to throw SyntaxError on a syntax error', + ); + } + const url = 'http://example.com/foo.js'; + let error; + try { + global.globalEvalWithSourceUrl('throw new Error()', url); + } catch (e) { + error = e; + } + if (!error) { + throw new Error( + 'Expected globalEvalWithSourceUrl to throw an Error object', + ); + } + const parsedStack = parseErrorStack(error?.stack); + if (parsedStack[0].file !== url) { + throw new Error( + `Expected first eval stack frame to be in ${url} but found ${String( + parsedStack[0].file, + )}`, + ); + } + TestModule.markTestCompleted(); + } + + render(): React.Node { + return ; + } +} + +GlobalEvalWithSourceUrlTest.displayName = 'GlobalEvalWithSourceUrlTest'; + +module.exports = GlobalEvalWithSourceUrlTest; diff --git a/IntegrationTests/ImageCachePolicyTest.js b/IntegrationTests/ImageCachePolicyTest.js index d6fa4f544a1bf9..808da56ce8e980 100644 --- a/IntegrationTests/ImageCachePolicyTest.js +++ b/IntegrationTests/ImageCachePolicyTest.js @@ -1,23 +1,19 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow - * @providesModule ImageCachePolicyTest + * @format + * @flow strict-local */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - Image, - View, - Text, - StyleSheet, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {Image, View, Text, StyleSheet} = ReactNative; +const {TestModule} = ReactNative.NativeModules; /* * The reload and force-cache tests don't actually verify that the complete functionality. @@ -31,22 +27,26 @@ var { TestModule } = ReactNative.NativeModules; const TESTS = ['only-if-cached', 'default', 'reload', 'force-cache']; -type Props = {} +type Props = {...}; type State = { 'only-if-cached'?: boolean, - 'default'?: boolean, - 'reload'?: boolean, + default?: boolean, + reload?: boolean, 'force-cache'?: boolean, -} + ... +}; class ImageCachePolicyTest extends React.Component { - state = {} + state: $FlowFixMe | {...} = {}; - shouldComponentUpdate(nextProps: Props, nextState: State) { + shouldComponentUpdate(nextProps: Props, nextState: State): boolean { const results: Array = TESTS.map(x => nextState[x]); if (!results.includes(undefined)) { - const result: boolean = results.reduce((x,y) => x === y === true, true); + const result: boolean = results.reduce( + (x, y) => (x === y) === true, + true, + ); TestModule.markTestPassed(result); } @@ -57,42 +57,50 @@ class ImageCachePolicyTest extends React.Component { this.setState({[name]: pass}); } - render() { + render(): React.Node { return ( - + Hello - this.testComplete('only-if-cached', false)} - onError={() => this.testComplete('only-if-cached', true)} - style={styles.base} - /> this.testComplete('only-if-cached', false)} + onError={() => this.testComplete('only-if-cached', true)} + style={styles.base} + /> + this.testComplete('default', true)} onError={() => this.testComplete('default', false)} style={styles.base} /> this.testComplete('reload', true)} onError={() => this.testComplete('reload', false)} style={styles.base} /> this.testComplete('force-cache', true)} onError={() => this.testComplete('force-cache', false)} style={styles.base} @@ -103,6 +111,9 @@ class ImageCachePolicyTest extends React.Component { } const styles = StyleSheet.create({ + container: { + flex: 1, + }, base: { width: 100, height: 100, diff --git a/IntegrationTests/ImageSnapshotTest.js b/IntegrationTests/ImageSnapshotTest.js index 8ba7734286d673..0d6aa3bb67f627 100644 --- a/IntegrationTests/ImageSnapshotTest.js +++ b/IntegrationTests/ImageSnapshotTest.js @@ -1,39 +1,38 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow - * @providesModule ImageSnapshotTest + * @format + * @flow strict-local */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - Image, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {Image} = ReactNative; +const {TestModule} = ReactNative.NativeModules; -class ImageSnapshotTest extends React.Component<{}> { +class ImageSnapshotTest extends React.Component<{...}> { componentDidMount() { if (!TestModule.verifySnapshot) { throw new Error('TestModule.verifySnapshot not defined.'); } } - done = (success : boolean) => { + done: (success: boolean) => void = (success: boolean) => { TestModule.markTestPassed(success); }; - render() { + render(): React.Node { return ( TestModule.verifySnapshot(this.done)} /> + onLoad={() => TestModule.verifySnapshot(this.done)} + /> ); } } diff --git a/IntegrationTests/IntegrationTestHarnessTest.js b/IntegrationTests/IntegrationTestHarnessTest.js index 6291c915d68643..3d76dc7c0681ee 100644 --- a/IntegrationTests/IntegrationTestHarnessTest.js +++ b/IntegrationTests/IntegrationTestHarnessTest.js @@ -1,37 +1,32 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule IntegrationTestHarnessTest */ + 'use strict'; -/* $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 requestAnimationFrame = require('fbjs/lib/requestAnimationFrame'); -var React = require('react'); -var PropTypes = require('prop-types'); -var ReactNative = require('react-native'); -var { - Text, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); + +const {Text, View, StyleSheet} = ReactNative; +const {TestModule} = ReactNative.NativeModules; -class IntegrationTestHarnessTest extends React.Component<{ +type Props = $ReadOnly<{| shouldThrow?: boolean, waitOneFrame?: boolean, -}, $FlowFixMeState> { - static propTypes = { - shouldThrow: PropTypes.bool, - waitOneFrame: PropTypes.bool, - }; +|}>; - state = { +type State = {| + done: boolean, +|}; + +class IntegrationTestHarnessTest extends React.Component { + state: State = { done: false, }; @@ -43,7 +38,7 @@ class IntegrationTestHarnessTest extends React.Component<{ } } - runTest = () => { + runTest: () => void = () => { if (this.props.shouldThrow) { throw new Error('Throwing error because shouldThrow'); } @@ -57,15 +52,17 @@ class IntegrationTestHarnessTest extends React.Component<{ }); }; - render() { + render(): React.Node { return ( - + { - /* $FlowFixMe(>=0.54.0 site=react_native_fb,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. */ - this.constructor.displayName + ': '} + /* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,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. + */ + this.constructor.displayName + ': ' + } {this.state.done ? 'Done' : 'Testing...'} @@ -73,6 +70,13 @@ class IntegrationTestHarnessTest extends React.Component<{ } } +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + padding: 40, + }, +}); + IntegrationTestHarnessTest.displayName = 'IntegrationTestHarnessTest'; module.exports = IntegrationTestHarnessTest; diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index 6b255c2befe54a..bb8efcbcd497a4 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -1,27 +1,23 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule IntegrationTestsApp */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - AppRegistry, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} = ReactNative; +require('react-native/Libraries/Core/InitializeCore'); +const React = require('react'); +const ReactNative = require('react-native'); +const {AppRegistry, ScrollView, StyleSheet, Text, TouchableOpacity, View} = + ReactNative; // Keep this list in sync with RNTesterIntegrationTests.m -var TESTS = [ +const TESTS = [ require('./IntegrationTestHarnessTest'), require('./TimersTest'), require('./AsyncStorageTest'), @@ -31,25 +27,25 @@ var TESTS = [ require('./ImageCachePolicyTest'), require('./ImageSnapshotTest'), require('./PromiseTest'), - require('./WebViewTest'), require('./SyncMethodTest'), require('./WebSocketTest'), require('./AccessibilityManagerTest'), + require('./GlobalEvalWithSourceUrlTest'), ]; TESTS.forEach( - /* $FlowFixMe(>=0.54.0 site=react_native_fb,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. */ - (test) => AppRegistry.registerComponent(test.displayName, () => test) + /* $FlowFixMe[incompatible-call] (>=0.54.0 site=react_native_fb,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. */ + test => AppRegistry.registerComponent(test.displayName, () => test), ); // Modules required for integration tests -require('LoggingTestModule'); +require('./LoggingTestModule'); type Test = any; -class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> { +class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> { state = { test: (null: ?Test), }; @@ -58,9 +54,10 @@ class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> { if (this.state.test) { return ( - {/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for - * React. To see the error delete this comment and run Flow. */} + {/* $FlowFixMe[type-as-value] (>=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. */} ); @@ -69,20 +66,22 @@ class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> { Click on a test to run it in this shell for easier debugging and - development. Run all tests in the testing environment with cmd+U in + development. Run all tests in the testing environment with cmd+U in Xcode. - {TESTS.map((test) => [ + {TESTS.map(test => [ this.setState({test})} + /* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_native_fb) + * This comment suppresses an error found when Flow v0.115 was + * deployed. To see the error, delete this comment and run Flow. + */ style={styles.row}> - - {test.displayName} - + {test.displayName} , - + , ])} @@ -90,7 +89,7 @@ class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { backgroundColor: 'white', marginTop: 40, diff --git a/IntegrationTests/LayoutEventsTest.js b/IntegrationTests/LayoutEventsTest.js index 769ff98f6be8b5..46efb579d825c7 100644 --- a/IntegrationTests/LayoutEventsTest.js +++ b/IntegrationTests/LayoutEventsTest.js @@ -1,41 +1,34 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule LayoutEventsTest - * @flow + * @format + * @flow strict-local */ + 'use strict'; -var React = require('react'); -var createReactClass = require('create-react-class'); -var ReactNative = require('react-native'); -var { - Image, - LayoutAnimation, - StyleSheet, - Text, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; - -var deepDiffer = require('deepDiffer'); - -function debug(...args) { +const React = require('react'); +const ReactNative = require('react-native'); +const {Image, LayoutAnimation, StyleSheet, Text, View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; + +import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'; + +const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer'); + +function debug(...args: Array) { // console.log.apply(null, arguments); } -import type {Layout, LayoutEvent} from 'CoreEventTypes'; -type Style = { - margin?: number, - padding?: number, - borderColor?: string, - borderWidth?: number, - backgroundColor?: string, - width?: number, -}; +import type { + Layout, + LayoutEvent, +} from 'react-native/Libraries/Types/CoreEventTypes'; + +type Props = $ReadOnly<{||}>; type State = { didAnimation: boolean, @@ -43,48 +36,59 @@ type State = { imageLayout?: Layout, textLayout?: Layout, viewLayout?: Layout, - viewStyle?: Style, - containerStyle?: Style, + viewStyle?: ViewStyleProp, + containerStyle?: ViewStyleProp, + ... }; -var LayoutEventsTest = createReactClass({ - displayName: 'LayoutEventsTest', - getInitialState(): State { - return { - didAnimation: false, - }; - }, - animateViewLayout: function() { +class LayoutEventsTest extends React.Component { + _view: ?React.ElementRef; + _img: ?React.ElementRef; + _txt: ?React.ElementRef; + + state: State = { + didAnimation: false, + }; + + animateViewLayout() { debug('animateViewLayout invoked'); - LayoutAnimation.configureNext( - LayoutAnimation.Presets.spring, - () => { - debug('animateViewLayout done'); - this.checkLayout(this.addWrapText); - } - ); + LayoutAnimation.configureNext(LayoutAnimation.Presets.spring, () => { + debug('animateViewLayout done'); + this.checkLayout(this.addWrapText); + }); this.setState({viewStyle: {margin: 60}}); - }, - addWrapText: function() { + } + + addWrapText: () => void = () => { debug('addWrapText invoked'); this.setState( {extraText: ' And a bunch more text to wrap around a few lines.'}, - () => this.checkLayout(this.changeContainer) + () => this.checkLayout(this.changeContainer), ); - }, - changeContainer: function() { + }; + + changeContainer: () => void = () => { debug('changeContainer invoked'); - this.setState( - {containerStyle: {width: 280}}, - () => this.checkLayout(TestModule.markTestCompleted) + this.setState({containerStyle: {width: 280}}, () => + this.checkLayout(TestModule.markTestCompleted), ); - }, - checkLayout: function(next?: ?Function) { - if (!this.isMounted()) { + }; + + checkLayout: (next?: ?() => void) => void = (next?: ?() => void) => { + const view = this._view; + const txt = this._txt; + const img = this._img; + + if (view == null || txt == null || img == null) { return; } - this.refs.view.measure((x, y, width, height) => { - this.compare('view', {x, y, width, height}, this.state.viewLayout); + + view.measure((x, y, width, height) => { + this.compare( + 'view', + {x, y, width, height}, + this.state.viewLayout || null, + ); if (typeof next === 'function') { next(); } else if (!this.state.didAnimation) { @@ -93,49 +97,72 @@ var LayoutEventsTest = createReactClass({ this.state.didAnimation = true; } }); - this.refs.txt.measure((x, y, width, height) => { + + txt.measure((x, y, width, height) => { this.compare('txt', {x, y, width, height}, this.state.textLayout); }); - this.refs.img.measure((x, y, width, height) => { + + img.measure((x, y, width, height) => { this.compare('img', {x, y, width, height}, this.state.imageLayout); }); - }, - compare: function(node: string, measured: any, onLayout: any): void { + }; + + compare(node: string, measured: Layout, onLayout?: ?Layout): void { if (deepDiffer(measured, onLayout)) { - var data = {measured, onLayout}; + const data = {measured, onLayout}; throw new Error( - node + ' onLayout mismatch with measure ' + - JSON.stringify(data, null, ' ') + node + + ' onLayout mismatch with measure ' + + JSON.stringify(data, null, ' '), ); } - }, - onViewLayout: function(e: LayoutEvent) { + } + + onViewLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => { + // $FlowFixMe[incompatible-call] debug('received view layout event\n', e.nativeEvent); this.setState({viewLayout: e.nativeEvent.layout}, this.checkLayout); - }, - onTextLayout: function(e: LayoutEvent) { + }; + + onTextLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => { + // $FlowFixMe[incompatible-call] debug('received text layout event\n', e.nativeEvent); this.setState({textLayout: e.nativeEvent.layout}, this.checkLayout); - }, - onImageLayout: function(e: LayoutEvent) { + }; + + onImageLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => { + // $FlowFixMe[incompatible-call] debug('received image layout event\n', e.nativeEvent); this.setState({imageLayout: e.nativeEvent.layout}, this.checkLayout); - }, - render: function() { - var viewStyle = [styles.view, this.state.viewStyle]; - var textLayout = this.state.textLayout || {width: '?', height: '?'}; - var imageLayout = this.state.imageLayout || {x: '?', y: '?'}; + }; + + render(): React.Node { + const viewStyle = [styles.view, this.state.viewStyle]; + const textLayout = this.state.textLayout || {width: '?', height: '?'}; + const imageLayout = this.state.imageLayout || {x: '?', y: '?'}; debug('viewLayout', this.state.viewLayout); return ( - + { + this._view = ref; + }} + onLayout={this.onViewLayout} + style={viewStyle}> { + this._img = ref; + }} onLayout={this.onImageLayout} style={styles.image} source={{uri: 'uie_thumb_big.png'}} /> - + { + this._txt = ref; + }} + onLayout={this.onTextLayout} + style={styles.text}> A simple piece of text.{this.state.extraText} @@ -147,9 +174,9 @@ var LayoutEventsTest = createReactClass({ ); } -}); +} -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { margin: 40, }, @@ -174,5 +201,4 @@ var styles = StyleSheet.create({ }); LayoutEventsTest.displayName = 'LayoutEventsTest'; - module.exports = LayoutEventsTest; diff --git a/IntegrationTests/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js index 25b409353ce1d4..c18cd8b4254378 100644 --- a/IntegrationTests/LoggingTestModule.js +++ b/IntegrationTests/LoggingTestModule.js @@ -1,44 +1,41 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule LoggingTestModule + * @format */ + 'use strict'; -var BatchedBridge = require('BatchedBridge'); +const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge'); -var warning = require('fbjs/lib/warning'); -var invariant = require('fbjs/lib/invariant'); +const invariant = require('invariant'); -var LoggingTestModule = { - logToConsole: function(str) { +const LoggingTestModule = { + logToConsole: function (str) { console.log(str); }, - logToConsoleAfterWait: function(str,timeout_ms) { - setTimeout(function() { + logToConsoleAfterWait: function (str, timeout_ms) { + setTimeout(function () { console.log(str); }, timeout_ms); }, - warning: function(str) { - warning(false, str); + warning: function (str) { + console.warn(str); }, - invariant: function(str) { + invariant: function (str) { invariant(false, str); }, - logErrorToConsole: function(str) { + logErrorToConsole: function (str) { console.error(str); }, - throwError: function(str) { + throwError: function (str) { throw new Error(str); - } + }, }; -BatchedBridge.registerCallableModule( - 'LoggingTestModule', - LoggingTestModule -); +BatchedBridge.registerCallableModule('LoggingTestModule', LoggingTestModule); module.exports = LoggingTestModule; diff --git a/IntegrationTests/PromiseTest.js b/IntegrationTests/PromiseTest.js index f6a81e3d6d5d16..39b7c49d47ca12 100644 --- a/IntegrationTests/PromiseTest.js +++ b/IntegrationTests/PromiseTest.js @@ -1,24 +1,25 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule PromiseTest */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { View } = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; -class PromiseTest extends React.Component<{}> { - shouldResolve = false; - shouldReject = false; - shouldSucceedAsync = false; - shouldThrowAsync = false; +class PromiseTest extends React.Component<{...}> { + shouldResolve: boolean = false; + shouldReject: boolean = false; + shouldSucceedAsync: boolean = false; + shouldThrowAsync: boolean = false; componentDidMount() { Promise.all([ @@ -26,27 +27,29 @@ class PromiseTest extends React.Component<{}> { this.testShouldReject(), this.testShouldSucceedAsync(), this.testShouldThrowAsync(), - ]).then(() => TestModule.markTestPassed( - this.shouldResolve && this.shouldReject && - this.shouldSucceedAsync && this.shouldThrowAsync - )); + ]).then(() => + TestModule.markTestPassed( + this.shouldResolve && + this.shouldReject && + this.shouldSucceedAsync && + this.shouldThrowAsync, + ), + ); } - testShouldResolve = () => { - return TestModule - .shouldResolve() - .then(() => this.shouldResolve = true) - .catch(() => this.shouldResolve = false); + testShouldResolve: () => any = () => { + return TestModule.shouldResolve() + .then(() => (this.shouldResolve = true)) + .catch(() => (this.shouldResolve = false)); }; - testShouldReject = () => { - return TestModule - .shouldReject() - .then(() => this.shouldReject = false) - .catch(() => this.shouldReject = true); + testShouldReject: () => any = () => { + return TestModule.shouldReject() + .then(() => (this.shouldReject = false)) + .catch(() => (this.shouldReject = true)); }; - testShouldSucceedAsync = async (): Promise => { + testShouldSucceedAsync: () => Promise = async (): Promise => { try { await TestModule.shouldResolve(); this.shouldSucceedAsync = true; @@ -55,7 +58,7 @@ class PromiseTest extends React.Component<{}> { } }; - testShouldThrowAsync = async (): Promise => { + testShouldThrowAsync: () => Promise = async (): Promise => { try { await TestModule.shouldReject(); this.shouldThrowAsync = false; diff --git a/IntegrationTests/PropertiesUpdateTest.js b/IntegrationTests/PropertiesUpdateTest.js index 623dc82c8ee94b..5c1355ac0513d5 100644 --- a/IntegrationTests/PropertiesUpdateTest.js +++ b/IntegrationTests/PropertiesUpdateTest.js @@ -1,28 +1,26 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * @providesModule PropertiesUpdateTest + * + * @format */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { - View, -} = ReactNative; +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const {TestModule} = ReactNative.NativeModules; class PropertiesUpdateTest extends React.Component { render() { if (this.props.markTestPassed) { TestModule.markTestPassed(true); } - return ( - - ); + return ; } } diff --git a/IntegrationTests/RCTRootViewIntegrationTestApp.js b/IntegrationTests/RCTRootViewIntegrationTestApp.js index a97873d2c3224e..5fd9e57d932f38 100644 --- a/IntegrationTests/RCTRootViewIntegrationTestApp.js +++ b/IntegrationTests/RCTRootViewIntegrationTestApp.js @@ -1,36 +1,29 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule RCTRootViewIntegrationTestApp + * @format */ -'use strict'; -require('regenerator-runtime/runtime'); +'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); +const React = require('react'); +const ReactNative = require('react-native'); -var { - AppRegistry, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} = ReactNative; +const {AppRegistry, ScrollView, StyleSheet, Text, TouchableOpacity, View} = + ReactNative; /* Keep this list in sync with RCTRootViewIntegrationTests.m */ -var TESTS = [ +const TESTS = [ require('./PropertiesUpdateTest'), require('./ReactContentSizeUpdateTest'), require('./SizeFlexibilityUpdateTest'), ]; -TESTS.forEach( - (test) => AppRegistry.registerComponent(test.displayName, () => test) +TESTS.forEach(test => + AppRegistry.registerComponent(test.displayName, () => test), ); class RCTRootViewIntegrationTestApp extends React.Component { @@ -50,20 +43,18 @@ class RCTRootViewIntegrationTestApp extends React.Component { Click on a test to run it in this shell for easier debugging and - development. Run all tests in the testing environment with cmd+U in + development. Run all tests in the testing environment with cmd+U in Xcode. - {TESTS.map((test) => [ + {TESTS.map(test => [ this.setState({test})} style={styles.row}> - - {test.displayName} - + {test.displayName} , - + , ])} @@ -71,7 +62,7 @@ class RCTRootViewIntegrationTestApp extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { backgroundColor: 'white', marginTop: 40, @@ -89,4 +80,7 @@ var styles = StyleSheet.create({ }, }); -AppRegistry.registerComponent('RCTRootViewIntegrationTestApp', () => RCTRootViewIntegrationTestApp); +AppRegistry.registerComponent( + 'RCTRootViewIntegrationTestApp', + () => RCTRootViewIntegrationTestApp, +); diff --git a/IntegrationTests/ReactContentSizeUpdateTest.js b/IntegrationTests/ReactContentSizeUpdateTest.js index c5f5ae80c246b0..ea00f975c37515 100644 --- a/IntegrationTests/ReactContentSizeUpdateTest.js +++ b/IntegrationTests/ReactContentSizeUpdateTest.js @@ -1,75 +1,89 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * @providesModule ReactContentSizeUpdateTest + * + * @format + * @flow strict-local */ -'use strict'; -var React = require('react'); -var createReactClass = require('create-react-class'); -var ReactNative = require('react-native'); -var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter'); -var Subscribable = require('Subscribable'); -var TimerMixin = require('react-timer-mixin'); +const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter'); +const React = require('react'); +const ReactNative = require('react-native'); + +const {View} = ReactNative; + +const {TestModule} = ReactNative.NativeModules; +import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter'; + +const reactViewWidth = 101; +const reactViewHeight = 102; +const newReactViewWidth = 201; +const newReactViewHeight = 202; -var { View } = ReactNative; +type Props = {||}; -var { TestModule } = ReactNative.NativeModules; +type State = {| + height: number, + width: number, +|}; -var reactViewWidth = 101; -var reactViewHeight = 102; -var newReactViewWidth = 201; -var newReactViewHeight = 202; +class ReactContentSizeUpdateTest extends React.Component { + _timeoutID: ?TimeoutID = null; + _subscription: ?EventSubscription = null; -var ReactContentSizeUpdateTest = createReactClass({ - displayName: 'ReactContentSizeUpdateTest', - mixins: [Subscribable.Mixin, - TimerMixin], + state: State = { + height: reactViewHeight, + width: reactViewWidth, + }; - UNSAFE_componentWillMount: function() { - this.addListenerOn( - RCTNativeAppEventEmitter, + UNSAFE_componentWillMount() { + this._subscription = RCTNativeAppEventEmitter.addListener( 'rootViewDidChangeIntrinsicSize', - this.rootViewDidChangeIntrinsicSize + this.rootViewDidChangeIntrinsicSize, ); - }, + } + + componentDidMount() { + this._timeoutID = setTimeout(() => { + this.updateViewSize(); + }, 1000); + } - getInitialState: function() { - return { - height: reactViewHeight, - width: reactViewWidth, - }; - }, + componentWillUnmount() { + if (this._timeoutID != null) { + clearTimeout(this._timeoutID); + } - updateViewSize: function() { + if (this._subscription != null) { + this._subscription.remove(); + } + } + + updateViewSize() { this.setState({ height: newReactViewHeight, width: newReactViewWidth, }); - }, - - componentDidMount: function() { - this.setTimeout( - () => { this.updateViewSize(); }, - 1000 - ); - }, + } - rootViewDidChangeIntrinsicSize: function(intrinsicSize) { - if (intrinsicSize.height === newReactViewHeight && intrinsicSize.width === newReactViewWidth) { + rootViewDidChangeIntrinsicSize: (intrinsicSize: State) => void = ( + intrinsicSize: State, + ) => { + if ( + intrinsicSize.height === newReactViewHeight && + intrinsicSize.width === newReactViewWidth + ) { TestModule.markTestPassed(true); } - }, + }; - render() { + render(): React.Node { return ( - + ); } -}); - -ReactContentSizeUpdateTest.displayName = 'ReactContentSizeUpdateTest'; +} module.exports = ReactContentSizeUpdateTest; diff --git a/IntegrationTests/SimpleSnapshotTest.js b/IntegrationTests/SimpleSnapshotTest.js index 1c0be2a431d469..27e5d2c12d5454 100644 --- a/IntegrationTests/SimpleSnapshotTest.js +++ b/IntegrationTests/SimpleSnapshotTest.js @@ -1,28 +1,22 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow - * @providesModule SimpleSnapshotTest + * @format + * @flow strict-local */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -/* $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 requestAnimationFrame = require('fbjs/lib/requestAnimationFrame'); +const React = require('react'); +const ReactNative = require('react-native'); -var { - StyleSheet, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const {StyleSheet, View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; -class SimpleSnapshotTest extends React.Component<{}> { +class SimpleSnapshotTest extends React.Component<{...}> { componentDidMount() { if (!TestModule.verifySnapshot) { throw new Error('TestModule.verifySnapshot not defined.'); @@ -30,13 +24,13 @@ class SimpleSnapshotTest extends React.Component<{}> { requestAnimationFrame(() => TestModule.verifySnapshot(this.done)); } - done = (success : boolean) => { + done: (success: boolean) => void = (success: boolean) => { TestModule.markTestPassed(success); }; - render() { + render(): React.Node { return ( - + @@ -44,7 +38,11 @@ class SimpleSnapshotTest extends React.Component<{}> { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + padding: 100, + }, box1: { width: 80, height: 50, diff --git a/IntegrationTests/SizeFlexibilityUpdateTest.js b/IntegrationTests/SizeFlexibilityUpdateTest.js index 679694a0d631ce..79635eaffe52d9 100644 --- a/IntegrationTests/SizeFlexibilityUpdateTest.js +++ b/IntegrationTests/SizeFlexibilityUpdateTest.js @@ -1,45 +1,59 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * @providesModule SizeFlexibilityUpdateTest + * + * @format + * @flow strict-local */ -'use strict'; -var React = require('react'); -var createReactClass = require('create-react-class'); -var ReactNative = require('react-native'); -var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter'); -var Subscribable = require('Subscribable'); -var { View } = ReactNative; +const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter'); +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; + +const {TestModule} = ReactNative.NativeModules; +import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter'; -var { TestModule } = ReactNative.NativeModules; +const reactViewWidth = 111; +const reactViewHeight = 222; -var reactViewWidth = 111; -var reactViewHeight = 222; +let finalState = false; -var finalState = false; +type Props = $ReadOnly<{| + width: boolean, + height: boolean, + both: boolean, + none: boolean, +|}>; -var SizeFlexibilityUpdateTest = createReactClass({ - displayName: 'SizeFlexibilityUpdateTest', - mixins: [Subscribable.Mixin], +class SizeFlexibilityUpdateTest extends React.Component { + _subscription: ?EventSubscription = null; - UNSAFE_componentWillMount: function() { - this.addListenerOn( - RCTNativeAppEventEmitter, + UNSAFE_componentWillMount() { + this._subscription = RCTNativeAppEventEmitter.addListener( 'rootViewDidChangeIntrinsicSize', - this.rootViewDidChangeIntrinsicSize + this.rootViewDidChangeIntrinsicSize, ); - }, + } - markPassed: function() { + componentWillUnmount() { + if (this._subscription != null) { + this._subscription.remove(); + } + } + + markPassed: () => void = () => { TestModule.markTestPassed(true); finalState = true; - }, - - rootViewDidChangeIntrinsicSize: function(intrinsicSize) { + }; + rootViewDidChangeIntrinsicSize: (intrinsicSize: { + height: number, + width: number, + ... + }) => void = (intrinsicSize: {width: number, height: number, ...}) => { if (finalState) { // If a test reaches its final state, it is not expected to do anything more TestModule.markTestPassed(false); @@ -47,38 +61,46 @@ var SizeFlexibilityUpdateTest = createReactClass({ } if (this.props.both) { - if (intrinsicSize.width === reactViewWidth && intrinsicSize.height === reactViewHeight) { + if ( + intrinsicSize.width === reactViewWidth && + intrinsicSize.height === reactViewHeight + ) { this.markPassed(); return; } } if (this.props.height) { - if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height === reactViewHeight) { + if ( + intrinsicSize.width !== reactViewWidth && + intrinsicSize.height === reactViewHeight + ) { this.markPassed(); return; } } if (this.props.width) { - if (intrinsicSize.width === reactViewWidth && intrinsicSize.height !== reactViewHeight) { + if ( + intrinsicSize.width === reactViewWidth && + intrinsicSize.height !== reactViewHeight + ) { this.markPassed(); return; } } if (this.props.none) { - if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height !== reactViewHeight) { + if ( + intrinsicSize.width !== reactViewWidth && + intrinsicSize.height !== reactViewHeight + ) { this.markPassed(); return; } } - }, + }; - render() { - return ( - - ); + render(): React.Node { + return ; } -}); - -SizeFlexibilityUpdateTest.displayName = 'SizeFlexibilityUpdateTest'; +} module.exports = SizeFlexibilityUpdateTest; diff --git a/IntegrationTests/SyncMethodTest.js b/IntegrationTests/SyncMethodTest.js index 6318eaa4f14282..febe02154375f9 100644 --- a/IntegrationTests/SyncMethodTest.js +++ b/IntegrationTests/SyncMethodTest.js @@ -1,33 +1,46 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow - * @providesModule SyncMethodTest + * @format + * @flow strict-local */ -'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { View } = ReactNative; +'use strict'; -const { - TestModule, - RNTesterTestModule, -} = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; +const {TestModule, RNTesterTestModule} = ReactNative.NativeModules; -class SyncMethodTest extends React.Component<{}> { +class SyncMethodTest extends React.Component<{...}> { componentDidMount() { - if (RNTesterTestModule.echoString('test string value') !== 'test string value') { - throw new Error('Something wrong with sync method export'); + if ( + RNTesterTestModule.echoString('test string value') !== 'test string value' + ) { + throw new Error('Something wrong with echoString sync method'); } if (RNTesterTestModule.methodThatReturnsNil() != null) { - throw new Error('Something wrong with sync method export'); + throw new Error('Something wrong with methodThatReturnsNil sync method'); } - TestModule.markTestCompleted(); + let response; + RNTesterTestModule.methodThatCallsCallbackWithString('test', echo => { + response = echo; + }); + requestAnimationFrame(() => { + if (response === 'test') { + TestModule.markTestCompleted(); + } else { + throw new Error( + 'Something wrong with methodThatCallsCallbackWithString sync method, ' + + 'got response ' + + JSON.stringify(response), + ); + } + }); } render(): React.Node { diff --git a/IntegrationTests/TimersTest.js b/IntegrationTests/TimersTest.js index 23da99dc6edbdc..64b3f1233b6a65 100644 --- a/IntegrationTests/TimersTest.js +++ b/IntegrationTests/TimersTest.js @@ -1,164 +1,280 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule TimersTest */ + 'use strict'; -var React = require('react'); -var createReactClass = require('create-react-class'); -var ReactNative = require('react-native'); -/* $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 { - StyleSheet, - Text, - View, -} = ReactNative; -var { TestModule } = ReactNative.NativeModules; - -var TimersTest = createReactClass({ - displayName: 'TimersTest', - mixins: [TimerMixin], - - _nextTest: () => {}, - _interval: -1, - - getInitialState() { - return { - count: 0, - done: false, - }; - }, +const React = require('react'); +const ReactNative = require('react-native'); +const {StyleSheet, Text, View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; + +type Props = $ReadOnly<{||}>; + +type State = {| + count: number, + done: boolean, +|}; + +type ImmediateID = Object; + +class TimersTest extends React.Component { + _nextTest = () => {}; + _interval: ?IntervalID = null; + + _timeoutIDs: Set = new Set(); + _intervalIDs: Set = new Set(); + _immediateIDs: Set = new Set(); + _animationFrameIDs: Set = new Set(); + + state: State = { + count: 0, + done: false, + }; + + setTimeout(fn: () => void, time: number): TimeoutID { + const id = setTimeout(() => { + this._timeoutIDs.delete(id); + fn(); + }, time); + + this._timeoutIDs.add(id); + + return id; + } + + clearTimeout(id: TimeoutID) { + this._timeoutIDs.delete(id); + clearTimeout(id); + } + + setInterval(fn: () => void, time: number): IntervalID { + const id = setInterval(() => { + fn(); + }, time); + + this._intervalIDs.add(id); + + return id; + } + + clearInterval(id: IntervalID) { + this._intervalIDs.delete(id); + clearInterval(id); + } + + setImmediate(fn: () => void): ImmediateID { + const id = setImmediate(() => { + this._immediateIDs.delete(id); + fn(); + }); + + this._immediateIDs.add(id); + + return id; + } + + requestAnimationFrame(fn: () => void): AnimationFrameID { + const id = requestAnimationFrame(() => { + this._animationFrameIDs.delete(id); + fn(); + }); + + this._animationFrameIDs.add(id); + + return id; + } + + cancelAnimationFrame(id: AnimationFrameID): void { + this._animationFrameIDs.delete(id); + cancelAnimationFrame(id); + } componentDidMount() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testSetTimeout0, 1000); - }, + } testSetTimeout0() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testSetTimeout1, 0); - }, + } testSetTimeout1() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testSetTimeout50, 1); - }, + } testSetTimeout50() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testRequestAnimationFrame, 50); - }, + } testRequestAnimationFrame() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.requestAnimationFrame(this.testSetInterval0); - }, + } testSetInterval0() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this._nextTest = this.testSetInterval20; + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this._interval = this.setInterval(this._incrementInterval, 0); - }, + } testSetInterval20() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this._nextTest = this.testSetImmediate; + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this._interval = this.setInterval(this._incrementInterval, 20); - }, + } testSetImmediate() { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setImmediate(this.testClearTimeout0); - }, + } testClearTimeout0() { - var timeout = this.setTimeout(() => this._fail('testClearTimeout0'), 0); + const timeout = this.setTimeout(() => this._fail('testClearTimeout0'), 0); this.clearTimeout(timeout); this.testClearTimeout30(); - }, + } testClearTimeout30() { - var timeout = this.setTimeout(() => this._fail('testClearTimeout30'), 30); + const timeout = this.setTimeout(() => this._fail('testClearTimeout30'), 30); this.clearTimeout(timeout); + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testClearMulti, 50); - }, + } testClearMulti() { - var fails = []; + const fails = []; fails.push(this.setTimeout(() => this._fail('testClearMulti-1'), 20)); fails.push(this.setTimeout(() => this._fail('testClearMulti-2'), 50)); - var delayClear = this.setTimeout(() => this._fail('testClearMulti-3'), 50); + const delayClear = this.setTimeout( + () => this._fail('testClearMulti-3'), + 50, + ); fails.push(this.setTimeout(() => this._fail('testClearMulti-4'), 0)); fails.push(this.setTimeout(() => this._fail('testClearMulti-5'), 10)); - fails.forEach((timeout) => this.clearTimeout(timeout)); + fails.forEach(timeout => this.clearTimeout(timeout)); this.setTimeout(() => this.clearTimeout(delayClear), 20); + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.testOrdering, 50); - }, + } testOrdering() { // Clear timers are set first because it's more likely to uncover bugs. - var fail0; + let fail0; this.setImmediate(() => this.clearTimeout(fail0)); fail0 = this.setTimeout( - () => this._fail('testOrdering-t0, setImmediate should happen before ' + - 'setTimeout 0'), - 0 + () => + this._fail( + 'testOrdering-t0, setImmediate should happen before ' + + 'setTimeout 0', + ), + 0, ); - var failAnim; // This should fail without the t=0 fastpath feature. + let failAnim; // This should fail without the t=0 fastpath feature. this.setTimeout(() => this.cancelAnimationFrame(failAnim), 0); - failAnim = this.requestAnimationFrame( - () => this._fail('testOrdering-Anim, setTimeout 0 should happen before ' + - 'requestAnimationFrame') + failAnim = this.requestAnimationFrame(() => + this._fail( + 'testOrdering-Anim, setTimeout 0 should happen before ' + + 'requestAnimationFrame', + ), ); - var fail25; - this.setTimeout(() => { this.clearTimeout(fail25); }, 20); + let fail25; + this.setTimeout(() => { + this.clearTimeout(fail25); + }, 20); fail25 = this.setTimeout( - () => this._fail('testOrdering-t25, setTimeout 20 should happen before ' + - 'setTimeout 25'), - 25 + () => + this._fail( + 'testOrdering-t25, setTimeout 20 should happen before ' + + 'setTimeout 25', + ), + 25, ); + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this.setTimeout(this.done, 50); - }, + } done() { this.setState({done: true}, () => { TestModule.markTestCompleted(); }); - }, + } - render() { + componentWillUnmount() { + for (const timeoutID of this._timeoutIDs) { + clearTimeout(timeoutID); + } + + for (const intervalID of this._intervalIDs) { + clearInterval(intervalID); + } + + for (const requestAnimationFrameID of this._animationFrameIDs) { + cancelAnimationFrame(requestAnimationFrameID); + } + + for (const immediateID of this._immediateIDs) { + clearImmediate(immediateID); + } + + this._timeoutIDs = new Set(); + this._intervalIDs = new Set(); + this._animationFrameIDs = new Set(); + this._immediateIDs = new Set(); + + if (this._interval != null) { + clearInterval(this._interval); + this._interval = null; + } + } + + render(): React.Node { return ( - {this.constructor.displayName + ': \n'} + {this.constructor.name + ': \n'} Intervals: {this.state.count + '\n'} {this.state.done ? 'Done' : 'Testing...'} ); - }, + } _incrementInterval() { if (this.state.count > 3) { throw new Error('interval incremented past end.'); } if (this.state.count === 3) { - this.clearInterval(this._interval); + if (this._interval != null) { + this.clearInterval(this._interval); + this._interval = null; + } + // $FlowFixMe[method-unbinding] this.setState({count: 0}, this._nextTest); return; } this.setState({count: this.state.count + 1}); - }, + } - _fail(caller : string) : void { + _fail(caller: string): void { throw new Error('_fail called by ' + caller); - }, -}); + } +} -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { backgroundColor: 'white', padding: 40, @@ -166,5 +282,4 @@ var styles = StyleSheet.create({ }); TimersTest.displayName = 'TimersTest'; - module.exports = TimersTest; diff --git a/IntegrationTests/WebSocketTest.js b/IntegrationTests/WebSocketTest.js index 6e459f913babc3..6fbcbfb6cd8fa2 100644 --- a/IntegrationTests/WebSocketTest.js +++ b/IntegrationTests/WebSocketTest.js @@ -1,46 +1,37 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow - * @providesModule WebSocketTest */ + 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var { View } = ReactNative; -var { TestModule } = ReactNative.NativeModules; +const React = require('react'); +const ReactNative = require('react-native'); +const {View} = ReactNative; +const {TestModule} = ReactNative.NativeModules; const DEFAULT_WS_URL = 'ws://localhost:5555/'; -const WS_EVENTS = [ - 'close', - 'error', - 'message', - 'open', -]; -const WS_STATES = [ - /* 0 */ 'CONNECTING', - /* 1 */ 'OPEN', - /* 2 */ 'CLOSING', - /* 3 */ 'CLOSED', -]; +const WS_EVENTS = ['close', 'error', 'message', 'open']; type State = { - url: string; - fetchStatus: ?string; - socket: ?WebSocket; - socketState: ?number; - lastSocketEvent: ?string; - lastMessage: ?string | ?ArrayBuffer; - testMessage: string; - testExpectedResponse: string; + url: string, + fetchStatus: ?string, + socket: ?WebSocket, + socketState: ?number, + lastSocketEvent: ?string, + lastMessage: ?string | ?ArrayBuffer, + testMessage: string, + testExpectedResponse: string, + ... }; -class WebSocketTest extends React.Component<{}, State> { +class WebSocketTest extends React.Component<{...}, State> { state: State = { url: DEFAULT_WS_URL, fetchStatus: null, @@ -49,13 +40,12 @@ class WebSocketTest extends React.Component<{}, State> { lastSocketEvent: null, lastMessage: null, testMessage: 'testMessage', - testExpectedResponse: 'testMessage_response' + testExpectedResponse: 'testMessage_response', }; _waitFor = (condition: any, timeout: any, callback: any) => { - var remaining = timeout; - var t; - var timeoutFunction = function() { + let remaining = timeout; + const timeoutFunction = function () { if (condition()) { callback(true); return; @@ -64,11 +54,11 @@ class WebSocketTest extends React.Component<{}, State> { if (remaining === 0) { callback(false); } else { - t = setTimeout(timeoutFunction,1000); + setTimeout(timeoutFunction, 1000); } }; - t = setTimeout(timeoutFunction,1000); - } + setTimeout(timeoutFunction, 1000); + }; _connect = () => { const socket = new WebSocket(this.state.url); @@ -81,11 +71,11 @@ class WebSocketTest extends React.Component<{}, State> { _socketIsConnected = () => { return this.state.socketState === 1; //'OPEN' - } + }; _socketIsDisconnected = () => { return this.state.socketState === 3; //'CLOSED' - } + }; _disconnect = () => { if (!this.state.socket) { @@ -117,44 +107,41 @@ class WebSocketTest extends React.Component<{}, State> { }; _receivedTestExpectedResponse = () => { - return (this.state.lastMessage === this.state.testExpectedResponse); + return this.state.lastMessage === this.state.testExpectedResponse; }; componentDidMount() { this.testConnect(); } - testConnect = () => { - var component = this; - component._connect(); - component._waitFor(component._socketIsConnected, 5, function(connectSucceeded) { + testConnect: () => void = () => { + this._connect(); + this._waitFor(this._socketIsConnected, 5, connectSucceeded => { if (!connectSucceeded) { TestModule.markTestPassed(false); return; } - component.testSendAndReceive(); + this.testSendAndReceive(); }); - } + }; - testSendAndReceive = () => { - var component = this; - component._sendTestMessage(); - component._waitFor(component._receivedTestExpectedResponse, 5, function(messageReceived) { + testSendAndReceive: () => void = () => { + this._sendTestMessage(); + this._waitFor(this._receivedTestExpectedResponse, 5, messageReceived => { if (!messageReceived) { TestModule.markTestPassed(false); return; } - component.testDisconnect(); + this.testDisconnect(); }); - } + }; - testDisconnect = () => { - var component = this; - component._disconnect(); - component._waitFor(component._socketIsDisconnected, 5, function(disconnectSucceeded) { + testDisconnect: () => void = () => { + this._disconnect(); + this._waitFor(this._socketIsDisconnected, 5, disconnectSucceeded => { TestModule.markTestPassed(disconnectSucceeded); }); - } + }; render(): React.Node { return ; diff --git a/IntegrationTests/WebViewTest.js b/IntegrationTests/WebViewTest.js deleted file mode 100644 index a41e244b58be3a..00000000000000 --- a/IntegrationTests/WebViewTest.js +++ /dev/null @@ -1,59 +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 WebViewTest - */ -'use strict'; - -var React = require('react'); -var ReactNative = require('react-native'); -var { - WebView, -} = ReactNative; - -var { TestModule } = ReactNative.NativeModules; - -class WebViewTest extends React.Component { - - render() { - var firstMessageReceived = false; - var secondMessageReceived = false; - function processMessage(e) { - var message = e.nativeEvent.data; - if (message === 'First') {firstMessageReceived = true;} - if (message === 'Second') {secondMessageReceived = true;} - - // got both messages - if (firstMessageReceived && secondMessageReceived) {TestModule.markTestPassed(true);} - // wait for next message - else if (firstMessageReceived && !secondMessageReceived) {return;} - // first message got lost - else if (!firstMessageReceived && secondMessageReceived) {throw new Error('First message got lost');} - } - var html = 'Hello world' - + ''; - - // fail if messages didn't get through; - window.setTimeout(function() { throw new Error(firstMessageReceived ? 'Both messages got lost' : 'Second message got lost');}, 10000); - - var source = { - html: html, - }; - - return ( - - ); - } -} - -WebViewTest.displayName = 'WebViewTest'; - -module.exports = WebViewTest; diff --git a/IntegrationTests/launchWebSocketServer.command b/IntegrationTests/launchWebSocketServer.command deleted file mode 100755 index a6531c5f8d78b2..00000000000000 --- a/IntegrationTests/launchWebSocketServer.command +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -# Set terminal title -echo -en "\033]0;Web Socket Test Server\a" -clear - -THIS_DIR=$(dirname "$0") -pushd "$THIS_DIR" -./websocket_integration_test_server.js -popd - -echo "Process terminated. Press to close the window" -read diff --git a/IntegrationTests/launchWebSocketServer.sh b/IntegrationTests/launchWebSocketServer.sh new file mode 100755 index 00000000000000..cc736a5ceffd42 --- /dev/null +++ b/IntegrationTests/launchWebSocketServer.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) +pushd "$THIS_DIR" || exit +./websocket_integration_test_server.js +popd || exit + +echo "Process terminated." diff --git a/IntegrationTests/websocket_integration_test_server.js b/IntegrationTests/websocket_integration_test_server.js index e338d85ae3b89d..0f0b597b63a74c 100755 --- a/IntegrationTests/websocket_integration_test_server.js +++ b/IntegrationTests/websocket_integration_test_server.js @@ -1,21 +1,18 @@ #!/usr/bin/env node - /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow - * @providesModule websocket_integration_test_server + * @format */ + 'use strict'; /* eslint-env node */ -/* $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. */ const WebSocket = require('ws'); console.log(`\ @@ -27,14 +24,13 @@ An incoming message of 'exit' will shut down the server. `); const server = new WebSocket.Server({port: 5555}); -server.on('connection', (ws) => { - ws.on('message', (message) => { +server.on('connection', ws => { + ws.on('message', message => { console.log('Received message:', message); if (message === 'exit') { console.log('WebSocket integration test server exit'); process.exit(0); } - console.log('Cookie:', ws.upgradeReq.headers.cookie); ws.send(message + '_response'); }); diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index e5918c01da7009..00000000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,206 +0,0 @@ -import groovy.json.JsonSlurperClassic - -def runPipeline() { - try { - ansiColor('xterm') { - runStages(); - } - } catch(err) { - echo "Error: ${err}" - currentBuild.result = "FAILED" - } -} - -def pullDockerImage(imageName) { - def result = sh(script: "docker pull ${imageName}", returnStatus: true) - - if (result != 0) { - throw new Exception("Failed to pull image[${imageName}]") - } -} - -def buildDockerfile(dockerfilePath = "Dockerfile", imageName) { - def buildCmd = "docker build -f ${dockerfilePath} -t ${imageName} ." - echo "${buildCmd}" - - def result = sh(script: buildCmd, returnStatus: true) - - if (result != 0) { - throw new Exception("Failed to build image[${imageName}] from '${dockerfilePath}'") - } -} - -def runCmdOnDockerImage(imageName, cmd, run_opts = '') { - def result = sh(script: "docker run ${run_opts} -i ${imageName} sh -c '${cmd}'", returnStatus: true) - - if(result != 0) { - throw new Exception("Failed to run cmd[${cmd}] on image[${imageName}]") - } -} - -def calculateGithubInfo() { - return [ - branch: env.BRANCH_NAME, - sha: sh(returnStdout: true, script: 'git rev-parse HEAD').trim(), - tag: null, - isPR: "${env.CHANGE_URL}".contains('/pull/') - ] -} - -def getParallelInstrumentationTests(testDir, parallelCount, imageName) { - def integrationTests = [:] - def testCount = sh(script: "ls ${testDir} | wc -l", returnStdout: true).trim().toInteger() - def testPerParallel = testCount.intdiv(parallelCount) + 1 - - def ignoredTests = 'CatalystNativeJavaToJSReturnValuesTestCase|CatalystUIManagerTestCase|CatalystMeasureLayoutTest|CatalystNativeJavaToJSArgumentsTestCase|CatalystNativeJSToJavaParametersTestCase|ReactScrollViewTestCase|ReactHorizontalScrollViewTestCase|ViewRenderingTestCase'; - - for (def x = 0; (x*testPerParallel) < testCount; x++) { - def offset = x - integrationTests["android integration tests: ${offset}"] = { - run: { - runCmdOnDockerImage(imageName, "bash /app/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --offset=${offset} --count=${testPerParallel} --ignore=\"${ignoredTests}\"", '--privileged --rm') - } - } - } - - return integrationTests -} - -def runStages() { - def buildInfo = [ - image: [ - name: "facebook/react-native", - tag: null - ], - scm: [ - branch: null, - sha: null, - tag: null, - isPR: false - ] - ] - - node { - def jsDockerBuild, androidDockerBuild - def jsTag, androidTag, jsImageName, androidImageName, parallelInstrumentationTests - - try { - stage('Setup') { - parallel( - 'pull images': { - pullDockerImage('containership/android-base:latest') - } - ) - } - - stage('Build') { - checkout scm - - def githubInfo = calculateGithubInfo() - buildInfo.scm.branch = githubInfo.branch - buildInfo.scm.sha = githubInfo.sha - buildInfo.scm.tag = githubInfo.tag - buildInfo.scm.isPR = githubInfo.isPR - buildInfo.image.tag = "${buildInfo.scm.sha}-${env.BUILD_TAG.replace(" ", "-").replace("/", "-").replace("%2F", "-")}" - - jsTag = "${buildInfo.image.tag}" - androidTag = "${buildInfo.image.tag}" - jsImageName = "${buildInfo.image.name}-js:${jsTag}" - androidImageName = "${buildInfo.image.name}-android:${androidTag}" - - parallelInstrumentationTests = getParallelInstrumentationTests('./ReactAndroid/src/androidTest/java/com/facebook/react/tests', 3, androidImageName) - - parallel( - 'javascript build': { - jsDockerBuild = docker.build("${jsImageName}", "-f ContainerShip/Dockerfile.javascript .") - }, - 'android build': { - androidDockerBuild = docker.build("${androidImageName}", "-f ContainerShip/Dockerfile.android .") - } - ) - - } - - stage('Tests JS') { - try { - parallel( - 'javascript flow': { - runCmdOnDockerImage(jsImageName, 'yarn run flow -- check', '--rm') - }, - 'javascript tests': { - runCmdOnDockerImage(jsImageName, 'yarn test --maxWorkers=4', '--rm') - }, - 'documentation tests': { - runCmdOnDockerImage(jsImageName, 'cd website && yarn test', '--rm') - }, - 'documentation generation': { - runCmdOnDockerImage(jsImageName, 'cd website && node ./server/generate.js', '--rm') - } - ) - } catch(e) { - currentBuild.result = "FAILED" - echo "Test JS Stage Error: ${e}" - } - } - - stage('Tests Android') { - try { - parallel( - 'android unit tests': { - runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-android-docker-unit-tests.sh', '--privileged --rm') - }, - 'android e2e tests': { - runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-ci-e2e-tests.sh --android --js', '--privileged --rm') - } - ) - } catch(e) { - currentBuild.result = "FAILED" - echo "Tests Android Stage Error: ${e}" - } - } - - stage('Tests Android Instrumentation') { - // run all tests in parallel - try { - parallel(parallelInstrumentationTests) - } catch(e) { - currentBuild.result = "FAILED" - echo "Tests Android Instrumentation Stage Error: ${e}" - } - } - - stage('Cleanup') { - cleanupImage(jsDockerBuild) - cleanupImage(androidDockerBuild) - } - } catch(err) { - cleanupImage(jsDockerBuild) - cleanupImage(androidDockerBuild) - - throw err - } - } - -} - -def isMasterBranch() { - return env.GIT_BRANCH == 'master' -} - -def gitCommit() { - return sh(returnStdout: true, script: 'git rev-parse HEAD').trim() -} - -def cleanupImage(image) { - if (image) { - try { - sh "docker ps -a | awk '{ print \$1,\$2 }' | grep ${image.id} | awk '{print \$1 }' | xargs -I {} docker rm {}" - sh "docker rmi -f ${image.id}" - } catch(e) { - echo "Error cleaning up ${image.id}" - echo "${e}" - } - } -} - -runPipeline() diff --git a/LICENSE b/LICENSE index 9e051010d82dc6..b93be90515ccd0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-present, Facebook, Inc. +Copyright (c) Meta Platforms, Inc. and affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Libraries/ART/ART.xcodeproj/project.pbxproj b/Libraries/ART/ART.xcodeproj/project.pbxproj deleted file mode 100644 index 75eec5411710d8..00000000000000 --- a/Libraries/ART/ART.xcodeproj/project.pbxproj +++ /dev/null @@ -1,491 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */; }; - 0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE01AF0549300FF9E5C /* ARTNode.m */; }; - 0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */; }; - 0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE41AF0549300FF9E5C /* ARTShape.m */; }; - 0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */; }; - 0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE81AF0549300FF9E5C /* ARTText.m */; }; - 0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */; }; - 0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */; }; - 0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */; }; - 0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */; }; - 0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */; }; - 0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */; }; - 0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */; }; - 0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */; }; - 0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */; }; - 0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */; }; - 0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */; }; - 0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */; }; - 325CF7AD1E5F2ABA00AC9606 /* ARTBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */; }; - 325CF7AE1E5F2ABA00AC9606 /* ARTLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */; }; - 325CF7AF1E5F2ABA00AC9606 /* ARTPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */; }; - 325CF7B01E5F2ABA00AC9606 /* ARTRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */; }; - 325CF7B11E5F2ABA00AC9606 /* ARTSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */; }; - 325CF7B21E5F2ABA00AC9606 /* ARTGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */; }; - 325CF7B31E5F2ABA00AC9606 /* ARTNodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */; }; - 325CF7B41E5F2ABA00AC9606 /* ARTRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */; }; - 325CF7B51E5F2ABA00AC9606 /* ARTShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */; }; - 325CF7B61E5F2ABA00AC9606 /* ARTSurfaceViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */; }; - 325CF7B71E5F2ABA00AC9606 /* ARTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */; }; - 325CF7B81E5F2ABA00AC9606 /* ARTGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */; }; - 325CF7B91E5F2ABA00AC9606 /* ARTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE01AF0549300FF9E5C /* ARTNode.m */; }; - 325CF7BA1E5F2ABA00AC9606 /* ARTRenderable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */; }; - 325CF7BB1E5F2ABA00AC9606 /* ARTShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE41AF0549300FF9E5C /* ARTShape.m */; }; - 325CF7BC1E5F2ABA00AC9606 /* ARTSurfaceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */; }; - 325CF7BD1E5F2ABA00AC9606 /* ARTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE81AF0549300FF9E5C /* ARTText.m */; }; - 325CF7BE1E5F2ABA00AC9606 /* RCTConvert+ART.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 323A12851E5F266B004975B8 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 0CF68AC11AF0540F00FF9E5C /* libART.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libART.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTCGFloatArray.h; sourceTree = ""; }; - 0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTContainer.h; sourceTree = ""; }; - 0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroup.h; sourceTree = ""; }; - 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroup.m; sourceTree = ""; }; - 0CF68ADF1AF0549300FF9E5C /* ARTNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNode.h; sourceTree = ""; }; - 0CF68AE01AF0549300FF9E5C /* ARTNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNode.m; sourceTree = ""; }; - 0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderable.h; sourceTree = ""; }; - 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderable.m; sourceTree = ""; }; - 0CF68AE31AF0549300FF9E5C /* ARTShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShape.h; sourceTree = ""; }; - 0CF68AE41AF0549300FF9E5C /* ARTShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShape.m; sourceTree = ""; }; - 0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceView.h; sourceTree = ""; }; - 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceView.m; sourceTree = ""; }; - 0CF68AE71AF0549300FF9E5C /* ARTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTText.h; sourceTree = ""; }; - 0CF68AE81AF0549300FF9E5C /* ARTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTText.m; sourceTree = ""; }; - 0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextFrame.h; sourceTree = ""; }; - 0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTBrush.h; sourceTree = ""; }; - 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTBrush.m; sourceTree = ""; }; - 0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLinearGradient.h; sourceTree = ""; }; - 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLinearGradient.m; sourceTree = ""; }; - 0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPattern.h; sourceTree = ""; }; - 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPattern.m; sourceTree = ""; }; - 0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRadialGradient.h; sourceTree = ""; }; - 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRadialGradient.m; sourceTree = ""; }; - 0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSolidColor.h; sourceTree = ""; }; - 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSolidColor.m; sourceTree = ""; }; - 0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+ART.h"; sourceTree = ""; }; - 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+ART.m"; sourceTree = ""; }; - 0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroupManager.h; sourceTree = ""; }; - 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroupManager.m; sourceTree = ""; }; - 0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNodeManager.h; sourceTree = ""; }; - 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNodeManager.m; sourceTree = ""; }; - 0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderableManager.h; sourceTree = ""; }; - 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderableManager.m; sourceTree = ""; }; - 0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShapeManager.h; sourceTree = ""; }; - 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShapeManager.m; sourceTree = ""; }; - 0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceViewManager.h; sourceTree = ""; }; - 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceViewManager.m; sourceTree = ""; }; - 0CF68B031AF0549300FF9E5C /* ARTTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextManager.h; sourceTree = ""; }; - 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTTextManager.m; sourceTree = ""; }; - 323A12871E5F266B004975B8 /* libART-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libART-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 0CF68ABE1AF0540F00FF9E5C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 323A12841E5F266B004975B8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0CF68AB81AF0540F00FF9E5C = { - isa = PBXGroup; - children = ( - 0CF68AEA1AF0549300FF9E5C /* Brushes */, - 0CF68AF81AF0549300FF9E5C /* ViewManagers */, - 0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */, - 0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */, - 0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */, - 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */, - 0CF68ADF1AF0549300FF9E5C /* ARTNode.h */, - 0CF68AE01AF0549300FF9E5C /* ARTNode.m */, - 0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */, - 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */, - 0CF68AE31AF0549300FF9E5C /* ARTShape.h */, - 0CF68AE41AF0549300FF9E5C /* ARTShape.m */, - 0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */, - 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */, - 0CF68AE71AF0549300FF9E5C /* ARTText.h */, - 0CF68AE81AF0549300FF9E5C /* ARTText.m */, - 0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */, - 0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */, - 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */, - 0CF68AC21AF0540F00FF9E5C /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 0CF68AC21AF0540F00FF9E5C /* Products */ = { - isa = PBXGroup; - children = ( - 0CF68AC11AF0540F00FF9E5C /* libART.a */, - 323A12871E5F266B004975B8 /* libART-tvOS.a */, - ); - name = Products; - sourceTree = ""; - }; - 0CF68AEA1AF0549300FF9E5C /* Brushes */ = { - isa = PBXGroup; - children = ( - 0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */, - 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */, - 0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */, - 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */, - 0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */, - 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */, - 0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */, - 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */, - 0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */, - 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */, - ); - path = Brushes; - sourceTree = ""; - }; - 0CF68AF81AF0549300FF9E5C /* ViewManagers */ = { - isa = PBXGroup; - children = ( - 0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */, - 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */, - 0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */, - 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */, - 0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */, - 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */, - 0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */, - 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */, - 0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */, - 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */, - 0CF68B031AF0549300FF9E5C /* ARTTextManager.h */, - 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */, - ); - path = ViewManagers; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 0CF68AC01AF0540F00FF9E5C /* ART */ = { - isa = PBXNativeTarget; - buildConfigurationList = 0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */; - buildPhases = ( - 0CF68ABD1AF0540F00FF9E5C /* Sources */, - 0CF68ABE1AF0540F00FF9E5C /* Frameworks */, - 0CF68ABF1AF0540F00FF9E5C /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = ART; - productName = ART; - productReference = 0CF68AC11AF0540F00FF9E5C /* libART.a */; - productType = "com.apple.product-type.library.static"; - }; - 323A12861E5F266B004975B8 /* ART-tvOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 323A128D1E5F266B004975B8 /* Build configuration list for PBXNativeTarget "ART-tvOS" */; - buildPhases = ( - 323A12831E5F266B004975B8 /* Sources */, - 323A12841E5F266B004975B8 /* Frameworks */, - 323A12851E5F266B004975B8 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "ART-tvOS"; - productName = "ART-tvOS"; - productReference = 323A12871E5F266B004975B8 /* libART-tvOS.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 0CF68AB91AF0540F00FF9E5C /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0620; - TargetAttributes = { - 0CF68AC01AF0540F00FF9E5C = { - CreatedOnToolsVersion = 6.2; - }; - 323A12861E5F266B004975B8 = { - CreatedOnToolsVersion = 6.2; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = 0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 0CF68AB81AF0540F00FF9E5C; - productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 0CF68AC01AF0540F00FF9E5C /* ART */, - 323A12861E5F266B004975B8 /* ART-tvOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 0CF68ABD1AF0540F00FF9E5C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */, - 0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */, - 0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */, - 0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */, - 0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */, - 0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */, - 0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */, - 0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */, - 0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */, - 0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */, - 0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */, - 0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */, - 0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */, - 0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */, - 0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */, - 0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */, - 0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */, - 0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 323A12831E5F266B004975B8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 325CF7B71E5F2ABA00AC9606 /* ARTTextManager.m in Sources */, - 325CF7B21E5F2ABA00AC9606 /* ARTGroupManager.m in Sources */, - 325CF7AF1E5F2ABA00AC9606 /* ARTPattern.m in Sources */, - 325CF7BD1E5F2ABA00AC9606 /* ARTText.m in Sources */, - 325CF7B31E5F2ABA00AC9606 /* ARTNodeManager.m in Sources */, - 325CF7B81E5F2ABA00AC9606 /* ARTGroup.m in Sources */, - 325CF7B41E5F2ABA00AC9606 /* ARTRenderableManager.m in Sources */, - 325CF7BC1E5F2ABA00AC9606 /* ARTSurfaceView.m in Sources */, - 325CF7B01E5F2ABA00AC9606 /* ARTRadialGradient.m in Sources */, - 325CF7B61E5F2ABA00AC9606 /* ARTSurfaceViewManager.m in Sources */, - 325CF7BB1E5F2ABA00AC9606 /* ARTShape.m in Sources */, - 325CF7BA1E5F2ABA00AC9606 /* ARTRenderable.m in Sources */, - 325CF7BE1E5F2ABA00AC9606 /* RCTConvert+ART.m in Sources */, - 325CF7B91E5F2ABA00AC9606 /* ARTNode.m in Sources */, - 325CF7B11E5F2ABA00AC9606 /* ARTSolidColor.m in Sources */, - 325CF7AE1E5F2ABA00AC9606 /* ARTLinearGradient.m in Sources */, - 325CF7AD1E5F2ABA00AC9606 /* ARTBrush.m in Sources */, - 325CF7B51E5F2ABA00AC9606 /* ARTShapeManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 0CF68AD31AF0540F00FF9E5C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 0CF68AD41AF0540F00FF9E5C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 0CF68AD61AF0540F00FF9E5C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 0CF68AD71AF0540F00FF9E5C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 323A128E1E5F266B004975B8 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.2; - }; - name = Debug; - }; - 323A128F1E5F266B004975B8 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_NO_COMMON_BLOCKS = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.2; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 0CF68AD31AF0540F00FF9E5C /* Debug */, - 0CF68AD41AF0540F00FF9E5C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 0CF68AD61AF0540F00FF9E5C /* Debug */, - 0CF68AD71AF0540F00FF9E5C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 323A128D1E5F266B004975B8 /* Build configuration list for PBXNativeTarget "ART-tvOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 323A128E1E5F266B004975B8 /* Debug */, - 323A128F1E5F266B004975B8 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 0CF68AB91AF0540F00FF9E5C /* Project object */; -} diff --git a/Libraries/ART/ARTCGFloatArray.h b/Libraries/ART/ARTCGFloatArray.h deleted file mode 100644 index 72286a50527e84..00000000000000 --- a/Libraries/ART/ARTCGFloatArray.h +++ /dev/null @@ -1,18 +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. - */ - -// A little helper to make sure we have the right memory allocation ready for use. -// We assume that we will only this in one place so no reference counting is necessary. -// Needs to be freed when dealloced. - -// This is fragile since this relies on these values not getting reused. Consider -// wrapping these in an Obj-C class or some ARC hackery to get refcounting. - -typedef struct { - size_t count; - CGFloat *array; -} ARTCGFloatArray; diff --git a/Libraries/ART/ARTContainer.h b/Libraries/ART/ARTContainer.h deleted file mode 100644 index 532145825bd74b..00000000000000 --- a/Libraries/ART/ARTContainer.h +++ /dev/null @@ -1,16 +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. - */ - -#import - -@protocol ARTContainer - -// This is used as a hook for child to mark it's parent as dirty. -// This bubbles up to the root which gets marked as dirty. -- (void)invalidate; - -@end diff --git a/Libraries/ART/ARTGroup.h b/Libraries/ART/ARTGroup.h deleted file mode 100644 index d9dcb48c87b180..00000000000000 --- a/Libraries/ART/ARTGroup.h +++ /dev/null @@ -1,17 +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. - */ - -#import - -#import "ARTContainer.h" -#import "ARTNode.h" - -@interface ARTGroup : ARTNode - -@property (nonatomic, assign) CGRect clipping; - -@end diff --git a/Libraries/ART/ARTGroup.m b/Libraries/ART/ARTGroup.m deleted file mode 100644 index 1bc70725c030e8..00000000000000 --- a/Libraries/ART/ARTGroup.m +++ /dev/null @@ -1,24 +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. - */ - -#import "ARTGroup.h" - -@implementation ARTGroup - -- (void)renderLayerTo:(CGContextRef)context -{ - - if (!CGRectIsEmpty(self.clipping)) { - CGContextClipToRect(context, self.clipping); - } - - for (ARTNode *node in self.subviews) { - [node renderTo:context]; - } -} - -@end diff --git a/Libraries/ART/ARTNode.h b/Libraries/ART/ARTNode.h deleted file mode 100644 index 8b66c205ef2440..00000000000000 --- a/Libraries/ART/ARTNode.h +++ /dev/null @@ -1,30 +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. - */ - -#import - -/** - * ART nodes are implemented as empty UIViews but this is just an implementation detail to fit - * into the existing view management. They should also be shadow views and painted on a background - * thread. - */ - -@interface ARTNode : UIView - -@property (nonatomic, assign) CGFloat opacity; - -- (void)invalidate; -- (void)renderTo:(CGContextRef)context; - -/** - * renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity - * specified, then composite that onto the context. renderLayerTo always draws at opacity=1. - * @abstract - */ -- (void)renderLayerTo:(CGContextRef)context; - -@end diff --git a/Libraries/ART/ARTNode.m b/Libraries/ART/ARTNode.m deleted file mode 100644 index b27b014b9e7d0e..00000000000000 --- a/Libraries/ART/ARTNode.m +++ /dev/null @@ -1,80 +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. - */ - -#import "ARTNode.h" - -#import "ARTContainer.h" - -@implementation ARTNode - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - [self insertSubview:subview atIndex:atIndex]; - [self invalidate]; -} - -- (void)removeReactSubview:(UIView *)subview -{ - [super removeReactSubview:subview]; - [self invalidate]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are inserted by insertReactSubview: -} - -- (void)setOpacity:(CGFloat)opacity -{ - [self invalidate]; - _opacity = opacity; -} - -- (void)setTransform:(CGAffineTransform)transform -{ - [self invalidate]; - super.transform = transform; -} - -- (void)invalidate -{ - id container = (id)self.superview; - [container invalidate]; -} - -- (void)renderTo:(CGContextRef)context -{ - if (self.opacity <= 0) { - // Nothing to paint - return; - } - if (self.opacity >= 1) { - // Just paint at full opacity - CGContextSaveGState(context); - CGContextConcatCTM(context, self.transform); - CGContextSetAlpha(context, 1); - [self renderLayerTo:context]; - CGContextRestoreGState(context); - return; - } - // This needs to be painted on a layer before being composited. - CGContextSaveGState(context); - CGContextConcatCTM(context, self.transform); - CGContextSetAlpha(context, self.opacity); - CGContextBeginTransparencyLayer(context, NULL); - [self renderLayerTo:context]; - CGContextEndTransparencyLayer(context); - CGContextRestoreGState(context); -} - -- (void)renderLayerTo:(CGContextRef)context -{ - // abstract -} - -@end diff --git a/Libraries/ART/ARTRenderable.h b/Libraries/ART/ARTRenderable.h deleted file mode 100644 index e5735a1fbd81ef..00000000000000 --- a/Libraries/ART/ARTRenderable.h +++ /dev/null @@ -1,23 +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. - */ - -#import - -#import "ARTBrush.h" -#import "ARTCGFloatArray.h" -#import "ARTNode.h" - -@interface ARTRenderable : ARTNode - -@property (nonatomic, strong) ARTBrush *fill; -@property (nonatomic, assign) CGColorRef stroke; -@property (nonatomic, assign) CGFloat strokeWidth; -@property (nonatomic, assign) CGLineCap strokeCap; -@property (nonatomic, assign) CGLineJoin strokeJoin; -@property (nonatomic, assign) ARTCGFloatArray strokeDash; - -@end diff --git a/Libraries/ART/ARTRenderable.m b/Libraries/ART/ARTRenderable.m deleted file mode 100644 index d7a3115586f3dd..00000000000000 --- a/Libraries/ART/ARTRenderable.m +++ /dev/null @@ -1,87 +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. - */ - -#import "ARTRenderable.h" - -@implementation ARTRenderable - -- (void)setFill:(ARTBrush *)fill -{ - [self invalidate]; - _fill = fill; -} - -- (void)setStroke:(CGColorRef)stroke -{ - if (stroke == _stroke) { - return; - } - [self invalidate]; - CGColorRelease(_stroke); - _stroke = CGColorRetain(stroke); -} - -- (void)setStrokeWidth:(CGFloat)strokeWidth -{ - [self invalidate]; - _strokeWidth = strokeWidth; -} - -- (void)setStrokeCap:(CGLineCap)strokeCap -{ - [self invalidate]; - _strokeCap = strokeCap; -} - -- (void)setStrokeJoin:(CGLineJoin)strokeJoin -{ - [self invalidate]; - _strokeJoin = strokeJoin; -} - -- (void)setStrokeDash:(ARTCGFloatArray)strokeDash -{ - if (strokeDash.array == _strokeDash.array) { - return; - } - if (_strokeDash.array) { - free(_strokeDash.array); - } - [self invalidate]; - _strokeDash = strokeDash; -} - -- (void)dealloc -{ - CGColorRelease(_stroke); - if (_strokeDash.array) { - free(_strokeDash.array); - } -} - -- (void)renderTo:(CGContextRef)context -{ - if (self.opacity <= 0 || self.opacity >= 1 || (self.fill && self.stroke)) { - // If we have both fill and stroke, we will need to paint this using normal compositing - [super renderTo: context]; - return; - } - // This is a terminal with only one painting. Therefore we don't need to paint this - // off-screen. We can just composite it straight onto the buffer. - CGContextSaveGState(context); - CGContextConcatCTM(context, self.transform); - CGContextSetAlpha(context, self.opacity); - [self renderLayerTo:context]; - CGContextRestoreGState(context); -} - -- (void)renderLayerTo:(CGContextRef)context -{ - // abstract -} - -@end diff --git a/Libraries/ART/ARTSerializablePath.js b/Libraries/ART/ARTSerializablePath.js deleted file mode 100644 index 7f52bc6540eda2..00000000000000 --- a/Libraries/ART/ARTSerializablePath.js +++ /dev/null @@ -1,74 +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 ARTSerializablePath - */ -'use strict'; - -// TODO: Move this into an ART mode called "serialized" or something - -var Class = require('art/core/class.js'); -var Path = require('art/core/path.js'); - -var MOVE_TO = 0; -var CLOSE = 1; -var LINE_TO = 2; -var CURVE_TO = 3; -var ARC = 4; - -var SerializablePath = Class(Path, { - - initialize: function(path) { - this.reset(); - if (path instanceof SerializablePath) { - this.path = path.path.slice(0); - } else if (path) { - if (path.applyToPath) { - path.applyToPath(this); - } else { - this.push(path); - } - } - }, - - onReset: function() { - this.path = []; - }, - - onMove: function(sx, sy, x, y) { - this.path.push(MOVE_TO, x, y); - }, - - onLine: function(sx, sy, x, y) { - this.path.push(LINE_TO, x, y); - }, - - onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) { - this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y); - }, - - _arcToBezier: Path.prototype.onArc, - - onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) { - if (rx !== ry || rotation) { - return this._arcToBezier( - sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation - ); - } - this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1); - }, - - onClose: function() { - this.path.push(CLOSE); - }, - - toJSON: function() { - return this.path; - } - -}); - -module.exports = SerializablePath; diff --git a/Libraries/ART/ARTShape.h b/Libraries/ART/ARTShape.h deleted file mode 100644 index ce685c564b06be..00000000000000 --- a/Libraries/ART/ARTShape.h +++ /dev/null @@ -1,16 +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. - */ - -#import - -#import "ARTRenderable.h" - -@interface ARTShape : ARTRenderable - -@property (nonatomic, assign) CGPathRef d; - -@end diff --git a/Libraries/ART/ARTShape.m b/Libraries/ART/ARTShape.m deleted file mode 100644 index 935c8894994a2b..00000000000000 --- a/Libraries/ART/ARTShape.m +++ /dev/null @@ -1,66 +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. - */ - -#import "ARTShape.h" - -@implementation ARTShape - -- (void)setD:(CGPathRef)d -{ - if (d == _d) { - return; - } - [self invalidate]; - CGPathRelease(_d); - _d = CGPathRetain(d); -} - -- (void)dealloc -{ - CGPathRelease(_d); -} - -- (void)renderLayerTo:(CGContextRef)context -{ - if ((!self.fill && !self.stroke) || !self.d) { - return; - } - - CGPathDrawingMode mode = kCGPathStroke; - if (self.fill) { - if ([self.fill applyFillColor:context]) { - mode = kCGPathFill; - } else { - CGContextSaveGState(context); - CGContextAddPath(context, self.d); - CGContextClip(context); - [self.fill paint:context]; - CGContextRestoreGState(context); - if (!self.stroke) { - return; - } - } - } - if (self.stroke) { - CGContextSetStrokeColorWithColor(context, self.stroke); - CGContextSetLineWidth(context, self.strokeWidth); - CGContextSetLineCap(context, self.strokeCap); - CGContextSetLineJoin(context, self.strokeJoin); - ARTCGFloatArray dash = self.strokeDash; - if (dash.count) { - CGContextSetLineDash(context, 0, dash.array, dash.count); - } - if (mode == kCGPathFill) { - mode = kCGPathFillStroke; - } - } - - CGContextAddPath(context, self.d); - CGContextDrawPath(context, mode); -} - -@end diff --git a/Libraries/ART/ARTSurfaceView.h b/Libraries/ART/ARTSurfaceView.h deleted file mode 100644 index cb42928cedbe12..00000000000000 --- a/Libraries/ART/ARTSurfaceView.h +++ /dev/null @@ -1,14 +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. - */ - -#import - -#import "ARTContainer.h" - -@interface ARTSurfaceView : UIView - -@end diff --git a/Libraries/ART/ARTSurfaceView.m b/Libraries/ART/ARTSurfaceView.m deleted file mode 100644 index c8f46069038603..00000000000000 --- a/Libraries/ART/ARTSurfaceView.m +++ /dev/null @@ -1,56 +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. - */ - -#import "ARTSurfaceView.h" - -#import - -#import "ARTNode.h" - -@implementation ARTSurfaceView - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - self.opaque = NO; - } - - return self; -} - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - [self insertSubview:subview atIndex:atIndex]; - [self invalidate]; -} - -- (void)removeReactSubview:(UIView *)subview -{ - [super removeReactSubview:subview]; - [self invalidate]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are inserted by insertReactSubview: -} - -- (void)invalidate -{ - [self setNeedsDisplay]; -} - -- (void)drawRect:(CGRect)rect -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - for (ARTNode *node in self.subviews) { - [node renderTo:context]; - } -} - -@end diff --git a/Libraries/ART/ARTText.h b/Libraries/ART/ARTText.h deleted file mode 100644 index cdf8393a137629..00000000000000 --- a/Libraries/ART/ARTText.h +++ /dev/null @@ -1,18 +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. - */ - -#import - -#import "ARTRenderable.h" -#import "ARTTextFrame.h" - -@interface ARTText : ARTRenderable - -@property (nonatomic, assign) CTTextAlignment alignment; -@property (nonatomic, assign) ARTTextFrame textFrame; - -@end diff --git a/Libraries/ART/ARTText.m b/Libraries/ART/ARTText.m deleted file mode 100644 index 0953c850dc293e..00000000000000 --- a/Libraries/ART/ARTText.m +++ /dev/null @@ -1,125 +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. - */ - -#import "ARTText.h" - -#import - -@implementation ARTText - -- (void)setAlignment:(CTTextAlignment)alignment -{ - [self invalidate]; - _alignment = alignment; -} - -static void ARTFreeTextFrame(ARTTextFrame frame) -{ - if (frame.count) { - // We must release each line before freeing up this struct - for (int i = 0; i < frame.count; i++) { - CFRelease(frame.lines[i]); - } - free(frame.lines); - free(frame.widths); - } -} - -- (void)setTextFrame:(ARTTextFrame)frame -{ - if (frame.lines != _textFrame.lines) { - ARTFreeTextFrame(_textFrame); - } - [self invalidate]; - _textFrame = frame; -} - -- (void)dealloc -{ - ARTFreeTextFrame(_textFrame); -} - -- (void)renderLayerTo:(CGContextRef)context -{ - ARTTextFrame frame = self.textFrame; - - if ((!self.fill && !self.stroke) || !frame.count) { - return; - } - - // to-do: draw along a path - - CGTextDrawingMode mode = kCGTextStroke; - if (self.fill) { - if ([self.fill applyFillColor:context]) { - mode = kCGTextFill; - } else { - - for (int i = 0; i < frame.count; i++) { - CGContextSaveGState(context); - // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space - CGContextScaleCTM(context, 1.0, -1.0); - CGContextSetTextDrawingMode(context, kCGTextClip); - [self renderLineTo:context atIndex:i]; - // Inverse the coordinate space back to the original before filling - CGContextScaleCTM(context, 1.0, -1.0); - [self.fill paint:context]; - // Restore the state so that the next line can be clipped separately - CGContextRestoreGState(context); - } - - if (!self.stroke) { - return; - } - } - } - if (self.stroke) { - CGContextSetStrokeColorWithColor(context, self.stroke); - CGContextSetLineWidth(context, self.strokeWidth); - CGContextSetLineCap(context, self.strokeCap); - CGContextSetLineJoin(context, self.strokeJoin); - ARTCGFloatArray dash = self.strokeDash; - if (dash.count) { - CGContextSetLineDash(context, 0, dash.array, dash.count); - } - if (mode == kCGTextFill) { - mode = kCGTextFillStroke; - } - } - - CGContextSetTextDrawingMode(context, mode); - - // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space - CGContextScaleCTM(context, 1.0, -1.0); - for (int i = 0; i < frame.count; i++) { - [self renderLineTo:context atIndex:i]; - } -} - -- (void)renderLineTo:(CGContextRef)context atIndex:(int)index -{ - ARTTextFrame frame = self.textFrame; - CGFloat shift; - switch (self.alignment) { - case kCTTextAlignmentRight: - shift = frame.widths[index]; - break; - case kCTTextAlignmentCenter: - shift = (frame.widths[index] / 2); - break; - default: - shift = 0; - break; - } - // We should consider snapping this shift to device pixels to improve rendering quality - // when a line has subpixel width. - CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index); - CTLineRef line = frame.lines[index]; - CTLineDraw(line, context); -} - -@end diff --git a/Libraries/ART/ARTTextFrame.h b/Libraries/ART/ARTTextFrame.h deleted file mode 100644 index 8ad06e0a01baed..00000000000000 --- a/Libraries/ART/ARTTextFrame.h +++ /dev/null @@ -1,23 +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. - */ - -#import - -// A little helper to make sure we have a set of lines including width ready for use. -// We assume that we will only this in one place so no reference counting is necessary. -// Needs to be freed when dealloced. - -// This is fragile since this relies on these values not getting reused. Consider -// wrapping these in an Obj-C class or some ARC hackery to get refcounting. - -typedef struct { - size_t count; - CGFloat baseLine; // Distance from the origin to the base line of the first line - CGFloat lineHeight; // Distance between lines - CTLineRef *lines; - CGFloat *widths; // Width of each line -} ARTTextFrame; diff --git a/Libraries/ART/Brushes/ARTBrush.h b/Libraries/ART/Brushes/ARTBrush.h deleted file mode 100644 index 95709e65f19bc9..00000000000000 --- a/Libraries/ART/Brushes/ARTBrush.h +++ /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. - */ - -#import -#import - -@interface ARTBrush : NSObject - -/* @abstract */ -- (instancetype)initWithArray:(NSArray *)data NS_DESIGNATED_INITIALIZER; - -/** - * For certain brushes we can fast path a combined fill and stroke. - * For those brushes we override applyFillColor which sets the fill - * color to be used by those batch paints. Those return YES. - * We can't batch gradient painting in CoreGraphics, so those will - * return NO and paint gets called instead. - * @abstract - */ -- (BOOL)applyFillColor:(CGContextRef)context; - -/** - * paint fills the context with a brush. The context is assumed to - * be clipped. - * @abstract - */ -- (void)paint:(CGContextRef)context; - -@end diff --git a/Libraries/ART/Brushes/ARTBrush.m b/Libraries/ART/Brushes/ARTBrush.m deleted file mode 100644 index 4ade2c221143f7..00000000000000 --- a/Libraries/ART/Brushes/ARTBrush.m +++ /dev/null @@ -1,31 +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. - */ - -#import "ARTBrush.h" - -#import - -@implementation ARTBrush - -- (instancetype)initWithArray:(NSArray *)data -{ - return [super init]; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)init) - -- (BOOL)applyFillColor:(CGContextRef)context -{ - return NO; -} - -- (void)paint:(CGContextRef)context -{ - // abstract -} - -@end diff --git a/Libraries/ART/Brushes/ARTLinearGradient.h b/Libraries/ART/Brushes/ARTLinearGradient.h deleted file mode 100644 index 8e6abbc5fbbfde..00000000000000 --- a/Libraries/ART/Brushes/ARTLinearGradient.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTBrush.h" - -@interface ARTLinearGradient : ARTBrush - -@end diff --git a/Libraries/ART/Brushes/ARTLinearGradient.m b/Libraries/ART/Brushes/ARTLinearGradient.m deleted file mode 100644 index 1415a6ffcbc80f..00000000000000 --- a/Libraries/ART/Brushes/ARTLinearGradient.m +++ /dev/null @@ -1,48 +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. - */ - -#import "ARTLinearGradient.h" - -#import - -#import "RCTConvert+ART.h" - -@implementation ARTLinearGradient -{ - CGGradientRef _gradient; - CGPoint _startPoint; - CGPoint _endPoint; -} - -- (instancetype)initWithArray:(NSArray *)array -{ - if ((self = [super initWithArray:array])) { - if (array.count < 5) { - RCTLogError(@"-[%@ %@] expects 5 elements, received %@", - self.class, NSStringFromSelector(_cmd), array); - return nil; - } - _startPoint = [RCTConvert CGPoint:array offset:1]; - _endPoint = [RCTConvert CGPoint:array offset:3]; - _gradient = CGGradientRetain([RCTConvert CGGradient:array offset:5]); - } - return self; -} - -- (void)dealloc -{ - CGGradientRelease(_gradient); -} - -- (void)paint:(CGContextRef)context -{ - CGGradientDrawingOptions extendOptions = - kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; - CGContextDrawLinearGradient(context, _gradient, _startPoint, _endPoint, extendOptions); -} - -@end diff --git a/Libraries/ART/Brushes/ARTPattern.h b/Libraries/ART/Brushes/ARTPattern.h deleted file mode 100644 index ce1767cc9b0bc8..00000000000000 --- a/Libraries/ART/Brushes/ARTPattern.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTBrush.h" - -@interface ARTPattern : ARTBrush - -@end diff --git a/Libraries/ART/Brushes/ARTPattern.m b/Libraries/ART/Brushes/ARTPattern.m deleted file mode 100644 index 5c43586452d198..00000000000000 --- a/Libraries/ART/Brushes/ARTPattern.m +++ /dev/null @@ -1,49 +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. - */ - -#import "ARTPattern.h" - -#import - -#import "RCTConvert+ART.h" - -@implementation ARTPattern -{ - CGImageRef _image; - CGRect _rect; -} - -- (instancetype)initWithArray:(NSArray *)array -{ - if ((self = [super initWithArray:array])) { - if (array.count < 6) { - RCTLogError(@"-[%@ %@] expects 6 elements, received %@", - self.class, NSStringFromSelector(_cmd), array); - return nil; - } - _image = CGImageRetain([RCTConvert CGImage:array[1]]); - _rect = [RCTConvert CGRect:array offset:2]; - } - return self; -} - -- (void)dealloc -{ - CGImageRelease(_image); -} - -// Note: This could use applyFillColor with a pattern. This could be more efficient but -// to do that, we need to calculate our own user space CTM. - -- (void)paint:(CGContextRef)context -{ - CGContextDrawTiledImage(context, _rect, _image); -} - - - -@end diff --git a/Libraries/ART/Brushes/ARTRadialGradient.h b/Libraries/ART/Brushes/ARTRadialGradient.h deleted file mode 100644 index d7895f8b74d084..00000000000000 --- a/Libraries/ART/Brushes/ARTRadialGradient.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTBrush.h" - -@interface ARTRadialGradient : ARTBrush - -@end diff --git a/Libraries/ART/Brushes/ARTRadialGradient.m b/Libraries/ART/Brushes/ARTRadialGradient.m deleted file mode 100644 index 19db9cb97b567a..00000000000000 --- a/Libraries/ART/Brushes/ARTRadialGradient.m +++ /dev/null @@ -1,55 +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. - */ - -#import "ARTRadialGradient.h" - -#import - -#import "RCTConvert+ART.h" - -@implementation ARTRadialGradient -{ - CGGradientRef _gradient; - CGPoint _focusPoint; - CGPoint _centerPoint; - CGFloat _radius; - CGFloat _radiusRatio; -} - -- (instancetype)initWithArray:(NSArray *)array -{ - if ((self = [super initWithArray:array])) { - if (array.count < 7) { - RCTLogError(@"-[%@ %@] expects 7 elements, received %@", - self.class, NSStringFromSelector(_cmd), array); - return nil; - } - _radius = [RCTConvert CGFloat:array[3]]; - _radiusRatio = [RCTConvert CGFloat:array[4]] / _radius; - _focusPoint.x = [RCTConvert CGFloat:array[1]]; - _focusPoint.y = [RCTConvert CGFloat:array[2]] / _radiusRatio; - _centerPoint.x = [RCTConvert CGFloat:array[5]]; - _centerPoint.y = [RCTConvert CGFloat:array[6]] / _radiusRatio; - _gradient = CGGradientRetain([RCTConvert CGGradient:array offset:7]); - } - return self; -} - -- (void)dealloc -{ - CGGradientRelease(_gradient); -} - -- (void)paint:(CGContextRef)context -{ - CGAffineTransform transform = CGAffineTransformMakeScale(1, _radiusRatio); - CGContextConcatCTM(context, transform); - CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation; - CGContextDrawRadialGradient(context, _gradient, _focusPoint, 0, _centerPoint, _radius, extendOptions); -} - -@end diff --git a/Libraries/ART/Brushes/ARTSolidColor.h b/Libraries/ART/Brushes/ARTSolidColor.h deleted file mode 100644 index a16c2b915c72f8..00000000000000 --- a/Libraries/ART/Brushes/ARTSolidColor.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTBrush.h" - -@interface ARTSolidColor : ARTBrush - -@end diff --git a/Libraries/ART/Brushes/ARTSolidColor.m b/Libraries/ART/Brushes/ARTSolidColor.m deleted file mode 100644 index 096c4e043ed429..00000000000000 --- a/Libraries/ART/Brushes/ARTSolidColor.m +++ /dev/null @@ -1,38 +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. - */ - -#import "ARTSolidColor.h" - -#import - -#import "RCTConvert+ART.h" - -@implementation ARTSolidColor -{ - CGColorRef _color; -} - -- (instancetype)initWithArray:(NSArray *)array -{ - if ((self = [super initWithArray:array])) { - _color = CGColorRetain([RCTConvert CGColor:array offset:1]); - } - return self; -} - -- (void)dealloc -{ - CGColorRelease(_color); -} - -- (BOOL)applyFillColor:(CGContextRef)context -{ - CGContextSetFillColorWithColor(context, _color); - return YES; -} - -@end diff --git a/Libraries/ART/RCTConvert+ART.h b/Libraries/ART/RCTConvert+ART.h deleted file mode 100644 index 1deb3c600176ab..00000000000000 --- a/Libraries/ART/RCTConvert+ART.h +++ /dev/null @@ -1,29 +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. - */ - -#import - -#import - -#import "ARTBrush.h" -#import "ARTCGFloatArray.h" -#import "ARTTextFrame.h" - -@interface RCTConvert (ART) - -+ (CGPathRef)CGPath:(id)json; -+ (CTTextAlignment)CTTextAlignment:(id)json; -+ (ARTTextFrame)ARTTextFrame:(id)json; -+ (ARTCGFloatArray)ARTCGFloatArray:(id)json; -+ (ARTBrush *)ARTBrush:(id)json; - -+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset; -+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset; -+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; -+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset; - -@end diff --git a/Libraries/ART/RCTConvert+ART.m b/Libraries/ART/RCTConvert+ART.m deleted file mode 100644 index ea22d5426f18f9..00000000000000 --- a/Libraries/ART/RCTConvert+ART.m +++ /dev/null @@ -1,222 +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. - */ - -#import "RCTConvert+ART.h" - -#import -#import - -#import "ARTLinearGradient.h" -#import "ARTPattern.h" -#import "ARTRadialGradient.h" -#import "ARTSolidColor.h" - -@implementation RCTConvert (ART) - -+ (CGPathRef)CGPath:(id)json -{ - NSArray *arr = [self NSNumberArray:json]; - - NSUInteger count = [arr count]; - -#define NEXT_VALUE [self double:arr[i++]] - - CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 0, 0); - - @try { - NSUInteger i = 0; - while (i < count) { - NSUInteger type = [arr[i++] unsignedIntegerValue]; - switch (type) { - case 0: - CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE); - break; - case 1: - CGPathCloseSubpath(path); - break; - case 2: - CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE); - break; - case 3: - CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE); - break; - case 4: - CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0); - break; - default: - RCTLogError(@"Invalid CGPath type %llu at element %llu of %@", (unsigned long long)type, (unsigned long long)i, arr); - CGPathRelease(path); - return NULL; - } - } - } - @catch (NSException *exception) { - RCTLogError(@"Invalid CGPath format: %@", arr); - CGPathRelease(path); - return NULL; - } - - return (CGPathRef)CFAutorelease(path); -} - -RCT_ENUM_CONVERTER(CTTextAlignment, (@{ - @"auto": @(kCTTextAlignmentNatural), - @"left": @(kCTTextAlignmentLeft), - @"center": @(kCTTextAlignmentCenter), - @"right": @(kCTTextAlignmentRight), - @"justify": @(kCTTextAlignmentJustified), -}), kCTTextAlignmentNatural, integerValue) - -// This takes a tuple of text lines and a font to generate a CTLine for each text line. -// This prepares everything for rendering a frame of text in ARTText. -+ (ARTTextFrame)ARTTextFrame:(id)json -{ - NSDictionary *dict = [self NSDictionary:json]; - ARTTextFrame frame; - frame.count = 0; - - NSArray *lines = [self NSArray:dict[@"lines"]]; - NSUInteger lineCount = [lines count]; - if (lineCount == 0) { - return frame; - } - - CTFontRef font = (__bridge CTFontRef)[self UIFont:dict[@"font"]]; - if (!font) { - return frame; - } - - // Create a dictionary for this font - CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{ - (NSString *)kCTFontAttributeName:(__bridge id)font, - (NSString *)kCTForegroundColorFromContextAttributeName: @YES - }; - - // Set up text frame with font metrics - CGFloat size = CTFontGetSize(font); - frame.count = lineCount; - frame.baseLine = size; // estimate base line - frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate - frame.lines = malloc(sizeof(CTLineRef) * lineCount); - frame.widths = malloc(sizeof(CGFloat) * lineCount); - - [lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) { - - CFStringRef string = (__bridge CFStringRef)text; - CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); - CTLineRef line = CTLineCreateWithAttributedString(attrString); - CFRelease(attrString); - - frame.lines[i] = line; - frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL); - }]; - - return frame; -} - -+ (ARTCGFloatArray)ARTCGFloatArray:(id)json -{ - NSArray *arr = [self NSNumberArray:json]; - NSUInteger count = arr.count; - - ARTCGFloatArray array; - array.count = count; - array.array = NULL; - - if (count) { - // Ideally, these arrays should already use the same memory layout. - // In that case we shouldn't need this new malloc. - array.array = malloc(sizeof(CGFloat) * count); - for (NSUInteger i = 0; i < count; i++) { - array.array[i] = [arr[i] doubleValue]; - } - } - - return array; -} - -+ (ARTBrush *)ARTBrush:(id)json -{ - NSArray *arr = [self NSArray:json]; - NSUInteger type = [self NSUInteger:arr.firstObject]; - switch (type) { - case 0: // solid color - // These are probably expensive allocations since it's often the same value. - // We should memoize colors but look ups may be just as expensive. - return [[ARTSolidColor alloc] initWithArray:arr]; - case 1: // linear gradient - return [[ARTLinearGradient alloc] initWithArray:arr]; - case 2: // radial gradient - return [[ARTRadialGradient alloc] initWithArray:arr]; - case 3: // pattern - return [[ARTPattern alloc] initWithArray:arr]; - default: - RCTLogError(@"Unknown brush type: %llu", (unsigned long long)type); - return nil; - } -} - -+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset -{ - NSArray *arr = [self NSArray:json]; - if (arr.count < offset + 2) { - RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(2 + offset), arr); - return CGPointZero; - } - return (CGPoint){ - [self CGFloat:arr[offset]], - [self CGFloat:arr[offset + 1]], - }; -} - -+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset -{ - NSArray *arr = [self NSArray:json]; - if (arr.count < offset + 4) { - RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr); - return CGRectZero; - } - return (CGRect){ - {[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]}, - {[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]}, - }; -} - -+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset -{ - NSArray *arr = [self NSArray:json]; - if (arr.count < offset + 4) { - RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr); - return NULL; - } - return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]]; -} - -+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset -{ - NSArray *arr = [self NSArray:json]; - if (arr.count < offset) { - RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)offset, arr); - return NULL; - } - arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}]; - ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr]; - size_t stops = colorsAndOffsets.count / 5; - CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); - CGGradientRef gradient = CGGradientCreateWithColorComponents( - rgb, - colorsAndOffsets.array, - colorsAndOffsets.array + stops * 4, - stops - ); - CGColorSpaceRelease(rgb); - free(colorsAndOffsets.array); - return (CGGradientRef)CFAutorelease(gradient); -} - -@end diff --git a/Libraries/ART/ReactNativeART.js b/Libraries/ART/ReactNativeART.js deleted file mode 100644 index 7d9f1b59063459..00000000000000 --- a/Libraries/ART/ReactNativeART.js +++ /dev/null @@ -1,588 +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 ReactNativeART - */ -'use strict'; - -var Color = require('art/core/color'); -var Path = require('ARTSerializablePath'); -var Transform = require('art/core/transform'); - -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); - -var createReactNativeComponentClass = require('createReactNativeComponentClass'); -var merge = require('merge'); -var invariant = require('fbjs/lib/invariant'); - -// Diff Helpers - -function arrayDiffer(a, b) { - if (a == null || b == null) { - return true; - } - if (a.length !== b.length) { - return true; - } - for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return true; - } - } - return false; -} - -function fontAndLinesDiffer(a, b) { - if (a === b) { - return false; - } - if (a.font !== b.font) { - if (a.font === null) { - return true; - } - if (b.font === null) { - return true; - } - - if ( - a.font.fontFamily !== b.font.fontFamily || - a.font.fontSize !== b.font.fontSize || - a.font.fontWeight !== b.font.fontWeight || - a.font.fontStyle !== b.font.fontStyle - ) { - return true; - } - } - return arrayDiffer(a.lines, b.lines); -} - -// Native Attributes - -var SurfaceViewAttributes = merge(ReactNativeViewAttributes.UIView, { - // This should contain pixel information such as width, height and - // resolution to know what kind of buffer needs to be allocated. - // Currently we rely on UIViews and style to figure that out. -}); - -var NodeAttributes = { - transform: { diff: arrayDiffer }, - opacity: true, -}; - -var GroupAttributes = merge(NodeAttributes, { - clipping: { diff: arrayDiffer } -}); - -var RenderableAttributes = merge(NodeAttributes, { - fill: { diff: arrayDiffer }, - stroke: { diff: arrayDiffer }, - strokeWidth: true, - strokeCap: true, - strokeJoin: true, - strokeDash: { diff: arrayDiffer }, -}); - -var ShapeAttributes = merge(RenderableAttributes, { - d: { diff: arrayDiffer }, -}); - -var TextAttributes = merge(RenderableAttributes, { - alignment: true, - frame: { diff: fontAndLinesDiffer }, - path: { diff: arrayDiffer } -}); - -// Native Components - -var NativeSurfaceView = createReactNativeComponentClass('ARTSurfaceView', - () => ({ - validAttributes: SurfaceViewAttributes, - uiViewClassName: 'ARTSurfaceView', - })); - -var NativeGroup = createReactNativeComponentClass('ARTGroup', - () => ({ - validAttributes: GroupAttributes, - uiViewClassName: 'ARTGroup', - })); - -var NativeShape = createReactNativeComponentClass('ARTShape', - () => ({ - validAttributes: ShapeAttributes, - uiViewClassName: 'ARTShape', - })); - -var NativeText = createReactNativeComponentClass('ARTText', - () => ({ - validAttributes: TextAttributes, - uiViewClassName: 'ARTText', - })); - -// Utilities - -function childrenAsString(children) { - if (!children) { - return ''; - } - if (typeof children === 'string') { - return children; - } - if (children.length) { - return children.join('\n'); - } - return ''; -} - -// Surface - Root node of all ART - -class Surface extends React.Component { - static childContextTypes = { - isInSurface: PropTypes.bool, - }; - - getChildContext() { - return { isInSurface: true }; - } - - render() { - var props = this.props; - var w = extractNumber(props.width, 0); - var h = extractNumber(props.height, 0); - return ( - - {this.props.children} - - ); - } -} - -// Node Props - -// TODO: The desktop version of ART has title and cursor. We should have -// accessibility support here too even though hovering doesn't work. - -function extractNumber(value, defaultValue) { - if (value == null) { - return defaultValue; - } - return +value; -} - -var pooledTransform = new Transform(); - -function extractTransform(props) { - var scaleX = props.scaleX != null ? props.scaleX : - props.scale != null ? props.scale : 1; - var scaleY = props.scaleY != null ? props.scaleY : - props.scale != null ? props.scale : 1; - - pooledTransform - .transformTo(1, 0, 0, 1, 0, 0) - .move(props.x || 0, props.y || 0) - .rotate(props.rotation || 0, props.originX, props.originY) - .scale(scaleX, scaleY, props.originX, props.originY); - - if (props.transform != null) { - pooledTransform.transform(props.transform); - } - - return [ - pooledTransform.xx, pooledTransform.yx, - pooledTransform.xy, pooledTransform.yy, - pooledTransform.x, pooledTransform.y, - ]; -} - -function extractOpacity(props) { - // TODO: visible === false should also have no hit detection - if (props.visible === false) { - return 0; - } - if (props.opacity == null) { - return 1; - } - return +props.opacity; -} - -// Groups - -// Note: ART has a notion of width and height on Group but AFAIK it's a noop in -// ReactART. - -class Group extends React.Component { - static contextTypes = { - isInSurface: PropTypes.bool.isRequired, - }; - - render() { - var props = this.props; - invariant( - this.context.isInSurface, - 'ART: must be a child of a ' - ); - return ( - - {this.props.children} - - ); - } -} - -class ClippingRectangle extends React.Component { - render() { - var props = this.props; - var x = extractNumber(props.x, 0); - var y = extractNumber(props.y, 0); - var w = extractNumber(props.width, 0); - var h = extractNumber(props.height, 0); - var clipping = [x, y, w, h]; - // The current clipping API requires x and y to be ignored in the transform - var propsExcludingXAndY = merge(props); - delete propsExcludingXAndY.x; - delete propsExcludingXAndY.y; - return ( - - {this.props.children} - - ); - } -} - -// Renderables - -var SOLID_COLOR = 0; -var LINEAR_GRADIENT = 1; -var RADIAL_GRADIENT = 2; -var PATTERN = 3; - -function insertColorIntoArray(color, targetArray, atIndex) { - var c = new Color(color); - targetArray[atIndex + 0] = c.red / 255; - targetArray[atIndex + 1] = c.green / 255; - targetArray[atIndex + 2] = c.blue / 255; - targetArray[atIndex + 3] = c.alpha; -} - -function insertColorsIntoArray(stops, targetArray, atIndex) { - var i = 0; - if ('length' in stops) { - while (i < stops.length) { - insertColorIntoArray(stops[i], targetArray, atIndex + i * 4); - i++; - } - } else { - for (var offset in stops) { - insertColorIntoArray(stops[offset], targetArray, atIndex + i * 4); - i++; - } - } - return atIndex + i * 4; -} - -function insertOffsetsIntoArray(stops, targetArray, atIndex, multi, reverse) { - var offsetNumber; - var i = 0; - if ('length' in stops) { - while (i < stops.length) { - offsetNumber = i / (stops.length - 1) * multi; - targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber; - i++; - } - } else { - for (var offsetString in stops) { - offsetNumber = (+offsetString) * multi; - targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber; - i++; - } - } - return atIndex + i; -} - -function insertColorStopsIntoArray(stops, targetArray, atIndex) { - var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex); - insertOffsetsIntoArray(stops, targetArray, lastIndex, 1, false); -} - -function insertDoubleColorStopsIntoArray(stops, targetArray, atIndex) { - var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex); - lastIndex = insertColorsIntoArray(stops, targetArray, lastIndex); - lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false); - insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true); -} - -function applyBoundingBoxToBrushData(brushData, props) { - var type = brushData[0]; - var width = +props.width; - var height = +props.height; - if (type === LINEAR_GRADIENT) { - brushData[1] *= width; - brushData[2] *= height; - brushData[3] *= width; - brushData[4] *= height; - } else if (type === RADIAL_GRADIENT) { - brushData[1] *= width; - brushData[2] *= height; - brushData[3] *= width; - brushData[4] *= height; - brushData[5] *= width; - brushData[6] *= height; - } else if (type === PATTERN) { - // todo - } -} - -function extractBrush(colorOrBrush, props) { - if (colorOrBrush == null) { - return null; - } - if (colorOrBrush._brush) { - if (colorOrBrush._bb) { - // The legacy API for Gradients allow for the bounding box to be used - // as a convenience for specifying gradient positions. This should be - // deprecated. It's not properly implemented in canvas mode. ReactART - // doesn't handle update to the bounding box correctly. That's why we - // mutate this so that if it's reused, we reuse the same resolved box. - applyBoundingBoxToBrushData(colorOrBrush._brush, props); - colorOrBrush._bb = false; - } - return colorOrBrush._brush; - } - var c = new Color(colorOrBrush); - return [SOLID_COLOR, c.red / 255, c.green / 255, c.blue / 255, c.alpha]; -} - -function extractColor(color) { - if (color == null) { - return null; - } - var c = new Color(color); - return [c.red / 255, c.green / 255, c.blue / 255, c.alpha]; -} - -function extractStrokeCap(strokeCap) { - switch (strokeCap) { - case 'butt': return 0; - case 'square': return 2; - default: return 1; // round - } -} - -function extractStrokeJoin(strokeJoin) { - switch (strokeJoin) { - case 'miter': return 0; - case 'bevel': return 2; - default: return 1; // round - } -} - -// Shape - -// Note: ART has a notion of width and height on Shape but AFAIK it's a noop in -// ReactART. - -class Shape extends React.Component { - render() { - var props = this.props; - var path = props.d || childrenAsString(props.children); - var d = (path instanceof Path ? path : new Path(path)).toJSON(); - return ( - - ); - } -} - -// Text - -var cachedFontObjectsFromString = {}; - -var fontFamilyPrefix = /^[\s"']*/; -var fontFamilySuffix = /[\s"']*$/; - -function extractSingleFontFamily(fontFamilyString) { - // ART on the web allows for multiple font-families to be specified. - // For compatibility, we extract the first font-family, hoping - // we'll get a match. - return fontFamilyString.split(',')[0] - .replace(fontFamilyPrefix, '') - .replace(fontFamilySuffix, ''); -} - -function parseFontString(font) { - if (cachedFontObjectsFromString.hasOwnProperty(font)) { - return cachedFontObjectsFromString[font]; - } - var regexp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm\%]*(?:\s*\/.*?)?\s+)?\s*\"?([^\"]*)/i; - var match = regexp.exec(font); - if (!match) { - return null; - } - var fontFamily = extractSingleFontFamily(match[3]); - var fontSize = +match[2] || 12; - var isBold = /bold/.exec(match[1]); - var isItalic = /italic/.exec(match[1]); - cachedFontObjectsFromString[font] = { - fontFamily: fontFamily, - fontSize: fontSize, - fontWeight: isBold ? 'bold' : 'normal', - fontStyle: isItalic ? 'italic' : 'normal', - }; - return cachedFontObjectsFromString[font]; -} - -function extractFont(font) { - if (font == null) { - return null; - } - if (typeof font === 'string') { - return parseFontString(font); - } - var fontFamily = extractSingleFontFamily(font.fontFamily); - var fontSize = +font.fontSize || 12; - var fontWeight = font.fontWeight != null ? font.fontWeight.toString() : '400'; - return { - // Normalize - fontFamily: fontFamily, - fontSize: fontSize, - fontWeight: fontWeight, - fontStyle: font.fontStyle, - }; -} - -var newLine = /\n/g; -function extractFontAndLines(font, text) { - return { font: extractFont(font), lines: text.split(newLine) }; -} - -function extractAlignment(alignment) { - switch (alignment) { - case 'right': - return 1; - case 'center': - return 2; - default: - return 0; - } -} - -class Text extends React.Component { - render() { - var props = this.props; - var path = props.path; - var textPath = path ? (path instanceof Path ? path : new Path(path)).toJSON() : null; - var textFrame = extractFontAndLines( - props.font, - childrenAsString(props.children) - ); - return ( - - ); - } -} - -// Declarative fill type objects - API design not finalized - -function LinearGradient(stops, x1, y1, x2, y2) { - var type = LINEAR_GRADIENT; - - if (arguments.length < 5) { - var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180; - - var x = Math.cos(angle); - var y = -Math.sin(angle); - var l = (Math.abs(x) + Math.abs(y)) / 2; - - x *= l; y *= l; - - x1 = 0.5 - x; - x2 = 0.5 + x; - y1 = 0.5 - y; - y2 = 0.5 + y; - this._bb = true; - } else { - this._bb = false; - } - - var brushData = [type, +x1, +y1, +x2, +y2]; - insertColorStopsIntoArray(stops, brushData, 5); - this._brush = brushData; -} - -function RadialGradient(stops, fx, fy, rx, ry, cx, cy) { - if (ry == null) { - ry = rx; - } - if (cx == null) { - cx = fx; - } - if (cy == null) { - cy = fy; - } - if (fx == null) { - // As a convenience we allow the whole radial gradient to cover the - // bounding box. We should consider dropping this API. - fx = fy = rx = ry = cx = cy = 0.5; - this._bb = true; - } else { - this._bb = false; - } - // The ART API expects the radial gradient to be repeated at the edges. - // To simulate this we render the gradient twice as large and add double - // color stops. Ideally this API would become more restrictive so that this - // extra work isn't needed. - var brushData = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy]; - insertDoubleColorStopsIntoArray(stops, brushData, 7); - this._brush = brushData; -} - -function Pattern(url, width, height, left, top) { - this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height]; -} - -var ReactART = { - LinearGradient: LinearGradient, - RadialGradient: RadialGradient, - Pattern: Pattern, - Transform: Transform, - Path: Path, - Surface: Surface, - Group: Group, - ClippingRectangle: ClippingRectangle, - Shape: Shape, - Text: Text, -}; - -module.exports = ReactART; diff --git a/Libraries/ART/ViewManagers/ARTGroupManager.h b/Libraries/ART/ViewManagers/ARTGroupManager.h deleted file mode 100644 index a35e09481be639..00000000000000 --- a/Libraries/ART/ViewManagers/ARTGroupManager.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTNodeManager.h" - -@interface ARTGroupManager : ARTNodeManager - -@end diff --git a/Libraries/ART/ViewManagers/ARTGroupManager.m b/Libraries/ART/ViewManagers/ARTGroupManager.m deleted file mode 100644 index 2de0d0e5688739..00000000000000 --- a/Libraries/ART/ViewManagers/ARTGroupManager.m +++ /dev/null @@ -1,24 +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. - */ - -#import "ARTGroupManager.h" - -#import "ARTGroup.h" -#import "RCTConvert+ART.h" - -@implementation ARTGroupManager - -RCT_EXPORT_MODULE() - -- (ARTNode *)node -{ - return [ARTGroup new]; -} - -RCT_EXPORT_VIEW_PROPERTY(clipping, CGRect) - -@end diff --git a/Libraries/ART/ViewManagers/ARTNodeManager.h b/Libraries/ART/ViewManagers/ARTNodeManager.h deleted file mode 100644 index 3a8f99e7abdc7a..00000000000000 --- a/Libraries/ART/ViewManagers/ARTNodeManager.h +++ /dev/null @@ -1,16 +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. - */ - -#import - -@class ARTNode; - -@interface ARTNodeManager : RCTViewManager - -- (ARTNode *)node; - -@end diff --git a/Libraries/ART/ViewManagers/ARTNodeManager.m b/Libraries/ART/ViewManagers/ARTNodeManager.m deleted file mode 100644 index 8e568ce6d8efd4..00000000000000 --- a/Libraries/ART/ViewManagers/ARTNodeManager.m +++ /dev/null @@ -1,34 +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. - */ - -#import "ARTNodeManager.h" - -#import "ARTNode.h" - -@implementation ARTNodeManager - -RCT_EXPORT_MODULE() - -- (ARTNode *)node -{ - return [ARTNode new]; -} - -- (UIView *)view -{ - return [self node]; -} - -- (RCTShadowView *)shadowView -{ - return nil; -} - -RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform) - -@end diff --git a/Libraries/ART/ViewManagers/ARTRenderableManager.h b/Libraries/ART/ViewManagers/ARTRenderableManager.h deleted file mode 100644 index 1e4b554f0f79f7..00000000000000 --- a/Libraries/ART/ViewManagers/ARTRenderableManager.h +++ /dev/null @@ -1,15 +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. - */ - -#import "ARTNodeManager.h" -#import "ARTRenderable.h" - -@interface ARTRenderableManager : ARTNodeManager - -- (ARTRenderable *)node; - -@end diff --git a/Libraries/ART/ViewManagers/ARTRenderableManager.m b/Libraries/ART/ViewManagers/ARTRenderableManager.m deleted file mode 100644 index a30841b50c1046..00000000000000 --- a/Libraries/ART/ViewManagers/ARTRenderableManager.m +++ /dev/null @@ -1,28 +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. - */ - -#import "ARTRenderableManager.h" - -#import "RCTConvert+ART.h" - -@implementation ARTRenderableManager - -RCT_EXPORT_MODULE() - -- (ARTRenderable *)node -{ - return [ARTRenderable new]; -} - -RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(strokeCap, CGLineCap) -RCT_EXPORT_VIEW_PROPERTY(strokeJoin, CGLineJoin) -RCT_EXPORT_VIEW_PROPERTY(fill, ARTBrush) -RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor) -RCT_EXPORT_VIEW_PROPERTY(strokeDash, ARTCGFloatArray) - -@end diff --git a/Libraries/ART/ViewManagers/ARTShapeManager.h b/Libraries/ART/ViewManagers/ARTShapeManager.h deleted file mode 100644 index 2f31b23a0eab10..00000000000000 --- a/Libraries/ART/ViewManagers/ARTShapeManager.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTRenderableManager.h" - -@interface ARTShapeManager : ARTRenderableManager - -@end diff --git a/Libraries/ART/ViewManagers/ARTShapeManager.m b/Libraries/ART/ViewManagers/ARTShapeManager.m deleted file mode 100644 index 57b603ca1a16b6..00000000000000 --- a/Libraries/ART/ViewManagers/ARTShapeManager.m +++ /dev/null @@ -1,24 +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. - */ - -#import "ARTShapeManager.h" - -#import "ARTShape.h" -#import "RCTConvert+ART.h" - -@implementation ARTShapeManager - -RCT_EXPORT_MODULE() - -- (ARTRenderable *)node -{ - return [ARTShape new]; -} - -RCT_EXPORT_VIEW_PROPERTY(d, CGPath) - -@end diff --git a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.h b/Libraries/ART/ViewManagers/ARTSurfaceViewManager.h deleted file mode 100644 index 5915536bd6f917..00000000000000 --- a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.h +++ /dev/null @@ -1,12 +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. - */ - -#import - -@interface ARTSurfaceViewManager : RCTViewManager - -@end diff --git a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m b/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m deleted file mode 100644 index 54052a331d4585..00000000000000 --- a/Libraries/ART/ViewManagers/ARTSurfaceViewManager.m +++ /dev/null @@ -1,21 +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. - */ - -#import "ARTSurfaceViewManager.h" - -#import "ARTSurfaceView.h" - -@implementation ARTSurfaceViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [ARTSurfaceView new]; -} - -@end diff --git a/Libraries/ART/ViewManagers/ARTTextManager.h b/Libraries/ART/ViewManagers/ARTTextManager.h deleted file mode 100644 index c7d5304da3a3eb..00000000000000 --- a/Libraries/ART/ViewManagers/ARTTextManager.h +++ /dev/null @@ -1,12 +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. - */ - -#import "ARTRenderableManager.h" - -@interface ARTTextManager : ARTRenderableManager - -@end diff --git a/Libraries/ART/ViewManagers/ARTTextManager.m b/Libraries/ART/ViewManagers/ARTTextManager.m deleted file mode 100644 index ac6688c3d925b9..00000000000000 --- a/Libraries/ART/ViewManagers/ARTTextManager.m +++ /dev/null @@ -1,25 +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. - */ - -#import "ARTTextManager.h" - -#import "ARTText.h" -#import "RCTConvert+ART.h" - -@implementation ARTTextManager - -RCT_EXPORT_MODULE() - -- (ARTRenderable *)node -{ - return [ARTText new]; -} - -RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment) -RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame) - -@end diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js index 23db7680ab34e1..0904d14024f27f 100644 --- a/Libraries/ActionSheetIOS/ActionSheetIOS.js +++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js @@ -1,24 +1,24 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule ActionSheetIOS * @flow * @format */ -'use strict'; -const RCTActionSheetManager = require('NativeModules').ActionSheetManager; +import RCTActionSheetManager from './NativeActionSheetManager'; -const invariant = require('fbjs/lib/invariant'); -const processColor = require('processColor'); +const invariant = require('invariant'); +const processColor = require('../StyleSheet/processColor'); +import type {ColorValue} from '../StyleSheet/StyleSheet'; +import type {ProcessedColorValue} from '../StyleSheet/processColor'; /** * Display action sheets and share sheets on iOS. * - * See http://facebook.github.io/react-native/docs/actionsheetios.html + * See https://reactnative.dev/docs/actionsheetios */ const ActionSheetIOS = { /** @@ -28,24 +28,28 @@ const ActionSheetIOS = { * * - `options` (array of strings) - a list of button titles (required) * - `cancelButtonIndex` (int) - index of cancel button in `options` - * - `destructiveButtonIndex` (int) - index of destructive button in `options` + * - `destructiveButtonIndex` (int or array of ints) - index or indices of destructive buttons in `options` * - `title` (string) - a title to show above the action sheet * - `message` (string) - a message to show below the title + * - `disabledButtonIndices` (array of numbers) - a list of button indices which should be disabled * * The 'callback' function takes one parameter, the zero-based index * of the selected item. * - * See http://facebook.github.io/react-native/docs/actionsheetios.html#showactionsheetwithoptions + * See https://reactnative.dev/docs/actionsheetios#showactionsheetwithoptions */ showActionSheetWithOptions( options: {| +title?: ?string, +message?: ?string, +options: Array, - +destructiveButtonIndex?: ?number, + +destructiveButtonIndex?: ?number | ?Array, +cancelButtonIndex?: ?number, +anchor?: ?number, - +tintColor?: number | string, + +tintColor?: ColorValue | ProcessedColorValue, + +cancelButtonTintColor?: ColorValue | ProcessedColorValue, + +userInterfaceStyle?: string, + +disabledButtonIndices?: Array, |}, callback: (buttonIndex: number) => void, ) { @@ -54,9 +58,40 @@ const ActionSheetIOS = { 'Options must be a valid object', ); invariant(typeof callback === 'function', 'Must provide a valid callback'); + invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist"); + const { + tintColor, + cancelButtonTintColor, + destructiveButtonIndex, + ...remainingOptions + } = options; + let destructiveButtonIndices = null; + + if (Array.isArray(destructiveButtonIndex)) { + destructiveButtonIndices = destructiveButtonIndex; + } else if (typeof destructiveButtonIndex === 'number') { + destructiveButtonIndices = [destructiveButtonIndex]; + } + + const processedTintColor = processColor(tintColor); + const processedCancelButtonTintColor = processColor(cancelButtonTintColor); + invariant( + processedTintColor == null || typeof processedTintColor === 'number', + 'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor', + ); + invariant( + processedCancelButtonTintColor == null || + typeof processedCancelButtonTintColor === 'number', + 'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions cancelButtonTintColor', + ); RCTActionSheetManager.showActionSheetWithOptions( - {...options, tintColor: processColor(options.tintColor)}, + { + ...remainingOptions, + tintColor: processedTintColor, + cancelButtonTintColor: processedCancelButtonTintColor, + destructiveButtonIndices, + }, callback, ); }, @@ -82,7 +117,7 @@ const ActionSheetIOS = { * - a boolean value signifying success or failure * - a string that, in the case of success, indicates the method of sharing * - * See http://facebook.github.io/react-native/docs/actionsheetios.html#showshareactionsheetwithoptions + * See https://reactnative.dev/docs/actionsheetios#showshareactionsheetwithoptions */ showShareActionSheetWithOptions( options: Object, @@ -101,12 +136,20 @@ const ActionSheetIOS = { typeof successCallback === 'function', 'Must provide a valid successCallback', ); + invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist"); RCTActionSheetManager.showShareActionSheetWithOptions( {...options, tintColor: processColor(options.tintColor)}, failureCallback, successCallback, ); }, + + dismissActionSheet: () => { + invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist"); + if (typeof RCTActionSheetManager.dismissActionSheet === 'function') { + RCTActionSheetManager.dismissActionSheet(); + } + }, }; module.exports = ActionSheetIOS; diff --git a/Libraries/ActionSheetIOS/NativeActionSheetManager.js b/Libraries/ActionSheetIOS/NativeActionSheetManager.js new file mode 100644 index 00000000000000..1f9ead0bdd8f73 --- /dev/null +++ b/Libraries/ActionSheetIOS/NativeActionSheetManager.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getConstants: () => {||}; + +showActionSheetWithOptions: ( + options: {| + +title?: ?string, + +message?: ?string, + +options: ?Array, + +destructiveButtonIndices?: ?Array, + +cancelButtonIndex?: ?number, + +anchor?: ?number, + +tintColor?: ?number, + +cancelButtonTintColor?: ?number, + +userInterfaceStyle?: ?string, + +disabledButtonIndices?: Array, + |}, + callback: (buttonIndex: number) => void, + ) => void; + +showShareActionSheetWithOptions: ( + options: {| + +message?: ?string, + +url?: ?string, + +subject?: ?string, + +anchor?: ?number, + +tintColor?: ?number, + +cancelButtonTintColor?: ?number, + +excludedActivityTypes?: ?Array, + +userInterfaceStyle?: ?string, + |}, + failureCallback: (error: {| + +domain: string, + +code: string, + +userInfo?: ?Object, + +message: string, + |}) => void, + successCallback: (completed: boolean, activityType: ?string) => void, + ) => void; + +dismissActionSheet?: () => void; +} + +export default (TurboModuleRegistry.get('ActionSheetManager'): ?Spec); diff --git a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj b/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj deleted file mode 100644 index 40d08248bc3c8d..00000000000000 --- a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj +++ /dev/null @@ -1,235 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 14C644C41AB0DFC900DE3C65 /* RCTActionSheetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 134814201AA4EA6300B7C361 /* libRCTActionSheet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTActionSheet.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 14C644C11AB0DFC900DE3C65 /* RCTActionSheetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTActionSheetManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActionSheetManager.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXGroup section */ - 134814211AA4EA7D00B7C361 /* Products */ = { - isa = PBXGroup; - children = ( - 134814201AA4EA6300B7C361 /* libRCTActionSheet.a */, - ); - name = Products; - sourceTree = ""; - }; - 58B511D21A9E6C8500147676 = { - isa = PBXGroup; - children = ( - 14C644C11AB0DFC900DE3C65 /* RCTActionSheetManager.h */, - 14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */, - 134814211AA4EA7D00B7C361 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 58B511DA1A9E6C8500147676 /* RCTActionSheet */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTActionSheet" */; - buildPhases = ( - 58B511D71A9E6C8500147676 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTActionSheet; - productName = RCTDataManager; - productReference = 134814201AA4EA6300B7C361 /* libRCTActionSheet.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511D31A9E6C8500147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 58B511DA1A9E6C8500147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTActionSheet" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 58B511D21A9E6C8500147676; - productRefGroup = 58B511D21A9E6C8500147676; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B511DA1A9E6C8500147676 /* RCTActionSheet */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511D71A9E6C8500147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 14C644C41AB0DFC900DE3C65 /* RCTActionSheetManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B511ED1A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Debug; - }; - 58B511EE1A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - 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_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - VALIDATE_PRODUCT = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Release; - }; - 58B511F01A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTActionSheet; - RUN_CLANG_STATIC_ANALYZER = YES; - }; - name = Debug; - }; - 58B511F11A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTActionSheet; - RUN_CLANG_STATIC_ANALYZER = NO; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTActionSheet" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511ED1A9E6C8500147676 /* Debug */, - 58B511EE1A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTActionSheet" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511F01A9E6C8500147676 /* Debug */, - 58B511F11A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511D31A9E6C8500147676 /* Project object */; -} diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m deleted file mode 100644 index 91f047323769c7..00000000000000 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ /dev/null @@ -1,202 +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. - */ - -#import "RCTActionSheetManager.h" - -#import -#import -#import -#import -#import - -@interface RCTActionSheetManager () -@end - -@implementation RCTActionSheetManager -{ - // Use NSMapTable, as UIAlertViews do not implement - // which is required for NSDictionary keys - NSMapTable *_callbacks; -} - -RCT_EXPORT_MODULE() - -@synthesize bridge = _bridge; - -- (dispatch_queue_t)methodQueue -{ - return dispatch_get_main_queue(); -} - -/* - * The `anchor` option takes a view to set as the anchor for the share - * popup to point to, on iPads running iOS 8. If it is not passed, it - * defaults to centering the share popup on screen without any arrows. - */ -- (CGRect)sourceRectInView:(UIView *)sourceView - anchorViewTag:(NSNumber *)anchorViewTag -{ - if (anchorViewTag) { - UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag]; - return [anchorView convertRect:anchorView.bounds toView:sourceView]; - } else { - return (CGRect){sourceView.center, {1, 1}}; - } -} - -RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options - callback:(RCTResponseSenderBlock)callback) -{ - if (RCTRunningInAppExtension()) { - RCTLogError(@"Unable to show action sheet from app extension"); - return; - } - - if (!_callbacks) { - _callbacks = [NSMapTable strongToStrongObjectsMapTable]; - } - - NSString *title = [RCTConvert NSString:options[@"title"]]; - NSString *message = [RCTConvert NSString:options[@"message"]]; - NSArray *buttons = [RCTConvert NSStringArray:options[@"options"]]; - NSInteger destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSInteger:options[@"destructiveButtonIndex"]] : -1; - NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1; - - UIViewController *controller = RCTPresentedViewController(); - - if (controller == nil) { - RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); - return; - } - - /* - * The `anchor` option takes a view to set as the anchor for the share - * popup to point to, on iPads running iOS 8. If it is not passed, it - * defaults to centering the share popup on screen without any arrows. - */ - NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]]; - UIView *sourceView = controller.view; - CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag]; - - UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleActionSheet]; - - NSInteger index = 0; - for (NSString *option in buttons) { - UIAlertActionStyle style = UIAlertActionStyleDefault; - if (index == destructiveButtonIndex) { - style = UIAlertActionStyleDestructive; - } else if (index == cancelButtonIndex) { - style = UIAlertActionStyleCancel; - } - - NSInteger localIndex = index; - [alertController addAction:[UIAlertAction actionWithTitle:option - style:style - handler:^(__unused UIAlertAction *action){ - callback(@[@(localIndex)]); - }]]; - - index++; - } - - alertController.modalPresentationStyle = UIModalPresentationPopover; - alertController.popoverPresentationController.sourceView = sourceView; - alertController.popoverPresentationController.sourceRect = sourceRect; - if (!anchorViewTag) { - alertController.popoverPresentationController.permittedArrowDirections = 0; - } - [controller presentViewController:alertController animated:YES completion:nil]; - - alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]]; -} - -RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options - failureCallback:(RCTResponseErrorBlock)failureCallback - successCallback:(RCTResponseSenderBlock)successCallback) -{ - if (RCTRunningInAppExtension()) { - RCTLogError(@"Unable to show action sheet from app extension"); - return; - } - - NSMutableArray *items = [NSMutableArray array]; - NSString *message = [RCTConvert NSString:options[@"message"]]; - if (message) { - [items addObject:message]; - } - NSURL *URL = [RCTConvert NSURL:options[@"url"]]; - if (URL) { - if ([URL.scheme.lowercaseString isEqualToString:@"data"]) { - NSError *error; - NSData *data = [NSData dataWithContentsOfURL:URL - options:(NSDataReadingOptions)0 - error:&error]; - if (!data) { - failureCallback(error); - return; - } - [items addObject:data]; - } else { - [items addObject:URL]; - } - } - if (items.count == 0) { - RCTLogError(@"No `url` or `message` to share"); - return; - } - - UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; - - NSString *subject = [RCTConvert NSString:options[@"subject"]]; - if (subject) { - [shareController setValue:subject forKey:@"subject"]; - } - - NSArray *excludedActivityTypes = [RCTConvert NSStringArray:options[@"excludedActivityTypes"]]; - if (excludedActivityTypes) { - shareController.excludedActivityTypes = excludedActivityTypes; - } - - UIViewController *controller = RCTPresentedViewController(); - shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) { - if (activityError) { - failureCallback(activityError); - } else { - successCallback(@[@(completed), RCTNullIfNil(activityType)]); - } - }; - - shareController.modalPresentationStyle = UIModalPresentationPopover; - NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]]; - if (!anchorViewTag) { - shareController.popoverPresentationController.permittedArrowDirections = 0; - } - shareController.popoverPresentationController.sourceView = controller.view; - shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag]; - - [controller presentViewController:shareController animated:YES completion:nil]; - - shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]]; -} - -#pragma mark UIActionSheetDelegate Methods - -- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex -{ - RCTResponseSenderBlock callback = [_callbacks objectForKey:actionSheet]; - if (callback) { - callback(@[@(buttonIndex)]); - [_callbacks removeObjectForKey:actionSheet]; - } else { - RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title); - } -} - -@end diff --git a/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec b/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec new file mode 100644 index 00000000000000..a9bd4ca8235d0d --- /dev/null +++ b/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec @@ -0,0 +1,34 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +Pod::Spec.new do |s| + s.name = "React-RCTActionSheet" + s.version = version + s.summary = "An API for displaying iOS action sheets and share sheets." + s.homepage = "https://reactnative.dev/" + s.documentation_url = "https://reactnative.dev/docs/actionsheetios" + s.license = package["license"] + s.author = "Facebook, Inc. and its affiliates" + s.platforms = { :ios => "12.4" } + s.source = source + s.source_files = "*.{m}" + s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs" + s.header_dir = "RCTActionSheet" + + s.dependency "React-Core/RCTActionSheetHeaders", version +end diff --git a/Libraries/Alert/Alert.js b/Libraries/Alert/Alert.js index bcb51714440444..1a943e1bbd7b0f 100644 --- a/Libraries/Alert/Alert.js +++ b/Libraries/Alert/Alert.js @@ -1,114 +1,167 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule Alert + * @format * @flow */ -'use strict'; -const AlertIOS = require('AlertIOS'); -const NativeModules = require('NativeModules'); -const Platform = require('Platform'); +import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid'; -import type { AlertType, AlertButtonStyle } from 'AlertIOS'; +import Platform from '../Utilities/Platform'; +import RCTAlertManager from './RCTAlertManager'; +export type AlertType = + | 'default' + | 'plain-text' + | 'secure-text' + | 'login-password'; +export type AlertButtonStyle = 'default' | 'cancel' | 'destructive'; export type Buttons = Array<{ text?: string, onPress?: ?Function, style?: AlertButtonStyle, + ... }>; type Options = { cancelable?: ?boolean, - onDismiss?: ?Function, + userInterfaceStyle?: 'unspecified' | 'light' | 'dark', + onDismiss?: ?() => void, + ... }; /** * Launches an alert dialog with the specified title and message. * - * See http://facebook.github.io/react-native/docs/alert.html + * See https://reactnative.dev/docs/alert */ class Alert { - - /** - * Launches an alert dialog with the specified title and message. - * - * See http://facebook.github.io/react-native/docs/alert.html#alert - */ static alert( title: ?string, message?: ?string, buttons?: Buttons, options?: Options, - type?: AlertType, ): void { if (Platform.OS === 'ios') { - if (typeof type !== 'undefined') { - console.warn('Alert.alert() with a 5th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.'); - AlertIOS.alert(title, message, buttons, type); + Alert.prompt( + title, + message, + buttons, + 'default', + undefined, + undefined, + options, + ); + } else if (Platform.OS === 'android') { + const NativeDialogManagerAndroid = + require('../NativeModules/specs/NativeDialogManagerAndroid').default; + if (!NativeDialogManagerAndroid) { return; } - AlertIOS.alert(title, message, buttons); - } else if (Platform.OS === 'android') { - AlertAndroid.alert(title, message, buttons, options); - } - } -} + const constants = NativeDialogManagerAndroid.getConstants(); -/** - * Wrapper around the Android native module. - */ -class AlertAndroid { + const config: DialogOptions = { + title: title || '', + message: message || '', + cancelable: false, + }; - static alert( - title: ?string, - message?: ?string, - buttons?: Buttons, - options?: Options, - ): void { - var config = { - title: title || '', - message: message || '', - }; + if (options && options.cancelable) { + config.cancelable = options.cancelable; + } + // At most three buttons (neutral, negative, positive). Ignore rest. + // The text 'OK' should be probably localized. iOS Alert does that in native. + const defaultPositiveText = 'OK'; + const validButtons: Buttons = buttons + ? buttons.slice(0, 3) + : [{text: defaultPositiveText}]; + const buttonPositive = validButtons.pop(); + const buttonNegative = validButtons.pop(); + const buttonNeutral = validButtons.pop(); - if (options) { - config = {...config, cancelable: options.cancelable}; - } - // At most three buttons (neutral, negative, positive). Ignore rest. - // The text 'OK' should be probably localized. iOS Alert does that in native. - var validButtons: Buttons = buttons ? buttons.slice(0, 3) : [{text: 'OK'}]; - var buttonPositive = validButtons.pop(); - var buttonNegative = validButtons.pop(); - var buttonNeutral = validButtons.pop(); - if (buttonNeutral) { - config = {...config, buttonNeutral: buttonNeutral.text || '' }; - } - if (buttonNegative) { - config = {...config, buttonNegative: buttonNegative.text || '' }; - } - if (buttonPositive) { - config = {...config, buttonPositive: buttonPositive.text || '' }; - } - NativeModules.DialogManagerAndroid.showAlert( - config, - (errorMessage) => console.warn(errorMessage), - (action, buttonKey) => { - if (action === NativeModules.DialogManagerAndroid.buttonClicked) { - if (buttonKey === NativeModules.DialogManagerAndroid.buttonNeutral) { + if (buttonNeutral) { + config.buttonNeutral = buttonNeutral.text || ''; + } + if (buttonNegative) { + config.buttonNegative = buttonNegative.text || ''; + } + if (buttonPositive) { + config.buttonPositive = buttonPositive.text || defaultPositiveText; + } + + /* $FlowFixMe[missing-local-annot] The type annotation(s) required by + * Flow's LTI update could not be added via codemod */ + const onAction = (action, buttonKey) => { + if (action === constants.buttonClicked) { + if (buttonKey === constants.buttonNeutral) { buttonNeutral.onPress && buttonNeutral.onPress(); - } else if (buttonKey === NativeModules.DialogManagerAndroid.buttonNegative) { + } else if (buttonKey === constants.buttonNegative) { buttonNegative.onPress && buttonNegative.onPress(); - } else if (buttonKey === NativeModules.DialogManagerAndroid.buttonPositive) { + } else if (buttonKey === constants.buttonPositive) { buttonPositive.onPress && buttonPositive.onPress(); } - } else if (action === NativeModules.DialogManagerAndroid.dismissed) { + } else if (action === constants.dismissed) { options && options.onDismiss && options.onDismiss(); } + }; + const onError = (errorMessage: string) => console.warn(errorMessage); + NativeDialogManagerAndroid.showAlert(config, onError, onAction); + } + } + + static prompt( + title: ?string, + message?: ?string, + callbackOrButtons?: ?(((text: string) => void) | Buttons), + type?: ?AlertType = 'plain-text', + defaultValue?: string, + keyboardType?: string, + options?: Options, + ): void { + if (Platform.OS === 'ios') { + let callbacks = []; + const buttons = []; + let cancelButtonKey; + let destructiveButtonKey; + if (typeof callbackOrButtons === 'function') { + callbacks = [callbackOrButtons]; + } else if (Array.isArray(callbackOrButtons)) { + callbackOrButtons.forEach((btn, index) => { + callbacks[index] = btn.onPress; + if (btn.style === 'cancel') { + cancelButtonKey = String(index); + } else if (btn.style === 'destructive') { + destructiveButtonKey = String(index); + } + if (btn.text || index < (callbackOrButtons || []).length - 1) { + const btnDef: {[number]: string} = {}; + btnDef[index] = btn.text || ''; + buttons.push(btnDef); + } + }); } - ); + + RCTAlertManager.alertWithArgs( + { + title: title || '', + message: message || undefined, + buttons, + type: type || undefined, + defaultValue, + cancelButtonKey, + destructiveButtonKey, + keyboardType, + userInterfaceStyle: options?.userInterfaceStyle || undefined, + }, + (id, value) => { + const cb = callbacks[id]; + cb && cb(value); + }, + ); + } } } diff --git a/Libraries/Alert/AlertIOS.js b/Libraries/Alert/AlertIOS.js deleted file mode 100644 index b254406462399e..00000000000000 --- a/Libraries/Alert/AlertIOS.js +++ /dev/null @@ -1,172 +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 AlertIOS - * @flow - * @jsdoc - */ -'use strict'; - -var RCTAlertManager = require('NativeModules').AlertManager; - -/** - * An Alert button type - */ -export type AlertType = $Enum<{ - /** - * Default alert with no inputs - */ - 'default': string, - /** - * Plain text input alert - */ - 'plain-text': string, - /** - * Secure text input alert - */ - 'secure-text': string, - /** - * Login and password alert - */ - 'login-password': string, -}>; - -/** - * An Alert button style - */ -export type AlertButtonStyle = $Enum<{ - /** - * Default button style - */ - 'default': string, - /** - * Cancel button style - */ - 'cancel': string, - /** - * Destructive button style - */ - 'destructive': string, -}>; - -/** - * Array or buttons - * @typedef {Array} ButtonsArray - * @property {string=} text Button label - * @property {Function=} onPress Callback function when button pressed - * @property {AlertButtonStyle=} style Button style - */ -export type ButtonsArray = Array<{ - /** - * Button label - */ - text?: string, - /** - * Callback function when button pressed - */ - onPress?: ?Function, - /** - * Button style - */ - style?: AlertButtonStyle, -}>; - -/** - * Use `AlertIOS` to display an alert dialog with a message or to create a prompt for user input on iOS. If you don't need to prompt for user input, we recommend using `Alert.alert() for cross-platform support. - * - * See http://facebook.github.io/react-native/docs/alertios.html - */ -class AlertIOS { - /** - * Create and display a popup alert. - * - * See http://facebook.github.io/react-native/docs/alertios.html#alert - */ - static alert( - title: ?string, - message?: ?string, - callbackOrButtons?: ?(() => void) | ButtonsArray, - type?: AlertType, - ): void { - if (typeof type !== 'undefined') { - console.warn('AlertIOS.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.'); - this.prompt(title, message, callbackOrButtons, type); - return; - } - this.prompt(title, message, callbackOrButtons, 'default'); - } - - /** - * Create and display a prompt to enter some text. - * - * See http://facebook.github.io/react-native/docs/alertios.html#prompt - */ - static prompt( - title: ?string, - message?: ?string, - callbackOrButtons?: ?((text: string) => void) | ButtonsArray, - type?: ?AlertType = 'plain-text', - defaultValue?: string, - keyboardType?: string - ): void { - if (typeof type === 'function') { - console.warn( - 'You passed a callback function as the "type" argument to AlertIOS.prompt(). React Native is ' + - 'assuming you want to use the deprecated AlertIOS.prompt(title, defaultValue, buttons, callback) ' + - 'signature. The current signature is AlertIOS.prompt(title, message, callbackOrButtons, type, defaultValue, ' + - 'keyboardType) and the old syntax will be removed in a future version.'); - - var callback = type; - RCTAlertManager.alertWithArgs({ - title: title || '', - type: 'plain-text', - defaultValue: message, - }, (id, value) => { - callback(value); - }); - return; - } - - var callbacks = []; - var buttons = []; - var cancelButtonKey; - var destructiveButtonKey; - if (typeof callbackOrButtons === 'function') { - callbacks = [callbackOrButtons]; - } - else if (callbackOrButtons instanceof Array) { - callbackOrButtons.forEach((btn, index) => { - callbacks[index] = btn.onPress; - if (btn.style === 'cancel') { - cancelButtonKey = String(index); - } else if (btn.style === 'destructive') { - destructiveButtonKey = String(index); - } - if (btn.text || index < (callbackOrButtons || []).length - 1) { - var btnDef = {}; - btnDef[index] = btn.text || ''; - buttons.push(btnDef); - } - }); - } - - RCTAlertManager.alertWithArgs({ - title: title || '', - message: message || undefined, - buttons, - type: type || undefined, - defaultValue, - cancelButtonKey, - destructiveButtonKey, - keyboardType, - }, (id, value) => { - var cb = callbacks[id]; - cb && cb(value); - }); - } -} - -module.exports = AlertIOS; diff --git a/Libraries/Alert/NativeAlertManager.js b/Libraries/Alert/NativeAlertManager.js new file mode 100644 index 00000000000000..9597ef155a522d --- /dev/null +++ b/Libraries/Alert/NativeAlertManager.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export type Args = {| + title?: string, + message?: string, + buttons?: Array, // TODO(T67565166): have a better type + type?: string, + defaultValue?: string, + cancelButtonKey?: string, + destructiveButtonKey?: string, + keyboardType?: string, + userInterfaceStyle?: string, +|}; + +export interface Spec extends TurboModule { + +alertWithArgs: ( + args: Args, + callback: (id: number, value: string) => void, + ) => void; +} + +export default (TurboModuleRegistry.get('AlertManager'): ?Spec); diff --git a/Libraries/Alert/RCTAlertManager.android.js b/Libraries/Alert/RCTAlertManager.android.js index f819b55e1b3d1f..3a15b76ac57c55 100644 --- a/Libraries/Alert/RCTAlertManager.android.js +++ b/Libraries/Alert/RCTAlertManager.android.js @@ -1,23 +1,27 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule RCTAlertManager -*/ -'use strict'; + * @format + */ -var NativeModules = require('NativeModules'); +import NativeDialogManagerAndroid from '../NativeModules/specs/NativeDialogManagerAndroid'; function emptyCallback() {} module.exports = { - alertWithArgs: function(args, callback) { + alertWithArgs: function (args, callback) { // TODO(5998984): Polyfill it correctly with DialogManagerAndroid - NativeModules.DialogManagerAndroid.showAlert( - args, - emptyCallback, - callback || emptyCallback); + if (!NativeDialogManagerAndroid) { + return; + } + + NativeDialogManagerAndroid.showAlert( + args, + emptyCallback, + callback || emptyCallback, + ); }, }; diff --git a/Libraries/Alert/RCTAlertManager.ios.js b/Libraries/Alert/RCTAlertManager.ios.js index 3aef740abed4d5..01f055da53d402 100644 --- a/Libraries/Alert/RCTAlertManager.ios.js +++ b/Libraries/Alert/RCTAlertManager.ios.js @@ -1,14 +1,24 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule RCTAlertManager - * @flow + * @format + * @flow strict-local */ -'use strict'; -var RCTAlertManager = require('NativeModules').AlertManager; +import NativeAlertManager from './NativeAlertManager'; +import type {Args} from './NativeAlertManager'; -module.exports = RCTAlertManager; +module.exports = { + alertWithArgs( + args: Args, + callback: (id: number, value: string) => void, + ): void { + if (NativeAlertManager == null) { + return; + } + NativeAlertManager.alertWithArgs(args, callback); + }, +}; diff --git a/Libraries/Animated/Animated.js b/Libraries/Animated/Animated.js new file mode 100644 index 00000000000000..88fd9dbb188dbe --- /dev/null +++ b/Libraries/Animated/Animated.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import Platform from '../Utilities/Platform'; +import typeof AnimatedFlatList from './components/AnimatedFlatList'; +import typeof AnimatedImage from './components/AnimatedImage'; +import typeof AnimatedScrollView from './components/AnimatedScrollView'; +import typeof AnimatedSectionList from './components/AnimatedSectionList'; +import typeof AnimatedText from './components/AnimatedText'; +import typeof AnimatedView from './components/AnimatedView'; + +import * as AnimatedMock from './AnimatedMock'; +import * as AnimatedImplementation from './AnimatedImplementation'; + +const Animated = ((Platform.isTesting + ? AnimatedMock + : AnimatedImplementation): typeof AnimatedMock); + +module.exports = { + get FlatList(): AnimatedFlatList { + return require('./components/AnimatedFlatList'); + }, + get Image(): AnimatedImage { + return require('./components/AnimatedImage'); + }, + get ScrollView(): AnimatedScrollView { + return require('./components/AnimatedScrollView'); + }, + get SectionList(): AnimatedSectionList { + return require('./components/AnimatedSectionList'); + }, + get Text(): AnimatedText { + return require('./components/AnimatedText'); + }, + get View(): AnimatedView { + return require('./components/AnimatedView'); + }, + ...Animated, +}; diff --git a/Libraries/Animated/AnimatedEvent.js b/Libraries/Animated/AnimatedEvent.js new file mode 100644 index 00000000000000..af1d660a8de794 --- /dev/null +++ b/Libraries/Animated/AnimatedEvent.js @@ -0,0 +1,261 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedValue = require('./nodes/AnimatedValue'); +const AnimatedValueXY = require('./nodes/AnimatedValueXY'); +const NativeAnimatedHelper = require('./NativeAnimatedHelper'); +const ReactNative = require('../Renderer/shims/ReactNative'); + +const invariant = require('invariant'); + +const {shouldUseNativeDriver} = require('./NativeAnimatedHelper'); + +import type {PlatformConfig} from './AnimatedPlatformConfig'; + +export type Mapping = + | {[key: string]: Mapping, ...} + | AnimatedValue + | AnimatedValueXY; +export type EventConfig = { + listener?: ?Function, + useNativeDriver: boolean, + platformConfig?: PlatformConfig, +}; + +function attachNativeEvent( + viewRef: any, + eventName: string, + argMapping: $ReadOnlyArray, + platformConfig: ?PlatformConfig, +): {detach: () => void} { + // Find animated values in `argMapping` and create an array representing their + // key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x']. + const eventMappings = []; + + const traverse = (value: mixed, path: Array) => { + if (value instanceof AnimatedValue) { + value.__makeNative(platformConfig); + + eventMappings.push({ + nativeEventPath: path, + animatedValueTag: value.__getNativeTag(), + }); + } else if (value instanceof AnimatedValueXY) { + traverse(value.x, path.concat('x')); + traverse(value.y, path.concat('y')); + } else if (typeof value === 'object') { + for (const key in value) { + traverse(value[key], path.concat(key)); + } + } + }; + + invariant( + argMapping[0] && argMapping[0].nativeEvent, + 'Native driven events only support animated values contained inside `nativeEvent`.', + ); + + // Assume that the event containing `nativeEvent` is always the first argument. + traverse(argMapping[0].nativeEvent, []); + + const viewTag = ReactNative.findNodeHandle(viewRef); + if (viewTag != null) { + eventMappings.forEach(mapping => { + NativeAnimatedHelper.API.addAnimatedEventToView( + viewTag, + eventName, + mapping, + ); + }); + } + + return { + detach() { + if (viewTag != null) { + eventMappings.forEach(mapping => { + NativeAnimatedHelper.API.removeAnimatedEventFromView( + viewTag, + eventName, + // $FlowFixMe[incompatible-call] + mapping.animatedValueTag, + ); + }); + } + }, + }; +} + +function validateMapping(argMapping: $ReadOnlyArray, args: any) { + const validate = (recMapping: ?Mapping, recEvt: any, key: string) => { + if (recMapping instanceof AnimatedValue) { + invariant( + typeof recEvt === 'number', + 'Bad mapping of event key ' + + key + + ', should be number but got ' + + typeof recEvt, + ); + return; + } + if (recMapping instanceof AnimatedValueXY) { + invariant( + typeof recEvt.x === 'number' && typeof recEvt.y === 'number', + 'Bad mapping of event key ' + key + ', should be XY but got ' + recEvt, + ); + return; + } + if (typeof recEvt === 'number') { + invariant( + recMapping instanceof AnimatedValue, + 'Bad mapping of type ' + + typeof recMapping + + ' for key ' + + key + + ', event value must map to AnimatedValue', + ); + return; + } + invariant( + typeof recMapping === 'object', + 'Bad mapping of type ' + typeof recMapping + ' for key ' + key, + ); + invariant( + typeof recEvt === 'object', + 'Bad event of type ' + typeof recEvt + ' for key ' + key, + ); + for (const mappingKey in recMapping) { + validate(recMapping[mappingKey], recEvt[mappingKey], mappingKey); + } + }; + + invariant( + args.length >= argMapping.length, + 'Event has less arguments than mapping', + ); + argMapping.forEach((mapping, idx) => { + validate(mapping, args[idx], 'arg' + idx); + }); +} + +class AnimatedEvent { + _argMapping: $ReadOnlyArray; + _listeners: Array = []; + _attachedEvent: ?{detach: () => void, ...}; + __isNative: boolean; + __platformConfig: ?PlatformConfig; + + constructor(argMapping: $ReadOnlyArray, config: EventConfig) { + this._argMapping = argMapping; + + if (config == null) { + console.warn('Animated.event now requires a second argument for options'); + config = {useNativeDriver: false}; + } + + if (config.listener) { + this.__addListener(config.listener); + } + this._attachedEvent = null; + this.__isNative = shouldUseNativeDriver(config); + this.__platformConfig = config.platformConfig; + } + + __addListener(callback: Function): void { + this._listeners.push(callback); + } + + __removeListener(callback: Function): void { + this._listeners = this._listeners.filter(listener => listener !== callback); + } + + __attach(viewRef: any, eventName: string) { + invariant( + this.__isNative, + 'Only native driven events need to be attached.', + ); + + this._attachedEvent = attachNativeEvent( + viewRef, + eventName, + this._argMapping, + this.__platformConfig, + ); + } + + __detach(viewTag: any, eventName: string) { + invariant( + this.__isNative, + 'Only native driven events need to be detached.', + ); + + this._attachedEvent && this._attachedEvent.detach(); + } + + __getHandler(): any | ((...args: any) => void) { + if (this.__isNative) { + if (__DEV__) { + let validatedMapping = false; + return (...args: any) => { + if (!validatedMapping) { + validateMapping(this._argMapping, args); + validatedMapping = true; + } + this._callListeners(...args); + }; + } else { + return this._callListeners; + } + } + + let validatedMapping = false; + return (...args: any) => { + if (__DEV__ && !validatedMapping) { + validateMapping(this._argMapping, args); + validatedMapping = true; + } + + const traverse = ( + recMapping: ?(Mapping | AnimatedValue), + recEvt: any, + ) => { + if (recMapping instanceof AnimatedValue) { + if (typeof recEvt === 'number') { + recMapping.setValue(recEvt); + } + } else if (recMapping instanceof AnimatedValueXY) { + if (typeof recEvt === 'object') { + traverse(recMapping.x, recEvt.x); + traverse(recMapping.y, recEvt.y); + } + } else if (typeof recMapping === 'object') { + for (const mappingKey in recMapping) { + /* $FlowFixMe[prop-missing] (>=0.120.0) This comment suppresses an + * error found when Flow v0.120 was deployed. To see the error, + * delete this comment and run Flow. */ + traverse(recMapping[mappingKey], recEvt[mappingKey]); + } + } + }; + this._argMapping.forEach((mapping, idx) => { + traverse(mapping, args[idx]); + }); + + this._callListeners(...args); + }; + } + + _callListeners = (...args: any) => { + this._listeners.forEach(listener => listener(...args)); + }; +} + +module.exports = {AnimatedEvent, attachNativeEvent}; diff --git a/Libraries/Animated/AnimatedImplementation.js b/Libraries/Animated/AnimatedImplementation.js new file mode 100644 index 00000000000000..5aaa29f336537f --- /dev/null +++ b/Libraries/Animated/AnimatedImplementation.js @@ -0,0 +1,754 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent'); +const AnimatedAddition = require('./nodes/AnimatedAddition'); +const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp'); +const AnimatedDivision = require('./nodes/AnimatedDivision'); +const AnimatedInterpolation = require('./nodes/AnimatedInterpolation'); +const AnimatedModulo = require('./nodes/AnimatedModulo'); +const AnimatedMultiplication = require('./nodes/AnimatedMultiplication'); +const AnimatedNode = require('./nodes/AnimatedNode'); +const AnimatedSubtraction = require('./nodes/AnimatedSubtraction'); +const AnimatedTracking = require('./nodes/AnimatedTracking'); +const AnimatedValue = require('./nodes/AnimatedValue'); +const AnimatedValueXY = require('./nodes/AnimatedValueXY'); +const DecayAnimation = require('./animations/DecayAnimation'); +const SpringAnimation = require('./animations/SpringAnimation'); +const TimingAnimation = require('./animations/TimingAnimation'); + +const createAnimatedComponent = require('./createAnimatedComponent'); + +import type { + AnimationConfig, + EndCallback, + EndResult, +} from './animations/Animation'; +import type {TimingAnimationConfig} from './animations/TimingAnimation'; +import type {DecayAnimationConfig} from './animations/DecayAnimation'; +import type {SpringAnimationConfig} from './animations/SpringAnimation'; +import type {Mapping, EventConfig} from './AnimatedEvent'; + +import AnimatedColor from './nodes/AnimatedColor'; + +export type CompositeAnimation = { + start: (callback?: ?EndCallback) => void, + stop: () => void, + reset: () => void, + _startNativeLoop: (iterations?: number) => void, + _isUsingNativeDriver: () => boolean, + ... +}; + +const add = function ( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedAddition { + return new AnimatedAddition(a, b); +}; + +const subtract = function ( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedSubtraction { + return new AnimatedSubtraction(a, b); +}; + +const divide = function ( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedDivision { + return new AnimatedDivision(a, b); +}; + +const multiply = function ( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedMultiplication { + return new AnimatedMultiplication(a, b); +}; + +const modulo = function (a: AnimatedNode, modulus: number): AnimatedModulo { + return new AnimatedModulo(a, modulus); +}; + +const diffClamp = function ( + a: AnimatedNode, + min: number, + max: number, +): AnimatedDiffClamp { + return new AnimatedDiffClamp(a, min, max); +}; + +const _combineCallbacks = function ( + callback: ?EndCallback, + config: $ReadOnly<{...AnimationConfig, ...}>, +) { + if (callback && config.onComplete) { + return (...args: Array) => { + config.onComplete && config.onComplete(...args); + callback && callback(...args); + }; + } else { + return callback || config.onComplete; + } +}; + +const maybeVectorAnim = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: Object, + anim: (value: AnimatedValue, config: Object) => CompositeAnimation, +): ?CompositeAnimation { + if (value instanceof AnimatedValueXY) { + const configX = {...config}; + const configY = {...config}; + for (const key in config) { + const {x, y} = config[key]; + if (x !== undefined && y !== undefined) { + configX[key] = x; + configY[key] = y; + } + } + const aX = anim((value: AnimatedValueXY).x, configX); + const aY = anim((value: AnimatedValueXY).y, configY); + // We use `stopTogether: false` here because otherwise tracking will break + // because the second animation will get stopped before it can update. + return parallel([aX, aY], {stopTogether: false}); + } else if (value instanceof AnimatedColor) { + const configR = {...config}; + const configG = {...config}; + const configB = {...config}; + const configA = {...config}; + for (const key in config) { + const {r, g, b, a} = config[key]; + if ( + r !== undefined && + g !== undefined && + b !== undefined && + a !== undefined + ) { + configR[key] = r; + configG[key] = g; + configB[key] = b; + configA[key] = a; + } + } + const aR = anim((value: AnimatedColor).r, configR); + const aG = anim((value: AnimatedColor).g, configG); + const aB = anim((value: AnimatedColor).b, configB); + const aA = anim((value: AnimatedColor).a, configA); + // We use `stopTogether: false` here because otherwise tracking will break + // because the second animation will get stopped before it can update. + return parallel([aR, aG, aB, aA], {stopTogether: false}); + } + return null; +}; + +const spring = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: SpringAnimationConfig, +): CompositeAnimation { + const start = function ( + animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor, + configuration: SpringAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + if (configuration.toValue instanceof AnimatedNode) { + singleValue.track( + new AnimatedTracking( + singleValue, + configuration.toValue, + SpringAnimation, + singleConfig, + callback, + ), + ); + } else { + singleValue.animate(new SpringAnimation(singleConfig), callback); + } + }; + return ( + maybeVectorAnim(value, config, spring) || { + start: function (callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function (): void { + value.stopAnimation(); + }, + + reset: function (): void { + value.resetAnimation(); + }, + + _startNativeLoop: function (iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function (): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const timing = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: TimingAnimationConfig, +): CompositeAnimation { + const start = function ( + animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor, + configuration: TimingAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + if (configuration.toValue instanceof AnimatedNode) { + singleValue.track( + new AnimatedTracking( + singleValue, + configuration.toValue, + TimingAnimation, + singleConfig, + callback, + ), + ); + } else { + singleValue.animate(new TimingAnimation(singleConfig), callback); + } + }; + + return ( + maybeVectorAnim(value, config, timing) || { + start: function (callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function (): void { + value.stopAnimation(); + }, + + reset: function (): void { + value.resetAnimation(); + }, + + _startNativeLoop: function (iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function (): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const decay = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: DecayAnimationConfig, +): CompositeAnimation { + const start = function ( + animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor, + configuration: DecayAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + singleValue.animate(new DecayAnimation(singleConfig), callback); + }; + + return ( + maybeVectorAnim(value, config, decay) || { + start: function (callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function (): void { + value.stopAnimation(); + }, + + reset: function (): void { + value.resetAnimation(); + }, + + _startNativeLoop: function (iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function (): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const sequence = function ( + animations: Array, +): CompositeAnimation { + let current = 0; + return { + start: function (callback?: ?EndCallback) { + const onComplete = function (result: EndResult) { + if (!result.finished) { + callback && callback(result); + return; + } + + current++; + + if (current === animations.length) { + callback && callback(result); + return; + } + + animations[current].start(onComplete); + }; + + if (animations.length === 0) { + callback && callback({finished: true}); + } else { + animations[current].start(onComplete); + } + }, + + stop: function () { + if (current < animations.length) { + animations[current].stop(); + } + }, + + reset: function () { + animations.forEach((animation, idx) => { + if (idx <= current) { + animation.reset(); + } + }); + current = 0; + }, + + _startNativeLoop: function () { + throw new Error( + 'Loops run using the native driver cannot contain Animated.sequence animations', + ); + }, + + _isUsingNativeDriver: function (): boolean { + return false; + }, + }; +}; + +type ParallelConfig = { + // If one is stopped, stop all. default: true + stopTogether?: boolean, + ... +}; +const parallel = function ( + animations: Array, + config?: ?ParallelConfig, +): CompositeAnimation { + let doneCount = 0; + // Make sure we only call stop() at most once for each animation + const hasEnded = {}; + const stopTogether = !(config && config.stopTogether === false); + + const result = { + start: function (callback?: ?EndCallback) { + if (doneCount === animations.length) { + callback && callback({finished: true}); + return; + } + + animations.forEach((animation, idx) => { + const cb = function (endResult: EndResult | {finished: boolean}) { + hasEnded[idx] = true; + doneCount++; + if (doneCount === animations.length) { + doneCount = 0; + callback && callback(endResult); + return; + } + + if (!endResult.finished && stopTogether) { + result.stop(); + } + }; + + if (!animation) { + cb({finished: true}); + } else { + animation.start(cb); + } + }); + }, + + stop: function (): void { + animations.forEach((animation, idx) => { + !hasEnded[idx] && animation.stop(); + hasEnded[idx] = true; + }); + }, + + reset: function (): void { + animations.forEach((animation, idx) => { + animation.reset(); + hasEnded[idx] = false; + doneCount = 0; + }); + }, + + _startNativeLoop: function () { + throw new Error( + 'Loops run using the native driver cannot contain Animated.parallel animations', + ); + }, + + _isUsingNativeDriver: function (): boolean { + return false; + }, + }; + + return result; +}; + +const delay = function (time: number): CompositeAnimation { + // Would be nice to make a specialized implementation + return timing(new AnimatedValue(0), { + toValue: 0, + delay: time, + duration: 0, + useNativeDriver: false, + }); +}; + +const stagger = function ( + time: number, + animations: Array, +): CompositeAnimation { + return parallel( + animations.map((animation, i) => { + return sequence([delay(time * i), animation]); + }), + ); +}; + +type LoopAnimationConfig = { + iterations: number, + resetBeforeIteration?: boolean, + ... +}; + +const loop = function ( + animation: CompositeAnimation, + // $FlowFixMe[prop-missing] + {iterations = -1, resetBeforeIteration = true}: LoopAnimationConfig = {}, +): CompositeAnimation { + let isFinished = false; + let iterationsSoFar = 0; + return { + start: function (callback?: ?EndCallback) { + const restart = function (result: EndResult = {finished: true}): void { + if ( + isFinished || + iterationsSoFar === iterations || + result.finished === false + ) { + callback && callback(result); + } else { + iterationsSoFar++; + resetBeforeIteration && animation.reset(); + animation.start(restart); + } + }; + if (!animation || iterations === 0) { + callback && callback({finished: true}); + } else { + if (animation._isUsingNativeDriver()) { + animation._startNativeLoop(iterations); + } else { + restart(); // Start looping recursively on the js thread + } + } + }, + + stop: function (): void { + isFinished = true; + animation.stop(); + }, + + reset: function (): void { + iterationsSoFar = 0; + isFinished = false; + animation.reset(); + }, + + _startNativeLoop: function () { + throw new Error( + 'Loops run using the native driver cannot contain Animated.loop animations', + ); + }, + + _isUsingNativeDriver: function (): boolean { + return animation._isUsingNativeDriver(); + }, + }; +}; + +function forkEvent( + event: ?AnimatedEvent | ?Function, + listener: Function, +): AnimatedEvent | Function { + if (!event) { + return listener; + } else if (event instanceof AnimatedEvent) { + event.__addListener(listener); + return event; + } else { + return (...args) => { + typeof event === 'function' && event(...args); + listener(...args); + }; + } +} + +function unforkEvent( + event: ?AnimatedEvent | ?Function, + listener: Function, +): void { + if (event && event instanceof AnimatedEvent) { + event.__removeListener(listener); + } +} + +const event = function ( + argMapping: $ReadOnlyArray, + config: EventConfig, +): any { + const animatedEvent = new AnimatedEvent(argMapping, config); + if (animatedEvent.__isNative) { + return animatedEvent; + } else { + return animatedEvent.__getHandler(); + } +}; + +// All types of animated nodes that represent scalar numbers and can be interpolated (etc) +type AnimatedNumeric = + | AnimatedAddition + | AnimatedDiffClamp + | AnimatedDivision + | AnimatedInterpolation + | AnimatedModulo + | AnimatedMultiplication + | AnimatedSubtraction + | AnimatedValue; + +export type {AnimatedNumeric as Numeric}; + +/** + * The `Animated` library is designed to make animations fluid, powerful, and + * easy to build and maintain. `Animated` focuses on declarative relationships + * between inputs and outputs, with configurable transforms in between, and + * simple `start`/`stop` methods to control time-based animation execution. + * If additional transforms are added, be sure to include them in + * AnimatedMock.js as well. + * + * See https://reactnative.dev/docs/animated + */ +module.exports = { + /** + * Standard value class for driving animations. Typically initialized with + * `new Animated.Value(0);` + * + * See https://reactnative.dev/docs/animated#value + */ + Value: AnimatedValue, + /** + * 2D value class for driving 2D animations, such as pan gestures. + * + * See https://reactnative.dev/docs/animatedvaluexy + */ + ValueXY: AnimatedValueXY, + /** + * Value class for driving color animations. + */ + Color: AnimatedColor, + /** + * Exported to use the Interpolation type in flow. + * + * See https://reactnative.dev/docs/animated#interpolation + */ + Interpolation: AnimatedInterpolation, + /** + * Exported for ease of type checking. All animated values derive from this + * class. + * + * See https://reactnative.dev/docs/animated#node + */ + Node: AnimatedNode, + + /** + * Animates a value from an initial velocity to zero based on a decay + * coefficient. + * + * See https://reactnative.dev/docs/animated#decay + */ + decay, + /** + * Animates a value along a timed easing curve. The Easing module has tons of + * predefined curves, or you can use your own function. + * + * See https://reactnative.dev/docs/animated#timing + */ + timing, + /** + * Animates a value according to an analytical spring model based on + * damped harmonic oscillation. + * + * See https://reactnative.dev/docs/animated#spring + */ + spring, + + /** + * Creates a new Animated value composed from two Animated values added + * together. + * + * See https://reactnative.dev/docs/animated#add + */ + add, + + /** + * Creates a new Animated value composed by subtracting the second Animated + * value from the first Animated value. + * + * See https://reactnative.dev/docs/animated#subtract + */ + subtract, + + /** + * Creates a new Animated value composed by dividing the first Animated value + * by the second Animated value. + * + * See https://reactnative.dev/docs/animated#divide + */ + divide, + + /** + * Creates a new Animated value composed from two Animated values multiplied + * together. + * + * See https://reactnative.dev/docs/animated#multiply + */ + multiply, + + /** + * Creates a new Animated value that is the (non-negative) modulo of the + * provided Animated value. + * + * See https://reactnative.dev/docs/animated#modulo + */ + modulo, + + /** + * Create a new Animated value that is limited between 2 values. It uses the + * difference between the last value so even if the value is far from the + * bounds it will start changing when the value starts getting closer again. + * + * See https://reactnative.dev/docs/animated#diffclamp + */ + diffClamp, + + /** + * Starts an animation after the given delay. + * + * See https://reactnative.dev/docs/animated#delay + */ + delay, + /** + * Starts an array of animations in order, waiting for each to complete + * before starting the next. If the current running animation is stopped, no + * following animations will be started. + * + * See https://reactnative.dev/docs/animated#sequence + */ + sequence, + /** + * Starts an array of animations all at the same time. By default, if one + * of the animations is stopped, they will all be stopped. You can override + * this with the `stopTogether` flag. + * + * See https://reactnative.dev/docs/animated#parallel + */ + parallel, + /** + * Array of animations may run in parallel (overlap), but are started in + * sequence with successive delays. Nice for doing trailing effects. + * + * See https://reactnative.dev/docs/animated#stagger + */ + stagger, + /** + * Loops a given animation continuously, so that each time it reaches the + * end, it resets and begins again from the start. + * + * See https://reactnative.dev/docs/animated#loop + */ + loop, + + /** + * Takes an array of mappings and extracts values from each arg accordingly, + * then calls `setValue` on the mapped outputs. + * + * See https://reactnative.dev/docs/animated#event + */ + event, + + /** + * Make any React component Animatable. Used to create `Animated.View`, etc. + * + * See https://reactnative.dev/docs/animated#createanimatedcomponent + */ + createAnimatedComponent, + + /** + * Imperative API to attach an animated value to an event on a view. Prefer + * using `Animated.event` with `useNativeDrive: true` if possible. + * + * See https://reactnative.dev/docs/animated#attachnativeevent + */ + attachNativeEvent, + + /** + * Advanced imperative API for snooping on animated events that are passed in + * through props. Use values directly where possible. + * + * See https://reactnative.dev/docs/animated#forkevent + */ + forkEvent, + unforkEvent, + + /** + * Expose Event class, so it can be used as a type for type checkers. + */ + Event: AnimatedEvent, +}; diff --git a/Libraries/Animated/AnimatedMock.js b/Libraries/Animated/AnimatedMock.js new file mode 100644 index 00000000000000..b7af8042fb90e5 --- /dev/null +++ b/Libraries/Animated/AnimatedMock.js @@ -0,0 +1,197 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {EndResult} from './animations/Animation'; + +const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent'); +const AnimatedImplementation = require('./AnimatedImplementation'); +const AnimatedInterpolation = require('./nodes/AnimatedInterpolation'); +const AnimatedNode = require('./nodes/AnimatedNode'); +const AnimatedValue = require('./nodes/AnimatedValue'); +const AnimatedValueXY = require('./nodes/AnimatedValueXY'); + +const createAnimatedComponent = require('./createAnimatedComponent'); + +import type {EndCallback} from './animations/Animation'; +import type {TimingAnimationConfig} from './animations/TimingAnimation'; +import type {DecayAnimationConfig} from './animations/DecayAnimation'; +import type {SpringAnimationConfig} from './animations/SpringAnimation'; +import type {Numeric as AnimatedNumeric} from './AnimatedImplementation'; +import AnimatedColor from './nodes/AnimatedColor'; + +/** + * Animations are a source of flakiness in snapshot testing. This mock replaces + * animation functions from AnimatedImplementation with empty animations for + * predictability in tests. When possible the animation will run immediately + * to the final state. + */ + +// Prevent any callback invocation from recursively triggering another +// callback, which may trigger another animation +let inAnimationCallback = false; +function mockAnimationStart( + start: (callback?: ?EndCallback) => void, +): (callback?: ?EndCallback) => void { + return callback => { + const guardedCallback = + callback == null + ? callback + : (...args: Array) => { + if (inAnimationCallback) { + console.warn( + 'Ignoring recursive animation callback when running mock animations', + ); + return; + } + inAnimationCallback = true; + try { + callback(...args); + } finally { + inAnimationCallback = false; + } + }; + start(guardedCallback); + }; +} + +export type CompositeAnimation = { + start: (callback?: ?EndCallback) => void, + stop: () => void, + reset: () => void, + _startNativeLoop: (iterations?: number) => void, + _isUsingNativeDriver: () => boolean, + ... +}; + +const emptyAnimation = { + start: () => {}, + stop: () => {}, + reset: () => {}, + _startNativeLoop: () => {}, + _isUsingNativeDriver: () => { + return false; + }, +}; + +const mockCompositeAnimation = ( + animations: Array, +): CompositeAnimation => ({ + ...emptyAnimation, + start: mockAnimationStart((callback?: ?EndCallback): void => { + animations.forEach(animation => animation.start()); + callback?.({finished: true}); + }), +}); + +const spring = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: SpringAnimationConfig, +): CompositeAnimation { + const anyValue: any = value; + return { + ...emptyAnimation, + start: mockAnimationStart((callback?: ?EndCallback): void => { + anyValue.setValue(config.toValue); + callback?.({finished: true}); + }), + }; +}; + +const timing = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: TimingAnimationConfig, +): CompositeAnimation { + const anyValue: any = value; + return { + ...emptyAnimation, + start: mockAnimationStart((callback?: ?EndCallback): void => { + anyValue.setValue(config.toValue); + callback?.({finished: true}); + }), + }; +}; + +const decay = function ( + value: AnimatedValue | AnimatedValueXY | AnimatedColor, + config: DecayAnimationConfig, +): CompositeAnimation { + return emptyAnimation; +}; + +const sequence = function ( + animations: Array, +): CompositeAnimation { + return mockCompositeAnimation(animations); +}; + +type ParallelConfig = {stopTogether?: boolean, ...}; +const parallel = function ( + animations: Array, + config?: ?ParallelConfig, +): CompositeAnimation { + return mockCompositeAnimation(animations); +}; + +const delay = function (time: number): CompositeAnimation { + return emptyAnimation; +}; + +const stagger = function ( + time: number, + animations: Array, +): CompositeAnimation { + return mockCompositeAnimation(animations); +}; + +type LoopAnimationConfig = { + iterations: number, + resetBeforeIteration?: boolean, + ... +}; + +const loop = function ( + animation: CompositeAnimation, + // $FlowFixMe[prop-missing] + {iterations = -1}: LoopAnimationConfig = {}, +): CompositeAnimation { + return emptyAnimation; +}; + +export type {AnimatedNumeric as Numeric}; + +module.exports = { + Value: AnimatedValue, + ValueXY: AnimatedValueXY, + Color: AnimatedColor, + Interpolation: AnimatedInterpolation, + Node: AnimatedNode, + decay, + timing, + spring, + add: AnimatedImplementation.add, + subtract: AnimatedImplementation.subtract, + divide: AnimatedImplementation.divide, + multiply: AnimatedImplementation.multiply, + modulo: AnimatedImplementation.modulo, + diffClamp: AnimatedImplementation.diffClamp, + delay, + sequence, + parallel, + stagger, + loop, + event: AnimatedImplementation.event, + createAnimatedComponent, + attachNativeEvent, + forkEvent: AnimatedImplementation.forkEvent, + unforkEvent: AnimatedImplementation.unforkEvent, + Event: AnimatedEvent, +}; diff --git a/Libraries/Animated/AnimatedPlatformConfig.js b/Libraries/Animated/AnimatedPlatformConfig.js new file mode 100644 index 00000000000000..fb0b52f3f1c8bd --- /dev/null +++ b/Libraries/Animated/AnimatedPlatformConfig.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +export type PlatformConfig = {}; diff --git a/Libraries/Animated/AnimatedWeb.js b/Libraries/Animated/AnimatedWeb.js new file mode 100644 index 00000000000000..e1c016b753241b --- /dev/null +++ b/Libraries/Animated/AnimatedWeb.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +'use strict'; + +const AnimatedImplementation = require('./AnimatedImplementation'); + +module.exports = { + ...AnimatedImplementation, + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ + div: (AnimatedImplementation.createAnimatedComponent('div'): $FlowFixMe), + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ + span: (AnimatedImplementation.createAnimatedComponent('span'): $FlowFixMe), + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ + img: (AnimatedImplementation.createAnimatedComponent('img'): $FlowFixMe), +}; diff --git a/Libraries/Animated/src/Easing.js b/Libraries/Animated/Easing.js similarity index 77% rename from Libraries/Animated/src/Easing.js rename to Libraries/Animated/Easing.js index f5c00fd813f7dc..f8b5bac2f30404 100644 --- a/Libraries/Animated/src/Easing.js +++ b/Libraries/Animated/Easing.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule Easing - * @flow + * @format + * @flow strict */ + 'use strict'; let ease; @@ -56,20 +57,20 @@ let ease; * - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical * - [`out`](docs/easing.html#out) runs an easing function backwards */ -class Easing { +const Easing = { /** * A stepping function, returns 1 for any positive value of `n`. */ - static step0(n: number) { + step0(n: number): number { return n > 0 ? 1 : 0; - } + }, /** * A stepping function, returns 1 if `n` is greater than or equal to 1. */ - static step1(n: number) { + step1(n: number): number { return n >= 1 ? 1 : 0; - } + }, /** * A linear function, `f(t) = t`. Position correlates to elapsed time one to @@ -77,9 +78,9 @@ class Easing { * * http://cubic-bezier.com/#0,0,1,1 */ - static linear(t: number) { + linear(t: number): number { return t; - } + }, /** * A simple inertial interaction, similar to an object slowly accelerating to @@ -87,12 +88,12 @@ class Easing { * * http://cubic-bezier.com/#.42,0,1,1 */ - static ease(t: number): number { + ease(t: number): number { if (!ease) { ease = Easing.bezier(0.42, 0, 1, 1); } return ease(t); - } + }, /** * A quadratic function, `f(t) = t * t`. Position equals the square of elapsed @@ -100,9 +101,9 @@ class Easing { * * http://easings.net/#easeInQuad */ - static quad(t: number) { + quad(t: number): number { return t * t; - } + }, /** * A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed @@ -110,9 +111,9 @@ class Easing { * * http://easings.net/#easeInCubic */ - static cubic(t: number) { + cubic(t: number): number { return t * t * t; - } + }, /** * A power function. Position is equal to the Nth power of elapsed time. @@ -120,36 +121,36 @@ class Easing { * n = 4: http://easings.net/#easeInQuart * n = 5: http://easings.net/#easeInQuint */ - static poly(n: number) { + poly(n: number): (t: number) => number { return (t: number) => Math.pow(t, n); - } + }, /** * A sinusoidal function. * * http://easings.net/#easeInSine */ - static sin(t: number) { - return 1 - Math.cos(t * Math.PI / 2); - } + sin(t: number): number { + return 1 - Math.cos((t * Math.PI) / 2); + }, /** * A circular function. * * http://easings.net/#easeInCirc */ - static circle(t: number) { + circle(t: number): number { return 1 - Math.sqrt(1 - t * t); - } + }, /** * An exponential function. * * http://easings.net/#easeInExpo */ - static exp(t: number) { + exp(t: number): number { return Math.pow(2, 10 * (t - 1)); - } + }, /** * A simple elastic interaction, similar to a spring oscillating back and @@ -161,49 +162,44 @@ class Easing { * * http://easings.net/#easeInElastic */ - static elastic(bounciness: number = 1): (t: number) => number { + elastic(bounciness: number = 1): (t: number) => number { const p = bounciness * Math.PI; - return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p); - } + return t => 1 - Math.pow(Math.cos((t * Math.PI) / 2), 3) * Math.cos(t * p); + }, /** * Use with `Animated.parallel()` to create a simple effect where the object * animates back slightly as the animation starts. * - * Wolfram Plot: - * - * - http://tiny.cc/back_default (s = 1.70158, default) + * https://easings.net/#easeInBack */ - static back(s: number): (t: number) => number { - if (s === undefined) { - s = 1.70158; - } - return (t) => t * t * ((s + 1) * t - s); - } + back(s: number = 1.70158): (t: number) => number { + return t => t * t * ((s + 1) * t - s); + }, /** * Provides a simple bouncing effect. * * http://easings.net/#easeInBounce */ - static bounce(t: number): number { + bounce(t: number): number { if (t < 1 / 2.75) { return 7.5625 * t * t; } if (t < 2 / 2.75) { - t -= 1.5 / 2.75; - return 7.5625 * t * t + 0.75; + const t2 = t - 1.5 / 2.75; + return 7.5625 * t2 * t2 + 0.75; } if (t < 2.5 / 2.75) { - t -= 2.25 / 2.75; - return 7.5625 * t * t + 0.9375; + const t2 = t - 2.25 / 2.75; + return 7.5625 * t2 * t2 + 0.9375; } - t -= 2.625 / 2.75; - return 7.5625 * t * t + 0.984375; - } + const t2 = t - 2.625 / 2.75; + return 7.5625 * t2 * t2 + 0.984375; + }, /** * Provides a cubic bezier curve, equivalent to CSS Transitions' @@ -212,49 +208,43 @@ class Easing { * A useful tool to visualize cubic bezier curves can be found at * http://cubic-bezier.com/ */ - static bezier( + bezier( x1: number, y1: number, x2: number, - y2: number + y2: number, ): (t: number) => number { - const _bezier = require('bezier'); + const _bezier = require('./bezier'); return _bezier(x1, y1, x2, y2); - } + }, /** * Runs an easing function forwards. */ - static in( - easing: (t: number) => number, - ): (t: number) => number { + in(easing: (t: number) => number): (t: number) => number { return easing; - } + }, /** * Runs an easing function backwards. */ - static out( - easing: (t: number) => number, - ): (t: number) => number { - return (t) => 1 - easing(1 - t); - } + out(easing: (t: number) => number): (t: number) => number { + return t => 1 - easing(1 - t); + }, /** * Makes any easing function symmetrical. The easing function will run * forwards for half of the duration, then backwards for the rest of the * duration. */ - static inOut( - easing: (t: number) => number, - ): (t: number) => number { - return (t) => { + inOut(easing: (t: number) => number): (t: number) => number { + return t => { if (t < 0.5) { return easing(t * 2) / 2; } return 1 - easing((1 - t) * 2) / 2; }; - } -} + }, +}; module.exports = Easing; diff --git a/Libraries/Animated/NativeAnimatedHelper.js b/Libraries/Animated/NativeAnimatedHelper.js new file mode 100644 index 00000000000000..255f6a3ee9ff55 --- /dev/null +++ b/Libraries/Animated/NativeAnimatedHelper.js @@ -0,0 +1,582 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import NativeAnimatedNonTurboModule from './NativeAnimatedModule'; +import NativeAnimatedTurboModule from './NativeAnimatedTurboModule'; +import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; +import Platform from '../Utilities/Platform'; +import type {EventConfig} from './AnimatedEvent'; +import type { + EventMapping, + AnimatedNodeConfig, + AnimatingNodeConfig, +} from './NativeAnimatedModule'; +import type {AnimationConfig, EndCallback} from './animations/Animation'; +import type {InterpolationConfigType} from './nodes/AnimatedInterpolation'; +import ReactNativeFeatureFlags from '../ReactNative/ReactNativeFeatureFlags'; +import invariant from 'invariant'; +import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; +import type {EventSubscription} from '../vendor/emitter/EventEmitter'; + +// TODO T69437152 @petetheheat - Delete this fork when Fabric ships to 100%. +const NativeAnimatedModule = + Platform.OS === 'ios' && global.RN$Bridgeless === true + ? NativeAnimatedTurboModule + : NativeAnimatedNonTurboModule; + +let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ +let __nativeAnimationIdCount = 1; /* used for started animations */ + +let nativeEventEmitter; + +let waitingForQueuedOperations = new Set(); +let queueOperations = false; +let queue: Array<() => void> = []; +// $FlowFixMe +let singleOpQueue: Array = []; + +const useSingleOpBatching = + Platform.OS === 'android' && + !!NativeAnimatedModule?.queueAndExecuteBatchedOperations && + ReactNativeFeatureFlags.animatedShouldUseSingleOp(); +let flushQueueTimeout = null; + +const eventListenerGetValueCallbacks = {}; +const eventListenerAnimationFinishedCallbacks = {}; +let globalEventEmitterGetValueListener: ?EventSubscription = null; +let globalEventEmitterAnimationFinishedListener: ?EventSubscription = null; + +const nativeOps: ?typeof NativeAnimatedModule = useSingleOpBatching + ? ((function () { + const apis = [ + 'createAnimatedNode', // 1 + 'updateAnimatedNodeConfig', // 2 + 'getValue', // 3 + 'startListeningToAnimatedNodeValue', // 4 + 'stopListeningToAnimatedNodeValue', // 5 + 'connectAnimatedNodes', // 6 + 'disconnectAnimatedNodes', // 7 + 'startAnimatingNode', // 8 + 'stopAnimation', // 9 + 'setAnimatedNodeValue', // 10 + 'setAnimatedNodeOffset', // 11 + 'flattenAnimatedNodeOffset', // 12 + 'extractAnimatedNodeOffset', // 13 + 'connectAnimatedNodeToView', // 14 + 'disconnectAnimatedNodeFromView', // 15 + 'restoreDefaultValues', // 16 + 'dropAnimatedNode', // 17 + 'addAnimatedEventToView', // 18 + 'removeAnimatedEventFromView', // 19 + 'addListener', // 20 + 'removeListener', // 21 + ]; + return apis.reduce((acc, functionName, i) => { + // These indices need to be kept in sync with the indices in native (see NativeAnimatedModule in Java, or the equivalent for any other native platform). + // $FlowFixMe[prop-missing] + acc[functionName] = i + 1; + return acc; + }, {}); + })(): $FlowFixMe) + : NativeAnimatedModule; + +/** + * Wrappers around NativeAnimatedModule to provide flow and autocomplete support for + * the native module methods, and automatic queue management on Android + */ +const API = { + getValue: function ( + tag: number, + saveValueCallback: (value: number) => void, + ): void { + invariant(nativeOps, 'Native animated module is not available'); + if (useSingleOpBatching) { + if (saveValueCallback) { + eventListenerGetValueCallbacks[tag] = saveValueCallback; + } + // $FlowFixMe + API.queueOperation(nativeOps.getValue, tag); + } else { + API.queueOperation(nativeOps.getValue, tag, saveValueCallback); + } + }, + setWaitingForIdentifier: function (id: string): void { + waitingForQueuedOperations.add(id); + queueOperations = true; + if ( + ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush() && + flushQueueTimeout + ) { + clearTimeout(flushQueueTimeout); + } + }, + unsetWaitingForIdentifier: function (id: string): void { + waitingForQueuedOperations.delete(id); + + if (waitingForQueuedOperations.size === 0) { + queueOperations = false; + API.disableQueue(); + } + }, + disableQueue: function (): void { + invariant(nativeOps, 'Native animated module is not available'); + + if (ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush()) { + const prevTimeout = flushQueueTimeout; + clearImmediate(prevTimeout); + flushQueueTimeout = setImmediate(API.flushQueue); + } else { + API.flushQueue(); + } + }, + flushQueue: function (): void { + invariant(NativeAnimatedModule, 'Native animated module is not available'); + flushQueueTimeout = null; + + // Early returns before calling any APIs + if (useSingleOpBatching && singleOpQueue.length === 0) { + return; + } + if (!useSingleOpBatching && queue.length === 0) { + return; + } + + if (useSingleOpBatching) { + // Set up event listener for callbacks if it's not set up + if ( + !globalEventEmitterGetValueListener || + !globalEventEmitterAnimationFinishedListener + ) { + setupGlobalEventEmitterListeners(); + } + // Single op batching doesn't use callback functions, instead we + // use RCTDeviceEventEmitter. This reduces overhead of sending lots of + // JSI functions across to native code; but also, TM infrastructure currently + // does not support packing a function into native arrays. + NativeAnimatedModule.queueAndExecuteBatchedOperations?.(singleOpQueue); + singleOpQueue.length = 0; + } else { + Platform.OS === 'android' && NativeAnimatedModule.startOperationBatch?.(); + for (let q = 0, l = queue.length; q < l; q++) { + queue[q](); + } + queue.length = 0; + Platform.OS === 'android' && + NativeAnimatedModule.finishOperationBatch?.(); + } + }, + queueOperation: , Fn: (...Args) => void>( + fn: Fn, + ...args: Args + ): void => { + if (useSingleOpBatching) { + // Get the command ID from the queued function, and push that ID and any arguments needed to execute the operation + // $FlowFixMe: surprise, fn is actually a number + singleOpQueue.push(fn, ...args); + return; + } + + // If queueing is explicitly on, *or* the queue has not yet + // been flushed, use the queue. This is to prevent operations + // from being executed out of order. + if (queueOperations || queue.length !== 0) { + queue.push(() => fn(...args)); + } else { + fn(...args); + } + }, + createAnimatedNode: function (tag: number, config: AnimatedNodeConfig): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.createAnimatedNode, tag, config); + }, + updateAnimatedNodeConfig: function ( + tag: number, + config: AnimatedNodeConfig, + ): void { + invariant(nativeOps, 'Native animated module is not available'); + if (nativeOps.updateAnimatedNodeConfig) { + API.queueOperation(nativeOps.updateAnimatedNodeConfig, tag, config); + } + }, + startListeningToAnimatedNodeValue: function (tag: number) { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.startListeningToAnimatedNodeValue, tag); + }, + stopListeningToAnimatedNodeValue: function (tag: number) { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.stopListeningToAnimatedNodeValue, tag); + }, + connectAnimatedNodes: function (parentTag: number, childTag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.connectAnimatedNodes, parentTag, childTag); + }, + disconnectAnimatedNodes: function ( + parentTag: number, + childTag: number, + ): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.disconnectAnimatedNodes, parentTag, childTag); + }, + startAnimatingNode: function ( + animationId: number, + nodeTag: number, + config: AnimatingNodeConfig, + endCallback: EndCallback, + ): void { + invariant(nativeOps, 'Native animated module is not available'); + if (useSingleOpBatching) { + if (endCallback) { + eventListenerAnimationFinishedCallbacks[animationId] = endCallback; + } + // $FlowFixMe + API.queueOperation( + nativeOps.startAnimatingNode, + animationId, + nodeTag, + config, + ); + } else { + API.queueOperation( + nativeOps.startAnimatingNode, + animationId, + nodeTag, + config, + endCallback, + ); + } + }, + stopAnimation: function (animationId: number) { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.stopAnimation, animationId); + }, + setAnimatedNodeValue: function (nodeTag: number, value: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.setAnimatedNodeValue, nodeTag, value); + }, + setAnimatedNodeOffset: function (nodeTag: number, offset: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.setAnimatedNodeOffset, nodeTag, offset); + }, + flattenAnimatedNodeOffset: function (nodeTag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.flattenAnimatedNodeOffset, nodeTag); + }, + extractAnimatedNodeOffset: function (nodeTag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.extractAnimatedNodeOffset, nodeTag); + }, + connectAnimatedNodeToView: function (nodeTag: number, viewTag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.connectAnimatedNodeToView, nodeTag, viewTag); + }, + disconnectAnimatedNodeFromView: function ( + nodeTag: number, + viewTag: number, + ): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation( + nativeOps.disconnectAnimatedNodeFromView, + nodeTag, + viewTag, + ); + }, + restoreDefaultValues: function (nodeTag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + // Backwards compat with older native runtimes, can be removed later. + if (nativeOps.restoreDefaultValues != null) { + API.queueOperation(nativeOps.restoreDefaultValues, nodeTag); + } + }, + dropAnimatedNode: function (tag: number): void { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation(nativeOps.dropAnimatedNode, tag); + }, + addAnimatedEventToView: function ( + viewTag: number, + eventName: string, + eventMapping: EventMapping, + ) { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation( + nativeOps.addAnimatedEventToView, + viewTag, + eventName, + eventMapping, + ); + }, + removeAnimatedEventFromView( + viewTag: number, + eventName: string, + animatedNodeTag: number, + ) { + invariant(nativeOps, 'Native animated module is not available'); + API.queueOperation( + nativeOps.removeAnimatedEventFromView, + viewTag, + eventName, + animatedNodeTag, + ); + }, +}; + +function setupGlobalEventEmitterListeners() { + globalEventEmitterGetValueListener = RCTDeviceEventEmitter.addListener( + 'onNativeAnimatedModuleGetValue', + function (params) { + const {tag} = params; + const callback = eventListenerGetValueCallbacks[tag]; + if (!callback) { + return; + } + callback(params.value); + delete eventListenerGetValueCallbacks[tag]; + }, + ); + globalEventEmitterAnimationFinishedListener = + RCTDeviceEventEmitter.addListener( + 'onNativeAnimatedModuleAnimationFinished', + function (params) { + const {animationId} = params; + const callback = eventListenerAnimationFinishedCallbacks[animationId]; + if (!callback) { + return; + } + callback(params); + delete eventListenerAnimationFinishedCallbacks[animationId]; + }, + ); +} + +/** + * Styles allowed by the native animated implementation. + * + * In general native animated implementation should support any numeric or color property that + * doesn't need to be updated through the shadow view hierarchy (all non-layout properties). + */ +const SUPPORTED_COLOR_STYLES = { + backgroundColor: true, + borderBottomColor: true, + borderColor: true, + borderEndColor: true, + borderLeftColor: true, + borderRightColor: true, + borderStartColor: true, + borderTopColor: true, + color: true, + tintColor: true, +}; + +const SUPPORTED_STYLES = { + ...SUPPORTED_COLOR_STYLES, + borderBottomEndRadius: true, + borderBottomLeftRadius: true, + borderBottomRightRadius: true, + borderBottomStartRadius: true, + borderRadius: true, + borderTopEndRadius: true, + borderTopLeftRadius: true, + borderTopRightRadius: true, + borderTopStartRadius: true, + elevation: true, + opacity: true, + transform: true, + zIndex: true, + /* ios styles */ + shadowOpacity: true, + shadowRadius: true, + /* legacy android transform properties */ + scaleX: true, + scaleY: true, + translateX: true, + translateY: true, +}; + +const SUPPORTED_TRANSFORMS = { + translateX: true, + translateY: true, + scale: true, + scaleX: true, + scaleY: true, + rotate: true, + rotateX: true, + rotateY: true, + rotateZ: true, + perspective: true, +}; + +const SUPPORTED_INTERPOLATION_PARAMS = { + inputRange: true, + outputRange: true, + extrapolate: true, + extrapolateRight: true, + extrapolateLeft: true, +}; + +function addWhitelistedStyleProp(prop: string): void { + SUPPORTED_STYLES[prop] = true; +} + +function addWhitelistedTransformProp(prop: string): void { + SUPPORTED_TRANSFORMS[prop] = true; +} + +function addWhitelistedInterpolationParam(param: string): void { + SUPPORTED_INTERPOLATION_PARAMS[param] = true; +} + +function isSupportedColorStyleProp(prop: string): boolean { + return SUPPORTED_COLOR_STYLES.hasOwnProperty(prop); +} + +function isSupportedStyleProp(prop: string): boolean { + return SUPPORTED_STYLES.hasOwnProperty(prop); +} + +function isSupportedTransformProp(prop: string): boolean { + return SUPPORTED_TRANSFORMS.hasOwnProperty(prop); +} + +function isSupportedInterpolationParam(param: string): boolean { + return SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(param); +} + +function validateTransform( + configs: Array< + | { + type: 'animated', + property: string, + nodeTag: ?number, + ... + } + | { + type: 'static', + property: string, + value: number | string, + ... + }, + >, +): void { + configs.forEach(config => { + if (!isSupportedTransformProp(config.property)) { + throw new Error( + `Property '${config.property}' is not supported by native animated module`, + ); + } + }); +} + +function validateStyles(styles: {[key: string]: ?number, ...}): void { + for (const key in styles) { + if (!isSupportedStyleProp(key)) { + throw new Error( + `Style property '${key}' is not supported by native animated module`, + ); + } + } +} + +function validateInterpolation( + config: InterpolationConfigType, +): void { + for (const key in config) { + if (!isSupportedInterpolationParam(key)) { + throw new Error( + `Interpolation property '${key}' is not supported by native animated module`, + ); + } + } +} + +function generateNewNodeTag(): number { + return __nativeAnimatedNodeTagCount++; +} + +function generateNewAnimationId(): number { + return __nativeAnimationIdCount++; +} + +function assertNativeAnimatedModule(): void { + invariant(NativeAnimatedModule, 'Native animated module is not available'); +} + +let _warnedMissingNativeAnimated = false; + +function shouldUseNativeDriver( + config: $ReadOnly<{...AnimationConfig, ...}> | EventConfig, +): boolean { + if (config.useNativeDriver == null) { + console.warn( + 'Animated: `useNativeDriver` was not specified. This is a required ' + + 'option and must be explicitly set to `true` or `false`', + ); + } + + if (config.useNativeDriver === true && !NativeAnimatedModule) { + if (!_warnedMissingNativeAnimated) { + console.warn( + 'Animated: `useNativeDriver` is not supported because the native ' + + 'animated module is missing. Falling back to JS-based animation. To ' + + 'resolve this, add `RCTAnimation` module to this app, or remove ' + + '`useNativeDriver`. ' + + 'Make sure to run `bundle exec pod install` first. Read more about autolinking: https://github.com/react-native-community/cli/blob/master/docs/autolinking.md', + ); + _warnedMissingNativeAnimated = true; + } + return false; + } + + return config.useNativeDriver || false; +} + +function transformDataType(value: number | string): number | string { + // Change the string type to number type so we can reuse the same logic in + // iOS and Android platform + if (typeof value !== 'string') { + return value; + } + if (/deg$/.test(value)) { + const degrees = parseFloat(value) || 0; + const radians = (degrees * Math.PI) / 180.0; + return radians; + } else { + return value; + } +} + +module.exports = { + API, + isSupportedColorStyleProp, + isSupportedStyleProp, + isSupportedTransformProp, + isSupportedInterpolationParam, + addWhitelistedStyleProp, + addWhitelistedTransformProp, + addWhitelistedInterpolationParam, + validateStyles, + validateTransform, + validateInterpolation, + generateNewNodeTag, + generateNewAnimationId, + assertNativeAnimatedModule, + shouldUseNativeDriver, + transformDataType, + // $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppresion + // $FlowExpectedError[missing-type-arg] - unsafe getter lint suppresion + get nativeEventEmitter(): NativeEventEmitter { + if (!nativeEventEmitter) { + nativeEventEmitter = new NativeEventEmitter( + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeAnimatedModule, + ); + } + return nativeEventEmitter; + }, +}; diff --git a/Libraries/Animated/NativeAnimatedModule.js b/Libraries/Animated/NativeAnimatedModule.js new file mode 100644 index 00000000000000..73f3bb76f90c08 --- /dev/null +++ b/Libraries/Animated/NativeAnimatedModule.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +type EndResult = {finished: boolean, ...}; +type EndCallback = (result: EndResult) => void; +type SaveValueCallback = (value: number) => void; + +export type EventMapping = {| + nativeEventPath: Array, + animatedValueTag: ?number, +|}; + +// The config has different keys depending on the type of the Node +// TODO(T54896888): Make these types strict +export type AnimatedNodeConfig = Object; +export type AnimatingNodeConfig = Object; + +export interface Spec extends TurboModule { + +startOperationBatch: () => void; + +finishOperationBatch: () => void; + +createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void; + +updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void; + +getValue: (tag: number, saveValueCallback: SaveValueCallback) => void; + +startListeningToAnimatedNodeValue: (tag: number) => void; + +stopListeningToAnimatedNodeValue: (tag: number) => void; + +connectAnimatedNodes: (parentTag: number, childTag: number) => void; + +disconnectAnimatedNodes: (parentTag: number, childTag: number) => void; + +startAnimatingNode: ( + animationId: number, + nodeTag: number, + config: AnimatingNodeConfig, + endCallback: EndCallback, + ) => void; + +stopAnimation: (animationId: number) => void; + +setAnimatedNodeValue: (nodeTag: number, value: number) => void; + +setAnimatedNodeOffset: (nodeTag: number, offset: number) => void; + +flattenAnimatedNodeOffset: (nodeTag: number) => void; + +extractAnimatedNodeOffset: (nodeTag: number) => void; + +connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void; + +disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void; + +restoreDefaultValues: (nodeTag: number) => void; + +dropAnimatedNode: (tag: number) => void; + +addAnimatedEventToView: ( + viewTag: number, + eventName: string, + eventMapping: EventMapping, + ) => void; + +removeAnimatedEventFromView: ( + viewTag: number, + eventName: string, + animatedNodeTag: number, + ) => void; + + // Events + +addListener: (eventName: string) => void; + +removeListeners: (count: number) => void; + + // All of the above in a batched mode + +queueAndExecuteBatchedOperations?: (operationsAndArgs: Array) => void; +} + +export default (TurboModuleRegistry.get('NativeAnimatedModule'): ?Spec); diff --git a/Libraries/Animated/NativeAnimatedTurboModule.js b/Libraries/Animated/NativeAnimatedTurboModule.js new file mode 100644 index 00000000000000..3adac4237daed4 --- /dev/null +++ b/Libraries/Animated/NativeAnimatedTurboModule.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +type EndResult = {finished: boolean, ...}; +type EndCallback = (result: EndResult) => void; +type SaveValueCallback = (value: number) => void; + +export type EventMapping = {| + nativeEventPath: Array, + animatedValueTag: ?number, +|}; + +// The config has different keys depending on the type of the Node +// TODO(T54896888): Make these types strict +export type AnimatedNodeConfig = Object; +export type AnimatingNodeConfig = Object; + +export interface Spec extends TurboModule { + +startOperationBatch: () => void; + +finishOperationBatch: () => void; + +createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void; + +updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void; + +getValue: (tag: number, saveValueCallback: SaveValueCallback) => void; + +startListeningToAnimatedNodeValue: (tag: number) => void; + +stopListeningToAnimatedNodeValue: (tag: number) => void; + +connectAnimatedNodes: (parentTag: number, childTag: number) => void; + +disconnectAnimatedNodes: (parentTag: number, childTag: number) => void; + +startAnimatingNode: ( + animationId: number, + nodeTag: number, + config: AnimatingNodeConfig, + endCallback: EndCallback, + ) => void; + +stopAnimation: (animationId: number) => void; + +setAnimatedNodeValue: (nodeTag: number, value: number) => void; + +setAnimatedNodeOffset: (nodeTag: number, offset: number) => void; + +flattenAnimatedNodeOffset: (nodeTag: number) => void; + +extractAnimatedNodeOffset: (nodeTag: number) => void; + +connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void; + +disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void; + +restoreDefaultValues: (nodeTag: number) => void; + +dropAnimatedNode: (tag: number) => void; + +addAnimatedEventToView: ( + viewTag: number, + eventName: string, + eventMapping: EventMapping, + ) => void; + +removeAnimatedEventFromView: ( + viewTag: number, + eventName: string, + animatedNodeTag: number, + ) => void; + + // Events + +addListener: (eventName: string) => void; + +removeListeners: (count: number) => void; + + // All of the above in a batched mode + +queueAndExecuteBatchedOperations?: (operationsAndArgs: Array) => void; +} + +export default (TurboModuleRegistry.get( + 'NativeAnimatedTurboModule', +): ?Spec); diff --git a/Libraries/Animated/SpringConfig.js b/Libraries/Animated/SpringConfig.js new file mode 100644 index 00000000000000..c687f6b97126eb --- /dev/null +++ b/Libraries/Animated/SpringConfig.js @@ -0,0 +1,103 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict + */ + +'use strict'; + +type SpringConfigType = { + stiffness: number, + damping: number, + ... +}; + +function stiffnessFromOrigamiValue(oValue: number) { + return (oValue - 30) * 3.62 + 194; +} + +function dampingFromOrigamiValue(oValue: number) { + return (oValue - 8) * 3 + 25; +} + +function fromOrigamiTensionAndFriction( + tension: number, + friction: number, +): SpringConfigType { + return { + stiffness: stiffnessFromOrigamiValue(tension), + damping: dampingFromOrigamiValue(friction), + }; +} + +function fromBouncinessAndSpeed( + bounciness: number, + speed: number, +): SpringConfigType { + function normalize(value: number, startValue: number, endValue: number) { + return (value - startValue) / (endValue - startValue); + } + + function projectNormal(n: number, start: number, end: number) { + return start + n * (end - start); + } + + function linearInterpolation(t: number, start: number, end: number) { + return t * end + (1 - t) * start; + } + + function quadraticOutInterpolation(t: number, start: number, end: number) { + return linearInterpolation(2 * t - t * t, start, end); + } + + function b3Friction1(x: number) { + return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28; + } + + function b3Friction2(x: number) { + return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2; + } + + function b3Friction3(x: number) { + return ( + 0.00000045 * Math.pow(x, 3) - + 0.000332 * Math.pow(x, 2) + + 0.1078 * x + + 5.84 + ); + } + + function b3Nobounce(tension: number) { + if (tension <= 18) { + return b3Friction1(tension); + } else if (tension > 18 && tension <= 44) { + return b3Friction2(tension); + } else { + return b3Friction3(tension); + } + } + + let b = normalize(bounciness / 1.7, 0, 20); + b = projectNormal(b, 0, 0.8); + const s = normalize(speed / 1.7, 0, 20); + const bouncyTension = projectNormal(s, 0.5, 200); + const bouncyFriction = quadraticOutInterpolation( + b, + b3Nobounce(bouncyTension), + 0.01, + ); + + return { + stiffness: stiffnessFromOrigamiValue(bouncyTension), + damping: dampingFromOrigamiValue(bouncyFriction), + }; +} + +module.exports = { + fromOrigamiTensionAndFriction, + fromBouncinessAndSpeed, +}; diff --git a/Libraries/Animated/__tests__/Animated-test.js b/Libraries/Animated/__tests__/Animated-test.js new file mode 100644 index 00000000000000..8f62804c73b781 --- /dev/null +++ b/Libraries/Animated/__tests__/Animated-test.js @@ -0,0 +1,1086 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + */ + +import AnimatedProps from '../nodes/AnimatedProps'; +import TestRenderer from 'react-test-renderer'; +import * as React from 'react'; + +jest.mock('../../BatchedBridge/NativeModules', () => ({ + NativeAnimatedModule: {}, + PlatformConstants: { + getConstants() { + return {}; + }, + }, +})); + +let Animated = require('../Animated'); + +describe('Animated tests', () => { + beforeEach(() => { + jest.resetModules(); + }); + + describe('Animated', () => { + it('works end to end', () => { + const anim = new Animated.Value(0); + + const callback = jest.fn(); + + const node = new AnimatedProps( + { + style: { + backgroundColor: 'red', + opacity: anim, + transform: [ + { + translateX: anim.interpolate({ + inputRange: [0, 1], + outputRange: [100, 200], + }), + }, + {scale: anim}, + ], + shadowOffset: { + width: anim, + height: anim, + }, + }, + }, + callback, + ); + + expect(node.__getValue()).toEqual({ + style: { + backgroundColor: 'red', + opacity: 0, + transform: [{translateX: 100}, {scale: 0}], + shadowOffset: { + width: 0, + height: 0, + }, + }, + }); + + expect(anim.__getChildren().length).toBe(0); + + node.__attach(); + + expect(anim.__getChildren().length).toBe(3); + + anim.setValue(0.5); + + expect(callback).toBeCalled(); + + expect(node.__getValue()).toEqual({ + style: { + backgroundColor: 'red', + opacity: 0.5, + transform: [{translateX: 150}, {scale: 0.5}], + shadowOffset: { + width: 0.5, + height: 0.5, + }, + }, + }); + + node.__detach(); + expect(anim.__getChildren().length).toBe(0); + + anim.setValue(1); + expect(callback.mock.calls.length).toBe(1); + }); + + it('does not detach on updates', () => { + const opacity = new Animated.Value(0); + opacity.__detach = jest.fn(); + + const root = TestRenderer.create(); + expect(opacity.__detach).not.toBeCalled(); + + root.update(); + expect(opacity.__detach).not.toBeCalled(); + + root.unmount(); + expect(opacity.__detach).toBeCalled(); + }); + + it('stops animation when detached', () => { + const opacity = new Animated.Value(0); + const callback = jest.fn(); + + const root = TestRenderer.create(); + + Animated.timing(opacity, { + toValue: 10, + duration: 1000, + useNativeDriver: false, + }).start(callback); + + root.unmount(); + + expect(callback).toBeCalledWith({finished: false}); + }); + + it('triggers callback when spring is at rest', () => { + const anim = new Animated.Value(0); + const callback = jest.fn(); + Animated.spring(anim, { + toValue: 0, + velocity: 0, + useNativeDriver: false, + }).start(callback); + expect(callback).toBeCalled(); + }); + + it('send toValue when a critically damped spring stops', () => { + const anim = new Animated.Value(0); + const listener = jest.fn(); + anim.addListener(listener); + Animated.spring(anim, { + stiffness: 8000, + damping: 2000, + toValue: 15, + useNativeDriver: false, + }).start(); + jest.runAllTimers(); + const lastValue = + listener.mock.calls[listener.mock.calls.length - 2][0].value; + expect(lastValue).not.toBe(15); + expect(lastValue).toBeCloseTo(15); + expect(anim.__getValue()).toBe(15); + }); + + it('convert to JSON', () => { + expect(JSON.stringify(new Animated.Value(10))).toBe('10'); + }); + + it('bypasses `setNativeProps` in test environments', () => { + const opacity = new Animated.Value(0); + + const testRenderer = TestRenderer.create( + , + ); + + expect(testRenderer.toJSON().props.style.opacity).toEqual(0); + + Animated.timing(opacity, { + toValue: 1, + duration: 0, + useNativeDriver: false, + }).start(); + + expect(testRenderer.toJSON().props.style.opacity).toEqual(1); + }); + + it('warns if `useNativeDriver` is missing', () => { + jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + + Animated.spring(new Animated.Value(0), { + toValue: 0, + velocity: 0, + // useNativeDriver + }).start(); + + expect(console.warn).toBeCalledWith( + 'Animated: `useNativeDriver` was not specified. This is a required option and must be explicitly set to `true` or `false`', + ); + console.warn.mockRestore(); + }); + }); + + describe('Animated Sequence', () => { + it('works with an empty sequence', () => { + const cb = jest.fn(); + Animated.sequence([]).start(cb); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('sequences well', () => { + const anim1 = {start: jest.fn()}; + const anim2 = {start: jest.fn()}; + const cb = jest.fn(); + + const seq = Animated.sequence([anim1, anim2]); + + expect(anim1.start).not.toBeCalled(); + expect(anim2.start).not.toBeCalled(); + + seq.start(cb); + + expect(anim1.start).toBeCalled(); + expect(anim2.start).not.toBeCalled(); + expect(cb).not.toBeCalled(); + + anim1.start.mock.calls[0][0]({finished: true}); + + expect(anim2.start).toBeCalled(); + expect(cb).not.toBeCalled(); + + anim2.start.mock.calls[0][0]({finished: true}); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('supports interrupting sequence', () => { + const anim1 = {start: jest.fn()}; + const anim2 = {start: jest.fn()}; + const cb = jest.fn(); + + Animated.sequence([anim1, anim2]).start(cb); + + anim1.start.mock.calls[0][0]({finished: false}); + + expect(anim1.start).toBeCalled(); + expect(anim2.start).not.toBeCalled(); + expect(cb).toBeCalledWith({finished: false}); + }); + + it('supports stopping sequence', () => { + const anim1 = {start: jest.fn(), stop: jest.fn()}; + const anim2 = {start: jest.fn(), stop: jest.fn()}; + const cb = jest.fn(); + + const seq = Animated.sequence([anim1, anim2]); + seq.start(cb); + seq.stop(); + + expect(anim1.stop).toBeCalled(); + expect(anim2.stop).not.toBeCalled(); + expect(cb).not.toBeCalled(); + + anim1.start.mock.calls[0][0]({finished: false}); + + expect(cb).toBeCalledWith({finished: false}); + }); + }); + + describe('Animated Loop', () => { + it('loops indefinitely if config not specified', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 + expect(animation.reset).toHaveBeenCalledTimes(3); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 + expect(animation.reset).toHaveBeenCalledTimes(4); + expect(cb).not.toBeCalled(); + }); + + it('loops indefinitely if iterations is -1', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {iterations: -1}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 + expect(animation.reset).toHaveBeenCalledTimes(3); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 + expect(animation.reset).toHaveBeenCalledTimes(4); + expect(cb).not.toBeCalled(); + }); + + it('loops indefinitely if iterations not specified', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {anotherKey: 'value'}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 + expect(animation.reset).toHaveBeenCalledTimes(3); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 + expect(animation.reset).toHaveBeenCalledTimes(4); + expect(cb).not.toBeCalled(); + }); + + it('loops three times if iterations is 3', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {iterations: 3}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 + expect(animation.reset).toHaveBeenCalledTimes(3); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 + expect(animation.reset).toHaveBeenCalledTimes(3); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('does not loop if iterations is 1', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {iterations: 1}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(cb).toBeCalledWith({finished: true}); + }); + + it('does not animate if iterations is 0', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {iterations: 0}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).not.toBeCalled(); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('supports interrupting an indefinite loop', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + Animated.loop(animation).start(cb); + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop + expect(animation.reset).toHaveBeenCalledTimes(2); + expect(cb).toBeCalledWith({finished: false}); + }); + + it('supports stopping loop', () => { + const animation = { + start: jest.fn(), + stop: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation); + loop.start(cb); + loop.stop(); + + expect(animation.start).toBeCalled(); + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(animation.stop).toBeCalled(); + + animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop + expect(animation.reset).toHaveBeenCalledTimes(1); + expect(cb).toBeCalledWith({finished: false}); + }); + }); + + it('does not reset animation in a loop if resetBeforeIteration is false', () => { + const animation = { + start: jest.fn(), + reset: jest.fn(), + _isUsingNativeDriver: () => false, + }; + const cb = jest.fn(); + + const loop = Animated.loop(animation, {resetBeforeIteration: false}); + + expect(animation.start).not.toBeCalled(); + + loop.start(cb); + + expect(animation.start).toBeCalled(); + expect(animation.reset).not.toBeCalled(); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 + expect(animation.reset).not.toBeCalled(); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 + expect(animation.reset).not.toBeCalled(); + expect(cb).not.toBeCalled(); + + animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 + expect(animation.reset).not.toBeCalled(); + expect(cb).not.toBeCalled(); + }); + + describe('Animated Parallel', () => { + it('works with an empty parallel', () => { + const cb = jest.fn(); + Animated.parallel([]).start(cb); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('works with an empty element in array', () => { + const anim1 = {start: jest.fn()}; + const cb = jest.fn(); + Animated.parallel([null, anim1]).start(cb); + + expect(anim1.start).toBeCalled(); + anim1.start.mock.calls[0][0]({finished: true}); + + expect(cb).toBeCalledWith({finished: true}); + }); + + it('parellelizes well', () => { + const anim1 = {start: jest.fn()}; + const anim2 = {start: jest.fn()}; + const cb = jest.fn(); + + const par = Animated.parallel([anim1, anim2]); + + expect(anim1.start).not.toBeCalled(); + expect(anim2.start).not.toBeCalled(); + + par.start(cb); + + expect(anim1.start).toBeCalled(); + expect(anim2.start).toBeCalled(); + expect(cb).not.toBeCalled(); + + anim1.start.mock.calls[0][0]({finished: true}); + expect(cb).not.toBeCalled(); + + anim2.start.mock.calls[0][0]({finished: true}); + expect(cb).toBeCalledWith({finished: true}); + }); + + it('supports stopping parallel', () => { + const anim1 = {start: jest.fn(), stop: jest.fn()}; + const anim2 = {start: jest.fn(), stop: jest.fn()}; + const cb = jest.fn(); + + const seq = Animated.parallel([anim1, anim2]); + seq.start(cb); + seq.stop(); + + expect(anim1.stop).toBeCalled(); + expect(anim2.stop).toBeCalled(); + expect(cb).not.toBeCalled(); + + anim1.start.mock.calls[0][0]({finished: false}); + expect(cb).not.toBeCalled(); + + anim2.start.mock.calls[0][0]({finished: false}); + expect(cb).toBeCalledWith({finished: false}); + }); + + it('does not call stop more than once when stopping', () => { + const anim1 = {start: jest.fn(), stop: jest.fn()}; + const anim2 = {start: jest.fn(), stop: jest.fn()}; + const anim3 = {start: jest.fn(), stop: jest.fn()}; + const cb = jest.fn(); + + const seq = Animated.parallel([anim1, anim2, anim3]); + seq.start(cb); + + anim1.start.mock.calls[0][0]({finished: false}); + + expect(anim1.stop.mock.calls.length).toBe(0); + expect(anim2.stop.mock.calls.length).toBe(1); + expect(anim3.stop.mock.calls.length).toBe(1); + + anim2.start.mock.calls[0][0]({finished: false}); + + expect(anim1.stop.mock.calls.length).toBe(0); + expect(anim2.stop.mock.calls.length).toBe(1); + expect(anim3.stop.mock.calls.length).toBe(1); + + anim3.start.mock.calls[0][0]({finished: false}); + + expect(anim1.stop.mock.calls.length).toBe(0); + expect(anim2.stop.mock.calls.length).toBe(1); + expect(anim3.stop.mock.calls.length).toBe(1); + }); + }); + + describe('Animated delays', () => { + it('should call anim after delay in sequence', () => { + const anim = {start: jest.fn(), stop: jest.fn()}; + const cb = jest.fn(); + Animated.sequence([Animated.delay(1000), anim]).start(cb); + jest.runAllTimers(); + expect(anim.start.mock.calls.length).toBe(1); + expect(cb).not.toBeCalled(); + anim.start.mock.calls[0][0]({finished: true}); + expect(cb).toBeCalledWith({finished: true}); + }); + it('should run stagger to end', () => { + const cb = jest.fn(); + Animated.stagger(1000, [ + Animated.delay(1000), + Animated.delay(1000), + Animated.delay(1000), + ]).start(cb); + jest.runAllTimers(); + expect(cb).toBeCalledWith({finished: true}); + }); + }); + + describe('Animated Events', () => { + it('should map events', () => { + const value = new Animated.Value(0); + const handler = Animated.event([null, {state: {foo: value}}], { + useNativeDriver: false, + }); + handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}}); + expect(value.__getValue()).toBe(42); + }); + + it('should validate AnimatedValueXY mappings', () => { + const value = new Animated.ValueXY({x: 0, y: 0}); + const handler = Animated.event([{state: value}], { + useNativeDriver: false, + }); + handler({state: {x: 1, y: 2}}); + expect(value.__getValue()).toMatchObject({x: 1, y: 2}); + }); + + it('should call listeners', () => { + const value = new Animated.Value(0); + const listener = jest.fn(); + const handler = Animated.event([{foo: value}], { + listener, + useNativeDriver: false, + }); + handler({foo: 42}); + expect(value.__getValue()).toBe(42); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({foo: 42}); + }); + + it('should call forked event listeners, with Animated.event() listener', () => { + const value = new Animated.Value(0); + const listener = jest.fn(); + const handler = Animated.event([{foo: value}], { + listener, + useNativeDriver: false, + }); + const listener2 = jest.fn(); + const forkedHandler = Animated.forkEvent(handler, listener2); + forkedHandler({foo: 42}); + expect(value.__getValue()).toBe(42); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); + + it('should call forked event listeners, with js listener', () => { + const listener = jest.fn(); + const listener2 = jest.fn(); + const forkedHandler = Animated.forkEvent(listener, listener2); + forkedHandler({foo: 42}); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); + + it('should call forked event listeners, with undefined listener', () => { + const listener = undefined; + const listener2 = jest.fn(); + const forkedHandler = Animated.forkEvent(listener, listener2); + forkedHandler({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); + }); + + describe('Animated Interactions', () => { + /*eslint-disable no-shadow*/ + let Animated; + /*eslint-enable*/ + let InteractionManager; + + beforeEach(() => { + jest.mock('../../Interaction/InteractionManager'); + Animated = require('../Animated'); + InteractionManager = require('../../Interaction/InteractionManager'); + }); + + afterEach(() => { + jest.unmock('../../Interaction/InteractionManager'); + }); + + it('registers an interaction by default', () => { + InteractionManager.createInteractionHandle.mockReturnValue(777); + + const value = new Animated.Value(0); + const callback = jest.fn(); + Animated.timing(value, { + toValue: 100, + duration: 100, + useNativeDriver: false, + }).start(callback); + jest.runAllTimers(); + + expect(InteractionManager.createInteractionHandle).toBeCalled(); + expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777); + expect(callback).toBeCalledWith({finished: true}); + }); + + it('does not register an interaction when specified', () => { + const value = new Animated.Value(0); + const callback = jest.fn(); + Animated.timing(value, { + toValue: 100, + duration: 100, + isInteraction: false, + useNativeDriver: false, + }).start(callback); + jest.runAllTimers(); + + expect(InteractionManager.createInteractionHandle).not.toBeCalled(); + expect(InteractionManager.clearInteractionHandle).not.toBeCalled(); + expect(callback).toBeCalledWith({finished: true}); + }); + }); + + describe('Animated Tracking', () => { + it('should track values', () => { + const value1 = new Animated.Value(0); + const value2 = new Animated.Value(0); + Animated.timing(value2, { + toValue: value1, + duration: 0, + useNativeDriver: false, + }).start(); + value1.setValue(42); + expect(value2.__getValue()).toBe(42); + value1.setValue(7); + expect(value2.__getValue()).toBe(7); + }); + + it('should track interpolated values', () => { + const value1 = new Animated.Value(0); + const value2 = new Animated.Value(0); + Animated.timing(value2, { + toValue: value1.interpolate({ + inputRange: [0, 2], + outputRange: [0, 1], + }), + duration: 0, + useNativeDriver: false, + }).start(); + value1.setValue(42); + expect(value2.__getValue()).toBe(42 / 2); + }); + + it('should stop tracking when animated', () => { + const value1 = new Animated.Value(0); + const value2 = new Animated.Value(0); + Animated.timing(value2, { + toValue: value1, + duration: 0, + useNativeDriver: false, + }).start(); + value1.setValue(42); + expect(value2.__getValue()).toBe(42); + Animated.timing(value2, { + toValue: 7, + duration: 0, + useNativeDriver: false, + }).start(); + value1.setValue(1492); + expect(value2.__getValue()).toBe(7); + }); + + it('should start tracking immediately on animation start', () => { + const value1 = new Animated.Value(42); + const value2 = new Animated.Value(0); + Animated.timing(value2, { + toValue: value1, + duration: 0, + useNativeDriver: false, + }).start(); + expect(value2.__getValue()).toBe(42); + value1.setValue(7); + expect(value2.__getValue()).toBe(7); + }); + }); + + describe('Animated Vectors', () => { + it('should animate vectors', () => { + const vec = new Animated.ValueXY(); + + const callback = jest.fn(); + + const node = new AnimatedProps( + { + style: { + opacity: vec.x.interpolate({ + inputRange: [0, 42], + outputRange: [0.2, 0.8], + }), + transform: vec.getTranslateTransform(), + ...vec.getLayout(), + }, + }, + callback, + ); + + expect(node.__getValue()).toEqual({ + style: { + opacity: 0.2, + transform: [{translateX: 0}, {translateY: 0}], + left: 0, + top: 0, + }, + }); + + node.__attach(); + + expect(callback.mock.calls.length).toBe(0); + + vec.setValue({x: 42, y: 1492}); + + expect(callback.mock.calls.length).toBe(2); // once each for x, y + + expect(node.__getValue()).toEqual({ + style: { + opacity: 0.8, + transform: [{translateX: 42}, {translateY: 1492}], + left: 42, + top: 1492, + }, + }); + + node.__detach(); + + vec.setValue({x: 1, y: 1}); + expect(callback.mock.calls.length).toBe(2); + }); + + it('should track vectors', () => { + const value1 = new Animated.ValueXY(); + const value2 = new Animated.ValueXY(); + Animated.timing(value2, { + toValue: value1, + duration: 0, + useNativeDriver: false, + }).start(); + value1.setValue({x: 42, y: 1492}); + expect(value2.__getValue()).toEqual({x: 42, y: 1492}); + + // Make sure tracking keeps working (see stopTogether in ParallelConfig used + // by maybeVectorAnim). + value1.setValue({x: 3, y: 4}); + expect(value2.__getValue()).toEqual({x: 3, y: 4}); + }); + + it('should track with springs', () => { + const value1 = new Animated.ValueXY(); + const value2 = new Animated.ValueXY(); + Animated.spring(value2, { + toValue: value1, + tension: 3000, // faster spring for faster test + friction: 60, + useNativeDriver: false, + }).start(); + value1.setValue({x: 1, y: 1}); + jest.runAllTimers(); + expect(Math.round(value2.__getValue().x)).toEqual(1); + expect(Math.round(value2.__getValue().y)).toEqual(1); + value1.setValue({x: 2, y: 2}); + jest.runAllTimers(); + expect(Math.round(value2.__getValue().x)).toEqual(2); + expect(Math.round(value2.__getValue().y)).toEqual(2); + }); + }); + + describe('Animated Listeners', () => { + it('should get updates', () => { + const value1 = new Animated.Value(0); + const listener = jest.fn(); + const id = value1.addListener(listener); + value1.setValue(42); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({value: 42}); + expect(value1.__getValue()).toBe(42); + value1.setValue(7); + expect(listener.mock.calls.length).toBe(2); + expect(listener).toBeCalledWith({value: 7}); + expect(value1.__getValue()).toBe(7); + value1.removeListener(id); + value1.setValue(1492); + expect(listener.mock.calls.length).toBe(2); + expect(value1.__getValue()).toBe(1492); + }); + + it('should get updates for derived animated nodes', () => { + const value1 = new Animated.Value(40); + const value2 = new Animated.Value(50); + const value3 = new Animated.Value(0); + const value4 = Animated.add(value3, Animated.multiply(value1, value2)); + const callback = jest.fn(); + const view = new AnimatedProps( + { + style: { + transform: [ + { + translateX: value4, + }, + ], + }, + }, + callback, + ); + view.__attach(); + const listener = jest.fn(); + const id = value4.addListener(listener); + value3.setValue(137); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({value: 2137}); + value1.setValue(0); + expect(listener.mock.calls.length).toBe(2); + expect(listener).toBeCalledWith({value: 137}); + expect(view.__getValue()).toEqual({ + style: { + transform: [ + { + translateX: 137, + }, + ], + }, + }); + value4.removeListener(id); + value1.setValue(40); + expect(listener.mock.calls.length).toBe(2); + expect(value4.__getValue()).toBe(2137); + }); + + it('should removeAll', () => { + const value1 = new Animated.Value(0); + const listener = jest.fn(); + [1, 2, 3, 4].forEach(() => value1.addListener(listener)); + value1.setValue(42); + expect(listener.mock.calls.length).toBe(4); + expect(listener).toBeCalledWith({value: 42}); + value1.removeAllListeners(); + value1.setValue(7); + expect(listener.mock.calls.length).toBe(4); + }); + }); + + describe('Animated Diff Clamp', () => { + it('should get the proper value', () => { + const inputValues = [0, 20, 40, 30, 0, -40, -10, -20, 0]; + const expectedValues = [0, 20, 20, 10, 0, 0, 20, 10, 20]; + const value = new Animated.Value(0); + const diffClampValue = Animated.diffClamp(value, 0, 20); + for (let i = 0; i < inputValues.length; i++) { + value.setValue(inputValues[i]); + expect(diffClampValue.__getValue()).toBe(expectedValues[i]); + } + }); + }); + + describe('Animated Colors', () => { + it('should normalize colors', () => { + let color = new Animated.Color(); + expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)'); + + color = new Animated.Color({r: 11, g: 22, b: 33, a: 1.0}); + expect(color.__getValue()).toEqual('rgba(11, 22, 33, 1)'); + + color = new Animated.Color('rgba(255, 0, 0, 1.0)'); + expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)'); + + color = new Animated.Color('#ff0000ff'); + expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)'); + + color = new Animated.Color('red'); + expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)'); + + color = new Animated.Color({ + r: new Animated.Value(255), + g: new Animated.Value(0), + b: new Animated.Value(0), + a: new Animated.Value(1.0), + }); + expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)'); + + color = new Animated.Color('unknown'); + expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)'); + + color = new Animated.Color({key: 'value'}); + expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)'); + }); + + it('should animate colors', () => { + const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0}); + const callback = jest.fn(); + const node = new AnimatedProps( + { + style: { + backgroundColor: color, + transform: [ + { + scale: color.a.interpolate({ + inputRange: [0, 1], + outputRange: [1, 2], + }), + }, + ], + }, + }, + callback, + ); + + expect(node.__getValue()).toEqual({ + style: { + backgroundColor: 'rgba(255, 0, 0, 1)', + transform: [{scale: 2}], + }, + }); + + node.__attach(); + expect(callback.mock.calls.length).toBe(0); + + color.setValue({r: 11, g: 22, b: 33, a: 0.5}); + expect(callback.mock.calls.length).toBe(4); + expect(node.__getValue()).toEqual({ + style: { + backgroundColor: 'rgba(11, 22, 33, 0.5)', + transform: [{scale: 1.5}], + }, + }); + + node.__detach(); + color.setValue({r: 255, g: 0, b: 0, a: 1.0}); + expect(callback.mock.calls.length).toBe(4); + }); + + it('should track colors', () => { + const color1 = new Animated.Color(); + const color2 = new Animated.Color(); + Animated.timing(color2, { + toValue: color1, + duration: 0, + useNativeDriver: false, + }).start(); + color1.setValue({r: 11, g: 22, b: 33, a: 0.5}); + expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)'); + + // Make sure tracking keeps working (see stopTogether in ParallelConfig used + // by maybeVectorAnim). + color1.setValue({r: 255, g: 0, b: 0, a: 1.0}); + expect(color2.__getValue()).toEqual('rgba(255, 0, 0, 1)'); + }); + + it('should track with springs', () => { + const color1 = new Animated.Color(); + const color2 = new Animated.Color(); + Animated.spring(color2, { + toValue: color1, + tension: 3000, // faster spring for faster test + friction: 60, + useNativeDriver: false, + }).start(); + color1.setValue({r: 11, g: 22, b: 33, a: 0.5}); + jest.runAllTimers(); + expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)'); + color1.setValue({r: 44, g: 55, b: 66, a: 0.0}); + jest.runAllTimers(); + expect(color2.__getValue()).toEqual('rgba(44, 55, 66, 0)'); + }); + }); +}); diff --git a/Libraries/Animated/__tests__/AnimatedMock-test.js b/Libraries/Animated/__tests__/AnimatedMock-test.js new file mode 100644 index 00000000000000..cc510feefefb33 --- /dev/null +++ b/Libraries/Animated/__tests__/AnimatedMock-test.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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'; + +const AnimatedMock = require('../AnimatedMock'); +const AnimatedImplementation = require('../AnimatedImplementation'); + +describe('Animated Mock', () => { + it('matches implementation keys', () => { + expect(Object.keys(AnimatedMock)).toEqual( + Object.keys(AnimatedImplementation), + ); + }); + it('matches implementation params', done => { + Object.keys(AnimatedImplementation).forEach(key => { + if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { + done( + new Error( + 'key ' + + key + + ' had different lengths: ' + + JSON.stringify( + { + impl: { + len: AnimatedImplementation[key].length, + type: typeof AnimatedImplementation[key], + val: AnimatedImplementation[key].toString(), + }, + mock: { + len: AnimatedMock[key].length, + type: typeof AnimatedMock[key], + val: AnimatedMock[key].toString(), + }, + }, + null, + 2, + ), + ), + ); + } + }); + done(); + }); +}); diff --git a/Libraries/Animated/__tests__/AnimatedNative-test.js b/Libraries/Animated/__tests__/AnimatedNative-test.js new file mode 100644 index 00000000000000..677527e22c9d15 --- /dev/null +++ b/Libraries/Animated/__tests__/AnimatedNative-test.js @@ -0,0 +1,999 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + */ + +jest + .clearAllMocks() + .mock('../../BatchedBridge/NativeModules', () => ({ + NativeAnimatedModule: {}, + PlatformConstants: { + getConstants() { + return {}; + }, + }, + })) + .mock('../NativeAnimatedModule') + .mock('../../EventEmitter/NativeEventEmitter') + // findNodeHandle is imported from ReactNative so mock that whole module. + .setMock('../../Renderer/shims/ReactNative', {findNodeHandle: () => 1}); + +import TestRenderer from 'react-test-renderer'; +import * as React from 'react'; + +const Animated = require('../Animated'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +describe('Native Animated', () => { + const NativeAnimatedModule = require('../NativeAnimatedModule').default; + + beforeEach(() => { + Object.assign(NativeAnimatedModule, { + getValue: jest.fn(), + addAnimatedEventToView: jest.fn(), + connectAnimatedNodes: jest.fn(), + connectAnimatedNodeToView: jest.fn(), + createAnimatedNode: jest.fn(), + disconnectAnimatedNodeFromView: jest.fn(), + disconnectAnimatedNodes: jest.fn(), + dropAnimatedNode: jest.fn(), + extractAnimatedNodeOffset: jest.fn(), + flattenAnimatedNodeOffset: jest.fn(), + removeAnimatedEventFromView: jest.fn(), + restoreDefaultValues: jest.fn(), + setAnimatedNodeOffset: jest.fn(), + setAnimatedNodeValue: jest.fn(), + startAnimatingNode: jest.fn(), + startListeningToAnimatedNodeValue: jest.fn(), + stopAnimation: jest.fn(), + stopListeningToAnimatedNodeValue: jest.fn(), + }); + }); + + describe('Animated Value', () => { + it('proxies `setValue` correctly', () => { + const opacity = new Animated.Value(0); + const ref = React.createRef(null); + + Animated.timing(opacity, { + toValue: 10, + duration: 1000, + useNativeDriver: true, + }).start(); + + TestRenderer.create(); + + expect(ref.current).not.toBeNull(); + jest.spyOn(ref.current, 'setNativeProps'); + + opacity.setValue(0.5); + + expect(NativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith( + expect.any(Number), + 0.5, + ); + expect(ref.current.setNativeProps).not.toHaveBeenCalled(); + }); + + it('should set offset', () => { + const opacity = new Animated.Value(0); + opacity.setOffset(10); + opacity.__makeNative(); + + TestRenderer.create(); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'value', value: 0, offset: 10}, + ); + opacity.setOffset(20); + expect(NativeAnimatedModule.setAnimatedNodeOffset).toBeCalledWith( + expect.any(Number), + 20, + ); + }); + + it('should flatten offset', () => { + const opacity = new Animated.Value(0); + opacity.__makeNative(); + + TestRenderer.create(); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'value', value: 0, offset: 0}, + ); + opacity.flattenOffset(); + expect(NativeAnimatedModule.flattenAnimatedNodeOffset).toBeCalledWith( + expect.any(Number), + ); + }); + + it('should save value on unmount', () => { + NativeAnimatedModule.getValue = jest.fn((tag, saveCallback) => { + saveCallback(1); + }); + const opacity = new Animated.Value(0); + + opacity.__makeNative(); + + const root = TestRenderer.create(); + const tag = opacity.__getNativeTag(); + + root.unmount(); + + expect(NativeAnimatedModule.getValue).toBeCalledWith( + tag, + expect.any(Function), + ); + expect(opacity.__getValue()).toBe(1); + }); + + it('should deduct offset when saving value on unmount', () => { + NativeAnimatedModule.getValue = jest.fn((tag, saveCallback) => { + // Assume current raw value of value node is 0.5, the NativeAnimated + // getValue API returns the sum of raw value and offset, so return 1. + saveCallback(1); + }); + const opacity = new Animated.Value(0); + opacity.setOffset(0.5); + opacity.__makeNative(); + + const root = TestRenderer.create(); + const tag = opacity.__getNativeTag(); + + root.unmount(); + + expect(NativeAnimatedModule.getValue).toBeCalledWith( + tag, + expect.any(Function), + ); + expect(opacity.__getValue()).toBe(1); + }); + + it('should extract offset', () => { + const opacity = new Animated.Value(0); + opacity.__makeNative(); + + TestRenderer.create(); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'value', value: 0, offset: 0}, + ); + opacity.extractOffset(); + expect(NativeAnimatedModule.extractAnimatedNodeOffset).toBeCalledWith( + expect.any(Number), + ); + }); + }); + + describe('Animated Listeners', () => { + it('should get updates', () => { + const value1 = new Animated.Value(0); + value1.__makeNative(); + const listener = jest.fn(); + const id = value1.addListener(listener); + expect( + NativeAnimatedModule.startListeningToAnimatedNodeValue, + ).toHaveBeenCalledWith(value1.__getNativeTag()); + + NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', { + value: 42, + tag: value1.__getNativeTag(), + }); + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toBeCalledWith({value: 42}); + expect(value1.__getValue()).toBe(42); + + NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', { + value: 7, + tag: value1.__getNativeTag(), + }); + expect(listener).toHaveBeenCalledTimes(2); + expect(listener).toBeCalledWith({value: 7}); + expect(value1.__getValue()).toBe(7); + + value1.removeListener(id); + expect( + NativeAnimatedModule.stopListeningToAnimatedNodeValue, + ).toHaveBeenCalledWith(value1.__getNativeTag()); + + NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', { + value: 1492, + tag: value1.__getNativeTag(), + }); + expect(listener).toHaveBeenCalledTimes(2); + expect(value1.__getValue()).toBe(7); + }); + + it('should removeAll', () => { + const value1 = new Animated.Value(0); + value1.__makeNative(); + const listener = jest.fn(); + [1, 2, 3, 4].forEach(() => value1.addListener(listener)); + expect( + NativeAnimatedModule.startListeningToAnimatedNodeValue, + ).toHaveBeenCalledWith(value1.__getNativeTag()); + + NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', { + value: 42, + tag: value1.__getNativeTag(), + }); + expect(listener).toHaveBeenCalledTimes(4); + expect(listener).toBeCalledWith({value: 42}); + + value1.removeAllListeners(); + expect( + NativeAnimatedModule.stopListeningToAnimatedNodeValue, + ).toHaveBeenCalledWith(value1.__getNativeTag()); + + NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', { + value: 7, + tag: value1.__getNativeTag(), + }); + expect(listener).toHaveBeenCalledTimes(4); + }); + }); + + describe('Animated Events', () => { + it('should map events', () => { + const value = new Animated.Value(0); + value.__makeNative(); + const event = Animated.event([{nativeEvent: {state: {foo: value}}}], { + useNativeDriver: true, + }); + + const root = TestRenderer.create(); + expect(NativeAnimatedModule.addAnimatedEventToView).toBeCalledWith( + expect.any(Number), + 'onTouchMove', + { + nativeEventPath: ['state', 'foo'], + animatedValueTag: value.__getNativeTag(), + }, + ); + + expect( + NativeAnimatedModule.removeAnimatedEventFromView, + ).not.toHaveBeenCalled(); + root.unmount(); + expect(NativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith( + expect.any(Number), + 'onTouchMove', + value.__getNativeTag(), + ); + }); + + it('shoud map AnimatedValueXY', () => { + const value = new Animated.ValueXY({x: 0, y: 0}); + value.__makeNative(); + const event = Animated.event([{nativeEvent: {state: value}}], { + useNativeDriver: true, + }); + + TestRenderer.create(); + ['x', 'y'].forEach((key, idx) => + expect( + NativeAnimatedModule.addAnimatedEventToView, + ).toHaveBeenNthCalledWith(idx + 1, expect.any(Number), 'onTouchMove', { + nativeEventPath: ['state', key], + animatedValueTag: value[key].__getNativeTag(), + }), + ); + }); + + it('should throw on invalid event path', () => { + const value = new Animated.Value(0); + value.__makeNative(); + const event = Animated.event([{notNativeEvent: {foo: value}}], { + useNativeDriver: true, + }); + + jest.spyOn(console, 'error').mockImplementationOnce((...args) => { + if (args[0].startsWith('The above error occurred in the')) { + return; + } + console.errorDebug(...args); + }); + + expect(() => { + TestRenderer.create(); + }).toThrowError(/nativeEvent/); + expect(NativeAnimatedModule.addAnimatedEventToView).not.toBeCalled(); + + console.error.mockRestore(); + }); + + it('should call listeners', () => { + const value = new Animated.Value(0); + value.__makeNative(); + const listener = jest.fn(); + const event = Animated.event([{nativeEvent: {foo: value}}], { + useNativeDriver: true, + listener, + }); + const handler = event.__getHandler(); + handler({nativeEvent: {foo: 42}}); + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toBeCalledWith({nativeEvent: {foo: 42}}); + }); + }); + + describe('Animated Graph', () => { + it('creates and detaches nodes', () => { + const opacity = new Animated.Value(0); + const root = TestRenderer.create(); + + Animated.timing(opacity, { + toValue: 10, + duration: 1000, + useNativeDriver: true, + }).start(); + + expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3); + expect(NativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes( + 2, + ); + + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'frames', + frames: expect.any(Array), + toValue: expect.any(Number), + iterations: 1, + }, + expect.any(Function), + ); + + expect( + NativeAnimatedModule.disconnectAnimatedNodes, + ).not.toHaveBeenCalled(); + expect(NativeAnimatedModule.dropAnimatedNode).not.toHaveBeenCalled(); + + root.unmount(); + + expect( + NativeAnimatedModule.disconnectAnimatedNodes, + ).toHaveBeenCalledTimes(2); + expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3); + }); + + it('sends a valid description for value, style and props nodes', () => { + const opacity = new Animated.Value(0); + TestRenderer.create(); + + Animated.timing(opacity, { + toValue: 10, + duration: 1000, + useNativeDriver: true, + }).start(); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'value', value: 0, offset: 0}, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'style', style: {opacity: expect.any(Number)}}, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'props', props: {style: expect.any(Number)}}, + ); + }); + + it('sends a valid graph description for Animated.add nodes', () => { + const first = new Animated.Value(1); + const second = new Animated.Value(2); + first.__makeNative(); + second.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'addition', input: expect.any(Array)}, + ); + const additionCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'addition', + ); + expect(additionCalls.length).toBe(1); + const additionCall = additionCalls[0]; + const additionNodeTag = additionCall[0]; + const additionConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === additionNodeTag, + ); + expect(additionConnectionCalls.length).toBe(2); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + additionCall[1].input[0], + { + type: 'value', + value: 1, + offset: 0, + }, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + additionCall[1].input[1], + { + type: 'value', + value: 2, + offset: 0, + }, + ); + }); + + it('sends a valid graph description for Animated.subtract nodes', () => { + const first = new Animated.Value(2); + const second = new Animated.Value(1); + first.__makeNative(); + second.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'subtraction', input: expect.any(Array)}, + ); + const subtractionCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'subtraction', + ); + expect(subtractionCalls.length).toBe(1); + const subtractionCall = subtractionCalls[0]; + const subtractionNodeTag = subtractionCall[0]; + const subtractionConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === subtractionNodeTag, + ); + expect(subtractionConnectionCalls.length).toBe(2); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + subtractionCall[1].input[0], + { + type: 'value', + value: 2, + offset: 0, + }, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + subtractionCall[1].input[1], + { + type: 'value', + value: 1, + offset: 0, + }, + ); + }); + + it('sends a valid graph description for Animated.multiply nodes', () => { + const first = new Animated.Value(2); + const second = new Animated.Value(1); + first.__makeNative(); + second.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'multiplication', input: expect.any(Array)}, + ); + const multiplicationCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'multiplication', + ); + expect(multiplicationCalls.length).toBe(1); + const multiplicationCall = multiplicationCalls[0]; + const multiplicationNodeTag = multiplicationCall[0]; + const multiplicationConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === multiplicationNodeTag, + ); + expect(multiplicationConnectionCalls.length).toBe(2); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + multiplicationCall[1].input[0], + { + type: 'value', + value: 2, + offset: 0, + }, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + multiplicationCall[1].input[1], + { + type: 'value', + value: 1, + offset: 0, + }, + ); + }); + + it('sends a valid graph description for Animated.divide nodes', () => { + const first = new Animated.Value(4); + const second = new Animated.Value(2); + first.__makeNative(); + second.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'division', input: expect.any(Array)}, + ); + const divisionCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'division', + ); + expect(divisionCalls.length).toBe(1); + const divisionCall = divisionCalls[0]; + const divisionNodeTag = divisionCall[0]; + const divisionConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === divisionNodeTag, + ); + expect(divisionConnectionCalls.length).toBe(2); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + divisionCall[1].input[0], + { + type: 'value', + value: 4, + offset: 0, + }, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + divisionCall[1].input[1], + { + type: 'value', + value: 2, + offset: 0, + }, + ); + }); + + it('sends a valid graph description for Animated.modulo nodes', () => { + const value = new Animated.Value(4); + value.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'modulus', modulus: 4, input: expect.any(Number)}, + ); + const moduloCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'modulus', + ); + expect(moduloCalls.length).toBe(1); + const moduloCall = moduloCalls[0]; + const moduloNodeTag = moduloCall[0]; + const moduloConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === moduloNodeTag, + ); + expect(moduloConnectionCalls.length).toBe(1); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + moduloCall[1].input, + { + type: 'value', + value: 4, + offset: 0, + }, + ); + }); + + it('sends a valid graph description for interpolate() nodes', () => { + const value = new Animated.Value(10); + value.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'value', value: 10, offset: 0}, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + { + type: 'interpolation', + inputRange: [10, 20], + outputRange: [0, 1], + extrapolateLeft: 'extend', + extrapolateRight: 'extend', + }, + ); + const interpolationNodeTag = + NativeAnimatedModule.createAnimatedNode.mock.calls.find( + call => call[1].type === 'interpolation', + )[0]; + const valueNodeTag = + NativeAnimatedModule.createAnimatedNode.mock.calls.find( + call => call[1].type === 'value', + )[0]; + expect(NativeAnimatedModule.connectAnimatedNodes).toBeCalledWith( + valueNodeTag, + interpolationNodeTag, + ); + }); + + it('sends a valid graph description for transform nodes', () => { + const translateX = new Animated.Value(0); + translateX.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + { + type: 'transform', + transforms: [ + { + nodeTag: expect.any(Number), + property: 'translateX', + type: 'animated', + }, + { + value: 2, + property: 'scale', + type: 'static', + }, + ], + }, + ); + }); + + it('sends a valid graph description for Animated.diffClamp nodes', () => { + const value = new Animated.Value(2); + value.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'diffclamp', input: expect.any(Number), max: 20, min: 0}, + ); + const diffClampCalls = + NativeAnimatedModule.createAnimatedNode.mock.calls.filter( + call => call[1].type === 'diffclamp', + ); + expect(diffClampCalls.length).toBe(1); + const diffClampCall = diffClampCalls[0]; + const diffClampNodeTag = diffClampCall[0]; + const diffClampConnectionCalls = + NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + call => call[1] === diffClampNodeTag, + ); + expect(diffClampConnectionCalls.length).toBe(1); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + diffClampCall[1].input, + { + type: 'value', + value: 2, + offset: 0, + }, + ); + }); + + it("doesn't call into native API if useNativeDriver is set to false", () => { + const opacity = new Animated.Value(0); + + const root = TestRenderer.create(); + + Animated.timing(opacity, { + toValue: 10, + duration: 1000, + useNativeDriver: false, + }).start(); + + root.unmount(); + + expect(NativeAnimatedModule.createAnimatedNode).not.toBeCalled(); + }); + + it('fails when trying to run non-native animation on native node', () => { + const opacity = new Animated.Value(0); + const ref = React.createRef(null); + + TestRenderer.create(); + + // Necessary to simulate the native animation. + expect(ref.current).not.toBeNull(); + ref.current.setNativeProps = jest.fn(); + + Animated.timing(opacity, { + toValue: 10, + duration: 50, + useNativeDriver: true, + }).start(); + jest.runAllTimers(); + + Animated.timing(opacity, { + toValue: 4, + duration: 500, + useNativeDriver: false, + }).start(); + try { + process.env.NODE_ENV = 'development'; + expect(jest.runAllTimers).toThrow( + 'Attempting to run JS driven animation on animated node that has ' + + 'been moved to "native" earlier by starting an animation with ' + + '`useNativeDriver: true`', + ); + } finally { + process.env.NODE_ENV = 'test'; + } + }); + + it('fails for unsupported styles', () => { + const left = new Animated.Value(0); + + TestRenderer.create(); + + const animation = Animated.timing(left, { + toValue: 10, + duration: 50, + useNativeDriver: true, + }); + expect(animation.start).toThrowError(/left/); + }); + + it('works for any `static` props and styles', () => { + // Passing "unsupported" props should work just fine as long as they are not animated + const opacity = new Animated.Value(0); + opacity.__makeNative(); + + TestRenderer.create( + , + ); + + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'style', style: {opacity: expect.any(Number)}}, + ); + expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith( + expect.any(Number), + {type: 'props', props: {style: expect.any(Number)}}, + ); + }); + }); + + describe('Animations', () => { + it('sends a valid timing animation description', () => { + const anim = new Animated.Value(0); + Animated.timing(anim, { + toValue: 10, + duration: 1000, + useNativeDriver: true, + }).start(); + + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'frames', + frames: expect.any(Array), + toValue: expect.any(Number), + iterations: 1, + }, + expect.any(Function), + ); + }); + + it('sends a valid spring animation description', () => { + const anim = new Animated.Value(0); + Animated.spring(anim, { + toValue: 10, + friction: 5, + tension: 164, + useNativeDriver: true, + }).start(); + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'spring', + stiffness: 679.08, + damping: 16, + mass: 1, + initialVelocity: 0, + overshootClamping: false, + restDisplacementThreshold: 0.001, + restSpeedThreshold: 0.001, + toValue: 10, + iterations: 1, + }, + expect.any(Function), + ); + + Animated.spring(anim, { + toValue: 10, + stiffness: 1000, + damping: 500, + mass: 3, + useNativeDriver: true, + }).start(); + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'spring', + stiffness: 1000, + damping: 500, + mass: 3, + initialVelocity: 0, + overshootClamping: false, + restDisplacementThreshold: 0.001, + restSpeedThreshold: 0.001, + toValue: 10, + iterations: 1, + }, + expect.any(Function), + ); + + Animated.spring(anim, { + toValue: 10, + bounciness: 8, + speed: 10, + useNativeDriver: true, + }).start(); + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'spring', + damping: 23.05223140901191, + initialVelocity: 0, + overshootClamping: false, + restDisplacementThreshold: 0.001, + restSpeedThreshold: 0.001, + stiffness: 299.61882352941177, + mass: 1, + toValue: 10, + iterations: 1, + }, + expect.any(Function), + ); + }); + + it('sends a valid decay animation description', () => { + const anim = new Animated.Value(0); + Animated.decay(anim, { + velocity: 10, + deceleration: 0.1, + useNativeDriver: true, + }).start(); + + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + {type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1}, + expect.any(Function), + ); + }); + + it('works with Animated.loop', () => { + const anim = new Animated.Value(0); + Animated.loop( + Animated.decay(anim, { + velocity: 10, + deceleration: 0.1, + useNativeDriver: true, + }), + {iterations: 10}, + ).start(); + + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + {type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10}, + expect.any(Function), + ); + }); + + it('sends stopAnimation command to native', () => { + const value = new Animated.Value(0); + const animation = Animated.timing(value, { + toValue: 10, + duration: 50, + useNativeDriver: true, + }); + + animation.start(); + expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith( + expect.any(Number), + expect.any(Number), + { + type: 'frames', + frames: expect.any(Array), + toValue: expect.any(Number), + iterations: 1, + }, + expect.any(Function), + ); + const animationId = + NativeAnimatedModule.startAnimatingNode.mock.calls[0][0]; + + animation.stop(); + expect(NativeAnimatedModule.stopAnimation).toBeCalledWith(animationId); + }); + + it('calls stopAnimation callback with native value', () => { + NativeAnimatedModule.getValue = jest.fn((tag, saveCallback) => { + saveCallback(1); + }); + + const anim = new Animated.Value(0); + Animated.timing(anim, { + duration: 1000, + useNativeDriver: true, + }).start(); + + const tag = anim.__getNativeTag(); + + let currentValue = 0; + anim.stopAnimation(value => (currentValue = value)); + + expect(NativeAnimatedModule.getValue).toBeCalledWith( + tag, + expect.any(Function), + ); + + expect(currentValue).toEqual(1); + }); + }); + + describe('Animated Components', () => { + it('Should restore default values on prop updates only', () => { + const opacity = new Animated.Value(0); + opacity.__makeNative(); + + const root = TestRenderer.create(); + expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled(); + + root.update(); + expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledWith( + expect.any(Number), + ); + + root.unmount(); + // Make sure it doesn't get called on unmount. + expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes( + 1, + ); + }); + }); +}); diff --git a/Libraries/Animated/__tests__/Easing-test.js b/Libraries/Animated/__tests__/Easing-test.js new file mode 100644 index 00000000000000..8efeed00037d74 --- /dev/null +++ b/Libraries/Animated/__tests__/Easing-test.js @@ -0,0 +1,246 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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'; + +const Easing = require('../Easing'); +describe('Easing', () => { + it('should work with linear', () => { + const easing = Easing.linear; + + expect(easing(0)).toBe(0); + expect(easing(0.5)).toBe(0.5); + expect(easing(0.8)).toBe(0.8); + expect(easing(1)).toBe(1); + }); + + it('should work with ease in linear', () => { + const easing = Easing.in(Easing.linear); + expect(easing(0)).toBe(0); + expect(easing(0.5)).toBe(0.5); + expect(easing(0.8)).toBe(0.8); + expect(easing(1)).toBe(1); + }); + + it('should work with easy out linear', () => { + const easing = Easing.out(Easing.linear); + expect(easing(0)).toBe(0); + expect(easing(0.5)).toBe(0.5); + expect(easing(0.6)).toBe(0.6); + expect(easing(1)).toBe(1); + }); + + it('should work with ease in quad', () => { + function easeInQuad(t) { + return t * t; + } + const easing = Easing.in(Easing.quad); + for (let t = -0.5; t < 1.5; t += 0.1) { + expect(easing(t)).toBe(easeInQuad(t)); + } + }); + + it('should work with ease out quad', () => { + function easeOutQuad(t) { + return -t * (t - 2); + } + const easing = Easing.out(Easing.quad); + for (let t = 0; t <= 1; t += 0.1) { + expect(easing(1)).toBe(easeOutQuad(1)); + } + }); + + it('should work with ease in-out quad', () => { + function easeInOutQuad(t) { + t = t * 2; + if (t < 1) { + return 0.5 * t * t; + } + return -((t - 1) * (t - 3) - 1) / 2; + } + const easing = Easing.inOut(Easing.quad); + for (let t = -0.5; t < 1.5; t += 0.1) { + expect(easing(t)).toBeCloseTo(easeInOutQuad(t), 4); + } + }); + + it('should satisfy boundary conditions with elastic', () => { + for (let b = 0; b < 4; b += 0.3) { + const easing = Easing.elastic(b); + expect(easing(0)).toBe(0); + expect(easing(1)).toBe(1); + } + }); + + function sampleEasingFunction(easing) { + const DURATION = 300; + const tickCount = Math.round((DURATION * 60) / 1000); + const samples = []; + for (let i = 0; i <= tickCount; i++) { + samples.push(easing(i / tickCount)); + } + return samples; + } + + const Samples = { + in_quad: [ + 0, 0.0030864197530864196, 0.012345679012345678, 0.027777777777777776, + 0.04938271604938271, 0.0771604938271605, 0.1111111111111111, + 0.15123456790123457, 0.19753086419753085, 0.25, 0.308641975308642, + 0.37345679012345684, 0.4444444444444444, 0.5216049382716049, + 0.6049382716049383, 0.6944444444444445, 0.7901234567901234, + 0.8919753086419753, 1, + ], + out_quad: [ + 0, 0.10802469135802469, 0.20987654320987653, 0.3055555555555555, + 0.3950617283950617, 0.47839506172839513, 0.5555555555555556, + 0.6265432098765432, 0.691358024691358, 0.75, 0.8024691358024691, + 0.8487654320987654, 0.888888888888889, 0.9228395061728394, + 0.9506172839506174, 0.9722222222222221, 0.9876543209876543, + 0.9969135802469136, 1, + ], + inOut_quad: [ + 0, 0.006172839506172839, 0.024691358024691357, 0.05555555555555555, + 0.09876543209876543, 0.154320987654321, 0.2222222222222222, + 0.30246913580246915, 0.3950617283950617, 0.5, 0.6049382716049383, + 0.697530864197531, 0.7777777777777777, 0.845679012345679, + 0.9012345679012346, 0.9444444444444444, 0.9753086419753086, + 0.9938271604938271, 1, + ], + in_cubic: [ + 0, 0.00017146776406035664, 0.0013717421124828531, 0.004629629629629629, + 0.010973936899862825, 0.021433470507544586, 0.037037037037037035, + 0.05881344307270234, 0.0877914951989026, 0.125, 0.1714677640603567, + 0.22822359396433475, 0.2962962962962963, 0.37671467764060357, + 0.4705075445816187, 0.5787037037037038, 0.7023319615912208, + 0.8424211248285322, 1, + ], + out_cubic: [ + 0, 0.15757887517146785, 0.2976680384087792, 0.42129629629629617, + 0.5294924554183813, 0.6232853223593964, 0.7037037037037036, + 0.7717764060356652, 0.8285322359396433, 0.875, 0.9122085048010974, + 0.9411865569272977, 0.9629629629629629, 0.9785665294924554, + 0.9890260631001372, 0.9953703703703703, 0.9986282578875172, + 0.9998285322359396, 1, + ], + inOut_cubic: [ + 0, 0.0006858710562414266, 0.0054869684499314125, 0.018518518518518517, + 0.0438957475994513, 0.08573388203017834, 0.14814814814814814, + 0.23525377229080935, 0.3511659807956104, 0.5, 0.6488340192043895, + 0.7647462277091908, 0.8518518518518519, 0.9142661179698217, + 0.9561042524005487, 0.9814814814814815, 0.9945130315500685, + 0.9993141289437586, 1, + ], + in_sin: [ + 0, 0.003805301908254455, 0.01519224698779198, 0.03407417371093169, + 0.06030737921409157, 0.09369221296335006, 0.1339745962155613, + 0.1808479557110082, 0.233955556881022, 0.2928932188134524, + 0.35721239031346064, 0.42642356364895384, 0.4999999999999999, + 0.5773817382593005, 0.6579798566743311, 0.7411809548974793, + 0.8263518223330696, 0.9128442572523416, 0.9999999999999999, + ], + out_sin: [ + 0, 0.08715574274765817, 0.17364817766693033, 0.25881904510252074, + 0.3420201433256687, 0.42261826174069944, 0.49999999999999994, + 0.573576436351046, 0.6427876096865393, 0.7071067811865475, + 0.766044443118978, 0.8191520442889918, 0.8660254037844386, + 0.9063077870366499, 0.9396926207859083, 0.9659258262890683, + 0.984807753012208, 0.9961946980917455, 1, + ], + inOut_sin: [ + 0, 0.00759612349389599, 0.030153689607045786, 0.06698729810778065, + 0.116977778440511, 0.17860619515673032, 0.24999999999999994, + 0.32898992833716556, 0.4131759111665348, 0.49999999999999994, + 0.5868240888334652, 0.6710100716628343, 0.7499999999999999, + 0.8213938048432696, 0.883022221559489, 0.9330127018922194, + 0.9698463103929542, 0.9924038765061041, 1, + ], + in_exp: [ + 0, 0.0014352875901128893, 0.002109491677524035, 0.0031003926796253885, + 0.004556754060844206, 0.006697218616039631, 0.009843133202303688, + 0.014466792379488908, 0.021262343752724643, 0.03125, 0.045929202883612456, + 0.06750373368076916, 0.09921256574801243, 0.1458161299470146, + 0.2143109957132682, 0.31498026247371835, 0.46293735614364506, + 0.6803950000871883, 1, + ], + out_exp: [ + 0, 0.31960499991281155, 0.5370626438563548, 0.6850197375262816, + 0.7856890042867318, 0.8541838700529854, 0.9007874342519875, + 0.9324962663192309, 0.9540707971163875, 0.96875, 0.9787376562472754, + 0.9855332076205111, 0.9901568667976963, 0.9933027813839603, + 0.9954432459391558, 0.9968996073203746, 0.9978905083224759, + 0.9985647124098871, 1, + ], + inOut_exp: [ + 0, 0.0010547458387620175, 0.002278377030422103, 0.004921566601151844, + 0.010631171876362321, 0.022964601441806228, 0.049606282874006216, + 0.1071554978566341, 0.23146867807182253, 0.5, 0.7685313219281775, + 0.892844502143366, 0.9503937171259937, 0.9770353985581938, + 0.9893688281236377, 0.9950784333988482, 0.9977216229695779, + 0.998945254161238, 1, + ], + in_circle: [ + 0, 0.0015444024660317135, 0.006192010000093506, 0.013986702816730645, + 0.025003956956430873, 0.03935464078941209, 0.057190958417936644, + 0.07871533601238889, 0.10419358352238339, 0.1339745962155614, + 0.1685205807169019, 0.20845517506805522, 0.2546440075000701, + 0.3083389112228482, 0.37146063894529113, 0.4472292016074334, + 0.5418771527091488, 0.6713289009389102, 1, + ], + out_circle: [ + 0, 0.3286710990610898, 0.45812284729085123, 0.5527707983925666, + 0.6285393610547089, 0.6916610887771518, 0.7453559924999298, + 0.7915448249319448, 0.8314794192830981, 0.8660254037844386, + 0.8958064164776166, 0.9212846639876111, 0.9428090415820634, + 0.9606453592105879, 0.9749960430435691, 0.9860132971832694, + 0.9938079899999065, 0.9984555975339683, 1, + ], + inOut_circle: [ + 0, 0.003096005000046753, 0.012501978478215436, 0.028595479208968322, + 0.052096791761191696, 0.08426029035845095, 0.12732200375003505, + 0.18573031947264557, 0.2709385763545744, 0.5, 0.7290614236454256, + 0.8142696805273546, 0.8726779962499649, 0.915739709641549, + 0.9479032082388084, 0.9714045207910317, 0.9874980215217846, + 0.9969039949999532, 1, + ], + in_back_: [ + 0, -0.004788556241426612, -0.017301289437585736, -0.0347587962962963, + -0.05438167352537723, -0.07339051783264748, -0.08900592592592595, + -0.09844849451303156, -0.0989388203017833, -0.08769750000000004, + -0.06194513031550073, -0.018902307956104283, 0.044210370370370254, + 0.13017230795610413, 0.2417629080932785, 0.3817615740740742, + 0.5529477091906719, 0.7581007167352535, 0.9999999999999998, + ], + out_back_: [ + 2.220446049250313e-16, 0.24189928326474652, 0.44705229080932807, + 0.6182384259259258, 0.7582370919067215, 0.8698276920438959, + 0.9557896296296297, 1.0189023079561044, 1.0619451303155008, 1.0876975, + 1.0989388203017834, 1.0984484945130315, 1.089005925925926, + 1.0733905178326475, 1.0543816735253773, 1.0347587962962963, + 1.0173012894375857, 1.0047885562414267, 1, + ], + }; + + Object.keys(Samples).forEach(function (type) { + it('should ease ' + type, function () { + const [modeName, easingName, isFunction] = type.split('_'); + let easing = Easing[easingName]; + if (isFunction !== undefined) { + easing = easing(); + } + const computed = sampleEasingFunction(Easing[modeName](easing)); + const samples = Samples[type]; + + computed.forEach((value, key) => { + expect(value).toBeCloseTo(samples[key], 2); + }); + }); + }); +}); diff --git a/Libraries/Animated/src/__tests__/Interpolation-test.js b/Libraries/Animated/__tests__/Interpolation-test.js similarity index 84% rename from Libraries/Animated/src/__tests__/Interpolation-test.js rename to Libraries/Animated/__tests__/Interpolation-test.js index 27f4438ab1568d..64585ac683bd9a 100644 --- a/Libraries/Animated/src/__tests__/Interpolation-test.js +++ b/Libraries/Animated/__tests__/Interpolation-test.js @@ -1,19 +1,21 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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'; -var AnimatedInterpolation = require('../nodes/AnimatedInterpolation'); -var Easing = require('Easing'); +const AnimatedInterpolation = require('../nodes/AnimatedInterpolation'); +const Easing = require('../Easing'); describe('Interpolation', () => { it('should work with defaults', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: [0, 1], }); @@ -25,7 +27,7 @@ describe('Interpolation', () => { }); it('should work with output range', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: [100, 200], }); @@ -37,7 +39,7 @@ describe('Interpolation', () => { }); it('should work with input range', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [100, 200], outputRange: [0, 1], }); @@ -65,7 +67,7 @@ describe('Interpolation', () => { }); it('should work with empty input range', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 10, 10], outputRange: [1, 2, 3], extrapolate: 'extend', @@ -79,7 +81,7 @@ describe('Interpolation', () => { }); it('should work with empty output range', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [1, 2, 3], outputRange: [0, 10, 10], extrapolate: 'extend', @@ -94,7 +96,7 @@ describe('Interpolation', () => { }); it('should work with easing', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: [0, 1], easing: Easing.quad, @@ -107,7 +109,7 @@ describe('Interpolation', () => { }); it('should work with extrapolate', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + let interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'extend', @@ -139,7 +141,7 @@ describe('Interpolation', () => { }); it('should work with keyframes with extrapolate', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 10, 100, 1000], outputRange: [0, 5, 50, 500], extrapolate: true, @@ -157,7 +159,7 @@ describe('Interpolation', () => { }); it('should work with keyframes without extrapolate', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1, 2], outputRange: [0.2, 1, 0.2], extrapolate: 'clamp', @@ -183,7 +185,7 @@ describe('Interpolation', () => { }); it('should work with negative infinite', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [-Infinity, 0], outputRange: [-Infinity, 0], easing: Easing.quad, @@ -199,7 +201,7 @@ describe('Interpolation', () => { }); it('should work with positive infinite', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [5, Infinity], outputRange: [5, Infinity], easing: Easing.quad, @@ -217,7 +219,7 @@ describe('Interpolation', () => { }); it('should work with output ranges as string', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'], }); @@ -228,7 +230,7 @@ describe('Interpolation', () => { }); it('should work with output ranges as short hex string', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['#024', '#9BF'], }); @@ -239,7 +241,7 @@ describe('Interpolation', () => { }); it('should work with output ranges as long hex string', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['#FF9500', '#87FC70'], }); @@ -250,7 +252,7 @@ describe('Interpolation', () => { }); it('should work with output ranges with mixed hex and rgba strings', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'], }); @@ -261,7 +263,7 @@ describe('Interpolation', () => { }); it('should work with negative and decimal values in string ranges', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['-100.5deg', '100deg'], }); @@ -272,7 +274,7 @@ describe('Interpolation', () => { }); it('should crash when chaining an interpolation that returns a string', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: [0, 1], }); @@ -282,7 +284,7 @@ describe('Interpolation', () => { }); it('should support a mix of color patterns', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1, 2], outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'], }); @@ -303,7 +305,7 @@ describe('Interpolation', () => { }); it('should round the alpha channel of a color to the nearest thousandth', () => { - var interpolation = AnimatedInterpolation.__createInterpolation({ + const interpolation = AnimatedInterpolation.__createInterpolation({ inputRange: [0, 1], outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'], }); diff --git a/Libraries/Animated/__tests__/TimingAnimation-test.js b/Libraries/Animated/__tests__/TimingAnimation-test.js new file mode 100644 index 00000000000000..e82407876a2d9c --- /dev/null +++ b/Libraries/Animated/__tests__/TimingAnimation-test.js @@ -0,0 +1,38 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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'; + +const TimingAnimation = require('../animations/TimingAnimation'); + +describe('Timing Animation', () => { + it('should return array of 61 items', () => { + const timingAnim = new TimingAnimation({ + duration: 1000, + useNativeDriver: false, + }); + const config = timingAnim.__getNativeAnimationConfig(); + + expect(config.frames.length).toBe(61); + expect(config.frames[60]).toBe(1); + expect(config.frames[59]).toBeLessThan(1); + }); + + it('should cope with zero duration', () => { + const timingAnim = new TimingAnimation({ + duration: 0, + useNativeDriver: false, + }); + const config = timingAnim.__getNativeAnimationConfig(); + + expect(config.frames.length).toBe(1); + expect(config.frames[0]).toBe(1); + }); +}); diff --git a/Libraries/Animated/__tests__/bezier-test.js b/Libraries/Animated/__tests__/bezier-test.js new file mode 100644 index 00000000000000..48bfdef0f2d605 --- /dev/null +++ b/Libraries/Animated/__tests__/bezier-test.js @@ -0,0 +1,150 @@ +/** + * Portions Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow + * @format + */ + +/** + * BezierEasing - use bezier curve for transition easing function + * https://github.com/gre/bezier-easing + * @copyright 2014-2015 Gaetan Renaudeau. MIT License. + */ + +'use strict'; + +const bezier = require('../bezier'); + +const identity = function (x: number) { + return x; +}; + +function assertClose(a: number, b: number, precision: number = 3) { + expect(a).toBeCloseTo(b, precision); +} + +function makeAssertCloseWithPrecision(precision: number) { + return function (a: number, b: number) { + assertClose(a, b, precision); + }; +} + +function allEquals( + be1: (x: number) => number, + be2: (x: number) => number, + samples: number, + assertion: $FlowFixMe, +) { + if (!assertion) { + assertion = assertClose; + } + for (let i = 0; i <= samples; ++i) { + const x = i / samples; + assertion(be1(x), be2(x)); + } +} + +function repeat(n: number) { + return function (f: () => void) { + for (let i = 0; i < n; ++i) { + f(); + } + }; +} + +describe('bezier', function () { + it('should be a function', function () { + expect(typeof bezier === 'function').toBe(true); + }); + it('should creates an object', function () { + expect(typeof bezier(0, 0, 1, 1) === 'function').toBe(true); + }); + it('should fail with wrong arguments', function () { + expect(function () { + bezier(0.5, 0.5, -5, 0.5); + }).toThrow(); + expect(function () { + bezier(0.5, 0.5, 5, 0.5); + }).toThrow(); + expect(function () { + bezier(-2, 0.5, 0.5, 0.5); + }).toThrow(); + expect(function () { + bezier(2, 0.5, 0.5, 0.5); + }).toThrow(); + }); + describe('linear curves', function () { + it('should be linear', function () { + allEquals(bezier(0, 0, 1, 1), bezier(1, 1, 0, 0), 100); + allEquals(bezier(0, 0, 1, 1), identity, 100); + }); + }); + describe('common properties', function () { + it('should be the right value at extremes', function () { + repeat(10)(function () { + const a = Math.random(), + b = 2 * Math.random() - 0.5, + c = Math.random(), + d = 2 * Math.random() - 0.5; + const easing = bezier(a, b, c, d); + expect(easing(0)).toBe(0); + expect(easing(1)).toBe(1); + }); + }); + + it('should approach the projected value of its x=y projected curve', function () { + repeat(10)(function () { + const a = Math.random(), + b = Math.random(), + c = Math.random(), + d = Math.random(); + const easing = bezier(a, b, c, d); + const projected = bezier(b, a, d, c); + const composed = function (x: number) { + return projected(easing(x)); + }; + allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2)); + }); + }); + }); + describe('two same instances', function () { + it('should be strictly equals', function () { + repeat(10)(function () { + const a = Math.random(), + b = 2 * Math.random() - 0.5, + c = Math.random(), + d = 2 * Math.random() - 0.5; + allEquals(bezier(a, b, c, d), bezier(a, b, c, d), 100, 0); + }); + }); + }); + describe('symmetric curves', function () { + it('should have a central value y~=0.5 at x=0.5', function () { + repeat(10)(function () { + const a = Math.random(), + b = 2 * Math.random() - 0.5, + c = 1 - a, + d = 1 - b; + const easing = bezier(a, b, c, d); + assertClose(easing(0.5), 0.5, 2); + }); + }); + it('should be symmetrical', function () { + repeat(10)(function () { + const a = Math.random(), + b = 2 * Math.random() - 0.5, + c = 1 - a, + d = 1 - b; + const easing = bezier(a, b, c, d); + const sym = function (x: number) { + return 1 - easing(1 - x); + }; + allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2)); + }); + }); + }); +}); diff --git a/Libraries/Animated/__tests__/createAnimatedComponentInjection-test.js b/Libraries/Animated/__tests__/createAnimatedComponentInjection-test.js new file mode 100644 index 00000000000000..9d8fa9b1869d99 --- /dev/null +++ b/Libraries/Animated/__tests__/createAnimatedComponentInjection-test.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow strict-local + * @format + */ + +'use strict'; + +const createAnimatedComponent = require('../createAnimatedComponent'); +const createAnimatedComponentInjection = require('../createAnimatedComponentInjection'); +const React = require('react'); + +function injected( + Component: React.AbstractComponent, +): React.AbstractComponent { + return createAnimatedComponent(Component); +} + +beforeEach(() => { + jest.resetModules(); + jest.resetAllMocks(); +}); + +test('does nothing without injection', () => { + expect(typeof createAnimatedComponent).toBe('function'); + expect(createAnimatedComponent).not.toBe(injected); +}); + +test('injection overrides `createAnimatedComponent`', () => { + createAnimatedComponentInjection.inject(injected); + + expect(createAnimatedComponent).toBe(injected); +}); + +test('injection errors if called too late', () => { + jest.spyOn(console, 'error').mockReturnValue(undefined); + + // Causes `createAnimatedComponent` to be initialized. + createAnimatedComponent; + + createAnimatedComponentInjection.inject(injected); + + expect(createAnimatedComponent).not.toBe(injected); + expect(console.error).toBeCalledWith( + 'createAnimatedComponentInjection: Must be called before `createAnimatedComponent`.', + ); +}); + +test('injection errors if called more than once', () => { + jest.spyOn(console, 'error').mockReturnValue(undefined); + + createAnimatedComponentInjection.inject(injected); + + expect(createAnimatedComponent).toBe(injected); + expect(console.error).not.toBeCalled(); + + createAnimatedComponentInjection.inject(injected); + + expect(console.error).toBeCalledWith( + 'createAnimatedComponentInjection: Cannot be called more than once.', + ); +}); diff --git a/Libraries/Animated/animations/Animation.js b/Libraries/Animated/animations/Animation.js new file mode 100644 index 00000000000000..f22c5e9e4adaa5 --- /dev/null +++ b/Libraries/Animated/animations/Animation.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type AnimatedValue from '../nodes/AnimatedValue'; + +export type EndResult = {finished: boolean, ...}; +export type EndCallback = (result: EndResult) => void; + +export type AnimationConfig = { + isInteraction?: boolean, + useNativeDriver: boolean, + platformConfig?: PlatformConfig, + onComplete?: ?EndCallback, + iterations?: number, +}; + +let startNativeAnimationNextId = 1; + +// Important note: start() and stop() will only be called at most once. +// Once an animation has been stopped or finished its course, it will +// not be reused. +class Animation { + __active: boolean; + __isInteraction: boolean; + __nativeId: number; + __onEnd: ?EndCallback; + __iterations: number; + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void {} + stop(): void { + if (this.__nativeId) { + NativeAnimatedHelper.API.stopAnimation(this.__nativeId); + } + } + __getNativeAnimationConfig(): any { + // Subclasses that have corresponding animation implementation done in native + // should override this method + throw new Error('This animation type cannot be offloaded to native'); + } + // Helper function for subclasses to make sure onEnd is only called once. + __debouncedOnEnd(result: EndResult): void { + const onEnd = this.__onEnd; + this.__onEnd = null; + onEnd && onEnd(result); + } + __startNativeAnimation(animatedValue: AnimatedValue): void { + const startNativeAnimationWaitId = `${startNativeAnimationNextId}:startAnimation`; + startNativeAnimationNextId += 1; + NativeAnimatedHelper.API.setWaitingForIdentifier( + startNativeAnimationWaitId, + ); + try { + const config = this.__getNativeAnimationConfig(); + animatedValue.__makeNative(config.platformConfig); + this.__nativeId = NativeAnimatedHelper.generateNewAnimationId(); + NativeAnimatedHelper.API.startAnimatingNode( + this.__nativeId, + animatedValue.__getNativeTag(), + config, + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this.__debouncedOnEnd.bind(this), + ); + } catch (e) { + throw e; + } finally { + NativeAnimatedHelper.API.unsetWaitingForIdentifier( + startNativeAnimationWaitId, + ); + } + } +} + +module.exports = Animation; diff --git a/Libraries/Animated/animations/DecayAnimation.js b/Libraries/Animated/animations/DecayAnimation.js new file mode 100644 index 00000000000000..c53ff08ac0fea4 --- /dev/null +++ b/Libraries/Animated/animations/DecayAnimation.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const Animation = require('./Animation'); + +const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type AnimatedValue from '../nodes/AnimatedValue'; +import type {AnimationConfig, EndCallback} from './Animation'; + +export type DecayAnimationConfig = { + ...AnimationConfig, + velocity: + | number + | { + x: number, + y: number, + ... + }, + deceleration?: number, +}; + +export type DecayAnimationConfigSingle = { + ...AnimationConfig, + velocity: number, + deceleration?: number, +}; + +class DecayAnimation extends Animation { + _startTime: number; + _lastValue: number; + _fromValue: number; + _deceleration: number; + _velocity: number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _useNativeDriver: boolean; + _platformConfig: ?PlatformConfig; + + constructor(config: DecayAnimationConfigSingle) { + super(); + this._deceleration = config.deceleration ?? 0.998; + this._velocity = config.velocity; + this._useNativeDriver = shouldUseNativeDriver(config); + this._platformConfig = config.platformConfig; + this.__isInteraction = config.isInteraction ?? !this._useNativeDriver; + this.__iterations = config.iterations ?? 1; + } + + __getNativeAnimationConfig(): {| + deceleration: number, + iterations: number, + platformConfig: ?PlatformConfig, + type: $TEMPORARY$string<'decay'>, + velocity: number, + |} { + return { + type: 'decay', + deceleration: this._deceleration, + velocity: this._velocity, + iterations: this.__iterations, + platformConfig: this._platformConfig, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._lastValue = fromValue; + this._fromValue = fromValue; + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + this._startTime = Date.now(); + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + onUpdate(): void { + const now = Date.now(); + + const value = + this._fromValue + + (this._velocity / (1 - this._deceleration)) * + (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); + + this._onUpdate(value); + + if (Math.abs(this._lastValue - value) < 0.1) { + this.__debouncedOnEnd({finished: true}); + return; + } + + this._lastValue = value; + if (this.__active) { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + stop(): void { + super.stop(); + this.__active = false; + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +} + +module.exports = DecayAnimation; diff --git a/Libraries/Animated/src/animations/SpringAnimation.js b/Libraries/Animated/animations/SpringAnimation.js similarity index 79% rename from Libraries/Animated/src/animations/SpringAnimation.js rename to Libraries/Animated/animations/SpringAnimation.js index 8b6f96fe67bdb6..e02ed31872daa7 100644 --- a/Libraries/Animated/src/animations/SpringAnimation.js +++ b/Libraries/Animated/animations/SpringAnimation.js @@ -1,31 +1,60 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule SpringAnimation * @flow * @format */ + 'use strict'; const AnimatedValue = require('../nodes/AnimatedValue'); const AnimatedValueXY = require('../nodes/AnimatedValueXY'); +const AnimatedInterpolation = require('../nodes/AnimatedInterpolation'); const Animation = require('./Animation'); const SpringConfig = require('../SpringConfig'); -const invariant = require('fbjs/lib/invariant'); +const invariant = require('invariant'); + const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); +import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type {AnimationConfig, EndCallback} from './Animation'; -export type SpringAnimationConfig = AnimationConfig & { - toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY, +import AnimatedColor from '../nodes/AnimatedColor'; + +export type SpringAnimationConfig = { + ...AnimationConfig, + toValue: + | number + | AnimatedValue + | { + x: number, + y: number, + ... + } + | AnimatedValueXY + | { + r: number, + g: number, + b: number, + a: number, + ... + } + | AnimatedColor + | AnimatedInterpolation, overshootClamping?: boolean, restDisplacementThreshold?: number, restSpeedThreshold?: number, - velocity?: number | {x: number, y: number}, + velocity?: + | number + | { + x: number, + y: number, + ... + }, bounciness?: number, speed?: number, tension?: number, @@ -36,8 +65,9 @@ export type SpringAnimationConfig = AnimationConfig & { delay?: number, }; -export type SpringAnimationConfigSingle = AnimationConfig & { - toValue: number | AnimatedValue, +export type SpringAnimationConfigSingle = { + ...AnimationConfig, + toValue: number, overshootClamping?: boolean, restDisplacementThreshold?: number, restSpeedThreshold?: number, @@ -52,13 +82,6 @@ export type SpringAnimationConfigSingle = AnimationConfig & { delay?: number, }; -function withDefault(value: ?T, defaultValue: T): T { - if (value === undefined || value === null) { - return defaultValue; - } - return value; -} - class SpringAnimation extends Animation { _overshootClamping: boolean; _restDisplacementThreshold: number; @@ -67,7 +90,7 @@ class SpringAnimation extends Animation { _startPosition: number; _lastPosition: number; _fromValue: number; - _toValue: any; + _toValue: number; _stiffness: number; _damping: number; _mass: number; @@ -80,24 +103,22 @@ class SpringAnimation extends Animation { _onUpdate: (value: number) => void; _animationFrame: any; _useNativeDriver: boolean; + _platformConfig: ?PlatformConfig; constructor(config: SpringAnimationConfigSingle) { super(); - this._overshootClamping = withDefault(config.overshootClamping, false); - this._restDisplacementThreshold = withDefault( - config.restDisplacementThreshold, - 0.001, - ); - this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001); - this._initialVelocity = withDefault(config.velocity, 0); - this._lastVelocity = withDefault(config.velocity, 0); + this._overshootClamping = config.overshootClamping ?? false; + this._restDisplacementThreshold = config.restDisplacementThreshold ?? 0.001; + this._restSpeedThreshold = config.restSpeedThreshold ?? 0.001; + this._initialVelocity = config.velocity ?? 0; + this._lastVelocity = config.velocity ?? 0; this._toValue = config.toValue; - this._delay = withDefault(config.delay, 0); + this._delay = config.delay ?? 0; this._useNativeDriver = shouldUseNativeDriver(config); - this.__isInteraction = - config.isInteraction !== undefined ? config.isInteraction : true; - this.__iterations = config.iterations !== undefined ? config.iterations : 1; + this._platformConfig = config.platformConfig; + this.__isInteraction = config.isInteraction ?? !this._useNativeDriver; + this.__iterations = config.iterations ?? 1; if ( config.stiffness !== undefined || @@ -111,9 +132,9 @@ class SpringAnimation extends Animation { config.friction === undefined, 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', ); - this._stiffness = withDefault(config.stiffness, 100); - this._damping = withDefault(config.damping, 10); - this._mass = withDefault(config.mass, 1); + this._stiffness = config.stiffness ?? 100; + this._damping = config.damping ?? 10; + this._mass = config.mass ?? 1; } else if (config.bounciness !== undefined || config.speed !== undefined) { // Convert the origami bounciness/speed values to stiffness/damping // We assume mass is 1. @@ -126,8 +147,8 @@ class SpringAnimation extends Animation { 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', ); const springConfig = SpringConfig.fromBouncinessAndSpeed( - withDefault(config.bounciness, 8), - withDefault(config.speed, 12), + config.bounciness ?? 8, + config.speed ?? 12, ); this._stiffness = springConfig.stiffness; this._damping = springConfig.damping; @@ -136,8 +157,8 @@ class SpringAnimation extends Animation { // Convert the origami tension/friction values to stiffness/damping // We assume mass is 1. const springConfig = SpringConfig.fromOrigamiTensionAndFriction( - withDefault(config.tension, 40), - withDefault(config.friction, 7), + config.tension ?? 40, + config.friction ?? 7, ); this._stiffness = springConfig.stiffness; this._damping = springConfig.damping; @@ -149,7 +170,19 @@ class SpringAnimation extends Animation { invariant(this._mass > 0, 'Mass value must be greater than 0'); } - __getNativeAnimationConfig() { + __getNativeAnimationConfig(): {| + damping: number, + initialVelocity: number, + iterations: number, + mass: number, + platformConfig: ?PlatformConfig, + overshootClamping: boolean, + restDisplacementThreshold: number, + restSpeedThreshold: number, + stiffness: number, + toValue: any, + type: $TEMPORARY$string<'spring'>, + |} { return { type: 'spring', overshootClamping: this._overshootClamping, @@ -158,9 +191,10 @@ class SpringAnimation extends Animation { stiffness: this._stiffness, damping: this._damping, mass: this._mass, - initialVelocity: withDefault(this._initialVelocity, this._lastVelocity), + initialVelocity: this._initialVelocity ?? this._lastVelocity, toValue: this._toValue, iterations: this.__iterations, + platformConfig: this._platformConfig, }; } @@ -267,7 +301,7 @@ class SpringAnimation extends Animation { position = this._toValue - envelope * - ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) + + (((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) + x0 * Math.cos(omega1 * t)); // This looks crazy -- it's actually just the derivative of the // oscillation function @@ -275,7 +309,7 @@ class SpringAnimation extends Animation { zeta * omega0 * envelope * - (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 + + ((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 + x0 * Math.cos(omega1 * t)) - envelope * (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - @@ -325,6 +359,7 @@ class SpringAnimation extends Animation { this.__debouncedOnEnd({finished: true}); return; } + // $FlowFixMe[method-unbinding] added when improving typing for this parameters this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); } diff --git a/Libraries/Animated/animations/TimingAnimation.js b/Libraries/Animated/animations/TimingAnimation.js new file mode 100644 index 00000000000000..94c4b3a4b8361b --- /dev/null +++ b/Libraries/Animated/animations/TimingAnimation.js @@ -0,0 +1,176 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedValue = require('../nodes/AnimatedValue'); +const AnimatedValueXY = require('../nodes/AnimatedValueXY'); +const AnimatedInterpolation = require('../nodes/AnimatedInterpolation'); +const Animation = require('./Animation'); + +const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type {AnimationConfig, EndCallback} from './Animation'; +import type {RgbaValue} from '../nodes/AnimatedColor'; + +import AnimatedColor from '../nodes/AnimatedColor'; + +export type TimingAnimationConfig = $ReadOnly<{ + ...AnimationConfig, + toValue: + | number + | AnimatedValue + | { + x: number, + y: number, + ... + } + | AnimatedValueXY + | RgbaValue + | AnimatedColor + | AnimatedInterpolation, + easing?: (value: number) => number, + duration?: number, + delay?: number, +}>; + +export type TimingAnimationConfigSingle = $ReadOnly<{ + ...AnimationConfig, + toValue: number, + easing?: (value: number) => number, + duration?: number, + delay?: number, +}>; + +let _easeInOut; +function easeInOut() { + if (!_easeInOut) { + const Easing = require('../Easing'); + _easeInOut = Easing.inOut(Easing.ease); + } + return _easeInOut; +} + +class TimingAnimation extends Animation { + _startTime: number; + _fromValue: number; + _toValue: number; + _duration: number; + _delay: number; + _easing: (value: number) => number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _timeout: any; + _useNativeDriver: boolean; + _platformConfig: ?PlatformConfig; + + constructor(config: TimingAnimationConfigSingle) { + super(); + this._toValue = config.toValue; + this._easing = config.easing ?? easeInOut(); + this._duration = config.duration ?? 500; + this._delay = config.delay ?? 0; + this.__iterations = config.iterations ?? 1; + this._useNativeDriver = shouldUseNativeDriver(config); + this._platformConfig = config.platformConfig; + this.__isInteraction = config.isInteraction ?? !this._useNativeDriver; + } + + __getNativeAnimationConfig(): any { + const frameDuration = 1000.0 / 60.0; + const frames = []; + const numFrames = Math.round(this._duration / frameDuration); + for (let frame = 0; frame < numFrames; frame++) { + frames.push(this._easing(frame / numFrames)); + } + frames.push(this._easing(1)); + return { + type: 'frames', + frames, + toValue: this._toValue, + iterations: this.__iterations, + platformConfig: this._platformConfig, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._fromValue = fromValue; + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + + const start = () => { + // Animations that sometimes have 0 duration and sometimes do not + // still need to use the native driver when duration is 0 so as to + // not cause intermixed JS and native animations. + if (this._duration === 0 && !this._useNativeDriver) { + this._onUpdate(this._toValue); + this.__debouncedOnEnd({finished: true}); + } else { + this._startTime = Date.now(); + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + this._animationFrame = requestAnimationFrame( + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this.onUpdate.bind(this), + ); + } + } + }; + if (this._delay) { + this._timeout = setTimeout(start, this._delay); + } else { + start(); + } + } + + onUpdate(): void { + const now = Date.now(); + if (now >= this._startTime + this._duration) { + if (this._duration === 0) { + this._onUpdate(this._toValue); + } else { + this._onUpdate( + this._fromValue + this._easing(1) * (this._toValue - this._fromValue), + ); + } + this.__debouncedOnEnd({finished: true}); + return; + } + + this._onUpdate( + this._fromValue + + this._easing((now - this._startTime) / this._duration) * + (this._toValue - this._fromValue), + ); + if (this.__active) { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + stop(): void { + super.stop(); + this.__active = false; + clearTimeout(this._timeout); + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +} + +module.exports = TimingAnimation; diff --git a/Libraries/Animated/bezier.js b/Libraries/Animated/bezier.js new file mode 100644 index 00000000000000..da60afa0ec32e5 --- /dev/null +++ b/Libraries/Animated/bezier.js @@ -0,0 +1,164 @@ +/** + * Portions Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +/** + * BezierEasing - use bezier curve for transition easing function + * https://github.com/gre/bezier-easing + * @copyright 2014-2015 Gaëtan Renaudeau. MIT License. + */ + +'use strict'; + +// These values are established by empiricism with tests (tradeoff: performance VS precision) +const NEWTON_ITERATIONS = 4; +const NEWTON_MIN_SLOPE = 0.001; +const SUBDIVISION_PRECISION = 0.0000001; +const SUBDIVISION_MAX_ITERATIONS = 10; + +const kSplineTableSize = 11; +const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + +const float32ArraySupported = typeof Float32Array === 'function'; + +function A(aA1: number, aA2: number) { + return 1.0 - 3.0 * aA2 + 3.0 * aA1; +} +function B(aA1: number, aA2: number) { + return 3.0 * aA2 - 6.0 * aA1; +} +function C(aA1: number) { + return 3.0 * aA1; +} + +// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. +function calcBezier(aT: number, aA1: number, aA2: number) { + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; +} + +// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. +function getSlope(aT: number, aA1: number, aA2: number) { + return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); +} + +function binarySubdivide( + aX: number, + _aA: number, + _aB: number, + mX1: number, + mX2: number, +) { + let currentX, + currentT, + i = 0, + aA = _aA, + aB = _aB; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while ( + Math.abs(currentX) > SUBDIVISION_PRECISION && + ++i < SUBDIVISION_MAX_ITERATIONS + ); + return currentT; +} + +function newtonRaphsonIterate( + aX: number, + _aGuessT: number, + mX1: number, + mX2: number, +) { + let aGuessT = _aGuessT; + for (let i = 0; i < NEWTON_ITERATIONS; ++i) { + const currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) { + return aGuessT; + } + const currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; +} + +module.exports = function bezier( + mX1: number, + mY1: number, + mX2: number, + mY2: number, +): (x: number) => number { + if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) { + throw new Error('bezier x values must be in [0, 1] range'); + } + + // Precompute samples table + const sampleValues = float32ArraySupported + ? new Float32Array(kSplineTableSize) + : new Array(kSplineTableSize); + if (mX1 !== mY1 || mX2 !== mY2) { + for (let i = 0; i < kSplineTableSize; ++i) { + sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + } + + function getTForX(aX: number) { + let intervalStart = 0.0; + let currentSample = 1; + const lastSample = kSplineTableSize - 1; + + for ( + ; + currentSample !== lastSample && sampleValues[currentSample] <= aX; + ++currentSample + ) { + intervalStart += kSampleStepSize; + } + --currentSample; + + // Interpolate to provide an initial guess for t + const dist = + (aX - sampleValues[currentSample]) / + (sampleValues[currentSample + 1] - sampleValues[currentSample]); + const guessForT = intervalStart + dist * kSampleStepSize; + + const initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } else if (initialSlope === 0.0) { + return guessForT; + } else { + return binarySubdivide( + aX, + intervalStart, + intervalStart + kSampleStepSize, + mX1, + mX2, + ); + } + } + + return function BezierEasing(x: number): number { + if (mX1 === mY1 && mX2 === mY2) { + return x; // linear + } + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if (x === 0) { + return 0; + } + if (x === 1) { + return 1; + } + return calcBezier(getTForX(x), mY1, mY2); + }; +}; diff --git a/Libraries/Animated/components/AnimatedFlatList.js b/Libraries/Animated/components/AnimatedFlatList.js new file mode 100644 index 00000000000000..ebe160f12e7545 --- /dev/null +++ b/Libraries/Animated/components/AnimatedFlatList.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const FlatList = require('../../Lists/FlatList'); +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +/** + * @see https://github.com/facebook/react-native/commit/b8c8562 + */ +const FlatListWithEventThrottle = React.forwardRef((props, ref) => ( + +)); + +module.exports = (createAnimatedComponent( + FlatListWithEventThrottle, +): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/components/AnimatedImage.js b/Libraries/Animated/components/AnimatedImage.js new file mode 100644 index 00000000000000..b0e7b9042020c2 --- /dev/null +++ b/Libraries/Animated/components/AnimatedImage.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const Image = require('../../Image/Image'); +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +module.exports = (createAnimatedComponent( + (Image: $FlowFixMe), +): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/components/AnimatedScrollView.js b/Libraries/Animated/components/AnimatedScrollView.js new file mode 100644 index 00000000000000..887948366ac849 --- /dev/null +++ b/Libraries/Animated/components/AnimatedScrollView.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const ScrollView = require('../../Components/ScrollView/ScrollView'); +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +/** + * @see https://github.com/facebook/react-native/commit/b8c8562 + */ +const ScrollViewWithEventThrottle = React.forwardRef((props, ref) => ( + +)); + +module.exports = (createAnimatedComponent( + ScrollViewWithEventThrottle, +): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/components/AnimatedSectionList.js b/Libraries/Animated/components/AnimatedSectionList.js new file mode 100644 index 00000000000000..2c467a66998282 --- /dev/null +++ b/Libraries/Animated/components/AnimatedSectionList.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +import SectionList from '../../Lists/SectionList'; +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +/** + * @see https://github.com/facebook/react-native/commit/b8c8562 + */ +const SectionListWithEventThrottle = React.forwardRef((props, ref) => ( + +)); + +module.exports = (createAnimatedComponent( + SectionListWithEventThrottle, +): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/components/AnimatedText.js b/Libraries/Animated/components/AnimatedText.js new file mode 100644 index 00000000000000..0cd6cd5245d301 --- /dev/null +++ b/Libraries/Animated/components/AnimatedText.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const Text = require('../../Text/Text'); +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +module.exports = (createAnimatedComponent( + (Text: $FlowFixMe), +): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/components/AnimatedView.js b/Libraries/Animated/components/AnimatedView.js new file mode 100644 index 00000000000000..75358b003d5dfc --- /dev/null +++ b/Libraries/Animated/components/AnimatedView.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +const View = require('../../Components/View/View'); +const createAnimatedComponent = require('../createAnimatedComponent'); + +import type {AnimatedComponentType} from '../createAnimatedComponent'; + +module.exports = (createAnimatedComponent(View): AnimatedComponentType< + React.ElementConfig, + React.ElementRef, +>); diff --git a/Libraries/Animated/createAnimatedComponent.js b/Libraries/Animated/createAnimatedComponent.js new file mode 100644 index 00000000000000..c9dbe808ed713f --- /dev/null +++ b/Libraries/Animated/createAnimatedComponent.js @@ -0,0 +1,281 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import * as createAnimatedComponentInjection from './createAnimatedComponentInjection'; + +const View = require('../Components/View/View'); +const {AnimatedEvent} = require('./AnimatedEvent'); +const AnimatedProps = require('./nodes/AnimatedProps'); +const React = require('react'); +const NativeAnimatedHelper = require('./NativeAnimatedHelper'); + +const invariant = require('invariant'); +const setAndForwardRef = require('../Utilities/setAndForwardRef'); + +let animatedComponentNextId = 1; + +export type AnimatedComponentType< + -Props: {+[string]: mixed, ...}, + +Instance = mixed, +> = React.AbstractComponent< + $ObjMap< + Props & + $ReadOnly<{ + passthroughAnimatedPropExplicitValues?: React.ElementConfig< + typeof View, + >, + }>, + () => any, + >, + Instance, +>; + +function createAnimatedComponent( + Component: React.AbstractComponent, +): AnimatedComponentType { + invariant( + typeof Component !== 'function' || + (Component.prototype && Component.prototype.isReactComponent), + '`createAnimatedComponent` does not support stateless functional components; ' + + 'use a class component instead.', + ); + + class AnimatedComponent extends React.Component { + _component: any; // TODO T53738161: flow type this, and the whole file + _invokeAnimatedPropsCallbackOnMount: boolean = false; + _prevComponent: any; + _propsAnimated: AnimatedProps; + _eventDetachers: Array = []; + _initialAnimatedProps: Object; + + // Only to be used in this file, and only in Fabric. + _animatedComponentId: string = `${animatedComponentNextId++}:animatedComponent`; + + _attachNativeEvents() { + // Make sure to get the scrollable node for components that implement + // `ScrollResponder.Mixin`. + const scrollableNode = this._component?.getScrollableNode + ? this._component.getScrollableNode() + : this._component; + + for (const key in this.props) { + const prop = this.props[key]; + if (prop instanceof AnimatedEvent && prop.__isNative) { + prop.__attach(scrollableNode, key); + this._eventDetachers.push(() => prop.__detach(scrollableNode, key)); + } + } + } + + _detachNativeEvents() { + this._eventDetachers.forEach(remove => remove()); + this._eventDetachers = []; + } + + _isFabric = (): boolean => { + // When called during the first render, `_component` is always null. + // Therefore, even if a component is rendered in Fabric, we can't detect + // that until ref is set, which happens sometime after the first render. + // In cases where this value switching between "false" and "true" on Fabric + // causes issues, add an additional check for _component nullity. + if (this._component == null) { + return false; + } + return ( + // eslint-disable-next-line dot-notation + this._component['_internalInstanceHandle']?.stateNode?.canonical != + null || + // Some components have a setNativeProps function but aren't a host component + // such as lists like FlatList and SectionList. These should also use + // forceUpdate in Fabric since setNativeProps doesn't exist on the underlying + // host component. This crazy hack is essentially special casing those lists and + // ScrollView itself to use forceUpdate in Fabric. + // If these components end up using forwardRef then these hacks can go away + // as this._component would actually be the underlying host component and the above check + // would be sufficient. + (this._component.getNativeScrollRef != null && + this._component.getNativeScrollRef() != null && + // eslint-disable-next-line dot-notation + this._component.getNativeScrollRef()['_internalInstanceHandle'] + ?.stateNode?.canonical != null) || + (this._component.getScrollResponder != null && + this._component.getScrollResponder() != null && + this._component.getScrollResponder().getNativeScrollRef != null && + this._component.getScrollResponder().getNativeScrollRef() != null && + this._component.getScrollResponder().getNativeScrollRef()[ + // eslint-disable-next-line dot-notation + '_internalInstanceHandle' + ]?.stateNode?.canonical != null) + ); + }; + + _waitForUpdate = (): void => { + if (this._isFabric()) { + NativeAnimatedHelper.API.setWaitingForIdentifier( + this._animatedComponentId, + ); + } + }; + + _markUpdateComplete = (): void => { + if (this._isFabric()) { + NativeAnimatedHelper.API.unsetWaitingForIdentifier( + this._animatedComponentId, + ); + } + }; + + // The system is best designed when setNativeProps is implemented. It is + // able to avoid re-rendering and directly set the attributes that changed. + // However, setNativeProps can only be implemented on leaf native + // components. If you want to animate a composite component, you need to + // re-render it. In this case, we have a fallback that uses forceUpdate. + // This fallback is also called in Fabric. + _animatedPropsCallback = () => { + if (this._component == null) { + // AnimatedProps is created in will-mount because it's used in render. + // But this callback may be invoked before mount in async mode, + // In which case we should defer the setNativeProps() call. + // React may throw away uncommitted work in async mode, + // So a deferred call won't always be invoked. + this._invokeAnimatedPropsCallbackOnMount = true; + } else if ( + process.env.NODE_ENV === 'test' || + // For animating properties of non-leaf/non-native components + typeof this._component.setNativeProps !== 'function' || + // In Fabric, force animations to go through forceUpdate and skip setNativeProps + this._isFabric() + ) { + this.forceUpdate(); + } else if (!this._propsAnimated.__isNative) { + this._component.setNativeProps( + this._propsAnimated.__getAnimatedValue(), + ); + } else { + throw new Error( + 'Attempting to run JS driven animation on animated ' + + 'node that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + }; + + _attachProps(nextProps: any) { + const oldPropsAnimated = this._propsAnimated; + + this._propsAnimated = new AnimatedProps( + nextProps, + this._animatedPropsCallback, + ); + this._propsAnimated.__attach(); + + // When you call detach, it removes the element from the parent list + // of children. If it goes to 0, then the parent also detaches itself + // and so on. + // An optimization is to attach the new elements and THEN detach the old + // ones instead of detaching and THEN attaching. + // This way the intermediate state isn't to go to 0 and trigger + // this expensive recursive detaching to then re-attach everything on + // the very next operation. + if (oldPropsAnimated) { + oldPropsAnimated.__restoreDefaultValues(); + oldPropsAnimated.__detach(); + } + } + + _setComponentRef = setAndForwardRef({ + getForwardedRef: () => this.props.forwardedRef, + setLocalRef: ref => { + this._prevComponent = this._component; + this._component = ref; + }, + }); + + render() { + const animatedProps = + this._propsAnimated.__getValue(this._initialAnimatedProps) || {}; + const {style = {}, ...props} = animatedProps; + const {style: passthruStyle = {}, ...passthruProps} = + this.props.passthroughAnimatedPropExplicitValues || {}; + const mergedStyle = {...style, ...passthruStyle}; + + if (!this._initialAnimatedProps) { + this._initialAnimatedProps = animatedProps; + } + + // Force `collapsable` to be false so that native view is not flattened. + // Flattened views cannot be accurately referenced by a native driver. + return ( + + ); + } + + UNSAFE_componentWillMount() { + this._waitForUpdate(); + this._attachProps(this.props); + } + + componentDidMount() { + if (this._invokeAnimatedPropsCallbackOnMount) { + this._invokeAnimatedPropsCallbackOnMount = false; + this._animatedPropsCallback(); + } + + this._propsAnimated.setNativeView(this._component); + this._attachNativeEvents(); + this._markUpdateComplete(); + } + + UNSAFE_componentWillReceiveProps(newProps: any) { + this._waitForUpdate(); + this._attachProps(newProps); + } + + componentDidUpdate(prevProps: any) { + if (this._component !== this._prevComponent) { + this._propsAnimated.setNativeView(this._component); + } + if (this._component !== this._prevComponent || prevProps !== this.props) { + this._detachNativeEvents(); + this._attachNativeEvents(); + } + this._markUpdateComplete(); + } + + componentWillUnmount() { + this._propsAnimated && this._propsAnimated.__detach(); + this._detachNativeEvents(); + this._markUpdateComplete(); + this._component = null; + this._prevComponent = null; + } + } + + return React.forwardRef(function AnimatedComponentWrapper(props, ref) { + return ( + + ); + }); +} + +// $FlowIgnore[incompatible-cast] - Will be compatible after refactors. +module.exports = (createAnimatedComponentInjection.recordAndRetrieve() ?? + createAnimatedComponent: typeof createAnimatedComponent); diff --git a/Libraries/Animated/createAnimatedComponentInjection.js b/Libraries/Animated/createAnimatedComponentInjection.js new file mode 100644 index 00000000000000..ab172bf12ca21d --- /dev/null +++ b/Libraries/Animated/createAnimatedComponentInjection.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import * as React from 'react'; + +type createAnimatedComponent = ( + Component: React.AbstractComponent, +) => React.AbstractComponent; + +// This can be undefined, null, or the experimental implementation. If this is +// null, that means `createAnimatedComponent` has already been initialized and +// it is too late to call `inject`. +let injected: ?createAnimatedComponent; + +/** + * Call during bundle initialization to opt-in to new `createAnimatedComponent`. + */ +export function inject(newInjected: createAnimatedComponent): void { + if (injected !== undefined) { + if (__DEV__) { + console.error( + 'createAnimatedComponentInjection: ' + + (injected == null + ? 'Must be called before `createAnimatedComponent`.' + : 'Cannot be called more than once.'), + ); + } + return; + } + injected = newInjected; +} + +/** + * Only called by `createAnimatedComponent.js`. + */ +export function recordAndRetrieve(): createAnimatedComponent | null { + if (injected === undefined) { + injected = null; + } + return injected; +} diff --git a/Libraries/Animated/createAnimatedComponent_EXPERIMENTAL.js b/Libraries/Animated/createAnimatedComponent_EXPERIMENTAL.js new file mode 100644 index 00000000000000..b2cd04ac56e44b --- /dev/null +++ b/Libraries/Animated/createAnimatedComponent_EXPERIMENTAL.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import useAnimatedProps from './useAnimatedProps'; +import useMergeRefs from '../Utilities/useMergeRefs'; +import StyleSheet from '../StyleSheet/StyleSheet'; +import * as React from 'react'; + +/** + * Experimental implementation of `createAnimatedComponent` that is intended to + * be compatible with concurrent rendering. + */ +export default function createAnimatedComponent( + Component: React.AbstractComponent, +): React.AbstractComponent { + return React.forwardRef((props, forwardedRef) => { + const [reducedProps, callbackRef] = useAnimatedProps( + props, + ); + const ref = useMergeRefs(callbackRef, forwardedRef); + + // Some components require explicit passthrough values for animation + // to work properly. For example, if an animated component is + // transformed and Pressable, onPress will not work after transform + // without these passthrough values. + // $FlowFixMe[prop-missing] + const {passthroughAnimatedPropExplicitValues, style} = reducedProps; + const {style: passthroughStyle, ...passthroughProps} = + passthroughAnimatedPropExplicitValues ?? {}; + const mergedStyle = StyleSheet.compose(style, passthroughStyle); + + return ( + + ); + }); +} diff --git a/Libraries/Animated/examples/demo.html b/Libraries/Animated/examples/demo.html deleted file mode 100644 index 4f95e114fa86a9..00000000000000 --- a/Libraries/Animated/examples/demo.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - Animated - - - - - - - - - - - -
- -

Animated

-

Animations have for a long time been a weak point of the React ecosystem. The Animated library aims at solving this problem. It embraces the declarative aspect of React and obtains performance by using raw DOM manipulation behind the scenes instead of the usual diff.

- -

Animated.Value

- -

The basic building block of this library is Animated.Value. This is a variable that's going to drive the animation. You use it like a normal value in style attribute. Only animated components such as Animated.div will understand it.

- - - -

setValue

- -

As you can see, the value is being used inside of render() as you would expect. However, you don't call setState() in order to update the value. Instead, you can call setValue() directly on the value itself. We are using a form of data binding.

- -

The Animated.div component when rendered tracks which animated values it received. This way, whenever that value changes, we don't need to re-render the entire component, we can directly update the specific style attribute that changed.

- - - -

Animated.timing

- -

Now that we understand how the system works, let's play with some animations! The hello world of animations is to move the element somewhere else. To do that, we're going to animate the value from the current value 0 to the value 400.

- -

On every frame (via requestAnimationFrame), the timing animation is going to figure out the new value based on the current time, update the animated value which in turn is going to update the corresponding DOM node.

- - - -

Interrupt Animations

- -

As a developer, the mental model is that most animations are fire and forget. When the user presses the button, you want it to shrink to 80% and when she releases, you want it to go back to 100%.

- -

There are multiple challenges to implement this correctly. You need to stop the current animation, grab the current value and restart an animation from there. As this is pretty tedious to do manually, Animated will do that automatically for you.

- - - -

Animated.spring

- -

Unfortunately, the timing animation doesn't feel good. The main reason is that no matter how far you are in the animation, it will trigger a new one with always the same duration.

- -

The commonly used solution for this problem is to use the equation of a real-world spring. Imagine that you attach a spring to the target value, stretch it to the current value and let it go. The spring movement is going to be the same as the update.

- -

It turns out that this model is useful in a very wide range of animations. I highly recommend you to always start with a spring animation instead of a timing animation. It will make your interface feels much better.

- - - -

interpolate

- -

It is very common to animate multiple attributes during the same animation. The usual way to implement it is to start a separate animation for each of the attribute. The downside is that you now have to manage a different state per attribute which is not ideal.

- -

With Animated, you can use a single state variable and render it in multiple attributes. When the value is updated, all the places will reflect the change.

- -

In the following example, we're going to model the animation with a variable where 1 means fully visible and 0 means fully hidden. We can pass it directly to the scale attribute as the ranges match. But for the rotation, we need to convert [0 ; 1] range to [260deg ; 0deg]. This is where interpolate() comes handy.

- - - -

stopAnimation

- -

The reason why we can get away with not calling render() and instead modify the DOM directly on updates is because the animated values are opaque. In render, you cannot know the current value, which prevents you from being able to modify the structure of the DOM.

- -

Animated can offload the animation to a different thread (CoreAnimation, CSS transitions, main thread...) and we don't have a good way to know the real value. If you try to query the value then modify it, you are going to be out of sync and the result will look terrible.

- -

There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in stopAnimation. It will not suffer from being out of sync since the animation is no longer running.

- - - - -

Gesture-based Animations

- -

Most animations libraries only deal with time-based animations. But, as we move to mobile, a lot of animations are also gesture driven. Even more problematic, they often switch between both modes: once the gesture is over, you start a time-based animation using the same interpolations.

- -

Animated has been designed with this use case in mind. The key aspect is that there are three distinct and separate concepts: inputs, value, output. The same value can be updated either from a time-based animation or a gesture-based one. Because we use this intermediate representation for the animation, we can keep the same rendering as output.

- -

HorizontalPan

- -

The code needed to drag elements around is very messy with the DOM APIs. On mousedown, you need to register a mousemove listener on window otherwise you may drop touches if you move too fast. removeEventListener takes the same arguments as addEventListener instead of an id like clearTimeout. It's also really easy to forget to remove a listener and have a leak. And finally, you need to store the current position and value at the beginning and update only compared to it.

- -

We introduce a little helper called HorizontalPan which handles all this annoying code for us. It takes an Animated.Value as first argument and returns the event handlers required for it to work. We just have to bind this value to the left attribute and we're good to go.

- - - -

Animated.decay

- -

One of the big breakthrough of the iPhone is the fact that when you release the finger while scrolling, it will not abruptly stop but instead keep going for some time.

- -

In order to implement this effect, we are using a second real-world simulation: an object moving on an icy surface. All it needs is two values: the current velocity and a deceleration coefficient. It is implemented by Animated.decay.

- - - -

Animation Chaining

- -

The target for an animation is usually a number but sometimes it is convenient to use another value as a target. This way, the first value will track the second. Using a spring animation, we can get a nice trailing effect.

- - - -

addListener

- -

As I said earlier, if you track a spring

- - - - -

Animated.sequence

- -

It is very common to animate

- - - - - - - - - - - - - -
- - diff --git a/Libraries/Animated/examples/pic1.jpg b/Libraries/Animated/examples/pic1.jpg deleted file mode 100644 index a9f2e824fe6903..00000000000000 Binary files a/Libraries/Animated/examples/pic1.jpg and /dev/null differ diff --git a/Libraries/Animated/examples/pic2.jpg b/Libraries/Animated/examples/pic2.jpg deleted file mode 100644 index 34d30966ae9868..00000000000000 Binary files a/Libraries/Animated/examples/pic2.jpg and /dev/null differ diff --git a/Libraries/Animated/examples/pic3.jpg b/Libraries/Animated/examples/pic3.jpg deleted file mode 100644 index 47a97c80180407..00000000000000 Binary files a/Libraries/Animated/examples/pic3.jpg and /dev/null differ diff --git a/Libraries/Animated/examples/style.css b/Libraries/Animated/examples/style.css deleted file mode 100644 index c188ad79b07fd2..00000000000000 --- a/Libraries/Animated/examples/style.css +++ /dev/null @@ -1,81 +0,0 @@ -html, h1, h2 { - font-family: 'Roboto', sans-serif; - font-weight: 300; -} - -.container { - width: 800px; - margin: 0 auto; -} - -.circle { - margin: 2px; - width: 50px; - height: 50px; - position: absolute; - display: inline-block; - box-shadow: 0 1px 2px #999; - text-shadow: 0 1px 2px #999; - background-image: url(pic1.jpg); - background-size: cover; - line-height: 80px; - vertical-align: bottom; - text-align: center; - color: white; - font-size: 10px; -} - -.circle:nth-child(2) { - background-image: url(pic2.jpg); -} - -.circle:nth-child(3) { - background-image: url(pic3.jpg); -} - -div.code { - box-shadow: 0 1px 2px #999; - width: 600px; - padding: 5px; - position: relative; - margin: 0 auto; - margin-bottom: 40px; -} - -div.code .reset { - float: right; -} - -div.code pre { - padding: 2px; -} - -hr { - border: none; - border-bottom: 1px solid #D9D9D9; - margin: 0; -} - -button { - vertical-align: top; -} - -.example > span { - color: #333; - font-size: 13px; -} - -.example { - position: relative; - height: 60px; -} - -.code pre { - margin: 0; - font-size: 11px; - line-height: 1; -} - -.highlight { - background: rgb(228, 254, 253); -} diff --git a/Libraries/Animated/src/nodes/AnimatedAddition.js b/Libraries/Animated/nodes/AnimatedAddition.js similarity index 75% rename from Libraries/Animated/src/nodes/AnimatedAddition.js rename to Libraries/Animated/nodes/AnimatedAddition.js index 2e3453965565f0..b311b39a6606fc 100644 --- a/Libraries/Animated/src/nodes/AnimatedAddition.js +++ b/Libraries/Animated/nodes/AnimatedAddition.js @@ -1,13 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AnimatedAddition * @flow * @format */ + 'use strict'; const AnimatedInterpolation = require('./AnimatedInterpolation'); @@ -15,6 +15,7 @@ const AnimatedNode = require('./AnimatedNode'); const AnimatedValue = require('./AnimatedValue'); const AnimatedWithChildren = require('./AnimatedWithChildren'); +import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type {InterpolationConfigType} from './AnimatedInterpolation'; class AnimatedAddition extends AnimatedWithChildren { @@ -27,17 +28,19 @@ class AnimatedAddition extends AnimatedWithChildren { this._b = typeof b === 'number' ? new AnimatedValue(b) : b; } - __makeNative() { - this._a.__makeNative(); - this._b.__makeNative(); - super.__makeNative(); + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + this._b.__makeNative(platformConfig); + super.__makeNative(platformConfig); } __getValue(): number { return this._a.__getValue() + this._b.__getValue(); } - interpolate(config: InterpolationConfigType): AnimatedInterpolation { + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { return new AnimatedInterpolation(this, config); } diff --git a/Libraries/Animated/nodes/AnimatedColor.js b/Libraries/Animated/nodes/AnimatedColor.js new file mode 100644 index 00000000000000..16a87f52800350 --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedColor.js @@ -0,0 +1,353 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import AnimatedValue from './AnimatedValue'; +import AnimatedWithChildren from './AnimatedWithChildren'; +import normalizeColor from '../../StyleSheet/normalizeColor'; +import {processColorObject} from '../../StyleSheet/PlatformColorValueTypes'; +import NativeAnimatedHelper from '../NativeAnimatedHelper'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type {ColorValue} from '../../StyleSheet/StyleSheet'; +import type {NativeColorValue} from '../../StyleSheet/PlatformColorValueTypes'; +import type {ProcessedColorValue} from '../../StyleSheet/processColor'; + +export type AnimatedColorConfig = $ReadOnly<{ + useNativeDriver: boolean, +}>; + +type ColorListenerCallback = (value: ColorValue) => mixed; + +export type RgbaValue = { + +r: number, + +g: number, + +b: number, + +a: number, + ... +}; + +type RgbaAnimatedValue = { + +r: AnimatedValue, + +g: AnimatedValue, + +b: AnimatedValue, + +a: AnimatedValue, + ... +}; + +const NativeAnimatedAPI = NativeAnimatedHelper.API; + +const defaultColor: RgbaValue = {r: 0, g: 0, b: 0, a: 1.0}; +let _uniqueId = 1; + +/* eslint no-bitwise: 0 */ +function processColor( + color?: ?(ColorValue | RgbaValue), +): ?(RgbaValue | NativeColorValue) { + if (color === undefined || color === null) { + return null; + } + + if (isRgbaValue(color)) { + // $FlowIgnore[incompatible-cast] - Type is verified above + return (color: RgbaValue); + } + + let normalizedColor: ?ProcessedColorValue = normalizeColor( + // $FlowIgnore[incompatible-cast] - Type is verified above + (color: ColorValue), + ); + if (normalizedColor === undefined || normalizedColor === null) { + return null; + } + + if (typeof normalizedColor === 'object') { + const processedColorObj: ?NativeColorValue = + processColorObject(normalizedColor); + if (processedColorObj != null) { + return processedColorObj; + } + } else if (typeof normalizedColor === 'number') { + const r: number = (normalizedColor & 0xff000000) >>> 24; + const g: number = (normalizedColor & 0x00ff0000) >>> 16; + const b: number = (normalizedColor & 0x0000ff00) >>> 8; + const a: number = (normalizedColor & 0x000000ff) / 255; + + return {r, g, b, a}; + } + + return null; +} + +function isRgbaValue(value: any): boolean { + return ( + value && + typeof value.r === 'number' && + typeof value.g === 'number' && + typeof value.b === 'number' && + typeof value.a === 'number' + ); +} + +function isRgbaAnimatedValue(value: any): boolean { + return ( + value && + value.r instanceof AnimatedValue && + value.g instanceof AnimatedValue && + value.b instanceof AnimatedValue && + value.a instanceof AnimatedValue + ); +} + +export default class AnimatedColor extends AnimatedWithChildren { + r: AnimatedValue; + g: AnimatedValue; + b: AnimatedValue; + a: AnimatedValue; + nativeColor: ?NativeColorValue; + _listeners: { + [key: string]: { + r: string, + g: string, + b: string, + a: string, + ... + }, + ... + } = {}; + + constructor( + valueIn?: ?(RgbaValue | RgbaAnimatedValue | ColorValue), + config?: ?AnimatedColorConfig, + ) { + super(); + let value: RgbaValue | RgbaAnimatedValue | ColorValue = + valueIn ?? defaultColor; + if (isRgbaAnimatedValue(value)) { + // $FlowIgnore[incompatible-cast] - Type is verified above + const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue); + this.r = rgbaAnimatedValue.r; + this.g = rgbaAnimatedValue.g; + this.b = rgbaAnimatedValue.b; + this.a = rgbaAnimatedValue.a; + } else { + const processedColor: RgbaValue | NativeColorValue = + // $FlowIgnore[incompatible-cast] - Type is verified above + processColor((value: ColorValue | RgbaValue)) ?? defaultColor; + let initColor: RgbaValue = defaultColor; + if (isRgbaValue(processedColor)) { + // $FlowIgnore[incompatible-cast] - Type is verified above + initColor = (processedColor: RgbaValue); + } else { + // $FlowIgnore[incompatible-cast] - Type is verified above + this.nativeColor = (processedColor: NativeColorValue); + } + + this.r = new AnimatedValue(initColor.r); + this.g = new AnimatedValue(initColor.g); + this.b = new AnimatedValue(initColor.b); + this.a = new AnimatedValue(initColor.a); + } + if (this.nativeColor || (config && config.useNativeDriver)) { + this.__makeNative(); + } + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + */ + setValue(value: RgbaValue | ColorValue): void { + let shouldUpdateNodeConfig = false; + if (this.__isNative) { + const nativeTag = this.__getNativeTag(); + NativeAnimatedAPI.setWaitingForIdentifier(nativeTag.toString()); + } + + const processedColor: RgbaValue | NativeColorValue = + processColor(value) ?? defaultColor; + if (isRgbaValue(processedColor)) { + // $FlowIgnore[incompatible-type] - Type is verified above + const rgbaValue: RgbaValue = processedColor; + this.r.setValue(rgbaValue.r); + this.g.setValue(rgbaValue.g); + this.b.setValue(rgbaValue.b); + this.a.setValue(rgbaValue.a); + if (this.nativeColor != null) { + this.nativeColor = null; + shouldUpdateNodeConfig = true; + } + } else { + // $FlowIgnore[incompatible-type] - Type is verified above + const nativeColor: NativeColorValue = processedColor; + if (this.nativeColor !== nativeColor) { + this.nativeColor = nativeColor; + shouldUpdateNodeConfig = true; + } + } + + if (this.__isNative) { + const nativeTag = this.__getNativeTag(); + if (shouldUpdateNodeConfig) { + NativeAnimatedAPI.updateAnimatedNodeConfig( + nativeTag, + this.__getNativeConfig(), + ); + } + NativeAnimatedAPI.unsetWaitingForIdentifier(nativeTag.toString()); + } + } + + /** + * Sets an offset that is applied on top of whatever value is set, whether + * via `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + */ + setOffset(offset: RgbaValue): void { + this.r.setOffset(offset.r); + this.g.setOffset(offset.g); + this.b.setOffset(offset.b); + this.a.setOffset(offset.a); + } + + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + */ + flattenOffset(): void { + this.r.flattenOffset(); + this.g.flattenOffset(); + this.b.flattenOffset(); + this.a.flattenOffset(); + } + + /** + * Sets the offset value to the base value, and resets the base value to + * zero. The final output of the value is unchanged. + */ + extractOffset(): void { + this.r.extractOffset(); + this.g.extractOffset(); + this.b.extractOffset(); + this.a.extractOffset(); + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to synchronously read + * the value because it might be driven natively. + * + * Returns a string that serves as an identifier for the listener. + */ + addListener(callback: ColorListenerCallback): string { + const id = String(_uniqueId++); + const jointCallback = ({value: number}: any) => { + callback(this.__getValue()); + }; + this._listeners[id] = { + r: this.r.addListener(jointCallback), + g: this.g.addListener(jointCallback), + b: this.b.addListener(jointCallback), + a: this.a.addListener(jointCallback), + }; + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + */ + removeListener(id: string): void { + this.r.removeListener(this._listeners[id].r); + this.g.removeListener(this._listeners[id].g); + this.b.removeListener(this._listeners[id].b); + this.a.removeListener(this._listeners[id].a); + delete this._listeners[id]; + } + + /** + * Remove all registered listeners. + */ + removeAllListeners(): void { + this.r.removeAllListeners(); + this.g.removeAllListeners(); + this.b.removeAllListeners(); + this.a.removeAllListeners(); + this._listeners = {}; + } + + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + */ + stopAnimation(callback?: ColorListenerCallback): void { + this.r.stopAnimation(); + this.g.stopAnimation(); + this.b.stopAnimation(); + this.a.stopAnimation(); + callback && callback(this.__getValue()); + } + + /** + * Stops any animation and resets the value to its original. + */ + resetAnimation(callback?: ColorListenerCallback): void { + this.r.resetAnimation(); + this.g.resetAnimation(); + this.b.resetAnimation(); + this.a.resetAnimation(); + callback && callback(this.__getValue()); + } + + __getValue(): ColorValue { + if (this.nativeColor != null) { + return this.nativeColor; + } else { + return `rgba(${this.r.__getValue()}, ${this.g.__getValue()}, ${this.b.__getValue()}, ${this.a.__getValue()})`; + } + } + + __attach(): void { + this.r.__addChild(this); + this.g.__addChild(this); + this.b.__addChild(this); + this.a.__addChild(this); + super.__attach(); + } + + __detach(): void { + this.r.__removeChild(this); + this.g.__removeChild(this); + this.b.__removeChild(this); + this.a.__removeChild(this); + super.__detach(); + } + + __makeNative(platformConfig: ?PlatformConfig) { + this.r.__makeNative(platformConfig); + this.g.__makeNative(platformConfig); + this.b.__makeNative(platformConfig); + this.a.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getNativeConfig(): {...} { + return { + type: 'color', + r: this.r.__getNativeTag(), + g: this.g.__getNativeTag(), + b: this.b.__getNativeTag(), + a: this.a.__getNativeTag(), + nativeColor: this.nativeColor, + }; + } +} diff --git a/Libraries/Animated/src/nodes/AnimatedDiffClamp.js b/Libraries/Animated/nodes/AnimatedDiffClamp.js similarity index 79% rename from Libraries/Animated/src/nodes/AnimatedDiffClamp.js rename to Libraries/Animated/nodes/AnimatedDiffClamp.js index 0f64e75c723c06..61b167c2fcba57 100644 --- a/Libraries/Animated/src/nodes/AnimatedDiffClamp.js +++ b/Libraries/Animated/nodes/AnimatedDiffClamp.js @@ -1,13 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AnimatedDiffClamp * @flow * @format */ + 'use strict'; const AnimatedInterpolation = require('./AnimatedInterpolation'); @@ -15,6 +15,7 @@ const AnimatedNode = require('./AnimatedNode'); const AnimatedWithChildren = require('./AnimatedWithChildren'); import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; class AnimatedDiffClamp extends AnimatedWithChildren { _a: AnimatedNode; @@ -32,12 +33,14 @@ class AnimatedDiffClamp extends AnimatedWithChildren { this._value = this._lastValue = this._a.__getValue(); } - __makeNative() { - this._a.__makeNative(); - super.__makeNative(); + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + super.__makeNative(platformConfig); } - interpolate(config: InterpolationConfigType): AnimatedInterpolation { + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { return new AnimatedInterpolation(this, config); } diff --git a/Libraries/Animated/nodes/AnimatedDivision.js b/Libraries/Animated/nodes/AnimatedDivision.js new file mode 100644 index 00000000000000..3d77e2de6453e7 --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedDivision.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +class AnimatedDivision extends AnimatedWithChildren { + _a: AnimatedNode; + _b: AnimatedNode; + _warnedAboutDivideByZero: boolean = false; + + constructor(a: AnimatedNode | number, b: AnimatedNode | number) { + super(); + if (b === 0 || (b instanceof AnimatedNode && b.__getValue() === 0)) { + console.error('Detected potential division by zero in AnimatedDivision'); + } + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + this._b.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getValue(): number { + const a = this._a.__getValue(); + const b = this._b.__getValue(); + if (b === 0) { + // Prevent spamming the console/LogBox + if (!this._warnedAboutDivideByZero) { + console.error('Detected division by zero in AnimatedDivision'); + this._warnedAboutDivideByZero = true; + } + // Passing infinity/NaN to Fabric will cause a native crash + return 0; + } + this._warnedAboutDivideByZero = false; + return a / b; + } + + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'division', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + +module.exports = AnimatedDivision; diff --git a/Libraries/Animated/nodes/AnimatedInterpolation.js b/Libraries/Animated/nodes/AnimatedInterpolation.js new file mode 100644 index 00000000000000..ee5ea21946241c --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedInterpolation.js @@ -0,0 +1,372 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +/* eslint no-bitwise: 0 */ + +'use strict'; + +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +const invariant = require('invariant'); +const normalizeColor = require('../../StyleSheet/normalizeColor'); + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +type ExtrapolateType = 'extend' | 'identity' | 'clamp'; + +export type InterpolationConfigType = $ReadOnly<{ + inputRange: $ReadOnlyArray, + outputRange: $ReadOnlyArray, + easing?: (input: number) => number, + extrapolate?: ExtrapolateType, + extrapolateLeft?: ExtrapolateType, + extrapolateRight?: ExtrapolateType, +}>; + +const linear = (t: number) => t; + +/** + * Very handy helper to map input ranges to output ranges with an easing + * function and custom behavior outside of the ranges. + */ +function createInterpolation( + config: InterpolationConfigType, +): (input: number) => OutputT { + if (config.outputRange && typeof config.outputRange[0] === 'string') { + return (createInterpolationFromStringOutputRange((config: any)): any); + } + + const outputRange: $ReadOnlyArray = (config.outputRange: any); + + const inputRange = config.inputRange; + + if (__DEV__) { + checkInfiniteRange('outputRange', outputRange); + checkInfiniteRange('inputRange', inputRange); + checkValidInputRange(inputRange); + + invariant( + inputRange.length === outputRange.length, + 'inputRange (' + + inputRange.length + + ') and outputRange (' + + outputRange.length + + ') must have the same length', + ); + } + + const easing = config.easing || linear; + + let extrapolateLeft: ExtrapolateType = 'extend'; + if (config.extrapolateLeft !== undefined) { + extrapolateLeft = config.extrapolateLeft; + } else if (config.extrapolate !== undefined) { + extrapolateLeft = config.extrapolate; + } + + let extrapolateRight: ExtrapolateType = 'extend'; + if (config.extrapolateRight !== undefined) { + extrapolateRight = config.extrapolateRight; + } else if (config.extrapolate !== undefined) { + extrapolateRight = config.extrapolate; + } + + return input => { + invariant( + typeof input === 'number', + 'Cannot interpolation an input which is not a number', + ); + + const range = findRange(input, inputRange); + return (interpolate( + input, + inputRange[range], + inputRange[range + 1], + outputRange[range], + outputRange[range + 1], + easing, + extrapolateLeft, + extrapolateRight, + ): any); + }; +} + +function interpolate( + input: number, + inputMin: number, + inputMax: number, + outputMin: number, + outputMax: number, + easing: (input: number) => number, + extrapolateLeft: ExtrapolateType, + extrapolateRight: ExtrapolateType, +) { + let result = input; + + // Extrapolate + if (result < inputMin) { + if (extrapolateLeft === 'identity') { + return result; + } else if (extrapolateLeft === 'clamp') { + result = inputMin; + } else if (extrapolateLeft === 'extend') { + // noop + } + } + + if (result > inputMax) { + if (extrapolateRight === 'identity') { + return result; + } else if (extrapolateRight === 'clamp') { + result = inputMax; + } else if (extrapolateRight === 'extend') { + // noop + } + } + + if (outputMin === outputMax) { + return outputMin; + } + + if (inputMin === inputMax) { + if (input <= inputMin) { + return outputMin; + } + return outputMax; + } + + // Input Range + if (inputMin === -Infinity) { + result = -result; + } else if (inputMax === Infinity) { + result = result - inputMin; + } else { + result = (result - inputMin) / (inputMax - inputMin); + } + + // Easing + result = easing(result); + + // Output Range + if (outputMin === -Infinity) { + result = -result; + } else if (outputMax === Infinity) { + result = result + outputMin; + } else { + result = result * (outputMax - outputMin) + outputMin; + } + + return result; +} + +function colorToRgba(input: string): string { + let normalizedColor = normalizeColor(input); + if (normalizedColor === null || typeof normalizedColor !== 'number') { + return input; + } + + normalizedColor = normalizedColor || 0; + + const r = (normalizedColor & 0xff000000) >>> 24; + const g = (normalizedColor & 0x00ff0000) >>> 16; + const b = (normalizedColor & 0x0000ff00) >>> 8; + const a = (normalizedColor & 0x000000ff) / 255; + + return `rgba(${r}, ${g}, ${b}, ${a})`; +} + +const stringShapeRegex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g; + +/** + * Supports string shapes by extracting numbers so new values can be computed, + * and recombines those values into new strings of the same shape. Supports + * things like: + * + * rgba(123, 42, 99, 0.36) // colors + * -45deg // values with units + */ +function createInterpolationFromStringOutputRange( + config: InterpolationConfigType, +): (input: number) => string { + let outputRange: Array = (config.outputRange: any); + invariant(outputRange.length >= 2, 'Bad output range'); + outputRange = outputRange.map(colorToRgba); + checkPattern(outputRange); + + // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] + // -> + // [ + // [0, 50], + // [100, 150], + // [200, 250], + // [0, 0.5], + // ] + /* $FlowFixMe[incompatible-use] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ + const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []); + outputRange.forEach(value => { + /* $FlowFixMe[incompatible-use] (>=0.18.0): `value.match()` can return + * `null`. Need to guard against this possibility. */ + value.match(stringShapeRegex).forEach((number, i) => { + outputRanges[i].push(+number); + }); + }); + + const interpolations = outputRange[0] + .match(stringShapeRegex) + /* $FlowFixMe[incompatible-use] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ + /* $FlowFixMe[incompatible-call] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ + .map((value, i) => { + return createInterpolation({ + ...config, + outputRange: outputRanges[i], + }); + }); + + // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to + // round the opacity (4th column). + const shouldRound = isRgbOrRgba(outputRange[0]); + + return input => { + let i = 0; + // 'rgba(0, 100, 200, 0)' + // -> + // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' + return outputRange[0].replace(stringShapeRegex, () => { + let val = +interpolations[i++](input); + if (shouldRound) { + val = i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000; + } + return String(val); + }); + }; +} + +function isRgbOrRgba(range: string) { + return typeof range === 'string' && range.startsWith('rgb'); +} + +function checkPattern(arr: $ReadOnlyArray) { + const pattern = arr[0].replace(stringShapeRegex, ''); + for (let i = 1; i < arr.length; ++i) { + invariant( + pattern === arr[i].replace(stringShapeRegex, ''), + 'invalid pattern ' + arr[0] + ' and ' + arr[i], + ); + } +} + +function findRange(input: number, inputRange: $ReadOnlyArray) { + let i; + for (i = 1; i < inputRange.length - 1; ++i) { + if (inputRange[i] >= input) { + break; + } + } + return i - 1; +} + +function checkValidInputRange(arr: $ReadOnlyArray) { + invariant(arr.length >= 2, 'inputRange must have at least 2 elements'); + const message = + 'inputRange must be monotonically non-decreasing ' + String(arr); + for (let i = 1; i < arr.length; ++i) { + invariant(arr[i] >= arr[i - 1], message); + } +} + +function checkInfiniteRange(name: string, arr: $ReadOnlyArray) { + invariant(arr.length >= 2, name + ' must have at least 2 elements'); + invariant( + arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, + /* $FlowFixMe[incompatible-type] (>=0.13.0) - In the addition expression + * below this comment, one or both of the operands may be something that + * doesn't cleanly convert to a string, like undefined, null, and object, + * etc. If you really mean this implicit string conversion, you can do + * something like String(myThing) */ + name + 'cannot be ]-infinity;+infinity[ ' + arr, + ); +} + +class AnimatedInterpolation< + OutputT: number | string, +> extends AnimatedWithChildren { + // Export for testing. + static __createInterpolation: ( + config: InterpolationConfigType, + ) => (input: number) => OutputT = createInterpolation; + + _parent: AnimatedNode; + _config: InterpolationConfigType; + _interpolation: (input: number) => OutputT; + + constructor(parent: AnimatedNode, config: InterpolationConfigType) { + super(); + this._parent = parent; + this._config = config; + this._interpolation = createInterpolation(config); + } + + __makeNative(platformConfig: ?PlatformConfig) { + this._parent.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getValue(): number | string { + const parentValue: number = this._parent.__getValue(); + invariant( + typeof parentValue === 'number', + 'Cannot interpolate an input which is not a number.', + ); + return this._interpolation(parentValue); + } + + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._parent.__addChild(this); + } + + __detach(): void { + this._parent.__removeChild(this); + super.__detach(); + } + + __transformDataType(range: $ReadOnlyArray): Array { + return range.map(NativeAnimatedHelper.transformDataType); + } + + __getNativeConfig(): any { + if (__DEV__) { + NativeAnimatedHelper.validateInterpolation(this._config); + } + + return { + inputRange: this._config.inputRange, + // Only the `outputRange` can contain strings so we don't need to transform `inputRange` here + outputRange: this.__transformDataType(this._config.outputRange), + extrapolateLeft: + this._config.extrapolateLeft || this._config.extrapolate || 'extend', + extrapolateRight: + this._config.extrapolateRight || this._config.extrapolate || 'extend', + type: 'interpolation', + }; + } +} + +module.exports = AnimatedInterpolation; diff --git a/Libraries/Animated/nodes/AnimatedModulo.js b/Libraries/Animated/nodes/AnimatedModulo.js new file mode 100644 index 00000000000000..bc3bc1b3ad2741 --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedModulo.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +class AnimatedModulo extends AnimatedWithChildren { + _a: AnimatedNode; + _modulus: number; + + constructor(a: AnimatedNode, modulus: number) { + super(); + this._a = a; + this._modulus = modulus; + } + + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getValue(): number { + return ( + ((this._a.__getValue() % this._modulus) + this._modulus) % this._modulus + ); + } + + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'modulus', + input: this._a.__getNativeTag(), + modulus: this._modulus, + }; + } +} + +module.exports = AnimatedModulo; diff --git a/Libraries/Animated/src/nodes/AnimatedMultiplication.js b/Libraries/Animated/nodes/AnimatedMultiplication.js similarity index 76% rename from Libraries/Animated/src/nodes/AnimatedMultiplication.js rename to Libraries/Animated/nodes/AnimatedMultiplication.js index dc01f7f17127f1..419927b71f53dc 100644 --- a/Libraries/Animated/src/nodes/AnimatedMultiplication.js +++ b/Libraries/Animated/nodes/AnimatedMultiplication.js @@ -1,13 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AnimatedMultiplication * @flow * @format */ + 'use strict'; const AnimatedInterpolation = require('./AnimatedInterpolation'); @@ -16,6 +16,7 @@ const AnimatedValue = require('./AnimatedValue'); const AnimatedWithChildren = require('./AnimatedWithChildren'); import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; class AnimatedMultiplication extends AnimatedWithChildren { _a: AnimatedNode; @@ -27,20 +28,21 @@ class AnimatedMultiplication extends AnimatedWithChildren { this._b = typeof b === 'number' ? new AnimatedValue(b) : b; } - __makeNative() { - this._a.__makeNative(); - this._b.__makeNative(); - super.__makeNative(); + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + this._b.__makeNative(platformConfig); + super.__makeNative(platformConfig); } __getValue(): number { return this._a.__getValue() * this._b.__getValue(); } - interpolate(config: InterpolationConfigType): AnimatedInterpolation { + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { return new AnimatedInterpolation(this, config); } - __attach(): void { this._a.__addChild(this); this._b.__addChild(this); diff --git a/Libraries/Animated/nodes/AnimatedNode.js b/Libraries/Animated/nodes/AnimatedNode.js new file mode 100644 index 00000000000000..b4a649ff302afd --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedNode.js @@ -0,0 +1,197 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +const NativeAnimatedAPI = NativeAnimatedHelper.API; +const invariant = require('invariant'); + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +type ValueListenerCallback = (state: {value: number, ...}) => mixed; + +let _uniqueId = 1; + +// Note(vjeux): this would be better as an interface but flow doesn't +// support them yet +class AnimatedNode { + _listeners: {[key: string]: ValueListenerCallback, ...}; + _platformConfig: ?PlatformConfig; + __nativeAnimatedValueListener: ?any; + __attach(): void {} + __detach(): void { + if (this.__isNative && this.__nativeTag != null) { + NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag); + this.__nativeTag = undefined; + } + } + __getValue(): any {} + __getAnimatedValue(): any { + return this.__getValue(); + } + __addChild(child: AnimatedNode) {} + __removeChild(child: AnimatedNode) {} + __getChildren(): Array { + return []; + } + + /* Methods and props used by native Animated impl */ + __isNative: boolean; + __nativeTag: ?number; + __shouldUpdateListenersForNewNativeTag: boolean; + + constructor() { + this._listeners = {}; + } + + __makeNative(platformConfig: ?PlatformConfig) { + if (!this.__isNative) { + throw new Error('This node cannot be made a "native" animated node'); + } + + this._platformConfig = platformConfig; + if (this.hasListeners()) { + this._startListeningToNativeValueUpdates(); + } + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to + * synchronously read the value because it might be driven natively. + * + * See https://reactnative.dev/docs/animatedvalue#addlistener + */ + addListener(callback: (value: any) => mixed): string { + const id = String(_uniqueId++); + this._listeners[id] = callback; + if (this.__isNative) { + this._startListeningToNativeValueUpdates(); + } + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + * + * See https://reactnative.dev/docs/animatedvalue#removelistener + */ + removeListener(id: string): void { + delete this._listeners[id]; + if (this.__isNative && !this.hasListeners()) { + this._stopListeningForNativeValueUpdates(); + } + } + + /** + * Remove all registered listeners. + * + * See https://reactnative.dev/docs/animatedvalue#removealllisteners + */ + removeAllListeners(): void { + this._listeners = {}; + if (this.__isNative) { + this._stopListeningForNativeValueUpdates(); + } + } + + hasListeners(): boolean { + return !!Object.keys(this._listeners).length; + } + + _startListeningToNativeValueUpdates() { + if ( + this.__nativeAnimatedValueListener && + !this.__shouldUpdateListenersForNewNativeTag + ) { + return; + } + + if (this.__shouldUpdateListenersForNewNativeTag) { + this.__shouldUpdateListenersForNewNativeTag = false; + this._stopListeningForNativeValueUpdates(); + } + + NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); + this.__nativeAnimatedValueListener = + NativeAnimatedHelper.nativeEventEmitter.addListener( + 'onAnimatedValueUpdate', + data => { + if (data.tag !== this.__getNativeTag()) { + return; + } + this.__onAnimatedValueUpdateReceived(data.value); + }, + ); + } + + __onAnimatedValueUpdateReceived(value: number) { + this.__callListeners(value); + } + + __callListeners(value: number): void { + for (const key in this._listeners) { + this._listeners[key]({value}); + } + } + + _stopListeningForNativeValueUpdates() { + if (!this.__nativeAnimatedValueListener) { + return; + } + + this.__nativeAnimatedValueListener.remove(); + this.__nativeAnimatedValueListener = null; + NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag()); + } + + __getNativeTag(): number { + NativeAnimatedHelper.assertNativeAnimatedModule(); + invariant( + this.__isNative, + 'Attempt to get native tag from node not marked as "native"', + ); + + const nativeTag = + this.__nativeTag ?? NativeAnimatedHelper.generateNewNodeTag(); + + if (this.__nativeTag == null) { + this.__nativeTag = nativeTag; + const config = this.__getNativeConfig(); + if (this._platformConfig) { + config.platformConfig = this._platformConfig; + } + NativeAnimatedHelper.API.createAnimatedNode(nativeTag, config); + this.__shouldUpdateListenersForNewNativeTag = true; + } + + return nativeTag; + } + __getNativeConfig(): Object { + throw new Error( + 'This JS animated node type cannot be used as native animated node', + ); + } + toJSON(): any { + return this.__getValue(); + } + + __getPlatformConfig(): ?PlatformConfig { + return this._platformConfig; + } + __setPlatformConfig(platformConfig: ?PlatformConfig) { + this._platformConfig = platformConfig; + } +} + +module.exports = AnimatedNode; diff --git a/Libraries/Animated/nodes/AnimatedProps.js b/Libraries/Animated/nodes/AnimatedProps.js new file mode 100644 index 00000000000000..d4ed70da8d6e9c --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedProps.js @@ -0,0 +1,191 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +const ReactNative = require('../../Renderer/shims/ReactNative'); +const {AnimatedEvent} = require('../AnimatedEvent'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedStyle = require('./AnimatedStyle'); +const invariant = require('invariant'); + +class AnimatedProps extends AnimatedNode { + _props: Object; + _animatedView: any; + _callback: () => void; + + constructor(props: Object, callback: () => void) { + super(); + if (props.style) { + props = { + ...props, + style: new AnimatedStyle(props.style), + }; + } + this._props = props; + this._callback = callback; + } + + __getValue(initialProps: ?Object): Object { + const props: {[string]: any | ((...args: any) => void)} = {}; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + // During initial render we want to use the initial value of both natively and non-natively + // driven nodes. On subsequent renders, we cannot use the value of natively driven nodes + // as they may not be up to date, so we use the initial value to ensure that values of + // native animated nodes do not impact rerenders. + if (value instanceof AnimatedStyle) { + props[key] = value.__getValue( + initialProps ? initialProps.style : null, + ); + } else if (!initialProps || !value.__isNative) { + props[key] = value.__getValue(); + } else if (initialProps.hasOwnProperty(key)) { + props[key] = initialProps[key]; + } + } else if (value instanceof AnimatedEvent) { + props[key] = value.__getHandler(); + } else { + props[key] = value; + } + } + + return props; + } + + __getAnimatedValue(): Object { + const props: {[string]: any} = {}; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + props[key] = value.__getAnimatedValue(); + } + } + return props; + } + + __attach(): void { + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__addChild(this); + } + } + } + + __detach(): void { + if (this.__isNative && this._animatedView) { + this.__disconnectAnimatedView(); + } + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__removeChild(this); + } + } + super.__detach(); + } + + update(): void { + this._callback(); + } + + __makeNative(platformConfig: ?PlatformConfig): void { + if (!this.__isNative) { + this.__isNative = true; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__makeNative(platformConfig); + } + } + + // Since this does not call the super.__makeNative, we need to store the + // supplied platformConfig here, before calling __connectAnimatedView + // where it will be needed to traverse the graph of attached values. + super.__setPlatformConfig(platformConfig); + + if (this._animatedView) { + this.__connectAnimatedView(); + } + } + } + + setNativeView(animatedView: any): void { + if (this._animatedView === animatedView) { + return; + } + this._animatedView = animatedView; + if (this.__isNative) { + this.__connectAnimatedView(); + } + } + + __connectAnimatedView(): void { + invariant(this.__isNative, 'Expected node to be marked as "native"'); + const nativeViewTag: ?number = ReactNative.findNodeHandle( + this._animatedView, + ); + invariant( + nativeViewTag != null, + 'Unable to locate attached view in the native tree', + ); + NativeAnimatedHelper.API.connectAnimatedNodeToView( + this.__getNativeTag(), + nativeViewTag, + ); + } + + __disconnectAnimatedView(): void { + invariant(this.__isNative, 'Expected node to be marked as "native"'); + const nativeViewTag: ?number = ReactNative.findNodeHandle( + this._animatedView, + ); + invariant( + nativeViewTag != null, + 'Unable to locate attached view in the native tree', + ); + NativeAnimatedHelper.API.disconnectAnimatedNodeFromView( + this.__getNativeTag(), + nativeViewTag, + ); + } + + __restoreDefaultValues(): void { + // When using the native driver, view properties need to be restored to + // their default values manually since react no longer tracks them. This + // is needed to handle cases where a prop driven by native animated is removed + // after having been changed natively by an animation. + if (this.__isNative) { + NativeAnimatedHelper.API.restoreDefaultValues(this.__getNativeTag()); + } + } + + __getNativeConfig(): Object { + const propsConfig: {[string]: number} = {}; + for (const propKey in this._props) { + const value = this._props[propKey]; + if (value instanceof AnimatedNode) { + value.__makeNative(this.__getPlatformConfig()); + propsConfig[propKey] = value.__getNativeTag(); + } + } + return { + type: 'props', + props: propsConfig, + }; + } +} + +module.exports = AnimatedProps; diff --git a/Libraries/Animated/nodes/AnimatedStyle.js b/Libraries/Animated/nodes/AnimatedStyle.js new file mode 100644 index 00000000000000..c64706a81585ac --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedStyle.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +const flattenStyle = require('../../StyleSheet/flattenStyle'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedTransform = require('./AnimatedTransform'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +class AnimatedStyle extends AnimatedWithChildren { + _style: Object; + + constructor(style: any) { + super(); + style = flattenStyle(style) || ({}: {[string]: any}); + if (style.transform) { + style = { + ...style, + transform: new AnimatedTransform(style.transform), + }; + } + this._style = style; + } + + // Recursively get values for nested styles (like iOS's shadowOffset) + _walkStyleAndGetValues(style: any, initialStyle: ?Object) { + const updatedStyle: {[string]: any | {...}} = {}; + for (const key in style) { + const value = style[key]; + if (value instanceof AnimatedNode) { + // During initial render we want to use the initial value of both natively and non-natively + // driven nodes. On subsequent renders, we cannot use the value of natively driven nodes + // as they may not be up to date, so we use the initial value to ensure that values of + // native animated nodes do not impact rerenders. + if (!initialStyle || !value.__isNative) { + updatedStyle[key] = value.__getValue(); + } else if (initialStyle.hasOwnProperty(key)) { + updatedStyle[key] = initialStyle[key]; + } + } else if (value && !Array.isArray(value) && typeof value === 'object') { + // Support animating nested values (for example: shadowOffset.height) + updatedStyle[key] = this._walkStyleAndGetValues(value, initialStyle); + } else { + updatedStyle[key] = value; + } + } + return updatedStyle; + } + + __getValue(initialStyle: ?Object): Object { + return this._walkStyleAndGetValues(this._style, initialStyle); + } + + // Recursively get animated values for nested styles (like iOS's shadowOffset) + _walkStyleAndGetAnimatedValues(style: any) { + const updatedStyle: {[string]: any | {...}} = {}; + for (const key in style) { + const value = style[key]; + if (value instanceof AnimatedNode) { + updatedStyle[key] = value.__getAnimatedValue(); + } else if (value && !Array.isArray(value) && typeof value === 'object') { + // Support animating nested values (for example: shadowOffset.height) + updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value); + } + } + return updatedStyle; + } + + __getAnimatedValue(): Object { + return this._walkStyleAndGetAnimatedValues(this._style); + } + + __attach(): void { + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__addChild(this); + } + } + } + + __detach(): void { + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__removeChild(this); + } + } + super.__detach(); + } + + __makeNative(platformConfig: ?PlatformConfig) { + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__makeNative(platformConfig); + } + } + super.__makeNative(platformConfig); + } + + __getNativeConfig(): Object { + const styleConfig: {[string]: ?number} = {}; + for (const styleKey in this._style) { + if (this._style[styleKey] instanceof AnimatedNode) { + const style = this._style[styleKey]; + style.__makeNative(this.__getPlatformConfig()); + styleConfig[styleKey] = style.__getNativeTag(); + } + // Non-animated styles are set using `setNativeProps`, no need + // to pass those as a part of the node config + } + NativeAnimatedHelper.validateStyles(styleConfig); + return { + type: 'style', + style: styleConfig, + }; + } +} + +module.exports = AnimatedStyle; diff --git a/Libraries/Animated/nodes/AnimatedSubtraction.js b/Libraries/Animated/nodes/AnimatedSubtraction.js new file mode 100644 index 00000000000000..b3a9198c662baa --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedSubtraction.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +class AnimatedSubtraction extends AnimatedWithChildren { + _a: AnimatedNode; + _b: AnimatedNode; + + constructor(a: AnimatedNode | number, b: AnimatedNode | number) { + super(); + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative(platformConfig: ?PlatformConfig) { + this._a.__makeNative(platformConfig); + this._b.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } + + __getValue(): number { + return this._a.__getValue() - this._b.__getValue(); + } + + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'subtraction', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + +module.exports = AnimatedSubtraction; diff --git a/Libraries/Animated/src/nodes/AnimatedTracking.js b/Libraries/Animated/nodes/AnimatedTracking.js similarity index 86% rename from Libraries/Animated/src/nodes/AnimatedTracking.js rename to Libraries/Animated/nodes/AnimatedTracking.js index cf20d6e8bbf3fc..e8f74eaa54ac75 100644 --- a/Libraries/Animated/src/nodes/AnimatedTracking.js +++ b/Libraries/Animated/nodes/AnimatedTracking.js @@ -1,13 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AnimatedTracking * @flow * @format */ + 'use strict'; const AnimatedValue = require('./AnimatedValue'); @@ -17,6 +17,7 @@ const { shouldUseNativeDriver, } = require('../NativeAnimatedHelper'); +import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type {EndCallback} from '../animations/Animation'; class AnimatedTracking extends AnimatedNode { @@ -44,11 +45,11 @@ class AnimatedTracking extends AnimatedNode { this.__attach(); } - __makeNative() { + __makeNative(platformConfig: ?PlatformConfig) { this.__isNative = true; - this._parent.__makeNative(); - super.__makeNative(); - this._value.__makeNative(); + this._parent.__makeNative(platformConfig); + super.__makeNative(platformConfig); + this._value.__makeNative(platformConfig); } __getValue(): Object { @@ -63,7 +64,8 @@ class AnimatedTracking extends AnimatedNode { // if we don't do this `update` method will get called. At that point it // may be too late as it would mean the JS driver has already started // updating node values - this.__makeNative(); + let {platformConfig} = this._animationConfig; + this.__makeNative(platformConfig); } } diff --git a/Libraries/Animated/src/nodes/AnimatedTransform.js b/Libraries/Animated/nodes/AnimatedTransform.js similarity index 87% rename from Libraries/Animated/src/nodes/AnimatedTransform.js rename to Libraries/Animated/nodes/AnimatedTransform.js index 48f82f9fe3443d..c3d4175fc6f4cd 100644 --- a/Libraries/Animated/src/nodes/AnimatedTransform.js +++ b/Libraries/Animated/nodes/AnimatedTransform.js @@ -1,18 +1,20 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AnimatedTransform * @flow * @format */ + 'use strict'; +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); const AnimatedNode = require('./AnimatedNode'); const AnimatedWithChildren = require('./AnimatedWithChildren'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); class AnimatedTransform extends AnimatedWithChildren { _transforms: $ReadOnlyArray; @@ -22,21 +24,21 @@ class AnimatedTransform extends AnimatedWithChildren { this._transforms = transforms; } - __makeNative() { - super.__makeNative(); + __makeNative(platformConfig: ?PlatformConfig) { this._transforms.forEach(transform => { for (const key in transform) { const value = transform[key]; if (value instanceof AnimatedNode) { - value.__makeNative(); + value.__makeNative(platformConfig); } } }); + super.__makeNative(platformConfig); } __getValue(): $ReadOnlyArray { return this._transforms.map(transform => { - const result = {}; + const result: {[string]: any} = {}; for (const key in transform) { const value = transform[key]; if (value instanceof AnimatedNode) { @@ -51,7 +53,7 @@ class AnimatedTransform extends AnimatedWithChildren { __getAnimatedValue(): $ReadOnlyArray { return this._transforms.map(transform => { - const result = {}; + const result: {[string]: any} = {}; for (const key in transform) { const value = transform[key]; if (value instanceof AnimatedNode) { @@ -104,7 +106,7 @@ class AnimatedTransform extends AnimatedWithChildren { transConfigs.push({ type: 'static', property: key, - value, + value: NativeAnimatedHelper.transformDataType(value), }); } } diff --git a/Libraries/Animated/nodes/AnimatedValue.js b/Libraries/Animated/nodes/AnimatedValue.js new file mode 100644 index 00000000000000..9530e090f923d7 --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedValue.js @@ -0,0 +1,307 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const InteractionManager = require('../../Interaction/InteractionManager'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +import type AnimatedNode from './AnimatedNode'; +import type Animation, {EndCallback} from '../animations/Animation'; +import type {InterpolationConfigType} from './AnimatedInterpolation'; +import type AnimatedTracking from './AnimatedTracking'; + +export type AnimatedValueConfig = $ReadOnly<{ + useNativeDriver: boolean, +}>; + +const NativeAnimatedAPI = NativeAnimatedHelper.API; + +/** + * Animated works by building a directed acyclic graph of dependencies + * transparently when you render your Animated components. + * + * new Animated.Value(0) + * .interpolate() .interpolate() new Animated.Value(1) + * opacity translateY scale + * style transform + * View#234 style + * View#123 + * + * A) Top Down phase + * When an Animated.Value is updated, we recursively go down through this + * graph in order to find leaf nodes: the views that we flag as needing + * an update. + * + * B) Bottom Up phase + * When a view is flagged as needing an update, we recursively go back up + * in order to build the new value that it needs. The reason why we need + * this two-phases process is to deal with composite props such as + * transform which can receive values from multiple parents. + */ +function _flush(rootNode: AnimatedValue): void { + const animatedStyles = new Set(); + function findAnimatedStyles(node: AnimatedValue | AnimatedNode) { + /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment + * suppresses an error found when Flow v0.68 was deployed. To see the error + * delete this comment and run Flow. */ + if (typeof node.update === 'function') { + animatedStyles.add(node); + } else { + node.__getChildren().forEach(findAnimatedStyles); + } + } + findAnimatedStyles(rootNode); + // $FlowFixMe[prop-missing] + animatedStyles.forEach(animatedStyle => animatedStyle.update()); +} + +/** + * Some operations are executed only on batch end, which is _mostly_ scheduled when + * Animated component props change. For some of the changes which require immediate execution + * (e.g. setValue), we create a separate batch in case none is scheduled. + */ +function _executeAsAnimatedBatch(id: string, operation: () => void) { + NativeAnimatedAPI.setWaitingForIdentifier(id); + operation(); + NativeAnimatedAPI.unsetWaitingForIdentifier(id); +} + +/** + * Standard value for driving animations. One `Animated.Value` can drive + * multiple properties in a synchronized fashion, but can only be driven by one + * mechanism at a time. Using a new mechanism (e.g. starting a new animation, + * or calling `setValue`) will stop any previous ones. + * + * See https://reactnative.dev/docs/animatedvalue + */ +class AnimatedValue extends AnimatedWithChildren { + _value: number; + _startingValue: number; + _offset: number; + _animation: ?Animation; + _tracking: ?AnimatedTracking; + + constructor(value: number, config?: ?AnimatedValueConfig) { + super(); + if (typeof value !== 'number') { + throw new Error('AnimatedValue: Attempting to set value to undefined'); + } + this._startingValue = this._value = value; + this._offset = 0; + this._animation = null; + if (config && config.useNativeDriver) { + this.__makeNative(); + } + } + + __detach() { + if (this.__isNative) { + NativeAnimatedAPI.getValue(this.__getNativeTag(), value => { + this._value = value - this._offset; + }); + } + this.stopAnimation(); + super.__detach(); + } + + __getValue(): number { + return this._value + this._offset; + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + * + * See https://reactnative.dev/docs/animatedvalue#setvalue + */ + setValue(value: number): void { + if (this._animation) { + this._animation.stop(); + this._animation = null; + } + this._updateValue( + value, + !this.__isNative /* don't perform a flush for natively driven values */, + ); + if (this.__isNative) { + _executeAsAnimatedBatch(this.__getNativeTag().toString(), () => + NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value), + ); + } + } + + /** + * Sets an offset that is applied on top of whatever value is set, whether via + * `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + * + * See https://reactnative.dev/docs/animatedvalue#setoffset + */ + setOffset(offset: number): void { + this._offset = offset; + if (this.__isNative) { + NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset); + } + } + + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + * + * See https://reactnative.dev/docs/animatedvalue#flattenoffset + */ + flattenOffset(): void { + this._value += this._offset; + this._offset = 0; + if (this.__isNative) { + NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag()); + } + } + + /** + * Sets the offset value to the base value, and resets the base value to zero. + * The final output of the value is unchanged. + * + * See https://reactnative.dev/docs/animatedvalue#extractoffset + */ + extractOffset(): void { + this._offset += this._value; + this._value = 0; + if (this.__isNative) { + NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()); + } + } + + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + * + * See https://reactnative.dev/docs/animatedvalue#stopanimation + */ + stopAnimation(callback?: ?(value: number) => void): void { + this.stopTracking(); + this._animation && this._animation.stop(); + this._animation = null; + if (callback) { + if (this.__isNative) { + NativeAnimatedAPI.getValue(this.__getNativeTag(), callback); + } else { + callback(this.__getValue()); + } + } + } + + /** + * Stops any animation and resets the value to its original. + * + * See https://reactnative.dev/docs/animatedvalue#resetanimation + */ + resetAnimation(callback?: ?(value: number) => void): void { + this.stopAnimation(callback); + this._value = this._startingValue; + if (this.__isNative) { + NativeAnimatedAPI.setAnimatedNodeValue( + this.__getNativeTag(), + this._startingValue, + ); + } + } + + __onAnimatedValueUpdateReceived(value: number): void { + this._updateValue(value, false /*flush*/); + } + + /** + * Interpolates the value before updating the property, e.g. mapping 0-1 to + * 0-10. + */ + interpolate( + config: InterpolationConfigType, + ): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + /** + * Typically only used internally, but could be used by a custom Animation + * class. + * + * See https://reactnative.dev/docs/animatedvalue#animate + */ + animate(animation: Animation, callback: ?EndCallback): void { + let handle = null; + if (animation.__isInteraction) { + handle = InteractionManager.createInteractionHandle(); + } + const previousAnimation = this._animation; + this._animation && this._animation.stop(); + this._animation = animation; + animation.start( + this._value, + value => { + // Natively driven animations will never call into that callback, therefore we can always + // pass flush = true to allow the updated value to propagate to native with setNativeProps + this._updateValue(value, true /* flush */); + }, + result => { + this._animation = null; + if (handle !== null) { + InteractionManager.clearInteractionHandle(handle); + } + callback && callback(result); + }, + previousAnimation, + this, + ); + } + + /** + * Typically only used internally. + */ + stopTracking(): void { + this._tracking && this._tracking.__detach(); + this._tracking = null; + } + + /** + * Typically only used internally. + */ + track(tracking: AnimatedTracking): void { + this.stopTracking(); + this._tracking = tracking; + // Make sure that the tracking animation starts executing + this._tracking && this._tracking.update(); + } + + _updateValue(value: number, flush: boolean): void { + if (value === undefined) { + throw new Error('AnimatedValue: Attempting to set value to undefined'); + } + + this._value = value; + if (flush) { + _flush(this); + } + super.__callListeners(this.__getValue()); + } + + __getNativeConfig(): Object { + return { + type: 'value', + value: this._value, + offset: this._offset, + }; + } +} + +module.exports = AnimatedValue; diff --git a/Libraries/Animated/nodes/AnimatedValueXY.js b/Libraries/Animated/nodes/AnimatedValueXY.js new file mode 100644 index 00000000000000..c54a9d9d6f55e5 --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedValueXY.js @@ -0,0 +1,253 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; + +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +const invariant = require('invariant'); + +export type AnimatedValueXYConfig = $ReadOnly<{ + useNativeDriver: boolean, +}>; +type ValueXYListenerCallback = (value: { + x: number, + y: number, + ... +}) => mixed; + +let _uniqueId = 1; + +/** + * 2D Value for driving 2D animations, such as pan gestures. Almost identical + * API to normal `Animated.Value`, but multiplexed. + * + * See https://reactnative.dev/docs/animatedvaluexy + */ +class AnimatedValueXY extends AnimatedWithChildren { + x: AnimatedValue; + y: AnimatedValue; + _listeners: { + [key: string]: { + x: string, + y: string, + ... + }, + ... + }; + + constructor( + valueIn?: ?{ + +x: number | AnimatedValue, + +y: number | AnimatedValue, + ... + }, + config?: ?AnimatedValueXYConfig, + ) { + super(); + const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any` + if (typeof value.x === 'number' && typeof value.y === 'number') { + this.x = new AnimatedValue(value.x); + this.y = new AnimatedValue(value.y); + } else { + invariant( + value.x instanceof AnimatedValue && value.y instanceof AnimatedValue, + 'AnimatedValueXY must be initialized with an object of numbers or ' + + 'AnimatedValues.', + ); + this.x = value.x; + this.y = value.y; + } + this._listeners = {}; + if (config && config.useNativeDriver) { + this.__makeNative(); + } + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + * + * See https://reactnative.dev/docs/animatedvaluexy#setvalue + */ + setValue(value: {x: number, y: number, ...}) { + this.x.setValue(value.x); + this.y.setValue(value.y); + } + + /** + * Sets an offset that is applied on top of whatever value is set, whether + * via `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + * + * See https://reactnative.dev/docs/animatedvaluexy#setoffset + */ + setOffset(offset: {x: number, y: number, ...}) { + this.x.setOffset(offset.x); + this.y.setOffset(offset.y); + } + + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + * + * See https://reactnative.dev/docs/animatedvaluexy#flattenoffset + */ + flattenOffset(): void { + this.x.flattenOffset(); + this.y.flattenOffset(); + } + + /** + * Sets the offset value to the base value, and resets the base value to + * zero. The final output of the value is unchanged. + * + * See https://reactnative.dev/docs/animatedvaluexy#extractoffset + */ + extractOffset(): void { + this.x.extractOffset(); + this.y.extractOffset(); + } + + __getValue(): { + x: number, + y: number, + ... + } { + return { + x: this.x.__getValue(), + y: this.y.__getValue(), + }; + } + + /** + * Stops any animation and resets the value to its original. + * + * See https://reactnative.dev/docs/animatedvaluexy#resetanimation + */ + resetAnimation( + callback?: (value: { + x: number, + y: number, + ... + }) => void, + ): void { + this.x.resetAnimation(); + this.y.resetAnimation(); + callback && callback(this.__getValue()); + } + + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + * + * See https://reactnative.dev/docs/animatedvaluexy#stopanimation + */ + stopAnimation( + callback?: (value: { + x: number, + y: number, + ... + }) => void, + ): void { + this.x.stopAnimation(); + this.y.stopAnimation(); + callback && callback(this.__getValue()); + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to synchronously read + * the value because it might be driven natively. + * + * Returns a string that serves as an identifier for the listener. + * + * See https://reactnative.dev/docs/animatedvaluexy#addlistener + */ + addListener(callback: ValueXYListenerCallback): string { + const id = String(_uniqueId++); + const jointCallback = ({value: number}: any) => { + callback(this.__getValue()); + }; + this._listeners[id] = { + x: this.x.addListener(jointCallback), + y: this.y.addListener(jointCallback), + }; + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + * + * See https://reactnative.dev/docs/animatedvaluexy#removelistener + */ + removeListener(id: string): void { + this.x.removeListener(this._listeners[id].x); + this.y.removeListener(this._listeners[id].y); + delete this._listeners[id]; + } + + /** + * Remove all registered listeners. + * + * See https://reactnative.dev/docs/animatedvaluexy#removealllisteners + */ + removeAllListeners(): void { + this.x.removeAllListeners(); + this.y.removeAllListeners(); + this._listeners = {}; + } + + /** + * Converts `{x, y}` into `{left, top}` for use in style. + * + * See https://reactnative.dev/docs/animatedvaluexy#getlayout + */ + getLayout(): {[key: string]: AnimatedValue, ...} { + return { + left: this.x, + top: this.y, + }; + } + + /** + * Converts `{x, y}` into a useable translation transform. + * + * See https://reactnative.dev/docs/animatedvaluexy#gettranslatetransform + */ + getTranslateTransform(): Array<{[key: string]: AnimatedValue, ...}> { + return [{translateX: this.x}, {translateY: this.y}]; + } + + __attach(): void { + this.x.__addChild(this); + this.y.__addChild(this); + super.__attach(); + } + + __detach(): void { + this.x.__removeChild(this); + this.y.__removeChild(this); + super.__detach(); + } + + __makeNative(platformConfig: ?PlatformConfig) { + this.x.__makeNative(platformConfig); + this.y.__makeNative(platformConfig); + super.__makeNative(platformConfig); + } +} + +module.exports = AnimatedValueXY; diff --git a/Libraries/Animated/nodes/AnimatedWithChildren.js b/Libraries/Animated/nodes/AnimatedWithChildren.js new file mode 100644 index 00000000000000..435365d1f6ee6d --- /dev/null +++ b/Libraries/Animated/nodes/AnimatedWithChildren.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +const AnimatedNode = require('./AnimatedNode'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +class AnimatedWithChildren extends AnimatedNode { + _children: Array; + + constructor() { + super(); + this._children = []; + } + + __makeNative(platformConfig: ?PlatformConfig) { + if (!this.__isNative) { + this.__isNative = true; + for (const child of this._children) { + child.__makeNative(platformConfig); + NativeAnimatedHelper.API.connectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + } + super.__makeNative(platformConfig); + } + + __addChild(child: AnimatedNode): void { + if (this._children.length === 0) { + this.__attach(); + } + this._children.push(child); + if (this.__isNative) { + // Only accept "native" animated nodes as children + child.__makeNative(this.__getPlatformConfig()); + NativeAnimatedHelper.API.connectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + } + + __removeChild(child: AnimatedNode): void { + const index = this._children.indexOf(child); + if (index === -1) { + console.warn("Trying to remove a child that doesn't exist"); + return; + } + if (this.__isNative && child.__isNative) { + NativeAnimatedHelper.API.disconnectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + this._children.splice(index, 1); + if (this._children.length === 0) { + this.__detach(); + } + } + + __getChildren(): Array { + return this._children; + } + + __callListeners(value: number): void { + super.__callListeners(value); + if (!this.__isNative) { + for (const child of this._children) { + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + if (child.__getValue) { + child.__callListeners(child.__getValue()); + } + } + } + } +} + +module.exports = AnimatedWithChildren; diff --git a/Libraries/Animated/release/gulpfile.js b/Libraries/Animated/release/gulpfile.js deleted file mode 100644 index b454ea790c2731..00000000000000 --- a/Libraries/Animated/release/gulpfile.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) 2013-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 gulpfile - */ - -'use strict'; - -var babel = require('gulp-babel'); -var babelPluginDEV = require('fbjs-scripts/babel/dev-expression'); -var babelPluginModules = require('fbjs-scripts/babel/rewrite-modules'); -var del = require('del'); -var derequire = require('gulp-derequire'); -var flatten = require('gulp-flatten'); -var gulp = require('gulp'); -var gulpUtil = require('gulp-util'); -var header = require('gulp-header'); -var objectAssign = require('object-assign'); -var runSequence = require('run-sequence'); -var webpackStream = require('webpack-stream'); - -var DEVELOPMENT_HEADER = [ - '/**', - ' * Animated v<%= version %>', - ' */' -].join('\n') + '\n'; -var PRODUCTION_HEADER = [ - '/**', - ' * Animated v<%= version %>', - ' *', - ' * Copyright (c) 2013-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.', - ' */' -].join('\n') + '\n'; - -var babelOpts = { - nonStandard: true, - loose: [ - 'es6.classes' - ], - stage: 1, - plugins: [babelPluginDEV, babelPluginModules], - _moduleMap: objectAssign({}, require('fbjs/module-map'), { - 'React': 'react', - }) -}; - -var buildDist = function(opts) { - var webpackOpts = { - debug: opts.debug, - externals: { - 'react': 'React', - }, - module: { - loaders: [ - {test: /\.js$/, loader: 'babel'} - ], - }, - output: { - filename: opts.output, - library: 'Animated' - }, - plugins: [ - new webpackStream.webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify( - opts.debug ? 'development' : 'production' - ), - }), - new webpackStream.webpack.optimize.OccurenceOrderPlugin(), - new webpackStream.webpack.optimize.DedupePlugin() - ] - }; - if (!opts.debug) { - webpackOpts.plugins.push( - new webpackStream.webpack.optimize.UglifyJsPlugin({ - compress: { - hoist_vars: true, - screw_ie8: true, - warnings: false - } - }) - ); - } - return webpackStream(webpackOpts, null, function(err, stats) { - if (err) { - throw new gulpUtil.PluginError('webpack', err); - } - if (stats.compilation.errors.length) { - throw new gulpUtil.PluginError('webpack', stats.toString()); - } - }); -}; - -var paths = { - dist: 'dist', - entry: 'lib/AnimatedWeb.js', - lib: 'lib', - src: [ - '*src/**/*.js', - '!src/**/__tests__/**/*.js', - '!src/**/__mocks__/**/*.js' - ], -}; - -gulp.task('clean', function(cb) { - del([paths.dist, paths.lib], cb); -}); - -gulp.task('modules', function() { - return gulp - .src(paths.src, {cwd: '../'}) - .pipe(babel(babelOpts)) - .pipe(flatten()) - .pipe(gulp.dest(paths.lib)); -}); - -gulp.task('dist', ['modules'], function () { - var distOpts = { - debug: true, - output: 'animated.js' - }; - return gulp - .src(paths.entry) - .pipe(buildDist(distOpts)) - .pipe(derequire()) - .pipe(header(DEVELOPMENT_HEADER, { - version: process.env.npm_package_version - })) - .pipe(gulp.dest(paths.dist)); -}); - -gulp.task('dist:min', ['modules'], function () { - var distOpts = { - debug: false, - output: 'animated.min.js' - }; - return gulp - .src(paths.entry) - .pipe(buildDist(distOpts)) - .pipe(header(PRODUCTION_HEADER, { - version: process.env.npm_package_version - })) - .pipe(gulp.dest(paths.dist)); -}); - -gulp.task('watch', function() { - gulp.watch(paths.src, ['modules']); -}); - -gulp.task('default', function(cb) { - runSequence('clean', 'modules', ['dist', 'dist:min'], cb); -}); diff --git a/Libraries/Animated/release/package.json b/Libraries/Animated/release/package.json deleted file mode 100644 index 74f457a4dcd33c..00000000000000 --- a/Libraries/Animated/release/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "react-animated", - "description": "Animated provides powerful mechanisms for animating your React views", - "version": "0.1.0", - "keywords": [ - "react", - "animated", - "animation" - ], - "license": "MIT", - "main": "Animated.js", - "dependencies": { - "fbjs": "^0.2.1" - }, - "scripts": { - "build": "gulp" - }, - "devDependencies": { - "babel-core": "^5.8.25", - "babel-loader": "^5.3.2", - "del": "^1.2.0", - "fbjs-scripts": "^0.2.0", - "gulp": "^3.9.0", - "gulp-babel": "^5.1.0", - "gulp-derequire": "^2.1.0", - "gulp-flatten": "^0.1.0", - "gulp-header": "^1.2.2", - "gulp-util": "^3.0.6", - "object-assign": "^3.0.0", - "run-sequence": "^1.1.2", - "webpack": "1.11.0", - "webpack-stream": "^2.1.0" - } -} diff --git a/Libraries/Animated/src/Animated.js b/Libraries/Animated/src/Animated.js deleted file mode 100644 index 32ec9ff8046954..00000000000000 --- a/Libraries/Animated/src/Animated.js +++ /dev/null @@ -1,30 +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 Animated - * @flow - * @format - */ - -'use strict'; - -const AnimatedImplementation = require('AnimatedImplementation'); -const Image = require('Image'); -const ScrollView = require('ScrollView'); -const Text = require('Text'); -const View = require('View'); - -const Animated = { - View: AnimatedImplementation.createAnimatedComponent(View), - Text: AnimatedImplementation.createAnimatedComponent(Text), - Image: AnimatedImplementation.createAnimatedComponent(Image), - ScrollView: AnimatedImplementation.createAnimatedComponent(ScrollView), -}; - -Object.assign((Animated: Object), AnimatedImplementation); - -module.exports = ((Animated: any): typeof AnimatedImplementation & - typeof Animated); diff --git a/Libraries/Animated/src/AnimatedEvent.js b/Libraries/Animated/src/AnimatedEvent.js deleted file mode 100644 index f9300feb4e79ea..00000000000000 --- a/Libraries/Animated/src/AnimatedEvent.js +++ /dev/null @@ -1,194 +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 AnimatedEvent - * @flow - * @format - */ -'use strict'; - -const AnimatedValue = require('./nodes/AnimatedValue'); -const NativeAnimatedHelper = require('./NativeAnimatedHelper'); -const ReactNative = require('ReactNative'); - -const invariant = require('fbjs/lib/invariant'); -const {shouldUseNativeDriver} = require('./NativeAnimatedHelper'); - -export type Mapping = {[key: string]: Mapping} | AnimatedValue; -export type EventConfig = { - listener?: ?Function, - useNativeDriver?: boolean, -}; - -function attachNativeEvent( - viewRef: any, - eventName: string, - argMapping: Array, -) { - // Find animated values in `argMapping` and create an array representing their - // key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x']. - const eventMappings = []; - - const traverse = (value, path) => { - if (value instanceof AnimatedValue) { - value.__makeNative(); - - eventMappings.push({ - nativeEventPath: path, - animatedValueTag: value.__getNativeTag(), - }); - } else if (typeof value === 'object') { - for (const key in value) { - traverse(value[key], path.concat(key)); - } - } - }; - - invariant( - argMapping[0] && argMapping[0].nativeEvent, - 'Native driven events only support animated values contained inside `nativeEvent`.', - ); - - // Assume that the event containing `nativeEvent` is always the first argument. - traverse(argMapping[0].nativeEvent, []); - - const viewTag = ReactNative.findNodeHandle(viewRef); - - eventMappings.forEach(mapping => { - NativeAnimatedHelper.API.addAnimatedEventToView( - viewTag, - eventName, - mapping, - ); - }); - - return { - detach() { - eventMappings.forEach(mapping => { - NativeAnimatedHelper.API.removeAnimatedEventFromView( - viewTag, - eventName, - mapping.animatedValueTag, - ); - }); - }, - }; -} - -class AnimatedEvent { - _argMapping: Array; - _listeners: Array = []; - _callListeners: Function; - _attachedEvent: ?{ - detach: () => void, - }; - __isNative: boolean; - - constructor(argMapping: Array, config?: EventConfig = {}) { - this._argMapping = argMapping; - if (config.listener) { - this.__addListener(config.listener); - } - this._callListeners = this._callListeners.bind(this); - this._attachedEvent = null; - this.__isNative = shouldUseNativeDriver(config); - - if (__DEV__) { - this._validateMapping(); - } - } - - __addListener(callback: Function): void { - this._listeners.push(callback); - } - - __removeListener(callback: Function): void { - this._listeners = this._listeners.filter(listener => listener !== callback); - } - - __attach(viewRef: any, eventName: string) { - invariant( - this.__isNative, - 'Only native driven events need to be attached.', - ); - - this._attachedEvent = attachNativeEvent( - viewRef, - eventName, - this._argMapping, - ); - } - - __detach(viewTag: any, eventName: string) { - invariant( - this.__isNative, - 'Only native driven events need to be detached.', - ); - - this._attachedEvent && this._attachedEvent.detach(); - } - - __getHandler() { - if (this.__isNative) { - return this._callListeners; - } - - return (...args: any) => { - const traverse = (recMapping, recEvt, key) => { - if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) { - recMapping.setValue(recEvt); - } else if (typeof recMapping === 'object') { - for (const mappingKey in recMapping) { - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for - * React. To see the error delete this comment and run Flow. */ - traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey); - } - } - }; - - if (!this.__isNative) { - this._argMapping.forEach((mapping, idx) => { - traverse(mapping, args[idx], 'arg' + idx); - }); - } - this._callListeners(...args); - }; - } - - _callListeners(...args) { - this._listeners.forEach(listener => listener(...args)); - } - - _validateMapping() { - const traverse = (recMapping, recEvt, key) => { - if (typeof recEvt === 'number') { - invariant( - recMapping instanceof AnimatedValue, - 'Bad mapping of type ' + - typeof recMapping + - ' for key ' + - key + - ', event value must map to AnimatedValue', - ); - return; - } - invariant( - typeof recMapping === 'object', - 'Bad mapping of type ' + typeof recMapping + ' for key ' + key, - ); - invariant( - typeof recEvt === 'object', - 'Bad event of type ' + typeof recEvt + ' for key ' + key, - ); - for (const mappingKey in recMapping) { - traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey); - } - }; - } -} - -module.exports = {AnimatedEvent, attachNativeEvent}; diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js deleted file mode 100644 index 69a96ba1d3e6ba..00000000000000 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ /dev/null @@ -1,674 +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 AnimatedImplementation - * @flow - * @format - * @preventMunge - */ -'use strict'; - -const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent'); -const AnimatedAddition = require('./nodes/AnimatedAddition'); -const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp'); -const AnimatedDivision = require('./nodes/AnimatedDivision'); -const AnimatedInterpolation = require('./nodes/AnimatedInterpolation'); -const AnimatedModulo = require('./nodes/AnimatedModulo'); -const AnimatedMultiplication = require('./nodes/AnimatedMultiplication'); -const AnimatedNode = require('./nodes/AnimatedNode'); -const AnimatedProps = require('./nodes/AnimatedProps'); -const AnimatedTracking = require('./nodes/AnimatedTracking'); -const AnimatedValue = require('./nodes/AnimatedValue'); -const AnimatedValueXY = require('./nodes/AnimatedValueXY'); -const DecayAnimation = require('./animations/DecayAnimation'); -const SpringAnimation = require('./animations/SpringAnimation'); -const TimingAnimation = require('./animations/TimingAnimation'); - -const createAnimatedComponent = require('./createAnimatedComponent'); - -import type { - AnimationConfig, - EndCallback, - EndResult, -} from './animations/Animation'; -import type {TimingAnimationConfig} from './animations/TimingAnimation'; -import type {DecayAnimationConfig} from './animations/DecayAnimation'; -import type {SpringAnimationConfig} from './animations/SpringAnimation'; -import type {Mapping, EventConfig} from './AnimatedEvent'; - -export type CompositeAnimation = { - start: (callback?: ?EndCallback) => void, - stop: () => void, - reset: () => void, - _startNativeLoop: (iterations?: number) => void, - _isUsingNativeDriver: () => boolean, -}; - -const add = function( - a: AnimatedNode | number, - b: AnimatedNode | number, -): AnimatedAddition { - return new AnimatedAddition(a, b); -}; - -const divide = function( - a: AnimatedNode | number, - b: AnimatedNode | number, -): AnimatedDivision { - return new AnimatedDivision(a, b); -}; - -const multiply = function( - a: AnimatedNode | number, - b: AnimatedNode | number, -): AnimatedMultiplication { - return new AnimatedMultiplication(a, b); -}; - -const modulo = function(a: AnimatedNode, modulus: number): AnimatedModulo { - return new AnimatedModulo(a, modulus); -}; - -const diffClamp = function( - a: AnimatedNode, - min: number, - max: number, -): AnimatedDiffClamp { - return new AnimatedDiffClamp(a, min, max); -}; - -const _combineCallbacks = function( - callback: ?EndCallback, - config: AnimationConfig, -) { - if (callback && config.onComplete) { - return (...args) => { - config.onComplete && config.onComplete(...args); - callback && callback(...args); - }; - } else { - return callback || config.onComplete; - } -}; - -const maybeVectorAnim = function( - value: AnimatedValue | AnimatedValueXY, - config: Object, - anim: (value: AnimatedValue, config: Object) => CompositeAnimation, -): ?CompositeAnimation { - if (value instanceof AnimatedValueXY) { - const configX = {...config}; - const configY = {...config}; - for (const key in config) { - const {x, y} = config[key]; - if (x !== undefined && y !== undefined) { - configX[key] = x; - configY[key] = y; - } - } - const aX = anim((value: AnimatedValueXY).x, configX); - const aY = anim((value: AnimatedValueXY).y, configY); - // We use `stopTogether: false` here because otherwise tracking will break - // because the second animation will get stopped before it can update. - return parallel([aX, aY], {stopTogether: false}); - } - return null; -}; - -const spring = function( - value: AnimatedValue | AnimatedValueXY, - config: SpringAnimationConfig, -): CompositeAnimation { - const start = function( - animatedValue: AnimatedValue | AnimatedValueXY, - configuration: SpringAnimationConfig, - callback?: ?EndCallback, - ): void { - callback = _combineCallbacks(callback, configuration); - const singleValue: any = animatedValue; - const singleConfig: any = configuration; - singleValue.stopTracking(); - if (configuration.toValue instanceof AnimatedNode) { - singleValue.track( - new AnimatedTracking( - singleValue, - configuration.toValue, - SpringAnimation, - singleConfig, - callback, - ), - ); - } else { - singleValue.animate(new SpringAnimation(singleConfig), callback); - } - }; - return ( - maybeVectorAnim(value, config, spring) || { - start: function(callback?: ?EndCallback): void { - start(value, config, callback); - }, - - stop: function(): void { - value.stopAnimation(); - }, - - reset: function(): void { - value.resetAnimation(); - }, - - _startNativeLoop: function(iterations?: number): void { - const singleConfig = {...config, iterations}; - start(value, singleConfig); - }, - - _isUsingNativeDriver: function(): boolean { - return config.useNativeDriver || false; - }, - } - ); -}; - -const timing = function( - value: AnimatedValue | AnimatedValueXY, - config: TimingAnimationConfig, -): CompositeAnimation { - const start = function( - animatedValue: AnimatedValue | AnimatedValueXY, - configuration: TimingAnimationConfig, - callback?: ?EndCallback, - ): void { - callback = _combineCallbacks(callback, configuration); - const singleValue: any = animatedValue; - const singleConfig: any = configuration; - singleValue.stopTracking(); - if (configuration.toValue instanceof AnimatedNode) { - singleValue.track( - new AnimatedTracking( - singleValue, - configuration.toValue, - TimingAnimation, - singleConfig, - callback, - ), - ); - } else { - singleValue.animate(new TimingAnimation(singleConfig), callback); - } - }; - - return ( - maybeVectorAnim(value, config, timing) || { - start: function(callback?: ?EndCallback): void { - start(value, config, callback); - }, - - stop: function(): void { - value.stopAnimation(); - }, - - reset: function(): void { - value.resetAnimation(); - }, - - _startNativeLoop: function(iterations?: number): void { - const singleConfig = {...config, iterations}; - start(value, singleConfig); - }, - - _isUsingNativeDriver: function(): boolean { - return config.useNativeDriver || false; - }, - } - ); -}; - -const decay = function( - value: AnimatedValue | AnimatedValueXY, - config: DecayAnimationConfig, -): CompositeAnimation { - const start = function( - animatedValue: AnimatedValue | AnimatedValueXY, - configuration: DecayAnimationConfig, - callback?: ?EndCallback, - ): void { - callback = _combineCallbacks(callback, configuration); - const singleValue: any = animatedValue; - const singleConfig: any = configuration; - singleValue.stopTracking(); - singleValue.animate(new DecayAnimation(singleConfig), callback); - }; - - return ( - maybeVectorAnim(value, config, decay) || { - start: function(callback?: ?EndCallback): void { - start(value, config, callback); - }, - - stop: function(): void { - value.stopAnimation(); - }, - - reset: function(): void { - value.resetAnimation(); - }, - - _startNativeLoop: function(iterations?: number): void { - const singleConfig = {...config, iterations}; - start(value, singleConfig); - }, - - _isUsingNativeDriver: function(): boolean { - return config.useNativeDriver || false; - }, - } - ); -}; - -const sequence = function( - animations: Array, -): CompositeAnimation { - let current = 0; - return { - start: function(callback?: ?EndCallback) { - const onComplete = function(result) { - if (!result.finished) { - callback && callback(result); - return; - } - - current++; - - if (current === animations.length) { - callback && callback(result); - return; - } - - animations[current].start(onComplete); - }; - - if (animations.length === 0) { - callback && callback({finished: true}); - } else { - animations[current].start(onComplete); - } - }, - - stop: function() { - if (current < animations.length) { - animations[current].stop(); - } - }, - - reset: function() { - animations.forEach((animation, idx) => { - if (idx <= current) { - animation.reset(); - } - }); - current = 0; - }, - - _startNativeLoop: function() { - throw new Error( - 'Loops run using the native driver cannot contain Animated.sequence animations', - ); - }, - - _isUsingNativeDriver: function(): boolean { - return false; - }, - }; -}; - -type ParallelConfig = { - stopTogether?: boolean, // If one is stopped, stop all. default: true -}; -const parallel = function( - animations: Array, - config?: ?ParallelConfig, -): CompositeAnimation { - let doneCount = 0; - // Make sure we only call stop() at most once for each animation - const hasEnded = {}; - const stopTogether = !(config && config.stopTogether === false); - - const result = { - start: function(callback?: ?EndCallback) { - if (doneCount === animations.length) { - callback && callback({finished: true}); - return; - } - - animations.forEach((animation, idx) => { - const cb = function(endResult) { - hasEnded[idx] = true; - doneCount++; - if (doneCount === animations.length) { - doneCount = 0; - callback && callback(endResult); - return; - } - - if (!endResult.finished && stopTogether) { - result.stop(); - } - }; - - if (!animation) { - cb({finished: true}); - } else { - animation.start(cb); - } - }); - }, - - stop: function(): void { - animations.forEach((animation, idx) => { - !hasEnded[idx] && animation.stop(); - hasEnded[idx] = true; - }); - }, - - reset: function(): void { - animations.forEach((animation, idx) => { - animation.reset(); - hasEnded[idx] = false; - doneCount = 0; - }); - }, - - _startNativeLoop: function() { - throw new Error( - 'Loops run using the native driver cannot contain Animated.parallel animations', - ); - }, - - _isUsingNativeDriver: function(): boolean { - return false; - }, - }; - - return result; -}; - -const delay = function(time: number): CompositeAnimation { - // Would be nice to make a specialized implementation - return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0}); -}; - -const stagger = function( - time: number, - animations: Array, -): CompositeAnimation { - return parallel( - animations.map((animation, i) => { - return sequence([delay(time * i), animation]); - }), - ); -}; - -type LoopAnimationConfig = {iterations: number}; - -const loop = function( - animation: CompositeAnimation, - {iterations = -1}: LoopAnimationConfig = {}, -): CompositeAnimation { - let isFinished = false; - let iterationsSoFar = 0; - return { - start: function(callback?: ?EndCallback) { - const restart = function(result: EndResult = {finished: true}): void { - if ( - isFinished || - iterationsSoFar === iterations || - result.finished === false - ) { - callback && callback(result); - } else { - iterationsSoFar++; - animation.reset(); - animation.start(restart); - } - }; - if (!animation || iterations === 0) { - callback && callback({finished: true}); - } else { - if (animation._isUsingNativeDriver()) { - animation._startNativeLoop(iterations); - } else { - restart(); // Start looping recursively on the js thread - } - } - }, - - stop: function(): void { - isFinished = true; - animation.stop(); - }, - - reset: function(): void { - iterationsSoFar = 0; - isFinished = false; - animation.reset(); - }, - - _startNativeLoop: function() { - throw new Error( - 'Loops run using the native driver cannot contain Animated.loop animations', - ); - }, - - _isUsingNativeDriver: function(): boolean { - return animation._isUsingNativeDriver(); - }, - }; -}; - -function forkEvent( - event: ?AnimatedEvent | ?Function, - listener: Function, -): AnimatedEvent | Function { - if (!event) { - return listener; - } else if (event instanceof AnimatedEvent) { - event.__addListener(listener); - return event; - } else { - return (...args) => { - typeof event === 'function' && event(...args); - listener(...args); - }; - } -} - -function unforkEvent( - event: ?AnimatedEvent | ?Function, - listener: Function, -): void { - if (event && event instanceof AnimatedEvent) { - event.__removeListener(listener); - } -} - -const event = function(argMapping: Array, config?: EventConfig): any { - const animatedEvent = new AnimatedEvent(argMapping, config); - if (animatedEvent.__isNative) { - return animatedEvent; - } else { - return animatedEvent.__getHandler(); - } -}; - -/** - * The `Animated` library is designed to make animations fluid, powerful, and - * easy to build and maintain. `Animated` focuses on declarative relationships - * between inputs and outputs, with configurable transforms in between, and - * simple `start`/`stop` methods to control time-based animation execution. - * - * See http://facebook.github.io/react-native/docs/animated.html - */ -module.exports = { - /** - * Standard value class for driving animations. Typically initialized with - * `new Animated.Value(0);` - * - * See http://facebook.github.io/react-native/docs/animated.html#value - */ - Value: AnimatedValue, - /** - * 2D value class for driving 2D animations, such as pan gestures. - * - * See https://facebook.github.io/react-native/releases/next/docs/animatedvaluexy.html - */ - ValueXY: AnimatedValueXY, - /** - * Exported to use the Interpolation type in flow. - * - * See http://facebook.github.io/react-native/docs/animated.html#interpolation - */ - Interpolation: AnimatedInterpolation, - /** - * Exported for ease of type checking. All animated values derive from this - * class. - * - * See http://facebook.github.io/react-native/docs/animated.html#node - */ - Node: AnimatedNode, - - /** - * Animates a value from an initial velocity to zero based on a decay - * coefficient. - * - * See http://facebook.github.io/react-native/docs/animated.html#decay - */ - decay, - /** - * Animates a value along a timed easing curve. The Easing module has tons of - * predefined curves, or you can use your own function. - * - * See http://facebook.github.io/react-native/docs/animated.html#timing - */ - timing, - /** - * Animates a value according to an analytical spring model based on - * damped harmonic oscillation. - * - * See http://facebook.github.io/react-native/docs/animated.html#spring - */ - spring, - - /** - * Creates a new Animated value composed from two Animated values added - * together. - * - * See http://facebook.github.io/react-native/docs/animated.html#add - */ - add, - - /** - * Creates a new Animated value composed by dividing the first Animated value - * by the second Animated value. - * - * See http://facebook.github.io/react-native/docs/animated.html#divide - */ - divide, - - /** - * Creates a new Animated value composed from two Animated values multiplied - * together. - * - * See http://facebook.github.io/react-native/docs/animated.html#multiply - */ - multiply, - - /** - * Creates a new Animated value that is the (non-negative) modulo of the - * provided Animated value. - * - * See http://facebook.github.io/react-native/docs/animated.html#modulo - */ - modulo, - - /** - * Create a new Animated value that is limited between 2 values. It uses the - * difference between the last value so even if the value is far from the - * bounds it will start changing when the value starts getting closer again. - * - * See http://facebook.github.io/react-native/docs/animated.html#diffclamp - */ - diffClamp, - - /** - * Starts an animation after the given delay. - * - * See http://facebook.github.io/react-native/docs/animated.html#delay - */ - delay, - /** - * Starts an array of animations in order, waiting for each to complete - * before starting the next. If the current running animation is stopped, no - * following animations will be started. - * - * See http://facebook.github.io/react-native/docs/animated.html#sequence - */ - sequence, - /** - * Starts an array of animations all at the same time. By default, if one - * of the animations is stopped, they will all be stopped. You can override - * this with the `stopTogether` flag. - * - * See http://facebook.github.io/react-native/docs/animated.html#parallel - */ - parallel, - /** - * Array of animations may run in parallel (overlap), but are started in - * sequence with successive delays. Nice for doing trailing effects. - * - * See http://facebook.github.io/react-native/docs/animated.html#stagger - */ - stagger, - /** - * Loops a given animation continuously, so that each time it reaches the - * end, it resets and begins again from the start. - * - * See http://facebook.github.io/react-native/docs/animated.html#loop - */ - loop, - - /** - * Takes an array of mappings and extracts values from each arg accordingly, - * then calls `setValue` on the mapped outputs. - * - * See http://facebook.github.io/react-native/docs/animated.html#event - */ - event, - - /** - * Make any React component Animatable. Used to create `Animated.View`, etc. - * - * See http://facebook.github.io/react-native/docs/animated.html#createanimatedcomponent - */ - createAnimatedComponent, - - /** - * Imperative API to attach an animated value to an event on a view. Prefer - * using `Animated.event` with `useNativeDrive: true` if possible. - * - * See http://facebook.github.io/react-native/docs/animated.html#attachnativeevent - */ - attachNativeEvent, - - /** - * Advanced imperative API for snooping on animated events that are passed in - * through props. Use values directly where possible. - * - * See http://facebook.github.io/react-native/docs/animated.html#forkevent - */ - forkEvent, - unforkEvent, - - __PropsOnlyForTests: AnimatedProps, -}; diff --git a/Libraries/Animated/src/AnimatedWeb.js b/Libraries/Animated/src/AnimatedWeb.js deleted file mode 100644 index f3af397b740931..00000000000000 --- a/Libraries/Animated/src/AnimatedWeb.js +++ /dev/null @@ -1,19 +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. - * - * @flow - * @providesModule AnimatedWeb - */ -'use strict'; - -var AnimatedImplementation = require('AnimatedImplementation'); - -module.exports = { - ...AnimatedImplementation, - div: AnimatedImplementation.createAnimatedComponent('div'), - span: AnimatedImplementation.createAnimatedComponent('span'), - img: AnimatedImplementation.createAnimatedComponent('img'), -}; diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js deleted file mode 100644 index 5765dcfc63b829..00000000000000 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ /dev/null @@ -1,272 +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 NativeAnimatedHelper - * @flow - * @format - */ -'use strict'; - -const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule; -const NativeEventEmitter = require('NativeEventEmitter'); - -const invariant = require('fbjs/lib/invariant'); - -import type {AnimationConfig} from './animations/Animation'; -import type {EventConfig} from './AnimatedEvent'; - -let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ -let __nativeAnimationIdCount = 1; /* used for started animations */ - -type EndResult = {finished: boolean}; -type EndCallback = (result: EndResult) => void; -type EventMapping = { - nativeEventPath: Array, - animatedValueTag: ?number, -}; - -let nativeEventEmitter; - -/** - * Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for - * the native module methods - */ -const API = { - createAnimatedNode: function(tag: ?number, config: Object): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.createAnimatedNode(tag, config); - }, - startListeningToAnimatedNodeValue: function(tag: ?number) { - assertNativeAnimatedModule(); - NativeAnimatedModule.startListeningToAnimatedNodeValue(tag); - }, - stopListeningToAnimatedNodeValue: function(tag: ?number) { - assertNativeAnimatedModule(); - NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag); - }, - connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag); - }, - disconnectAnimatedNodes: function( - parentTag: ?number, - childTag: ?number, - ): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag); - }, - startAnimatingNode: function( - animationId: ?number, - nodeTag: ?number, - config: Object, - endCallback: EndCallback, - ): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.startAnimatingNode( - animationId, - nodeTag, - config, - endCallback, - ); - }, - stopAnimation: function(animationId: ?number) { - assertNativeAnimatedModule(); - NativeAnimatedModule.stopAnimation(animationId); - }, - setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value); - }, - setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset); - }, - flattenAnimatedNodeOffset: function(nodeTag: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag); - }, - extractAnimatedNodeOffset: function(nodeTag: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag); - }, - connectAnimatedNodeToView: function( - nodeTag: ?number, - viewTag: ?number, - ): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag); - }, - disconnectAnimatedNodeFromView: function( - nodeTag: ?number, - viewTag: ?number, - ): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag); - }, - dropAnimatedNode: function(tag: ?number): void { - assertNativeAnimatedModule(); - NativeAnimatedModule.dropAnimatedNode(tag); - }, - addAnimatedEventToView: function( - viewTag: ?number, - eventName: string, - eventMapping: EventMapping, - ) { - assertNativeAnimatedModule(); - NativeAnimatedModule.addAnimatedEventToView( - viewTag, - eventName, - eventMapping, - ); - }, - removeAnimatedEventFromView( - viewTag: ?number, - eventName: string, - animatedNodeTag: ?number, - ) { - assertNativeAnimatedModule(); - NativeAnimatedModule.removeAnimatedEventFromView( - viewTag, - eventName, - animatedNodeTag, - ); - }, -}; - -/** - * Styles allowed by the native animated implementation. - * - * In general native animated implementation should support any numeric property that doesn't need - * to be updated through the shadow view hierarchy (all non-layout properties). - */ -const STYLES_WHITELIST = { - opacity: true, - transform: true, - /* ios styles */ - shadowOpacity: true, - shadowRadius: true, - /* legacy android transform properties */ - scaleX: true, - scaleY: true, - translateX: true, - translateY: true, -}; - -const TRANSFORM_WHITELIST = { - translateX: true, - translateY: true, - scale: true, - scaleX: true, - scaleY: true, - rotate: true, - rotateX: true, - rotateY: true, - perspective: true, -}; - -const SUPPORTED_INTERPOLATION_PARAMS = { - inputRange: true, - outputRange: true, - extrapolate: true, - extrapolateRight: true, - extrapolateLeft: true, -}; - -function addWhitelistedStyleProp(prop: string): void { - STYLES_WHITELIST[prop] = true; -} - -function addWhitelistedTransformProp(prop: string): void { - TRANSFORM_WHITELIST[prop] = true; -} - -function addWhitelistedInterpolationParam(param: string): void { - SUPPORTED_INTERPOLATION_PARAMS[param] = true; -} - -function validateTransform(configs: Array): void { - configs.forEach(config => { - if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) { - throw new Error( - `Property '${ - config.property - }' is not supported by native animated module`, - ); - } - }); -} - -function validateStyles(styles: Object): void { - for (var key in styles) { - if (!STYLES_WHITELIST.hasOwnProperty(key)) { - throw new Error( - `Style property '${key}' is not supported by native animated module`, - ); - } - } -} - -function validateInterpolation(config: Object): void { - for (var key in config) { - if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) { - throw new Error( - `Interpolation property '${key}' is not supported by native animated module`, - ); - } - } -} - -function generateNewNodeTag(): number { - return __nativeAnimatedNodeTagCount++; -} - -function generateNewAnimationId(): number { - return __nativeAnimationIdCount++; -} - -function assertNativeAnimatedModule(): void { - invariant(NativeAnimatedModule, 'Native animated module is not available'); -} - -let _warnedMissingNativeAnimated = false; - -function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean { - if (config.useNativeDriver && !NativeAnimatedModule) { - if (!_warnedMissingNativeAnimated) { - console.warn( - 'Animated: `useNativeDriver` is not supported because the native ' + - 'animated module is missing. Falling back to JS-based animation. To ' + - 'resolve this, add `RCTAnimation` module to this app, or remove ' + - '`useNativeDriver`. ' + - 'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420', - ); - _warnedMissingNativeAnimated = true; - } - return false; - } - - return config.useNativeDriver || false; -} - -module.exports = { - API, - addWhitelistedStyleProp, - addWhitelistedTransformProp, - addWhitelistedInterpolationParam, - validateStyles, - validateTransform, - validateInterpolation, - generateNewNodeTag, - generateNewAnimationId, - assertNativeAnimatedModule, - shouldUseNativeDriver, - get nativeEventEmitter() { - if (!nativeEventEmitter) { - nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); - } - return nativeEventEmitter; - }, -}; diff --git a/Libraries/Animated/src/SpringConfig.js b/Libraries/Animated/src/SpringConfig.js deleted file mode 100644 index 5b476f6723469e..00000000000000 --- a/Libraries/Animated/src/SpringConfig.js +++ /dev/null @@ -1,100 +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 SpringConfig - * @flow - */ - -'use strict'; - -type SpringConfigType = { - stiffness: number, - damping: number, -}; - -function stiffnessFromOrigamiValue(oValue) { - return (oValue - 30) * 3.62 + 194; -} - -function dampingFromOrigamiValue(oValue) { - return (oValue - 8) * 3 + 25; -} - -function fromOrigamiTensionAndFriction( - tension: number, - friction: number, -): SpringConfigType { - return { - stiffness: stiffnessFromOrigamiValue(tension), - damping: dampingFromOrigamiValue(friction), - }; -} - -function fromBouncinessAndSpeed( - bounciness: number, - speed: number, -): SpringConfigType { - function normalize(value, startValue, endValue) { - return (value - startValue) / (endValue - startValue); - } - - function projectNormal(n, start, end) { - return start + (n * (end - start)); - } - - function linearInterpolation(t, start, end) { - return t * end + (1 - t) * start; - } - - function quadraticOutInterpolation(t, start, end) { - return linearInterpolation(2 * t - t * t, start, end); - } - - function b3Friction1(x) { - return (0.0007 * Math.pow(x, 3)) - - (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; - } - - function b3Friction2(x) { - return (0.000044 * Math.pow(x, 3)) - - (0.006 * Math.pow(x, 2)) + 0.36 * x + 2; - } - - function b3Friction3(x) { - return (0.00000045 * Math.pow(x, 3)) - - (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; - } - - function b3Nobounce(tension) { - if (tension <= 18) { - return b3Friction1(tension); - } else if (tension > 18 && tension <= 44) { - return b3Friction2(tension); - } else { - return b3Friction3(tension); - } - } - - var b = normalize(bounciness / 1.7, 0, 20); - b = projectNormal(b, 0, 0.8); - var s = normalize(speed / 1.7, 0, 20); - var bouncyTension = projectNormal(s, 0.5, 200); - var bouncyFriction = quadraticOutInterpolation( - b, - b3Nobounce(bouncyTension), - 0.01 - ); - - return { - stiffness: stiffnessFromOrigamiValue(bouncyTension), - damping: dampingFromOrigamiValue(bouncyFriction), - }; -} - -module.exports = { - fromOrigamiTensionAndFriction, - fromBouncinessAndSpeed, -}; diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js deleted file mode 100644 index d6409d7f8a9224..00000000000000 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ /dev/null @@ -1,792 +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. - * - * @emails oncall+react_native - */ -'use strict'; - -var Animated = require('Animated'); -describe('Animated tests', () => { - beforeEach(() => { - jest.resetModules(); - }); - - describe('Animated', () => { - - it('works end to end', () => { - var anim = new Animated.Value(0); - - var callback = jest.fn(); - - var node = new Animated.__PropsOnlyForTests({ - style: { - backgroundColor: 'red', - opacity: anim, - transform: [ - {translateX: anim.interpolate({ - inputRange: [0, 1], - outputRange: [100, 200], - })}, - {scale: anim}, - ], - shadowOffset: { - width: anim, - height: anim, - }, - } - }, callback); - - expect(anim.__getChildren().length).toBe(3); - - expect(node.__getValue()).toEqual({ - style: { - backgroundColor: 'red', - opacity: 0, - transform: [ - {translateX: 100}, - {scale: 0}, - ], - shadowOffset: { - width: 0, - height: 0, - }, - }, - }); - - anim.setValue(0.5); - - expect(callback).toBeCalled(); - - expect(node.__getValue()).toEqual({ - style: { - backgroundColor: 'red', - opacity: 0.5, - transform: [ - {translateX: 150}, - {scale: 0.5}, - ], - shadowOffset: { - width: 0.5, - height: 0.5, - }, - }, - }); - - node.__detach(); - expect(anim.__getChildren().length).toBe(0); - - anim.setValue(1); - expect(callback.mock.calls.length).toBe(1); - }); - - it('does not detach on updates', () => { - var anim = new Animated.Value(0); - anim.__detach = jest.fn(); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.UNSAFE_componentWillMount(); - - expect(anim.__detach).not.toBeCalled(); - c._component = {}; - c.UNSAFE_componentWillReceiveProps({ - style: { - opacity: anim, - }, - }); - expect(anim.__detach).not.toBeCalled(); - - c.componentWillUnmount(); - expect(anim.__detach).toBeCalled(); - }); - - - it('stops animation when detached', () => { - var anim = new Animated.Value(0); - var callback = jest.fn(); - - var c = new Animated.View(); - c.props = { - style: { - opacity: anim, - }, - }; - c.UNSAFE_componentWillMount(); - - Animated.timing(anim, {toValue: 10, duration: 1000}).start(callback); - c._component = {}; - c.componentWillUnmount(); - - expect(callback).toBeCalledWith({finished: false}); - expect(callback).toBeCalledWith({finished: false}); - }); - - it('triggers callback when spring is at rest', () => { - var anim = new Animated.Value(0); - var callback = jest.fn(); - Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback); - expect(callback).toBeCalled(); - }); - - it('send toValue when an underdamped spring stops', () => { - var anim = new Animated.Value(0); - var listener = jest.fn(); - anim.addListener(listener); - Animated.spring(anim, {toValue: 15}).start(); - jest.runAllTimers(); - var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value; - expect(lastValue).not.toBe(15); - expect(lastValue).toBeCloseTo(15); - expect(anim.__getValue()).toBe(15); - }); - - it('send toValue when a critically damped spring stops', () => { - var anim = new Animated.Value(0); - var listener = jest.fn(); - anim.addListener(listener); - Animated.spring(anim, {stiffness: 8000, damping: 2000, toValue: 15}).start(); - jest.runAllTimers(); - var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value; - expect(lastValue).not.toBe(15); - expect(lastValue).toBeCloseTo(15); - expect(anim.__getValue()).toBe(15); - }); - - it('convert to JSON', () => { - expect(JSON.stringify(new Animated.Value(10))).toBe('10'); - }); - }); - - - describe('Animated Sequence', () => { - - it('works with an empty sequence', () => { - var cb = jest.fn(); - Animated.sequence([]).start(cb); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('sequences well', () => { - var anim1 = {start: jest.fn()}; - var anim2 = {start: jest.fn()}; - var cb = jest.fn(); - - var seq = Animated.sequence([anim1, anim2]); - - expect(anim1.start).not.toBeCalled(); - expect(anim2.start).not.toBeCalled(); - - seq.start(cb); - - expect(anim1.start).toBeCalled(); - expect(anim2.start).not.toBeCalled(); - expect(cb).not.toBeCalled(); - - anim1.start.mock.calls[0][0]({finished: true}); - - expect(anim2.start).toBeCalled(); - expect(cb).not.toBeCalled(); - - anim2.start.mock.calls[0][0]({finished: true}); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('supports interrupting sequence', () => { - var anim1 = {start: jest.fn()}; - var anim2 = {start: jest.fn()}; - var cb = jest.fn(); - - Animated.sequence([anim1, anim2]).start(cb); - - anim1.start.mock.calls[0][0]({finished: false}); - - expect(anim1.start).toBeCalled(); - expect(anim2.start).not.toBeCalled(); - expect(cb).toBeCalledWith({finished: false}); - }); - - it('supports stopping sequence', () => { - var anim1 = {start: jest.fn(), stop: jest.fn()}; - var anim2 = {start: jest.fn(), stop: jest.fn()}; - var cb = jest.fn(); - - var seq = Animated.sequence([anim1, anim2]); - seq.start(cb); - seq.stop(); - - expect(anim1.stop).toBeCalled(); - expect(anim2.stop).not.toBeCalled(); - expect(cb).not.toBeCalled(); - - anim1.start.mock.calls[0][0]({finished: false}); - - expect(cb).toBeCalledWith({finished: false}); - }); - }); - - describe('Animated Loop', () => { - - it('loops indefinitely if config not specified', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 - expect(animation.reset).toHaveBeenCalledTimes(3); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 - expect(animation.reset).toHaveBeenCalledTimes(4); - expect(cb).not.toBeCalled(); - }); - - it('loops indefinitely if iterations is -1', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation, { iterations: -1 }); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 - expect(animation.reset).toHaveBeenCalledTimes(3); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 - expect(animation.reset).toHaveBeenCalledTimes(4); - expect(cb).not.toBeCalled(); - }); - - it('loops indefinitely if iterations not specified', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation, { anotherKey: 'value' }); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 - expect(animation.reset).toHaveBeenCalledTimes(3); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 - expect(animation.reset).toHaveBeenCalledTimes(4); - expect(cb).not.toBeCalled(); - }); - - it('loops three times if iterations is 3', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation, { iterations: 3 }); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 2 - expect(animation.reset).toHaveBeenCalledTimes(3); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 3 - expect(animation.reset).toHaveBeenCalledTimes(3); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('does not loop if iterations is 1', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation, { iterations: 1 }); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).toBeCalled(); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(cb).toBeCalledWith({finished: true}); - }); - - it('does not animate if iterations is 0', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation, { iterations: 0 }); - - expect(animation.start).not.toBeCalled(); - - loop.start(cb); - - expect(animation.start).not.toBeCalled(); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('supports interrupting an indefinite loop', () => { - var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - Animated.loop(animation).start(cb); - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: true}); // End of loop 1 - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).not.toBeCalled(); - - animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop - expect(animation.reset).toHaveBeenCalledTimes(2); - expect(cb).toBeCalledWith({finished: false}); - }); - - it('supports stopping loop', () => { - var animation = {start: jest.fn(), stop: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false}; - var cb = jest.fn(); - - var loop = Animated.loop(animation); - loop.start(cb); - loop.stop(); - - expect(animation.start).toBeCalled(); - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(animation.stop).toBeCalled(); - - animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop - expect(animation.reset).toHaveBeenCalledTimes(1); - expect(cb).toBeCalledWith({finished: false}); - }); - }); - - describe('Animated Parallel', () => { - - it('works with an empty parallel', () => { - var cb = jest.fn(); - Animated.parallel([]).start(cb); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('works with an empty element in array', () => { - var anim1 = {start: jest.fn()}; - var cb = jest.fn(); - Animated.parallel([null, anim1]).start(cb); - - expect(anim1.start).toBeCalled(); - anim1.start.mock.calls[0][0]({finished: true}); - - expect(cb).toBeCalledWith({finished: true}); - }); - - it('parellelizes well', () => { - var anim1 = {start: jest.fn()}; - var anim2 = {start: jest.fn()}; - var cb = jest.fn(); - - var par = Animated.parallel([anim1, anim2]); - - expect(anim1.start).not.toBeCalled(); - expect(anim2.start).not.toBeCalled(); - - par.start(cb); - - expect(anim1.start).toBeCalled(); - expect(anim2.start).toBeCalled(); - expect(cb).not.toBeCalled(); - - anim1.start.mock.calls[0][0]({finished: true}); - expect(cb).not.toBeCalled(); - - anim2.start.mock.calls[0][0]({finished: true}); - expect(cb).toBeCalledWith({finished: true}); - }); - - it('supports stopping parallel', () => { - var anim1 = {start: jest.fn(), stop: jest.fn()}; - var anim2 = {start: jest.fn(), stop: jest.fn()}; - var cb = jest.fn(); - - var seq = Animated.parallel([anim1, anim2]); - seq.start(cb); - seq.stop(); - - expect(anim1.stop).toBeCalled(); - expect(anim2.stop).toBeCalled(); - expect(cb).not.toBeCalled(); - - anim1.start.mock.calls[0][0]({finished: false}); - expect(cb).not.toBeCalled(); - - anim2.start.mock.calls[0][0]({finished: false}); - expect(cb).toBeCalledWith({finished: false}); - }); - - - it('does not call stop more than once when stopping', () => { - var anim1 = {start: jest.fn(), stop: jest.fn()}; - var anim2 = {start: jest.fn(), stop: jest.fn()}; - var anim3 = {start: jest.fn(), stop: jest.fn()}; - var cb = jest.fn(); - - var seq = Animated.parallel([anim1, anim2, anim3]); - seq.start(cb); - - anim1.start.mock.calls[0][0]({finished: false}); - - expect(anim1.stop.mock.calls.length).toBe(0); - expect(anim2.stop.mock.calls.length).toBe(1); - expect(anim3.stop.mock.calls.length).toBe(1); - - anim2.start.mock.calls[0][0]({finished: false}); - - expect(anim1.stop.mock.calls.length).toBe(0); - expect(anim2.stop.mock.calls.length).toBe(1); - expect(anim3.stop.mock.calls.length).toBe(1); - - anim3.start.mock.calls[0][0]({finished: false}); - - expect(anim1.stop.mock.calls.length).toBe(0); - expect(anim2.stop.mock.calls.length).toBe(1); - expect(anim3.stop.mock.calls.length).toBe(1); - }); - }); - - describe('Animated delays', () => { - it('should call anim after delay in sequence', () => { - var anim = {start: jest.fn(), stop: jest.fn()}; - var cb = jest.fn(); - Animated.sequence([ - Animated.delay(1000), - anim, - ]).start(cb); - jest.runAllTimers(); - expect(anim.start.mock.calls.length).toBe(1); - expect(cb).not.toBeCalled(); - anim.start.mock.calls[0][0]({finished: true}); - expect(cb).toBeCalledWith({finished: true}); - }); - it('should run stagger to end', () => { - var cb = jest.fn(); - Animated.stagger(1000, [ - Animated.delay(1000), - Animated.delay(1000), - Animated.delay(1000), - ]).start(cb); - jest.runAllTimers(); - expect(cb).toBeCalledWith({finished: true}); - }); - }); - - describe('Animated Events', () => { - it('should map events', () => { - var value = new Animated.Value(0); - var handler = Animated.event( - [null, {state: {foo: value}}], - ); - handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}}); - expect(value.__getValue()).toBe(42); - }); - it('should call listeners', () => { - var value = new Animated.Value(0); - var listener = jest.fn(); - var handler = Animated.event( - [{foo: value}], - {listener}, - ); - handler({foo: 42}); - expect(value.__getValue()).toBe(42); - expect(listener.mock.calls.length).toBe(1); - expect(listener).toBeCalledWith({foo: 42}); - }); - it('should call forked event listeners', () => { - var value = new Animated.Value(0); - var listener = jest.fn(); - var handler = Animated.event( - [{foo: value}], - {listener}, - ); - var listener2 = jest.fn(); - var forkedHandler = Animated.forkEvent(handler, listener2); - forkedHandler({foo: 42}); - expect(value.__getValue()).toBe(42); - expect(listener.mock.calls.length).toBe(1); - expect(listener).toBeCalledWith({foo: 42}); - expect(listener2.mock.calls.length).toBe(1); - expect(listener2).toBeCalledWith({foo: 42}); - }); - }); - - describe('Animated Interactions', () => { - /*eslint-disable no-shadow*/ - var Animated; - /*eslint-enable*/ - var InteractionManager; - - beforeEach(() => { - jest.mock('InteractionManager'); - Animated = require('Animated'); - InteractionManager = require('InteractionManager'); - }); - - afterEach(()=> { - jest.unmock('InteractionManager'); - }); - - it('registers an interaction by default', () => { - InteractionManager.createInteractionHandle.mockReturnValue(777); - - var value = new Animated.Value(0); - var callback = jest.fn(); - Animated.timing(value, { - toValue: 100, - duration: 100, - }).start(callback); - jest.runAllTimers(); - - expect(InteractionManager.createInteractionHandle).toBeCalled(); - expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777); - expect(callback).toBeCalledWith({finished: true}); - }); - - it('does not register an interaction when specified', () => { - var value = new Animated.Value(0); - var callback = jest.fn(); - Animated.timing(value, { - toValue: 100, - duration: 100, - isInteraction: false, - }).start(callback); - jest.runAllTimers(); - - expect(InteractionManager.createInteractionHandle).not.toBeCalled(); - expect(InteractionManager.clearInteractionHandle).not.toBeCalled(); - expect(callback).toBeCalledWith({finished: true}); - }); - }); - - describe('Animated Tracking', () => { - it('should track values', () => { - var value1 = new Animated.Value(0); - var value2 = new Animated.Value(0); - Animated.timing(value2, { - toValue: value1, - duration: 0, - }).start(); - value1.setValue(42); - expect(value2.__getValue()).toBe(42); - value1.setValue(7); - expect(value2.__getValue()).toBe(7); - }); - - it('should track interpolated values', () => { - var value1 = new Animated.Value(0); - var value2 = new Animated.Value(0); - Animated.timing(value2, { - toValue: value1.interpolate({ - inputRange: [0, 2], - outputRange: [0, 1] - }), - duration: 0, - }).start(); - value1.setValue(42); - expect(value2.__getValue()).toBe(42 / 2); - }); - - it('should stop tracking when animated', () => { - var value1 = new Animated.Value(0); - var value2 = new Animated.Value(0); - Animated.timing(value2, { - toValue: value1, - duration: 0, - }).start(); - value1.setValue(42); - expect(value2.__getValue()).toBe(42); - Animated.timing(value2, { - toValue: 7, - duration: 0, - }).start(); - value1.setValue(1492); - expect(value2.__getValue()).toBe(7); - }); - }); - - describe('Animated Vectors', () => { - it('should animate vectors', () => { - var vec = new Animated.ValueXY(); - - var callback = jest.fn(); - - var node = new Animated.__PropsOnlyForTests({ - style: { - opacity: vec.x.interpolate({ - inputRange: [0, 42], - outputRange: [0.2, 0.8], - }), - transform: vec.getTranslateTransform(), - ...vec.getLayout(), - } - }, callback); - - expect(node.__getValue()).toEqual({ - style: { - opacity: 0.2, - transform: [ - {translateX: 0}, - {translateY: 0}, - ], - left: 0, - top: 0, - }, - }); - - vec.setValue({x: 42, y: 1492}); - - expect(callback.mock.calls.length).toBe(2); // once each for x, y - - expect(node.__getValue()).toEqual({ - style: { - opacity: 0.8, - transform: [ - {translateX: 42}, - {translateY: 1492}, - ], - left: 42, - top: 1492, - }, - }); - - node.__detach(); - - vec.setValue({x: 1, y: 1}); - expect(callback.mock.calls.length).toBe(2); - }); - - it('should track vectors', () => { - var value1 = new Animated.ValueXY(); - var value2 = new Animated.ValueXY(); - Animated.timing(value2, { - toValue: value1, - duration: 0, - }).start(); - value1.setValue({x: 42, y: 1492}); - expect(value2.__getValue()).toEqual({x: 42, y: 1492}); - - // Make sure tracking keeps working (see stopTogether in ParallelConfig used - // by maybeVectorAnim). - value1.setValue({x: 3, y: 4}); - expect(value2.__getValue()).toEqual({x: 3, y: 4}); - }); - - it('should track with springs', () => { - var value1 = new Animated.ValueXY(); - var value2 = new Animated.ValueXY(); - Animated.spring(value2, { - toValue: value1, - tension: 3000, // faster spring for faster test - friction: 60, - }).start(); - value1.setValue({x: 1, y: 1}); - jest.runAllTimers(); - expect(Math.round(value2.__getValue().x)).toEqual(1); - expect(Math.round(value2.__getValue().y)).toEqual(1); - value1.setValue({x: 2, y: 2}); - jest.runAllTimers(); - expect(Math.round(value2.__getValue().x)).toEqual(2); - expect(Math.round(value2.__getValue().y)).toEqual(2); - }); - }); - - describe('Animated Listeners', () => { - it('should get updates', () => { - var value1 = new Animated.Value(0); - var listener = jest.fn(); - var id = value1.addListener(listener); - value1.setValue(42); - expect(listener.mock.calls.length).toBe(1); - expect(listener).toBeCalledWith({value: 42}); - expect(value1.__getValue()).toBe(42); - value1.setValue(7); - expect(listener.mock.calls.length).toBe(2); - expect(listener).toBeCalledWith({value: 7}); - expect(value1.__getValue()).toBe(7); - value1.removeListener(id); - value1.setValue(1492); - expect(listener.mock.calls.length).toBe(2); - expect(value1.__getValue()).toBe(1492); - }); - - it('should removeAll', () => { - var value1 = new Animated.Value(0); - var listener = jest.fn(); - [1,2,3,4].forEach(() => value1.addListener(listener)); - value1.setValue(42); - expect(listener.mock.calls.length).toBe(4); - expect(listener).toBeCalledWith({value: 42}); - value1.removeAllListeners(); - value1.setValue(7); - expect(listener.mock.calls.length).toBe(4); - }); - }); - - describe('Animated Diff Clamp', () => { - it('should get the proper value', () => { - const inputValues = [0, 20, 40, 30, 0, -40, -10, -20, 0]; - const expectedValues = [0, 20, 20, 10, 0, 0, 20, 10, 20]; - const value = new Animated.Value(0); - const diffClampValue = Animated.diffClamp(value, 0, 20); - for (let i = 0; i < inputValues.length; i++) { - value.setValue(inputValues[i]); - expect(diffClampValue.__getValue()).toBe(expectedValues[i]); - } - }); - }); -}); diff --git a/Libraries/Animated/src/__tests__/AnimatedNative-test.js b/Libraries/Animated/src/__tests__/AnimatedNative-test.js deleted file mode 100644 index 1c6700e4dc0652..00000000000000 --- a/Libraries/Animated/src/__tests__/AnimatedNative-test.js +++ /dev/null @@ -1,704 +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. - * - * @emails oncall+react_native - */ -'use strict'; - -const ClassComponentMock = class {}; -ClassComponentMock.prototype.isReactComponent = true; - -jest - .clearAllMocks() - .setMock('Text', ClassComponentMock) - .setMock('View', ClassComponentMock) - .setMock('Image', ClassComponentMock) - .setMock('ScrollView', ClassComponentMock) - .setMock('React', {Component: class {}}) - .setMock('NativeModules', { - NativeAnimatedModule: {}, - }) - .mock('NativeEventEmitter') - // findNodeHandle is imported from ReactNative so mock that whole module. - .setMock('ReactNative', {findNodeHandle: () => 1}); - -const Animated = require('Animated'); -const NativeAnimatedHelper = require('NativeAnimatedHelper'); - -function createAndMountComponent(ComponentClass, props) { - const component = new ComponentClass(); - component.props = props; - component.UNSAFE_componentWillMount(); - // Simulate that refs were set. - component._component = {}; - component.componentDidMount(); - return component; -} - -describe('Native Animated', () => { - - const nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; - - beforeEach(() => { - nativeAnimatedModule.addAnimatedEventToView = jest.fn(); - nativeAnimatedModule.connectAnimatedNodes = jest.fn(); - nativeAnimatedModule.connectAnimatedNodeToView = jest.fn(); - nativeAnimatedModule.createAnimatedNode = jest.fn(); - nativeAnimatedModule.disconnectAnimatedNodeFromView = jest.fn(); - nativeAnimatedModule.disconnectAnimatedNodes = jest.fn(); - nativeAnimatedModule.dropAnimatedNode = jest.fn(); - nativeAnimatedModule.extractAnimatedNodeOffset = jest.fn(); - nativeAnimatedModule.flattenAnimatedNodeOffset = jest.fn(); - nativeAnimatedModule.removeAnimatedEventFromView = jest.fn(); - nativeAnimatedModule.setAnimatedNodeOffset = jest.fn(); - nativeAnimatedModule.setAnimatedNodeValue = jest.fn(); - nativeAnimatedModule.startAnimatingNode = jest.fn(); - nativeAnimatedModule.startListeningToAnimatedNodeValue = jest.fn(); - nativeAnimatedModule.stopAnimation = jest.fn(); - nativeAnimatedModule.stopListeningToAnimatedNodeValue = jest.fn(); - }); - - describe('Animated Value', () => { - it('proxies `setValue` correctly', () => { - const anim = new Animated.Value(0); - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - const c = createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - // We expect `setValue` not to propagate down to `setNativeProps`, otherwise it may try to access `setNativeProps` - // via component refs table that we override here. - c.refs = { - node: { - setNativeProps: jest.genMockFunction(), - }, - }; - - anim.setValue(0.5); - - expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(expect.any(Number), 0.5); - expect(c.refs.node.setNativeProps).not.toHaveBeenCalled(); - }); - - it('should set offset', () => { - const anim = new Animated.Value(0); - anim.setOffset(10); - anim.__makeNative(); - createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'value', value: 0, offset: 10}, - ); - anim.setOffset(20); - expect(nativeAnimatedModule.setAnimatedNodeOffset) - .toBeCalledWith(expect.any(Number), 20); - }); - - it('should flatten offset', () => { - const anim = new Animated.Value(0); - anim.__makeNative(); - createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'value', value: 0, offset: 0}, - ); - anim.flattenOffset(); - expect(nativeAnimatedModule.flattenAnimatedNodeOffset) - .toBeCalledWith(expect.any(Number)); - }); - - it('should extract offset', () => { - const anim = new Animated.Value(0); - anim.__makeNative(); - createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'value', value: 0, offset: 0}, - ); - anim.extractOffset(); - expect(nativeAnimatedModule.extractAnimatedNodeOffset) - .toBeCalledWith(expect.any(Number)); - }); - }); - - describe('Animated Listeners', () => { - it('should get updates', () => { - const value1 = new Animated.Value(0); - value1.__makeNative(); - const listener = jest.fn(); - const id = value1.addListener(listener); - expect(nativeAnimatedModule.startListeningToAnimatedNodeValue) - .toHaveBeenCalledWith(value1.__getNativeTag()); - - NativeAnimatedHelper.nativeEventEmitter.emit( - 'onAnimatedValueUpdate', - {value: 42, tag: value1.__getNativeTag()}, - ); - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toBeCalledWith({value: 42}); - expect(value1.__getValue()).toBe(42); - - NativeAnimatedHelper.nativeEventEmitter.emit( - 'onAnimatedValueUpdate', - {value: 7, tag: value1.__getNativeTag()}, - ); - expect(listener).toHaveBeenCalledTimes(2); - expect(listener).toBeCalledWith({value: 7}); - expect(value1.__getValue()).toBe(7); - - value1.removeListener(id); - expect(nativeAnimatedModule.stopListeningToAnimatedNodeValue) - .toHaveBeenCalledWith(value1.__getNativeTag()); - - NativeAnimatedHelper.nativeEventEmitter.emit( - 'onAnimatedValueUpdate', - {value: 1492, tag: value1.__getNativeTag()}, - ); - expect(listener).toHaveBeenCalledTimes(2); - expect(value1.__getValue()).toBe(7); - }); - - it('should removeAll', () => { - const value1 = new Animated.Value(0); - value1.__makeNative(); - const listener = jest.fn(); - [1,2,3,4].forEach(() => value1.addListener(listener)); - expect(nativeAnimatedModule.startListeningToAnimatedNodeValue) - .toHaveBeenCalledWith(value1.__getNativeTag()); - - NativeAnimatedHelper.nativeEventEmitter.emit( - 'onAnimatedValueUpdate', - {value: 42, tag: value1.__getNativeTag()}, - ); - expect(listener).toHaveBeenCalledTimes(4); - expect(listener).toBeCalledWith({value: 42}); - - value1.removeAllListeners(); - expect(nativeAnimatedModule.stopListeningToAnimatedNodeValue) - .toHaveBeenCalledWith(value1.__getNativeTag()); - - NativeAnimatedHelper.nativeEventEmitter.emit( - 'onAnimatedValueUpdate', - {value: 7, tag: value1.__getNativeTag()}, - ); - expect(listener).toHaveBeenCalledTimes(4); - }); - }); - - describe('Animated Events', () => { - it('should map events', () => { - const value = new Animated.Value(0); - value.__makeNative(); - const event = Animated.event( - [{nativeEvent: {state: {foo: value}}}], - {useNativeDriver: true}, - ); - const c = createAndMountComponent(Animated.View, {onTouchMove: event}); - expect(nativeAnimatedModule.addAnimatedEventToView).toBeCalledWith( - expect.any(Number), - 'onTouchMove', - {nativeEventPath: ['state', 'foo'], animatedValueTag: value.__getNativeTag()}, - ); - - c.componentWillUnmount(); - expect(nativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith( - expect.any(Number), - 'onTouchMove', - value.__getNativeTag(), - ); - }); - - it('should throw on invalid event path', () => { - const value = new Animated.Value(0); - value.__makeNative(); - const event = Animated.event( - [{notNativeEvent: {foo: value}}], - {useNativeDriver: true}, - ); - expect(() => createAndMountComponent(Animated.View, {onTouchMove: event})) - .toThrowError(/nativeEvent/); - expect(nativeAnimatedModule.addAnimatedEventToView).not.toBeCalled(); - }); - - it('should call listeners', () => { - const value = new Animated.Value(0); - value.__makeNative(); - const listener = jest.fn(); - const event = Animated.event( - [{nativeEvent: {foo: value}}], - {useNativeDriver: true, listener}, - ); - const handler = event.__getHandler(); - handler({foo: 42}); - expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toBeCalledWith({foo: 42}); - }); - }); - - describe('Animated Graph', () => { - it('creates and detaches nodes', () => { - const anim = new Animated.Value(0); - const c = createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - c.componentWillUnmount(); - - expect(nativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3); - expect(nativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(2); - - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - {type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1}, - expect.any(Function) - ); - - expect(nativeAnimatedModule.disconnectAnimatedNodes).toHaveBeenCalledTimes(2); - expect(nativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3); - }); - - it('sends a valid description for value, style and props nodes', () => { - const anim = new Animated.Value(0); - createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), {type: 'value', value: 0, offset: 0}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), {type: 'style', style: {opacity: expect.any(Number)}}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), {type: 'props', props: {style: expect.any(Number)}}); - }); - - it('sends a valid graph description for Animated.add nodes', () => { - const first = new Animated.Value(1); - const second = new Animated.Value(2); - first.__makeNative(); - second.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: Animated.add(first, second), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'addition', input: expect.any(Array)}, - ); - const additionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'addition' - ); - expect(additionCalls.length).toBe(1); - const additionCall = additionCalls[0]; - const additionNodeTag = additionCall[0]; - const additionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === additionNodeTag - ); - expect(additionConnectionCalls.length).toBe(2); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(additionCall[1].input[0], {type: 'value', value: 1, offset: 0}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(additionCall[1].input[1], {type: 'value', value: 2, offset: 0}); - }); - - it('sends a valid graph description for Animated.multiply nodes', () => { - const first = new Animated.Value(2); - const second = new Animated.Value(1); - first.__makeNative(); - second.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: Animated.multiply(first, second), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'multiplication', input: expect.any(Array)}, - ); - const multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'multiplication' - ); - expect(multiplicationCalls.length).toBe(1); - const multiplicationCall = multiplicationCalls[0]; - const multiplicationNodeTag = multiplicationCall[0]; - const multiplicationConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === multiplicationNodeTag - ); - expect(multiplicationConnectionCalls.length).toBe(2); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(multiplicationCall[1].input[0], {type: 'value', value: 2, offset: 0}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(multiplicationCall[1].input[1], {type: 'value', value: 1, offset: 0}); - }); - - it('sends a valid graph description for Animated.divide nodes', () => { - const first = new Animated.Value(4); - const second = new Animated.Value(2); - first.__makeNative(); - second.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: Animated.divide(first, second), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), {type: 'division', input: expect.any(Array)}); - const divisionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'division' - ); - expect(divisionCalls.length).toBe(1); - const divisionCall = divisionCalls[0]; - const divisionNodeTag = divisionCall[0]; - const divisionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === divisionNodeTag - ); - expect(divisionConnectionCalls.length).toBe(2); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(divisionCall[1].input[0], {type: 'value', value: 4, offset: 0}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(divisionCall[1].input[1], {type: 'value', value: 2, offset: 0}); - }); - - it('sends a valid graph description for Animated.modulo nodes', () => { - const value = new Animated.Value(4); - value.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: Animated.modulo(value, 4), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'modulus', modulus: 4, input: expect.any(Number)}, - ); - const moduloCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'modulus' - ); - expect(moduloCalls.length).toBe(1); - const moduloCall = moduloCalls[0]; - const moduloNodeTag = moduloCall[0]; - const moduloConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === moduloNodeTag - ); - expect(moduloConnectionCalls.length).toBe(1); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(moduloCall[1].input, {type: 'value', value: 4, offset: 0}); - }); - - it('sends a valid graph description for interpolate() nodes', () => { - const value = new Animated.Value(10); - value.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: value.interpolate({ - inputRange: [10, 20], - outputRange: [0, 1], - }), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'value', value: 10, offset: 0} - ); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), { - type: 'interpolation', - inputRange: [10, 20], - outputRange: [0, 1], - extrapolateLeft: 'extend', - extrapolateRight: 'extend', - }); - const interpolationNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find( - (call) => call[1].type === 'interpolation' - )[0]; - const valueNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find( - (call) => call[1].type === 'value' - )[0]; - expect(nativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(valueNodeTag, interpolationNodeTag); - }); - - it('sends a valid graph description for transform nodes', () => { - const value = new Animated.Value(0); - value.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - transform: [{translateX: value}, {scale: 2}], - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - { - type: 'transform', - transforms: [{ - nodeTag: expect.any(Number), - property: 'translateX', - type: 'animated', - }, { - value: 2, - property: 'scale', - type: 'static', - }], - }, - ); - }); - - it('sends a valid graph description for Animated.diffClamp nodes', () => { - const value = new Animated.Value(2); - value.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - opacity: Animated.diffClamp(value, 0, 20), - }, - }); - - expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith( - expect.any(Number), - {type: 'diffclamp', input: expect.any(Number), max: 20, min: 0}, - ); - const diffClampCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( - (call) => call[1].type === 'diffclamp' - ); - expect(diffClampCalls.length).toBe(1); - const diffClampCall = diffClampCalls[0]; - const diffClampNodeTag = diffClampCall[0]; - const diffClampConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( - (call) => call[1] === diffClampNodeTag - ); - expect(diffClampConnectionCalls.length).toBe(1); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(diffClampCall[1].input, {type: 'value', value: 2, offset: 0}); - }); - - it('doesn\'t call into native API if useNativeDriver is set to false', () => { - const anim = new Animated.Value(0); - - const c = createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: false}).start(); - - c.componentWillUnmount(); - - expect(nativeAnimatedModule.createAnimatedNode).not.toBeCalled(); - }); - - it('fails when trying to run non-native animation on native node', () => { - const anim = new Animated.Value(0); - - createAndMountComponent(Animated.View, { - style: { - opacity: anim, - }, - }); - - Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}).start(); - jest.runAllTimers(); - - Animated.timing(anim, {toValue: 4, duration: 500, useNativeDriver: false}).start(); - expect(jest.runAllTimers).toThrow(); - }); - - it('fails for unsupported styles', () => { - const anim = new Animated.Value(0); - - createAndMountComponent(Animated.View, { - style: { - left: anim, - }, - }); - - const animation = Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}); - expect(animation.start).toThrowError(/left/); - }); - - it('works for any `static` props and styles', () => { - // Passing "unsupported" props should work just fine as long as they are not animated - const value = new Animated.Value(0); - value.__makeNative(); - - createAndMountComponent(Animated.View, { - style: { - left: 10, - top: 20, - opacity: value, - }, - removeClippedSubviews: true, - }); - - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), { type: 'style', style: { opacity: expect.any(Number) }}); - expect(nativeAnimatedModule.createAnimatedNode) - .toBeCalledWith(expect.any(Number), { type: 'props', props: { style: expect.any(Number) }}); - }); - }); - - describe('Animations', () => { - it('sends a valid timing animation description', () => { - const anim = new Animated.Value(0); - Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); - - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - {type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1}, - expect.any(Function) - ); - }); - - it('sends a valid spring animation description', () => { - const anim = new Animated.Value(0); - Animated.spring(anim, {toValue: 10, friction: 5, tension: 164, useNativeDriver: true}).start(); - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - { - type: 'spring', - stiffness: 679.08, - damping: 16, - mass: 1, - initialVelocity: 0, - overshootClamping: false, - restDisplacementThreshold: 0.001, - restSpeedThreshold: 0.001, - toValue: 10, - iterations: 1, - }, - expect.any(Function) - ); - - Animated.spring(anim, { - toValue: 10, - stiffness: 1000, - damping: 500, - mass: 3, - useNativeDriver: true - }).start(); - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - { - type: 'spring', - stiffness: 1000, - damping: 500, - mass: 3, - initialVelocity: 0, - overshootClamping: false, - restDisplacementThreshold: 0.001, - restSpeedThreshold: 0.001, - toValue: 10, - iterations: 1, - }, - expect.any(Function) - ); - - Animated.spring(anim, {toValue: 10, bounciness: 8, speed: 10, useNativeDriver: true}).start(); - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - { - type: 'spring', - damping: 23.05223140901191, - initialVelocity: 0, - overshootClamping: false, - restDisplacementThreshold: 0.001, - restSpeedThreshold: 0.001, - stiffness: 299.61882352941177, - mass: 1, - toValue: 10, - iterations: 1, - }, - expect.any(Function) - ); - }); - - it('sends a valid decay animation description', () => { - const anim = new Animated.Value(0); - Animated.decay(anim, {velocity: 10, deceleration: 0.1, useNativeDriver: true}).start(); - - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - {type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1}, - expect.any(Function) - ); - }); - - it('works with Animated.loop', () => { - const anim = new Animated.Value(0); - Animated.loop( - Animated.decay(anim, {velocity: 10, deceleration: 0.1, useNativeDriver: true}), - { iterations: 10 }, - ).start(); - - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - {type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10}, - expect.any(Function) - ); - }); - - it('sends stopAnimation command to native', () => { - const value = new Animated.Value(0); - const animation = Animated.timing(value, {toValue: 10, duration: 50, useNativeDriver: true}); - - animation.start(); - expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith( - expect.any(Number), - expect.any(Number), - {type: 'frames', frames: expect.any(Array), toValue: expect.any(Number), iterations: 1}, - expect.any(Function) - ); - const animationId = nativeAnimatedModule.startAnimatingNode.mock.calls[0][0]; - - animation.stop(); - expect(nativeAnimatedModule.stopAnimation).toBeCalledWith(animationId); - }); - }); -}); diff --git a/Libraries/Animated/src/__tests__/Easing-test.js b/Libraries/Animated/src/__tests__/Easing-test.js deleted file mode 100644 index 14f18aacd628e0..00000000000000 --- a/Libraries/Animated/src/__tests__/Easing-test.js +++ /dev/null @@ -1,125 +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. - * - * @emails oncall+react_native - */ -'use strict'; - -var Easing = require('Easing'); -describe('Easing', () => { - it('should work with linear', () => { - var easing = Easing.linear; - - expect(easing(0)).toBe(0); - expect(easing(0.5)).toBe(0.5); - expect(easing(0.8)).toBe(0.8); - expect(easing(1)).toBe(1); - }); - - it('should work with ease in linear', () => { - var easing = Easing.in(Easing.linear); - expect(easing(0)).toBe(0); - expect(easing(0.5)).toBe(0.5); - expect(easing(0.8)).toBe(0.8); - expect(easing(1)).toBe(1); - }); - - it('should work with easy out linear', () => { - var easing = Easing.out(Easing.linear); - expect(easing(0)).toBe(0); - expect(easing(0.5)).toBe(0.5); - expect(easing(0.6)).toBe(0.6); - expect(easing(1)).toBe(1); - }); - - it('should work with ease in quad', () => { - function easeInQuad(t) { - return t * t; - } - var easing = Easing.in(Easing.quad); - for (var t = -0.5; t < 1.5; t += 0.1) { - expect(easing(t)).toBe(easeInQuad(t)); - } - }); - - it('should work with ease out quad', () => { - function easeOutQuad(t) { - return -t * (t - 2); - } - var easing = Easing.out(Easing.quad); - for (var t = 0; t <= 1; t += 0.1) { - expect(easing(1)).toBe(easeOutQuad(1)); - } - }); - - it('should work with ease in-out quad', () => { - function easeInOutQuad(t) { - t = t * 2; - if (t < 1) { - return 0.5 * t * t; - } - return -((t - 1) * (t - 3) - 1) / 2; - } - var easing = Easing.inOut(Easing.quad); - for (var t = -0.5; t < 1.5; t += 0.1) { - expect(easing(t)).toBeCloseTo(easeInOutQuad(t), 4); - } - }); - - it('should satisfy boundary conditions with elastic', () => { - for (var b = 0; b < 4; b += 0.3) { - var easing = Easing.elastic(b); - expect(easing(0)).toBe(0); - expect(easing(1)).toBe(1); - } - }); - - function sampleEasingFunction(easing) { - var DURATION = 300; - var tickCount = Math.round(DURATION * 60 / 1000); - var samples = []; - for (var i = 0; i <= tickCount; i++) { - samples.push(easing(i / tickCount)); - } - return samples; - } - - var Samples = { - in_quad: [0,0.0030864197530864196,0.012345679012345678,0.027777777777777776,0.04938271604938271,0.0771604938271605,0.1111111111111111,0.15123456790123457,0.19753086419753085,0.25,0.308641975308642,0.37345679012345684,0.4444444444444444,0.5216049382716049,0.6049382716049383,0.6944444444444445,0.7901234567901234,0.8919753086419753,1], - out_quad: [0,0.10802469135802469,0.20987654320987653,0.3055555555555555,0.3950617283950617,0.47839506172839513,0.5555555555555556,0.6265432098765432,0.691358024691358,0.75,0.8024691358024691,0.8487654320987654,0.888888888888889,0.9228395061728394,0.9506172839506174,0.9722222222222221,0.9876543209876543,0.9969135802469136,1], - inOut_quad: [0,0.006172839506172839,0.024691358024691357,0.05555555555555555,0.09876543209876543,0.154320987654321,0.2222222222222222,0.30246913580246915,0.3950617283950617,0.5,0.6049382716049383,0.697530864197531,0.7777777777777777,0.845679012345679,0.9012345679012346,0.9444444444444444,0.9753086419753086,0.9938271604938271,1], - in_cubic: [0,0.00017146776406035664,0.0013717421124828531,0.004629629629629629,0.010973936899862825,0.021433470507544586,0.037037037037037035,0.05881344307270234,0.0877914951989026,0.125,0.1714677640603567,0.22822359396433475,0.2962962962962963,0.37671467764060357,0.4705075445816187,0.5787037037037038,0.7023319615912208,0.8424211248285322,1], - out_cubic: [0,0.15757887517146785,0.2976680384087792,0.42129629629629617,0.5294924554183813,0.6232853223593964,0.7037037037037036,0.7717764060356652,0.8285322359396433,0.875,0.9122085048010974,0.9411865569272977,0.9629629629629629,0.9785665294924554,0.9890260631001372,0.9953703703703703,0.9986282578875172,0.9998285322359396,1], - inOut_cubic: [0,0.0006858710562414266,0.0054869684499314125,0.018518518518518517,0.0438957475994513,0.08573388203017834,0.14814814814814814,0.23525377229080935,0.3511659807956104,0.5,0.6488340192043895,0.7647462277091908,0.8518518518518519,0.9142661179698217,0.9561042524005487,0.9814814814814815,0.9945130315500685,0.9993141289437586,1], - in_sin: [0,0.003805301908254455,0.01519224698779198,0.03407417371093169,0.06030737921409157,0.09369221296335006,0.1339745962155613,0.1808479557110082,0.233955556881022,0.2928932188134524,0.35721239031346064,0.42642356364895384,0.4999999999999999,0.5773817382593005,0.6579798566743311,0.7411809548974793,0.8263518223330696,0.9128442572523416,0.9999999999999999], - out_sin: [0,0.08715574274765817,0.17364817766693033,0.25881904510252074,0.3420201433256687,0.42261826174069944,0.49999999999999994,0.573576436351046,0.6427876096865393,0.7071067811865475,0.766044443118978,0.8191520442889918,0.8660254037844386,0.9063077870366499,0.9396926207859083,0.9659258262890683,0.984807753012208,0.9961946980917455,1], - inOut_sin: [0,0.00759612349389599,0.030153689607045786,0.06698729810778065,0.116977778440511,0.17860619515673032,0.24999999999999994,0.32898992833716556,0.4131759111665348,0.49999999999999994,0.5868240888334652,0.6710100716628343,0.7499999999999999,0.8213938048432696,0.883022221559489,0.9330127018922194,0.9698463103929542,0.9924038765061041,1], - in_exp: [0,0.0014352875901128893,0.002109491677524035,0.0031003926796253885,0.004556754060844206,0.006697218616039631,0.009843133202303688,0.014466792379488908,0.021262343752724643,0.03125,0.045929202883612456,0.06750373368076916,0.09921256574801243,0.1458161299470146,0.2143109957132682,0.31498026247371835,0.46293735614364506,0.6803950000871883,1], - out_exp: [0,0.31960499991281155,0.5370626438563548,0.6850197375262816,0.7856890042867318,0.8541838700529854,0.9007874342519875,0.9324962663192309,0.9540707971163875,0.96875,0.9787376562472754,0.9855332076205111,0.9901568667976963,0.9933027813839603,0.9954432459391558,0.9968996073203746,0.9978905083224759,0.9985647124098871,1], - inOut_exp: [0,0.0010547458387620175,0.002278377030422103,0.004921566601151844,0.010631171876362321,0.022964601441806228,0.049606282874006216,0.1071554978566341,0.23146867807182253,0.5,0.7685313219281775,0.892844502143366,0.9503937171259937,0.9770353985581938,0.9893688281236377,0.9950784333988482,0.9977216229695779,0.998945254161238,1], - in_circle: [0,0.0015444024660317135,0.006192010000093506,0.013986702816730645,0.025003956956430873,0.03935464078941209,0.057190958417936644,0.07871533601238889,0.10419358352238339,0.1339745962155614,0.1685205807169019,0.20845517506805522,0.2546440075000701,0.3083389112228482,0.37146063894529113,0.4472292016074334,0.5418771527091488,0.6713289009389102,1], - out_circle: [0,0.3286710990610898,0.45812284729085123,0.5527707983925666,0.6285393610547089,0.6916610887771518,0.7453559924999298,0.7915448249319448,0.8314794192830981,0.8660254037844386,0.8958064164776166,0.9212846639876111,0.9428090415820634,0.9606453592105879,0.9749960430435691,0.9860132971832694,0.9938079899999065,0.9984555975339683,1], - inOut_circle: [0,0.003096005000046753,0.012501978478215436,0.028595479208968322,0.052096791761191696,0.08426029035845095,0.12732200375003505,0.18573031947264557,0.2709385763545744,0.5,0.7290614236454256,0.8142696805273546,0.8726779962499649,0.915739709641549,0.9479032082388084,0.9714045207910317,0.9874980215217846,0.9969039949999532,1], - in_back_: [0,-0.004788556241426612,-0.017301289437585736,-0.0347587962962963,-0.05438167352537723,-0.07339051783264748,-0.08900592592592595,-0.09844849451303156,-0.0989388203017833,-0.08769750000000004,-0.06194513031550073,-0.018902307956104283,0.044210370370370254,0.13017230795610413,0.2417629080932785,0.3817615740740742,0.5529477091906719,0.7581007167352535,0.9999999999999998], - out_back_: [2.220446049250313e-16,0.24189928326474652,0.44705229080932807,0.6182384259259258,0.7582370919067215,0.8698276920438959,0.9557896296296297,1.0189023079561044,1.0619451303155008,1.0876975,1.0989388203017834,1.0984484945130315,1.089005925925926,1.0733905178326475,1.0543816735253773,1.0347587962962963,1.0173012894375857,1.0047885562414267,1], - }; - - Object.keys(Samples).forEach(function(type) { - it('should ease ' + type, function() { - var [modeName, easingName, isFunction] = type.split('_'); - var easing = Easing[easingName]; - if (isFunction !== undefined) { - easing = easing(); - } - var computed = sampleEasingFunction(Easing[modeName](easing)); - var samples = Samples[type]; - - computed.forEach((value, key) => { - expect(value).toBeCloseTo(samples[key], 2); - }); - }); - }); -}); diff --git a/Libraries/Animated/src/__tests__/bezier-test.js b/Libraries/Animated/src/__tests__/bezier-test.js deleted file mode 100644 index 3f1070a48fd71e..00000000000000 --- a/Libraries/Animated/src/__tests__/bezier-test.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * BezierEasing - use bezier curve for transition easing function - * https://github.com/gre/bezier-easing - * - * @copyright 2014-2015 Gaetan Renaudeau. MIT License. - * @noflow - * @emails oncall+react_native - * @format - */ - -'use strict'; - -var bezier = require('bezier'); - -var identity = function(x) { - return x; -}; - -function assertClose(a, b, precision = 3) { - expect(a).toBeCloseTo(b, precision); -} - -function makeAssertCloseWithPrecision(precision) { - return function(a, b) { - assertClose(a, b, precision); - }; -} - -function allEquals(be1, be2, samples, assertion) { - if (!assertion) { - assertion = assertClose; - } - for (var i = 0; i <= samples; ++i) { - var x = i / samples; - assertion(be1(x), be2(x)); - } -} - -function repeat(n) { - return function(f) { - for (var i = 0; i < n; ++i) { - f(i); - } - }; -} - -describe('bezier', function() { - it('should be a function', function() { - expect(typeof bezier === 'function').toBe(true); - }); - it('should creates an object', function() { - expect(typeof bezier(0, 0, 1, 1) === 'function').toBe(true); - }); - it('should fail with wrong arguments', function() { - expect(function() { - bezier(0.5, 0.5, -5, 0.5); - }).toThrow(); - expect(function() { - bezier(0.5, 0.5, 5, 0.5); - }).toThrow(); - expect(function() { - bezier(-2, 0.5, 0.5, 0.5); - }).toThrow(); - expect(function() { - bezier(2, 0.5, 0.5, 0.5); - }).toThrow(); - }); - describe('linear curves', function() { - it('should be linear', function() { - allEquals(bezier(0, 0, 1, 1), bezier(1, 1, 0, 0), 100); - allEquals(bezier(0, 0, 1, 1), identity, 100); - }); - }); - describe('common properties', function() { - it('should be the right value at extremes', function() { - repeat(10)(function() { - var a = Math.random(), - b = 2 * Math.random() - 0.5, - c = Math.random(), - d = 2 * Math.random() - 0.5; - var easing = bezier(a, b, c, d); - expect(easing(0)).toBe(0); - expect(easing(1)).toBe(1); - }); - }); - - it('should approach the projected value of its x=y projected curve', function() { - repeat(10)(function() { - var a = Math.random(), - b = Math.random(), - c = Math.random(), - d = Math.random(); - var easing = bezier(a, b, c, d); - var projected = bezier(b, a, d, c); - var composed = function(x) { - return projected(easing(x)); - }; - allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2)); - }); - }); - }); - describe('two same instances', function() { - it('should be strictly equals', function() { - repeat(10)(function() { - var a = Math.random(), - b = 2 * Math.random() - 0.5, - c = Math.random(), - d = 2 * Math.random() - 0.5; - allEquals(bezier(a, b, c, d), bezier(a, b, c, d), 100, 0); - }); - }); - }); - describe('symetric curves', function() { - it('should have a central value y~=0.5 at x=0.5', function() { - repeat(10)(function() { - var a = Math.random(), - b = 2 * Math.random() - 0.5, - c = 1 - a, - d = 1 - b; - var easing = bezier(a, b, c, d); - assertClose(easing(0.5), 0.5, 2); - }); - }); - it('should be symmetrical', function() { - repeat(10)(function() { - var a = Math.random(), - b = 2 * Math.random() - 0.5, - c = 1 - a, - d = 1 - b; - var easing = bezier(a, b, c, d); - var sym = function(x) { - return 1 - easing(1 - x); - }; - allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2)); - }); - }); - }); -}); diff --git a/Libraries/Animated/src/animations/Animation.js b/Libraries/Animated/src/animations/Animation.js deleted file mode 100644 index 000e44d77f9bc8..00000000000000 --- a/Libraries/Animated/src/animations/Animation.js +++ /dev/null @@ -1,71 +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 Animation - * @flow - * @format - */ -'use strict'; - -const NativeAnimatedHelper = require('NativeAnimatedHelper'); - -import type AnimatedValue from '../nodes/AnimatedValue'; - -export type EndResult = {finished: boolean}; -export type EndCallback = (result: EndResult) => void; - -export type AnimationConfig = { - isInteraction?: boolean, - useNativeDriver?: boolean, - onComplete?: ?EndCallback, - iterations?: number, -}; - -// Important note: start() and stop() will only be called at most once. -// Once an animation has been stopped or finished its course, it will -// not be reused. -class Animation { - __active: boolean; - __isInteraction: boolean; - __nativeId: number; - __onEnd: ?EndCallback; - __iterations: number; - start( - fromValue: number, - onUpdate: (value: number) => void, - onEnd: ?EndCallback, - previousAnimation: ?Animation, - animatedValue: AnimatedValue, - ): void {} - stop(): void { - if (this.__nativeId) { - NativeAnimatedHelper.API.stopAnimation(this.__nativeId); - } - } - __getNativeAnimationConfig(): any { - // Subclasses that have corresponding animation implementation done in native - // should override this method - throw new Error('This animation type cannot be offloaded to native'); - } - // Helper function for subclasses to make sure onEnd is only called once. - __debouncedOnEnd(result: EndResult): void { - const onEnd = this.__onEnd; - this.__onEnd = null; - onEnd && onEnd(result); - } - __startNativeAnimation(animatedValue: AnimatedValue): void { - animatedValue.__makeNative(); - this.__nativeId = NativeAnimatedHelper.generateNewAnimationId(); - NativeAnimatedHelper.API.startAnimatingNode( - this.__nativeId, - animatedValue.__getNativeTag(), - this.__getNativeAnimationConfig(), - this.__debouncedOnEnd.bind(this), - ); - } -} - -module.exports = Animation; diff --git a/Libraries/Animated/src/animations/DecayAnimation.js b/Libraries/Animated/src/animations/DecayAnimation.js deleted file mode 100644 index 7df0e104b72f48..00000000000000 --- a/Libraries/Animated/src/animations/DecayAnimation.js +++ /dev/null @@ -1,110 +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 DecayAnimation - * @flow - * @format - */ -'use strict'; - -const Animation = require('./Animation'); - -const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); - -import type {AnimationConfig, EndCallback} from './Animation'; -import type AnimatedValue from '../nodes/AnimatedValue'; - -export type DecayAnimationConfig = AnimationConfig & { - velocity: number | {x: number, y: number}, - deceleration?: number, -}; - -export type DecayAnimationConfigSingle = AnimationConfig & { - velocity: number, - deceleration?: number, -}; - -class DecayAnimation extends Animation { - _startTime: number; - _lastValue: number; - _fromValue: number; - _deceleration: number; - _velocity: number; - _onUpdate: (value: number) => void; - _animationFrame: any; - _useNativeDriver: boolean; - - constructor(config: DecayAnimationConfigSingle) { - super(); - this._deceleration = - config.deceleration !== undefined ? config.deceleration : 0.998; - this._velocity = config.velocity; - this._useNativeDriver = shouldUseNativeDriver(config); - this.__isInteraction = - config.isInteraction !== undefined ? config.isInteraction : true; - this.__iterations = config.iterations !== undefined ? config.iterations : 1; - } - - __getNativeAnimationConfig() { - return { - type: 'decay', - deceleration: this._deceleration, - velocity: this._velocity, - iterations: this.__iterations, - }; - } - - start( - fromValue: number, - onUpdate: (value: number) => void, - onEnd: ?EndCallback, - previousAnimation: ?Animation, - animatedValue: AnimatedValue, - ): void { - this.__active = true; - this._lastValue = fromValue; - this._fromValue = fromValue; - this._onUpdate = onUpdate; - this.__onEnd = onEnd; - this._startTime = Date.now(); - if (this._useNativeDriver) { - this.__startNativeAnimation(animatedValue); - } else { - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - } - - onUpdate(): void { - const now = Date.now(); - - const value = - this._fromValue + - this._velocity / - (1 - this._deceleration) * - (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); - - this._onUpdate(value); - - if (Math.abs(this._lastValue - value) < 0.1) { - this.__debouncedOnEnd({finished: true}); - return; - } - - this._lastValue = value; - if (this.__active) { - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - } - - stop(): void { - super.stop(); - this.__active = false; - global.cancelAnimationFrame(this._animationFrame); - this.__debouncedOnEnd({finished: false}); - } -} - -module.exports = DecayAnimation; diff --git a/Libraries/Animated/src/animations/TimingAnimation.js b/Libraries/Animated/src/animations/TimingAnimation.js deleted file mode 100644 index 1b6a4ae94cd9b0..00000000000000 --- a/Libraries/Animated/src/animations/TimingAnimation.js +++ /dev/null @@ -1,153 +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 TimingAnimation - * @flow - * @format - */ -'use strict'; - -const AnimatedValue = require('../nodes/AnimatedValue'); -const AnimatedValueXY = require('../nodes/AnimatedValueXY'); -const Animation = require('./Animation'); - -const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); - -import type {AnimationConfig, EndCallback} from './Animation'; - -export type TimingAnimationConfig = AnimationConfig & { - toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY, - easing?: (value: number) => number, - duration?: number, - delay?: number, -}; - -export type TimingAnimationConfigSingle = AnimationConfig & { - toValue: number | AnimatedValue, - easing?: (value: number) => number, - duration?: number, - delay?: number, -}; - -let _easeInOut; -function easeInOut() { - if (!_easeInOut) { - const Easing = require('Easing'); - _easeInOut = Easing.inOut(Easing.ease); - } - return _easeInOut; -} - -class TimingAnimation extends Animation { - _startTime: number; - _fromValue: number; - _toValue: any; - _duration: number; - _delay: number; - _easing: (value: number) => number; - _onUpdate: (value: number) => void; - _animationFrame: any; - _timeout: any; - _useNativeDriver: boolean; - - constructor(config: TimingAnimationConfigSingle) { - super(); - this._toValue = config.toValue; - this._easing = config.easing !== undefined ? config.easing : easeInOut(); - this._duration = config.duration !== undefined ? config.duration : 500; - this._delay = config.delay !== undefined ? config.delay : 0; - this.__iterations = config.iterations !== undefined ? config.iterations : 1; - this.__isInteraction = - config.isInteraction !== undefined ? config.isInteraction : true; - this._useNativeDriver = shouldUseNativeDriver(config); - } - - __getNativeAnimationConfig(): any { - const frameDuration = 1000.0 / 60.0; - const frames = []; - for (let dt = 0.0; dt < this._duration; dt += frameDuration) { - frames.push(this._easing(dt / this._duration)); - } - frames.push(this._easing(1)); - return { - type: 'frames', - frames, - toValue: this._toValue, - iterations: this.__iterations, - }; - } - - start( - fromValue: number, - onUpdate: (value: number) => void, - onEnd: ?EndCallback, - previousAnimation: ?Animation, - animatedValue: AnimatedValue, - ): void { - this.__active = true; - this._fromValue = fromValue; - this._onUpdate = onUpdate; - this.__onEnd = onEnd; - - const start = () => { - // Animations that sometimes have 0 duration and sometimes do not - // still need to use the native driver when duration is 0 so as to - // not cause intermixed JS and native animations. - if (this._duration === 0 && !this._useNativeDriver) { - this._onUpdate(this._toValue); - this.__debouncedOnEnd({finished: true}); - } else { - this._startTime = Date.now(); - if (this._useNativeDriver) { - this.__startNativeAnimation(animatedValue); - } else { - this._animationFrame = requestAnimationFrame( - this.onUpdate.bind(this), - ); - } - } - }; - if (this._delay) { - this._timeout = setTimeout(start, this._delay); - } else { - start(); - } - } - - onUpdate(): void { - const now = Date.now(); - if (now >= this._startTime + this._duration) { - if (this._duration === 0) { - this._onUpdate(this._toValue); - } else { - this._onUpdate( - this._fromValue + this._easing(1) * (this._toValue - this._fromValue), - ); - } - this.__debouncedOnEnd({finished: true}); - return; - } - - this._onUpdate( - this._fromValue + - this._easing((now - this._startTime) / this._duration) * - (this._toValue - this._fromValue), - ); - if (this.__active) { - this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); - } - } - - stop(): void { - super.stop(); - this.__active = false; - clearTimeout(this._timeout); - global.cancelAnimationFrame(this._animationFrame); - this.__debouncedOnEnd({finished: false}); - } -} - -module.exports = TimingAnimation; diff --git a/Libraries/Animated/src/bezier.js b/Libraries/Animated/src/bezier.js deleted file mode 100644 index 2bd594930064c9..00000000000000 --- a/Libraries/Animated/src/bezier.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * BezierEasing - use bezier curve for transition easing function - * https://github.com/gre/bezier-easing - * - * @copyright 2014-2015 Gaëtan Renaudeau. MIT License. - * @providesModule bezier - * @noflow - */ -'use strict'; - - // These values are established by empiricism with tests (tradeoff: performance VS precision) - var NEWTON_ITERATIONS = 4; - var NEWTON_MIN_SLOPE = 0.001; - var SUBDIVISION_PRECISION = 0.0000001; - var SUBDIVISION_MAX_ITERATIONS = 10; - - var kSplineTableSize = 11; - var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); - - var float32ArraySupported = typeof Float32Array === 'function'; - - function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } - function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } - function C (aA1) { return 3.0 * aA1; } - - // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. - function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } - - // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. - function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } - - function binarySubdivide (aX, aA, aB, mX1, mX2) { - var currentX, currentT, i = 0; - do { - currentT = aA + (aB - aA) / 2.0; - currentX = calcBezier(currentT, mX1, mX2) - aX; - if (currentX > 0.0) { - aB = currentT; - } else { - aA = currentT; - } - } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); - return currentT; - } - - function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { - for (var i = 0; i < NEWTON_ITERATIONS; ++i) { - var currentSlope = getSlope(aGuessT, mX1, mX2); - if (currentSlope === 0.0) { - return aGuessT; - } - var currentX = calcBezier(aGuessT, mX1, mX2) - aX; - aGuessT -= currentX / currentSlope; - } - return aGuessT; - } - - module.exports = function bezier (mX1, mY1, mX2, mY2) { - if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { // eslint-disable-line yoda - throw new Error('bezier x values must be in [0, 1] range'); - } - - // Precompute samples table - var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); - if (mX1 !== mY1 || mX2 !== mY2) { - for (var i = 0; i < kSplineTableSize; ++i) { - sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); - } - } - - function getTForX (aX) { - var intervalStart = 0.0; - var currentSample = 1; - var lastSample = kSplineTableSize - 1; - - for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { - intervalStart += kSampleStepSize; - } - --currentSample; - - // Interpolate to provide an initial guess for t - var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); - var guessForT = intervalStart + dist * kSampleStepSize; - - var initialSlope = getSlope(guessForT, mX1, mX2); - if (initialSlope >= NEWTON_MIN_SLOPE) { - return newtonRaphsonIterate(aX, guessForT, mX1, mX2); - } else if (initialSlope === 0.0) { - return guessForT; - } else { - return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); - } - } - - return function BezierEasing (x) { - if (mX1 === mY1 && mX2 === mY2) { - return x; // linear - } - // Because JavaScript number are imprecise, we should guarantee the extremes are right. - if (x === 0) { - return 0; - } - if (x === 1) { - return 1; - } - return calcBezier(getTForX(x), mY1, mY2); - }; - }; diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js deleted file mode 100644 index 357ee0bd59bcd1..00000000000000 --- a/Libraries/Animated/src/createAnimatedComponent.js +++ /dev/null @@ -1,207 +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 createAnimatedComponent - * @flow - * @format - */ -'use strict'; - -const {AnimatedEvent} = require('./AnimatedEvent'); -const AnimatedProps = require('./nodes/AnimatedProps'); -const React = require('React'); -const ViewStylePropTypes = require('ViewStylePropTypes'); - -const invariant = require('fbjs/lib/invariant'); - -function createAnimatedComponent(Component: any): any { - invariant( - typeof Component === 'string' || - (Component.prototype && Component.prototype.isReactComponent), - '`createAnimatedComponent` does not support stateless functional components; ' + - 'use a class component instead.', - ); - - class AnimatedComponent extends React.Component { - _component: any; - _invokeAnimatedPropsCallbackOnMount: boolean = false; - _prevComponent: any; - _propsAnimated: AnimatedProps; - _eventDetachers: Array = []; - _setComponentRef: Function; - - static __skipSetNativeProps_FOR_TESTS_ONLY = false; - - constructor(props: Object) { - super(props); - this._setComponentRef = this._setComponentRef.bind(this); - } - - componentWillUnmount() { - this._propsAnimated && this._propsAnimated.__detach(); - this._detachNativeEvents(); - } - - setNativeProps(props) { - this._component.setNativeProps(props); - } - - UNSAFE_componentWillMount() { - this._attachProps(this.props); - } - - componentDidMount() { - if (this._invokeAnimatedPropsCallbackOnMount) { - this._invokeAnimatedPropsCallbackOnMount = false; - this._animatedPropsCallback(); - } - - this._propsAnimated.setNativeView(this._component); - this._attachNativeEvents(); - } - - _attachNativeEvents() { - // Make sure to get the scrollable node for components that implement - // `ScrollResponder.Mixin`. - const scrollableNode = this._component.getScrollableNode - ? this._component.getScrollableNode() - : this._component; - - for (const key in this.props) { - const prop = this.props[key]; - if (prop instanceof AnimatedEvent && prop.__isNative) { - prop.__attach(scrollableNode, key); - this._eventDetachers.push(() => prop.__detach(scrollableNode, key)); - } - } - } - - _detachNativeEvents() { - this._eventDetachers.forEach(remove => remove()); - this._eventDetachers = []; - } - - // The system is best designed when setNativeProps is implemented. It is - // able to avoid re-rendering and directly set the attributes that changed. - // However, setNativeProps can only be implemented on leaf native - // components. If you want to animate a composite component, you need to - // re-render it. In this case, we have a fallback that uses forceUpdate. - _animatedPropsCallback = () => { - if (this._component == null) { - // AnimatedProps is created in will-mount because it's used in render. - // But this callback may be invoked before mount in async mode, - // In which case we should defer the setNativeProps() call. - // React may throw away uncommitted work in async mode, - // So a deferred call won't always be invoked. - this._invokeAnimatedPropsCallbackOnMount = true; - } else if ( - AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY || - typeof this._component.setNativeProps !== 'function' - ) { - this.forceUpdate(); - } else if (!this._propsAnimated.__isNative) { - this._component.setNativeProps( - this._propsAnimated.__getAnimatedValue(), - ); - } else { - throw new Error( - 'Attempting to run JS driven animation on animated ' + - 'node that has been moved to "native" earlier by starting an ' + - 'animation with `useNativeDriver: true`', - ); - } - }; - - _attachProps(nextProps) { - const oldPropsAnimated = this._propsAnimated; - - this._propsAnimated = new AnimatedProps( - nextProps, - this._animatedPropsCallback, - ); - - // When you call detach, it removes the element from the parent list - // of children. If it goes to 0, then the parent also detaches itself - // and so on. - // An optimization is to attach the new elements and THEN detach the old - // ones instead of detaching and THEN attaching. - // This way the intermediate state isn't to go to 0 and trigger - // this expensive recursive detaching to then re-attach everything on - // the very next operation. - oldPropsAnimated && oldPropsAnimated.__detach(); - } - - UNSAFE_componentWillReceiveProps(newProps) { - this._attachProps(newProps); - } - - componentDidUpdate(prevProps) { - if (this._component !== this._prevComponent) { - this._propsAnimated.setNativeView(this._component); - } - if (this._component !== this._prevComponent || prevProps !== this.props) { - this._detachNativeEvents(); - this._attachNativeEvents(); - } - } - - render() { - const props = this._propsAnimated.__getValue(); - return ( - - ); - } - - _setComponentRef(c) { - this._prevComponent = this._component; - this._component = c; - } - - // A third party library can use getNode() - // to get the node reference of the decorated component - getNode() { - return this._component; - } - } - - const propTypes = Component.propTypes; - - AnimatedComponent.propTypes = { - style: function(props, propName, componentName) { - if (!propTypes) { - return; - } - - for (const key in ViewStylePropTypes) { - if (!propTypes[key] && props[key] !== undefined) { - console.warn( - 'You are setting the style `{ ' + - key + - ': ... }` as a prop. You ' + - 'should nest it in a style object. ' + - 'E.g. `{ style: { ' + - key + - ': ... } }`', - ); - } - } - }, - }; - - return AnimatedComponent; -} - -module.exports = createAnimatedComponent; diff --git a/Libraries/Animated/src/nodes/AnimatedDivision.js b/Libraries/Animated/src/nodes/AnimatedDivision.js deleted file mode 100644 index 606e63937b9a86..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedDivision.js +++ /dev/null @@ -1,68 +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 AnimatedDivision - * @flow - * @format - */ -'use strict'; - -const AnimatedInterpolation = require('./AnimatedInterpolation'); -const AnimatedNode = require('./AnimatedNode'); -const AnimatedValue = require('./AnimatedValue'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); - -import type {InterpolationConfigType} from './AnimatedInterpolation'; - -class AnimatedDivision extends AnimatedWithChildren { - _a: AnimatedNode; - _b: AnimatedNode; - - constructor(a: AnimatedNode | number, b: AnimatedNode | number) { - super(); - this._a = typeof a === 'number' ? new AnimatedValue(a) : a; - this._b = typeof b === 'number' ? new AnimatedValue(b) : b; - } - - __makeNative() { - this._a.__makeNative(); - this._b.__makeNative(); - super.__makeNative(); - } - - __getValue(): number { - const a = this._a.__getValue(); - const b = this._b.__getValue(); - if (b === 0) { - console.error('Detected division by zero in AnimatedDivision'); - } - return a / b; - } - - interpolate(config: InterpolationConfigType): AnimatedInterpolation { - return new AnimatedInterpolation(this, config); - } - - __attach(): void { - this._a.__addChild(this); - this._b.__addChild(this); - } - - __detach(): void { - this._a.__removeChild(this); - this._b.__removeChild(this); - super.__detach(); - } - - __getNativeConfig(): any { - return { - type: 'division', - input: [this._a.__getNativeTag(), this._b.__getNativeTag()], - }; - } -} - -module.exports = AnimatedDivision; diff --git a/Libraries/Animated/src/nodes/AnimatedInterpolation.js b/Libraries/Animated/src/nodes/AnimatedInterpolation.js deleted file mode 100644 index eb4cc5d6995d77..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedInterpolation.js +++ /dev/null @@ -1,386 +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 AnimatedInterpolation - * @flow - * @format - */ -/* eslint no-bitwise: 0 */ -'use strict'; - -const AnimatedNode = require('./AnimatedNode'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); - -const invariant = require('fbjs/lib/invariant'); -const normalizeColor = require('normalizeColor'); - -type ExtrapolateType = 'extend' | 'identity' | 'clamp'; - -export type InterpolationConfigType = { - inputRange: Array, - /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error - * detected during the deployment of v0.38.0. To see the error, remove this - * comment and run flow - */ - outputRange: Array | Array, - easing?: (input: number) => number, - extrapolate?: ExtrapolateType, - extrapolateLeft?: ExtrapolateType, - extrapolateRight?: ExtrapolateType, -}; - -const linear = t => t; - -/** - * Very handy helper to map input ranges to output ranges with an easing - * function and custom behavior outside of the ranges. - */ -function createInterpolation( - config: InterpolationConfigType, -): (input: number) => number | string { - if (config.outputRange && typeof config.outputRange[0] === 'string') { - return createInterpolationFromStringOutputRange(config); - } - - const outputRange: Array = (config.outputRange: any); - checkInfiniteRange('outputRange', outputRange); - - const inputRange = config.inputRange; - checkInfiniteRange('inputRange', inputRange); - checkValidInputRange(inputRange); - - invariant( - inputRange.length === outputRange.length, - 'inputRange (' + - inputRange.length + - ') and outputRange (' + - outputRange.length + - ') must have the same length', - ); - - const easing = config.easing || linear; - - let extrapolateLeft: ExtrapolateType = 'extend'; - if (config.extrapolateLeft !== undefined) { - extrapolateLeft = config.extrapolateLeft; - } else if (config.extrapolate !== undefined) { - extrapolateLeft = config.extrapolate; - } - - let extrapolateRight: ExtrapolateType = 'extend'; - if (config.extrapolateRight !== undefined) { - extrapolateRight = config.extrapolateRight; - } else if (config.extrapolate !== undefined) { - extrapolateRight = config.extrapolate; - } - - return input => { - invariant( - typeof input === 'number', - 'Cannot interpolation an input which is not a number', - ); - - const range = findRange(input, inputRange); - return interpolate( - input, - inputRange[range], - inputRange[range + 1], - outputRange[range], - outputRange[range + 1], - easing, - extrapolateLeft, - extrapolateRight, - ); - }; -} - -function interpolate( - input: number, - inputMin: number, - inputMax: number, - outputMin: number, - outputMax: number, - easing: (input: number) => number, - extrapolateLeft: ExtrapolateType, - extrapolateRight: ExtrapolateType, -) { - let result = input; - - // Extrapolate - if (result < inputMin) { - if (extrapolateLeft === 'identity') { - return result; - } else if (extrapolateLeft === 'clamp') { - result = inputMin; - } else if (extrapolateLeft === 'extend') { - // noop - } - } - - if (result > inputMax) { - if (extrapolateRight === 'identity') { - return result; - } else if (extrapolateRight === 'clamp') { - result = inputMax; - } else if (extrapolateRight === 'extend') { - // noop - } - } - - if (outputMin === outputMax) { - return outputMin; - } - - if (inputMin === inputMax) { - if (input <= inputMin) { - return outputMin; - } - return outputMax; - } - - // Input Range - if (inputMin === -Infinity) { - result = -result; - } else if (inputMax === Infinity) { - result = result - inputMin; - } else { - result = (result - inputMin) / (inputMax - inputMin); - } - - // Easing - result = easing(result); - - // Output Range - if (outputMin === -Infinity) { - result = -result; - } else if (outputMax === Infinity) { - result = result + outputMin; - } else { - result = result * (outputMax - outputMin) + outputMin; - } - - return result; -} - -function colorToRgba(input: string): string { - let int32Color = normalizeColor(input); - if (int32Color === null) { - return input; - } - - int32Color = int32Color || 0; - - const r = (int32Color & 0xff000000) >>> 24; - const g = (int32Color & 0x00ff0000) >>> 16; - const b = (int32Color & 0x0000ff00) >>> 8; - const a = (int32Color & 0x000000ff) / 255; - - return `rgba(${r}, ${g}, ${b}, ${a})`; -} - -const stringShapeRegex = /[0-9\.-]+/g; - -/** - * Supports string shapes by extracting numbers so new values can be computed, - * and recombines those values into new strings of the same shape. Supports - * things like: - * - * rgba(123, 42, 99, 0.36) // colors - * -45deg // values with units - */ -function createInterpolationFromStringOutputRange( - config: InterpolationConfigType, -): (input: number) => string { - let outputRange: Array = (config.outputRange: any); - invariant(outputRange.length >= 2, 'Bad output range'); - outputRange = outputRange.map(colorToRgba); - checkPattern(outputRange); - - // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] - // -> - // [ - // [0, 50], - // [100, 150], - // [200, 250], - // [0, 0.5], - // ] - /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to - * guard against this possibility. - */ - const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []); - outputRange.forEach(value => { - /* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard - * against this possibility. - */ - value.match(stringShapeRegex).forEach((number, i) => { - outputRanges[i].push(+number); - }); - }); - - /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to - * guard against this possibility. - */ - const interpolations = outputRange[0] - .match(stringShapeRegex) - .map((value, i) => { - return createInterpolation({ - ...config, - outputRange: outputRanges[i], - }); - }); - - // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to - // round the opacity (4th column). - const shouldRound = isRgbOrRgba(outputRange[0]); - - return input => { - let i = 0; - // 'rgba(0, 100, 200, 0)' - // -> - // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' - return outputRange[0].replace(stringShapeRegex, () => { - const val = +interpolations[i++](input); - const rounded = - shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000; - return String(rounded); - }); - }; -} - -function isRgbOrRgba(range) { - return typeof range === 'string' && range.startsWith('rgb'); -} - -function checkPattern(arr: Array) { - const pattern = arr[0].replace(stringShapeRegex, ''); - for (let i = 1; i < arr.length; ++i) { - invariant( - pattern === arr[i].replace(stringShapeRegex, ''), - 'invalid pattern ' + arr[0] + ' and ' + arr[i], - ); - } -} - -function findRange(input: number, inputRange: Array) { - let i; - for (i = 1; i < inputRange.length - 1; ++i) { - if (inputRange[i] >= input) { - break; - } - } - return i - 1; -} - -function checkValidInputRange(arr: Array) { - invariant(arr.length >= 2, 'inputRange must have at least 2 elements'); - for (let i = 1; i < arr.length; ++i) { - invariant( - arr[i] >= arr[i - 1], - /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, - * one or both of the operands may be something that doesn't cleanly - * convert to a string, like undefined, null, and object, etc. If you really - * mean this implicit string conversion, you can do something like - * String(myThing) - */ - 'inputRange must be monotonically increasing ' + arr, - ); - } -} - -function checkInfiniteRange(name: string, arr: Array) { - invariant(arr.length >= 2, name + ' must have at least 2 elements'); - invariant( - arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, - /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, - * one or both of the operands may be something that doesn't cleanly convert - * to a string, like undefined, null, and object, etc. If you really mean - * this implicit string conversion, you can do something like - * String(myThing) - */ - name + 'cannot be ]-infinity;+infinity[ ' + arr, - ); -} - -class AnimatedInterpolation extends AnimatedWithChildren { - // Export for testing. - static __createInterpolation = createInterpolation; - - _parent: AnimatedNode; - _config: InterpolationConfigType; - _interpolation: (input: number) => number | string; - - constructor(parent: AnimatedNode, config: InterpolationConfigType) { - super(); - this._parent = parent; - this._config = config; - this._interpolation = createInterpolation(config); - } - - __makeNative() { - this._parent.__makeNative(); - super.__makeNative(); - } - - __getValue(): number | string { - const parentValue: number = this._parent.__getValue(); - invariant( - typeof parentValue === 'number', - 'Cannot interpolate an input which is not a number.', - ); - return this._interpolation(parentValue); - } - - interpolate(config: InterpolationConfigType): AnimatedInterpolation { - return new AnimatedInterpolation(this, config); - } - - __attach(): void { - this._parent.__addChild(this); - } - - __detach(): void { - this._parent.__removeChild(this); - super.__detach(); - } - - __transformDataType(range: Array) { - // Change the string array type to number array - // So we can reuse the same logic in iOS and Android platform - return range.map(function(value) { - if (typeof value !== 'string') { - return value; - } - if (/deg$/.test(value)) { - const degrees = parseFloat(value) || 0; - const radians = degrees * Math.PI / 180.0; - return radians; - } else { - // Assume radians - return parseFloat(value) || 0; - } - }); - } - - __getNativeConfig(): any { - if (__DEV__) { - NativeAnimatedHelper.validateInterpolation(this._config); - } - - return { - inputRange: this._config.inputRange, - // Only the `outputRange` can contain strings so we don't need to transform `inputRange` here - outputRange: this.__transformDataType(this._config.outputRange), - extrapolateLeft: - this._config.extrapolateLeft || this._config.extrapolate || 'extend', - extrapolateRight: - this._config.extrapolateRight || this._config.extrapolate || 'extend', - type: 'interpolation', - }; - } -} - -module.exports = AnimatedInterpolation; diff --git a/Libraries/Animated/src/nodes/AnimatedModulo.js b/Libraries/Animated/src/nodes/AnimatedModulo.js deleted file mode 100644 index 24cd3f0b077c12..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedModulo.js +++ /dev/null @@ -1,62 +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 AnimatedModulo - * @flow - * @format - */ -'use strict'; - -const AnimatedInterpolation = require('./AnimatedInterpolation'); -const AnimatedNode = require('./AnimatedNode'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); - -import type {InterpolationConfigType} from './AnimatedInterpolation'; - -class AnimatedModulo extends AnimatedWithChildren { - _a: AnimatedNode; - _modulus: number; - - constructor(a: AnimatedNode, modulus: number) { - super(); - this._a = a; - this._modulus = modulus; - } - - __makeNative() { - this._a.__makeNative(); - super.__makeNative(); - } - - __getValue(): number { - return ( - (this._a.__getValue() % this._modulus + this._modulus) % this._modulus - ); - } - - interpolate(config: InterpolationConfigType): AnimatedInterpolation { - return new AnimatedInterpolation(this, config); - } - - __attach(): void { - this._a.__addChild(this); - } - - __detach(): void { - this._a.__removeChild(this); - super.__detach(); - } - - __getNativeConfig(): any { - return { - type: 'modulus', - input: this._a.__getNativeTag(), - modulus: this._modulus, - }; - } -} - -module.exports = AnimatedModulo; diff --git a/Libraries/Animated/src/nodes/AnimatedNode.js b/Libraries/Animated/src/nodes/AnimatedNode.js deleted file mode 100644 index e75cfaf0e5c8fb..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedNode.js +++ /dev/null @@ -1,71 +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 AnimatedNode - * @flow - * @format - */ -'use strict'; - -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); - -const invariant = require('fbjs/lib/invariant'); - -// Note(vjeux): this would be better as an interface but flow doesn't -// support them yet -class AnimatedNode { - __attach(): void {} - __detach(): void { - if (this.__isNative && this.__nativeTag != null) { - NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag); - this.__nativeTag = undefined; - } - } - __getValue(): any {} - __getAnimatedValue(): any { - return this.__getValue(); - } - __addChild(child: AnimatedNode) {} - __removeChild(child: AnimatedNode) {} - __getChildren(): Array { - return []; - } - - /* Methods and props used by native Animated impl */ - __isNative: boolean; - __nativeTag: ?number; - __makeNative() { - if (!this.__isNative) { - throw new Error('This node cannot be made a "native" animated node'); - } - } - __getNativeTag(): ?number { - NativeAnimatedHelper.assertNativeAnimatedModule(); - invariant( - this.__isNative, - 'Attempt to get native tag from node not marked as "native"', - ); - if (this.__nativeTag == null) { - const nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag(); - NativeAnimatedHelper.API.createAnimatedNode( - nativeTag, - this.__getNativeConfig(), - ); - this.__nativeTag = nativeTag; - } - return this.__nativeTag; - } - __getNativeConfig(): Object { - throw new Error( - 'This JS animated node type cannot be used as native animated node', - ); - } - toJSON(): any { - return this.__getValue(); - } -} - -module.exports = AnimatedNode; diff --git a/Libraries/Animated/src/nodes/AnimatedProps.js b/Libraries/Animated/src/nodes/AnimatedProps.js deleted file mode 100644 index 78cd11189edfa7..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedProps.js +++ /dev/null @@ -1,165 +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 AnimatedProps - * @flow - * @format - */ -'use strict'; - -const {AnimatedEvent} = require('../AnimatedEvent'); -const AnimatedNode = require('./AnimatedNode'); -const AnimatedStyle = require('./AnimatedStyle'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); -const ReactNative = require('ReactNative'); - -const invariant = require('fbjs/lib/invariant'); - -class AnimatedProps extends AnimatedNode { - _props: Object; - _animatedView: any; - _callback: () => void; - - constructor(props: Object, callback: () => void) { - super(); - if (props.style) { - props = { - ...props, - style: new AnimatedStyle(props.style), - }; - } - this._props = props; - this._callback = callback; - this.__attach(); - } - - __getValue(): Object { - const props = {}; - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof AnimatedNode) { - if (!value.__isNative || value instanceof AnimatedStyle) { - // We cannot use value of natively driven nodes this way as the value we have access from - // JS may not be up to date. - props[key] = value.__getValue(); - } - } else if (value instanceof AnimatedEvent) { - props[key] = value.__getHandler(); - } else { - props[key] = value; - } - } - return props; - } - - __getAnimatedValue(): Object { - const props = {}; - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof AnimatedNode) { - props[key] = value.__getAnimatedValue(); - } - } - return props; - } - - __attach(): void { - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof AnimatedNode) { - value.__addChild(this); - } - } - } - - __detach(): void { - if (this.__isNative && this._animatedView) { - this.__disconnectAnimatedView(); - } - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof AnimatedNode) { - value.__removeChild(this); - } - } - super.__detach(); - } - - update(): void { - this._callback(); - } - - __makeNative(): void { - if (!this.__isNative) { - this.__isNative = true; - for (const key in this._props) { - const value = this._props[key]; - if (value instanceof AnimatedNode) { - value.__makeNative(); - } - } - if (this._animatedView) { - this.__connectAnimatedView(); - } - } - } - - setNativeView(animatedView: any): void { - if (this._animatedView === animatedView) { - return; - } - this._animatedView = animatedView; - if (this.__isNative) { - this.__connectAnimatedView(); - } - } - - __connectAnimatedView(): void { - invariant(this.__isNative, 'Expected node to be marked as "native"'); - const nativeViewTag: ?number = ReactNative.findNodeHandle( - this._animatedView, - ); - invariant( - nativeViewTag != null, - 'Unable to locate attached view in the native tree', - ); - NativeAnimatedHelper.API.connectAnimatedNodeToView( - this.__getNativeTag(), - nativeViewTag, - ); - } - - __disconnectAnimatedView(): void { - invariant(this.__isNative, 'Expected node to be marked as "native"'); - const nativeViewTag: ?number = ReactNative.findNodeHandle( - this._animatedView, - ); - invariant( - nativeViewTag != null, - 'Unable to locate attached view in the native tree', - ); - NativeAnimatedHelper.API.disconnectAnimatedNodeFromView( - this.__getNativeTag(), - nativeViewTag, - ); - } - - __getNativeConfig(): Object { - const propsConfig = {}; - for (const propKey in this._props) { - const value = this._props[propKey]; - if (value instanceof AnimatedNode) { - propsConfig[propKey] = value.__getNativeTag(); - } - } - return { - type: 'props', - props: propsConfig, - }; - } -} - -module.exports = AnimatedProps; diff --git a/Libraries/Animated/src/nodes/AnimatedStyle.js b/Libraries/Animated/src/nodes/AnimatedStyle.js deleted file mode 100644 index c7bb24e3b42b14..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedStyle.js +++ /dev/null @@ -1,125 +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 AnimatedStyle - * @flow - * @format - */ -'use strict'; - -const AnimatedNode = require('./AnimatedNode'); -const AnimatedTransform = require('./AnimatedTransform'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); - -const flattenStyle = require('flattenStyle'); - -class AnimatedStyle extends AnimatedWithChildren { - _style: Object; - - constructor(style: any) { - super(); - style = flattenStyle(style) || {}; - if (style.transform) { - style = { - ...style, - transform: new AnimatedTransform(style.transform), - }; - } - this._style = style; - } - - // Recursively get values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetValues(style) { - const updatedStyle = {}; - for (const key in style) { - const value = style[key]; - if (value instanceof AnimatedNode) { - if (!value.__isNative) { - // We cannot use value of natively driven nodes this way as the value we have access from - // JS may not be up to date. - updatedStyle[key] = value.__getValue(); - } - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetValues(value); - } else { - updatedStyle[key] = value; - } - } - return updatedStyle; - } - - __getValue(): Object { - return this._walkStyleAndGetValues(this._style); - } - - // Recursively get animated values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetAnimatedValues(style) { - const updatedStyle = {}; - for (const key in style) { - const value = style[key]; - if (value instanceof AnimatedNode) { - updatedStyle[key] = value.__getAnimatedValue(); - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value); - } - } - return updatedStyle; - } - - __getAnimatedValue(): Object { - return this._walkStyleAndGetAnimatedValues(this._style); - } - - __attach(): void { - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof AnimatedNode) { - value.__addChild(this); - } - } - } - - __detach(): void { - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof AnimatedNode) { - value.__removeChild(this); - } - } - super.__detach(); - } - - __makeNative() { - super.__makeNative(); - for (const key in this._style) { - const value = this._style[key]; - if (value instanceof AnimatedNode) { - value.__makeNative(); - } - } - } - - __getNativeConfig(): Object { - const styleConfig = {}; - for (const styleKey in this._style) { - if (this._style[styleKey] instanceof AnimatedNode) { - styleConfig[styleKey] = this._style[styleKey].__getNativeTag(); - } - // Non-animated styles are set using `setNativeProps`, no need - // to pass those as a part of the node config - } - NativeAnimatedHelper.validateStyles(styleConfig); - return { - type: 'style', - style: styleConfig, - }; - } -} - -module.exports = AnimatedStyle; diff --git a/Libraries/Animated/src/nodes/AnimatedValue.js b/Libraries/Animated/src/nodes/AnimatedValue.js deleted file mode 100644 index 862d25f8e53a28..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedValue.js +++ /dev/null @@ -1,337 +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 AnimatedValue - * @flow - * @format - */ -'use strict'; - -const AnimatedInterpolation = require('./AnimatedInterpolation'); -const AnimatedNode = require('./AnimatedNode'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); -const InteractionManager = require('InteractionManager'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); - -import type Animation, {EndCallback} from '../animations/Animation'; -import type {InterpolationConfigType} from './AnimatedInterpolation'; -import type AnimatedTracking from './AnimatedTracking'; - -const NativeAnimatedAPI = NativeAnimatedHelper.API; - -type ValueListenerCallback = (state: {value: number}) => void; - -let _uniqueId = 1; - -/** - * Animated works by building a directed acyclic graph of dependencies - * transparently when you render your Animated components. - * - * new Animated.Value(0) - * .interpolate() .interpolate() new Animated.Value(1) - * opacity translateY scale - * style transform - * View#234 style - * View#123 - * - * A) Top Down phase - * When an Animated.Value is updated, we recursively go down through this - * graph in order to find leaf nodes: the views that we flag as needing - * an update. - * - * B) Bottom Up phase - * When a view is flagged as needing an update, we recursively go back up - * in order to build the new value that it needs. The reason why we need - * this two-phases process is to deal with composite props such as - * transform which can receive values from multiple parents. - */ -function _flush(rootNode: AnimatedValue): void { - const animatedStyles = new Set(); - function findAnimatedStyles(node) { - if (typeof node.update === 'function') { - animatedStyles.add(node); - } else { - node.__getChildren().forEach(findAnimatedStyles); - } - } - findAnimatedStyles(rootNode); - /* $FlowFixMe */ - animatedStyles.forEach(animatedStyle => animatedStyle.update()); -} - -/** - * Standard value for driving animations. One `Animated.Value` can drive - * multiple properties in a synchronized fashion, but can only be driven by one - * mechanism at a time. Using a new mechanism (e.g. starting a new animation, - * or calling `setValue`) will stop any previous ones. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html - */ -class AnimatedValue extends AnimatedWithChildren { - _value: number; - _startingValue: number; - _offset: number; - _animation: ?Animation; - _tracking: ?AnimatedTracking; - _listeners: {[key: string]: ValueListenerCallback}; - __nativeAnimatedValueListener: ?any; - - constructor(value: number) { - super(); - this._startingValue = this._value = value; - this._offset = 0; - this._animation = null; - this._listeners = {}; - } - - __detach() { - this.stopAnimation(); - super.__detach(); - } - - __getValue(): number { - return this._value + this._offset; - } - - __makeNative() { - super.__makeNative(); - - if (Object.keys(this._listeners).length) { - this._startListeningToNativeValueUpdates(); - } - } - - /** - * Directly set the value. This will stop any animations running on the value - * and update all the bound properties. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#setvalue - */ - setValue(value: number): void { - if (this._animation) { - this._animation.stop(); - this._animation = null; - } - this._updateValue( - value, - !this.__isNative /* don't perform a flush for natively driven values */, - ); - if (this.__isNative) { - NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value); - } - } - - /** - * Sets an offset that is applied on top of whatever value is set, whether via - * `setValue`, an animation, or `Animated.event`. Useful for compensating - * things like the start of a pan gesture. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#setoffset - */ - setOffset(offset: number): void { - this._offset = offset; - if (this.__isNative) { - NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset); - } - } - - /** - * Merges the offset value into the base value and resets the offset to zero. - * The final output of the value is unchanged. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#flattenoffset - */ - flattenOffset(): void { - this._value += this._offset; - this._offset = 0; - if (this.__isNative) { - NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag()); - } - } - - /** - * Sets the offset value to the base value, and resets the base value to zero. - * The final output of the value is unchanged. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#extractoffset - */ - extractOffset(): void { - this._offset += this._value; - this._value = 0; - if (this.__isNative) { - NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()); - } - } - - /** - * Adds an asynchronous listener to the value so you can observe updates from - * animations. This is useful because there is no way to - * synchronously read the value because it might be driven natively. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener - */ - addListener(callback: ValueListenerCallback): string { - const id = String(_uniqueId++); - this._listeners[id] = callback; - if (this.__isNative) { - this._startListeningToNativeValueUpdates(); - } - return id; - } - - /** - * Unregister a listener. The `id` param shall match the identifier - * previously returned by `addListener()`. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener - */ - removeListener(id: string): void { - delete this._listeners[id]; - if (this.__isNative && Object.keys(this._listeners).length === 0) { - this._stopListeningForNativeValueUpdates(); - } - } - - /** - * Remove all registered listeners. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners - */ - removeAllListeners(): void { - this._listeners = {}; - if (this.__isNative) { - this._stopListeningForNativeValueUpdates(); - } - } - - _startListeningToNativeValueUpdates() { - if (this.__nativeAnimatedValueListener) { - return; - } - - NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); - this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( - 'onAnimatedValueUpdate', - data => { - if (data.tag !== this.__getNativeTag()) { - return; - } - this._updateValue(data.value, false /* flush */); - }, - ); - } - - _stopListeningForNativeValueUpdates() { - if (!this.__nativeAnimatedValueListener) { - return; - } - - this.__nativeAnimatedValueListener.remove(); - this.__nativeAnimatedValueListener = null; - NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag()); - } - - /** - * Stops any running animation or tracking. `callback` is invoked with the - * final value after stopping the animation, which is useful for updating - * state to match the animation position with layout. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#stopanimation - */ - stopAnimation(callback?: ?(value: number) => void): void { - this.stopTracking(); - this._animation && this._animation.stop(); - this._animation = null; - callback && callback(this.__getValue()); - } - - /** - * Stops any animation and resets the value to its original. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#resetanimation - */ - resetAnimation(callback?: ?(value: number) => void): void { - this.stopAnimation(callback); - this._value = this._startingValue; - } - - /** - * Interpolates the value before updating the property, e.g. mapping 0-1 to - * 0-10. - */ - interpolate(config: InterpolationConfigType): AnimatedInterpolation { - return new AnimatedInterpolation(this, config); - } - - /** - * Typically only used internally, but could be used by a custom Animation - * class. - * - * See http://facebook.github.io/react-native/docs/animatedvalue.html#animate - */ - animate(animation: Animation, callback: ?EndCallback): void { - let handle = null; - if (animation.__isInteraction) { - handle = InteractionManager.createInteractionHandle(); - } - const previousAnimation = this._animation; - this._animation && this._animation.stop(); - this._animation = animation; - animation.start( - this._value, - value => { - // Natively driven animations will never call into that callback, therefore we can always - // pass flush = true to allow the updated value to propagate to native with setNativeProps - this._updateValue(value, true /* flush */); - }, - result => { - this._animation = null; - if (handle !== null) { - InteractionManager.clearInteractionHandle(handle); - } - callback && callback(result); - }, - previousAnimation, - this, - ); - } - - /** - * Typically only used internally. - */ - stopTracking(): void { - this._tracking && this._tracking.__detach(); - this._tracking = null; - } - - /** - * Typically only used internally. - */ - track(tracking: AnimatedTracking): void { - this.stopTracking(); - this._tracking = tracking; - } - - _updateValue(value: number, flush: boolean): void { - this._value = value; - if (flush) { - _flush(this); - } - for (const key in this._listeners) { - this._listeners[key]({value: this.__getValue()}); - } - } - - __getNativeConfig(): Object { - return { - type: 'value', - value: this._value, - offset: this._offset, - }; - } -} - -module.exports = AnimatedValue; diff --git a/Libraries/Animated/src/nodes/AnimatedValueXY.js b/Libraries/Animated/src/nodes/AnimatedValueXY.js deleted file mode 100644 index 52c207c0f42ffe..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedValueXY.js +++ /dev/null @@ -1,195 +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 AnimatedValueXY - * @flow - * @format - */ -'use strict'; - -const AnimatedValue = require('./AnimatedValue'); -const AnimatedWithChildren = require('./AnimatedWithChildren'); - -const invariant = require('fbjs/lib/invariant'); - -type ValueXYListenerCallback = (value: {x: number, y: number}) => void; - -let _uniqueId = 1; - -/** - * 2D Value for driving 2D animations, such as pan gestures. Almost identical - * API to normal `Animated.Value`, but multiplexed. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html - */ -class AnimatedValueXY extends AnimatedWithChildren { - x: AnimatedValue; - y: AnimatedValue; - _listeners: {[key: string]: {x: string, y: string}}; - - constructor( - valueIn?: ?{+x: number | AnimatedValue, +y: number | AnimatedValue}, - ) { - super(); - const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any` - if (typeof value.x === 'number' && typeof value.y === 'number') { - this.x = new AnimatedValue(value.x); - this.y = new AnimatedValue(value.y); - } else { - invariant( - value.x instanceof AnimatedValue && value.y instanceof AnimatedValue, - 'AnimatedValueXY must be initialized with an object of numbers or ' + - 'AnimatedValues.', - ); - this.x = value.x; - this.y = value.y; - } - this._listeners = {}; - } - - /** - * Directly set the value. This will stop any animations running on the value - * and update all the bound properties. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setvalue - */ - setValue(value: {x: number, y: number}) { - this.x.setValue(value.x); - this.y.setValue(value.y); - } - - /** - * Sets an offset that is applied on top of whatever value is set, whether - * via `setValue`, an animation, or `Animated.event`. Useful for compensating - * things like the start of a pan gesture. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setoffset - */ - setOffset(offset: {x: number, y: number}) { - this.x.setOffset(offset.x); - this.y.setOffset(offset.y); - } - - /** - * Merges the offset value into the base value and resets the offset to zero. - * The final output of the value is unchanged. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#flattenoffset - */ - flattenOffset(): void { - this.x.flattenOffset(); - this.y.flattenOffset(); - } - - /** - * Sets the offset value to the base value, and resets the base value to - * zero. The final output of the value is unchanged. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#extractoffset - */ - extractOffset(): void { - this.x.extractOffset(); - this.y.extractOffset(); - } - - __getValue(): {x: number, y: number} { - return { - x: this.x.__getValue(), - y: this.y.__getValue(), - }; - } - - /** - * Stops any animation and resets the value to its original. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#resetanimation - */ - resetAnimation(callback?: (value: {x: number, y: number}) => void): void { - this.x.resetAnimation(); - this.y.resetAnimation(); - callback && callback(this.__getValue()); - } - - /** - * Stops any running animation or tracking. `callback` is invoked with the - * final value after stopping the animation, which is useful for updating - * state to match the animation position with layout. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#stopanimation - */ - stopAnimation(callback?: (value: {x: number, y: number}) => void): void { - this.x.stopAnimation(); - this.y.stopAnimation(); - callback && callback(this.__getValue()); - } - - /** - * Adds an asynchronous listener to the value so you can observe updates from - * animations. This is useful because there is no way to synchronously read - * the value because it might be driven natively. - * - * Returns a string that serves as an identifier for the listener. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#addlistener - */ - addListener(callback: ValueXYListenerCallback): string { - const id = String(_uniqueId++); - const jointCallback = ({value: number}) => { - callback(this.__getValue()); - }; - this._listeners[id] = { - x: this.x.addListener(jointCallback), - y: this.y.addListener(jointCallback), - }; - return id; - } - - /** - * Unregister a listener. The `id` param shall match the identifier - * previously returned by `addListener()`. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removelistener - */ - removeListener(id: string): void { - this.x.removeListener(this._listeners[id].x); - this.y.removeListener(this._listeners[id].y); - delete this._listeners[id]; - } - - /** - * Remove all registered listeners. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removealllisteners - */ - removeAllListeners(): void { - this.x.removeAllListeners(); - this.y.removeAllListeners(); - this._listeners = {}; - } - - /** - * Converts `{x, y}` into `{left, top}` for use in style. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#getlayout - */ - getLayout(): {[key: string]: AnimatedValue} { - return { - left: this.x, - top: this.y, - }; - } - - /** - * Converts `{x, y}` into a useable translation transform. - * - * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#gettranslatetransform - */ - getTranslateTransform(): Array<{[key: string]: AnimatedValue}> { - return [{translateX: this.x}, {translateY: this.y}]; - } -} - -module.exports = AnimatedValueXY; diff --git a/Libraries/Animated/src/nodes/AnimatedWithChildren.js b/Libraries/Animated/src/nodes/AnimatedWithChildren.js deleted file mode 100644 index 1f58cd834e165e..00000000000000 --- a/Libraries/Animated/src/nodes/AnimatedWithChildren.js +++ /dev/null @@ -1,75 +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 AnimatedWithChildren - * @flow - * @format - */ -'use strict'; - -const AnimatedNode = require('./AnimatedNode'); -const NativeAnimatedHelper = require('../NativeAnimatedHelper'); - -class AnimatedWithChildren extends AnimatedNode { - _children: Array; - - constructor() { - super(); - this._children = []; - } - - __makeNative() { - if (!this.__isNative) { - this.__isNative = true; - for (const child of this._children) { - child.__makeNative(); - NativeAnimatedHelper.API.connectAnimatedNodes( - this.__getNativeTag(), - child.__getNativeTag(), - ); - } - } - } - - __addChild(child: AnimatedNode): void { - if (this._children.length === 0) { - this.__attach(); - } - this._children.push(child); - if (this.__isNative) { - // Only accept "native" animated nodes as children - child.__makeNative(); - NativeAnimatedHelper.API.connectAnimatedNodes( - this.__getNativeTag(), - child.__getNativeTag(), - ); - } - } - - __removeChild(child: AnimatedNode): void { - const index = this._children.indexOf(child); - if (index === -1) { - console.warn("Trying to remove a child that doesn't exist"); - return; - } - if (this.__isNative && child.__isNative) { - NativeAnimatedHelper.API.disconnectAnimatedNodes( - this.__getNativeTag(), - child.__getNativeTag(), - ); - } - this._children.splice(index, 1); - if (this._children.length === 0) { - this.__detach(); - } - } - - __getChildren(): Array { - return this._children; - } -} - -module.exports = AnimatedWithChildren; diff --git a/Libraries/Animated/src/polyfills/InteractionManager.js b/Libraries/Animated/src/polyfills/InteractionManager.js deleted file mode 100644 index c063e9234b3df1..00000000000000 --- a/Libraries/Animated/src/polyfills/InteractionManager.js +++ /dev/null @@ -1,13 +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. - */ - -'use strict'; - -module.exports = { - createInteractionHandle: function() {}, - clearInteractionHandle: function() {} -}; diff --git a/Libraries/Animated/src/polyfills/Set.js b/Libraries/Animated/src/polyfills/Set.js deleted file mode 100644 index fd071410d42395..00000000000000 --- a/Libraries/Animated/src/polyfills/Set.js +++ /dev/null @@ -1,24 +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. - */ - -'use strict'; - -function SetPolyfill() { - this._cache = []; -} - -SetPolyfill.prototype.add = function(e) { - if (this._cache.indexOf(e) === -1) { - this._cache.push(e); - } -}; - -SetPolyfill.prototype.forEach = function(cb) { - this._cache.forEach(cb); -}; - -module.exports = SetPolyfill; diff --git a/Libraries/Animated/src/polyfills/flattenStyle.js b/Libraries/Animated/src/polyfills/flattenStyle.js deleted file mode 100644 index 2e4c8a1d36b476..00000000000000 --- a/Libraries/Animated/src/polyfills/flattenStyle.js +++ /dev/null @@ -1,10 +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. - */ -'use strict'; -module.exports = function(style) { - return style; -}; diff --git a/Libraries/Animated/useAnimatedProps.js b/Libraries/Animated/useAnimatedProps.js new file mode 100644 index 00000000000000..9ab24a99ce1774 --- /dev/null +++ b/Libraries/Animated/useAnimatedProps.js @@ -0,0 +1,212 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import AnimatedProps from './nodes/AnimatedProps'; +import {AnimatedEvent} from './AnimatedEvent'; +import useRefEffect from '../Utilities/useRefEffect'; +import NativeAnimatedHelper from './NativeAnimatedHelper'; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, +} from 'react'; + +type ReducedProps = { + ...TProps, + collapsable: boolean, + ... +}; +type CallbackRef = T => mixed; + +let animatedComponentNextId = 1; + +export default function useAnimatedProps( + props: TProps, +): [ReducedProps, CallbackRef] { + const [, scheduleUpdate] = useReducer(count => count + 1, 0); + const onUpdateRef = useRef void>(null); + + // TODO: Only invalidate `node` if animated props or `style` change. In the + // previous implementation, we permitted `style` to override props with the + // same name property name as styles, so we can probably continue doing that. + // The ordering of other props *should* not matter. + const node = useMemo( + () => new AnimatedProps(props, () => onUpdateRef.current?.()), + [props], + ); + useAnimatedPropsLifecycle(node); + + // TODO: This "effect" does three things: + // + // 1) Call `setNativeView`. + // 2) Update `onUpdateRef`. + // 3) Update listeners for `AnimatedEvent` props. + // + // Ideally, each of these would be separat "effects" so that they are not + // unnecessarily re-run when irrelevant dependencies change. For example, we + // should be able to hoist all `AnimatedEvent` props and only do #3 if either + // the `AnimatedEvent` props change or `instance` changes. + // + // But there is no way to transparently compose three separate callback refs, + // so we just combine them all into one for now. + const refEffect = useCallback( + instance => { + // NOTE: This may be called more often than necessary (e.g. when `props` + // changes), but `setNativeView` already optimizes for that. + node.setNativeView(instance); + + // NOTE: This callback is only used by the JavaScript animation driver. + onUpdateRef.current = () => { + if ( + process.env.NODE_ENV === 'test' || + typeof instance !== 'object' || + typeof instance?.setNativeProps !== 'function' || + isFabricInstance(instance) + ) { + // Schedule an update for this component to update `reducedProps`, + // but do not compute it immediately. If a parent also updated, we + // need to merge those new props in before updating. + scheduleUpdate(); + } else if (!node.__isNative) { + // $FlowIgnore[not-a-function] - Assume it's still a function. + // $FlowFixMe[incompatible-use] + instance.setNativeProps(node.__getAnimatedValue()); + } else { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + }; + + const target = getEventTarget(instance); + const events = []; + + for (const propName in props) { + const propValue = props[propName]; + if (propValue instanceof AnimatedEvent && propValue.__isNative) { + propValue.__attach(target, propName); + events.push([propName, propValue]); + } + } + + return () => { + onUpdateRef.current = null; + + for (const [propName, propValue] of events) { + propValue.__detach(target, propName); + } + }; + }, + [props, node], + ); + const callbackRef = useRefEffect(refEffect); + + return [reduceAnimatedProps(node), callbackRef]; +} + +function reduceAnimatedProps( + node: AnimatedProps, +): ReducedProps { + // Force `collapsable` to be false so that the native view is not flattened. + // Flattened views cannot be accurately referenced by the native driver. + return { + ...node.__getValue(), + collapsable: false, + }; +} + +/** + * Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach` + * and `__detach`. However, this is more complicated because `AnimatedProps` + * uses reference counting to determine when to recursively detach its children + * nodes. So in order to optimize this, we avoid detaching until the next attach + * unless we are unmounting. + */ +function useAnimatedPropsLifecycle(node: AnimatedProps): void { + const prevNodeRef = useRef(null); + const isUnmountingRef = useRef(false); + + const [animatedComponentId] = useState( + () => `${animatedComponentNextId++}:animatedComponent`, + ); + + useLayoutEffect(() => { + NativeAnimatedHelper.API.setWaitingForIdentifier(animatedComponentId); + }); + + useEffect(() => { + NativeAnimatedHelper.API.unsetWaitingForIdentifier(animatedComponentId); + }); + + useLayoutEffect(() => { + isUnmountingRef.current = false; + return () => { + isUnmountingRef.current = true; + }; + }, []); + + useLayoutEffect(() => { + node.__attach(); + if (prevNodeRef.current != null) { + const prevNode = prevNodeRef.current; + // TODO: Stop restoring default values (unless `reset` is called). + prevNode.__restoreDefaultValues(); + prevNode.__detach(); + prevNodeRef.current = null; + } + return () => { + if (isUnmountingRef.current) { + // NOTE: Do not restore default values on unmount, see D18197735. + node.__detach(); + } else { + prevNodeRef.current = node; + } + }; + }, [node]); +} + +function getEventTarget(instance: TInstance): TInstance { + return typeof instance === 'object' && + typeof instance?.getScrollableNode === 'function' + ? // $FlowFixMe[incompatible-use] - Legacy instance assumptions. + instance.getScrollableNode() + : instance; +} + +// $FlowFixMe[unclear-type] - Legacy instance assumptions. +function isFabricInstance(instance: any): boolean { + return ( + hasFabricHandle(instance) || + // Some components have a setNativeProps function but aren't a host component + // such as lists like FlatList and SectionList. These should also use + // forceUpdate in Fabric since setNativeProps doesn't exist on the underlying + // host component. This crazy hack is essentially special casing those lists and + // ScrollView itself to use forceUpdate in Fabric. + // If these components end up using forwardRef then these hacks can go away + // as instance would actually be the underlying host component and the above check + // would be sufficient. + hasFabricHandle(instance?.getNativeScrollRef?.()) || + hasFabricHandle(instance?.getScrollResponder?.()?.getNativeScrollRef?.()) + ); +} + +// $FlowFixMe[unclear-type] - Legacy instance assumptions. +function hasFabricHandle(instance: any): boolean { + // eslint-disable-next-line dot-notation + return instance?.['_internalInstanceHandle']?.stateNode?.canonical != null; +} diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index f2faa9480d2b57..99e731bd43fb55 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -1,72 +1,85 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule AppState - * @flow + * @flow strict-local + * @format */ -'use strict'; -const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim'); -const NativeEventEmitter = require('NativeEventEmitter'); -const NativeModules = require('NativeModules'); -const RCTAppState = NativeModules.AppState; +import {type EventSubscription} from '../vendor/emitter/EventEmitter'; +import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; +import logError from '../Utilities/logError'; +import NativeAppState from './NativeAppState'; +import Platform from '../Utilities/Platform'; -const logError = require('logError'); -const invariant = require('fbjs/lib/invariant'); +export type AppStateValues = 'inactive' | 'background' | 'active'; + +type AppStateEventDefinitions = { + change: [AppStateValues], + memoryWarning: [], + blur: [], + focus: [], +}; + +type NativeAppStateEventDefinitions = { + appStateDidChange: [{app_state: AppStateValues}], + appStateFocusChange: [boolean], + memoryWarning: [], +}; /** * `AppState` can tell you if the app is in the foreground or background, * and notify you when the state changes. * - * See http://facebook.github.io/react-native/docs/appstate.html + * See https://reactnative.dev/docs/appstate */ -class AppState extends NativeEventEmitter { +class AppState { + currentState: ?string = null; + isAvailable: boolean; - _eventHandlers: Object; - currentState: ?string; - isAvailable: boolean = true; + _emitter: ?NativeEventEmitter; constructor() { - super(RCTAppState); - - this.isAvailable = true; - this._eventHandlers = { - change: new Map(), - memoryWarning: new Map(), - }; - - // TODO: Remove the 'active' fallback after `initialAppState` is exported by - // the Android implementation. - this.currentState = RCTAppState.initialAppState || 'active'; - - let eventUpdated = false; - - // TODO: this is a terrible solution - in order to ensure `currentState` - // prop is up to date, we have to register an observer that updates it - // whenever the state changes, even if nobody cares. We should just - // deprecate the `currentState` property and get rid of this. - this.addListener( - 'appStateDidChange', - (appStateData) => { + if (NativeAppState == null) { + this.isAvailable = false; + } else { + this.isAvailable = true; + + const emitter: NativeEventEmitter = + new NativeEventEmitter( + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeAppState, + ); + this._emitter = emitter; + + this.currentState = NativeAppState.getConstants().initialAppState; + + let eventUpdated = false; + + // TODO: this is a terrible solution - in order to ensure `currentState` + // prop is up to date, we have to register an observer that updates it + // whenever the state changes, even if nobody cares. We should just + // deprecate the `currentState` property and get rid of this. + emitter.addListener('appStateDidChange', appStateData => { eventUpdated = true; this.currentState = appStateData.app_state; - } - ); - - // TODO: see above - this request just populates the value of `currentState` - // when the module is first initialized. Would be better to get rid of the - // prop and expose `getCurrentAppState` method directly. - RCTAppState.getCurrentAppState( - (appStateData) => { - if (!eventUpdated) { + }); + + // TODO: see above - this request just populates the value of `currentState` + // when the module is first initialized. Would be better to get rid of the + // prop and expose `getCurrentAppState` method directly. + // $FlowExpectedError[incompatible-call] + NativeAppState.getCurrentAppState(appStateData => { + // It's possible that the state will have changed here & listeners need to be notified + if (!eventUpdated && this.currentState !== appStateData.app_state) { this.currentState = appStateData.app_state; + emitter.emit('appStateDidChange', appStateData); } - }, - logError - ); + }, logError); + } } // TODO: now that AppState is a subclass of NativeEventEmitter, we could @@ -74,82 +87,46 @@ class AppState extends NativeEventEmitter { // addListener` and `listener.remove()` directly. That will be a breaking // change though, as both the method and event names are different // (addListener events are currently required to be globally unique). - /** + /** * Add a handler to AppState changes by listening to the `change` event type * and providing the handler. * - * See http://facebook.github.io/react-native/docs/appstate.html#addeventlistener - */ - addEventListener( - type: string, - handler: Function - ) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to subscribe to unknown event: "%s"', type - ); - if (type === 'change') { - this._eventHandlers[type].set(handler, this.addListener( - 'appStateDidChange', - (appStateData) => { - handler(appStateData.app_state); - } - )); - } else if (type === 'memoryWarning') { - this._eventHandlers[type].set(handler, this.addListener( - 'memoryWarning', - handler - )); - } - } - - /** - * Remove a handler by passing the `change` event type and the handler. - * - * See http://facebook.github.io/react-native/docs/appstate.html#removeeventlistener + * See https://reactnative.dev/docs/appstate#addeventlistener */ - removeEventListener( - type: string, - handler: Function - ) { - invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, - 'Trying to remove listener for unknown event: "%s"', type - ); - if (!this._eventHandlers[type].has(handler)) { - return; + addEventListener>( + type: K, + handler: (...$ElementType) => void, + ): EventSubscription { + const emitter = this._emitter; + if (emitter == null) { + throw new Error('Cannot use AppState when `isAvailable` is false.'); } - this._eventHandlers[type].get(handler).remove(); - this._eventHandlers[type].delete(handler); - } -} - -if (__DEV__ && !RCTAppState) { - class MissingNativeAppStateShim extends MissingNativeEventEmitterShim { - constructor() { - super('RCTAppState', 'AppState'); - } - - get currentState(): ?string { - this.throwMissingNativeModule(); - } - - addEventListener(...args: Array) { - this.throwMissingNativeModule(); - } - - removeEventListener(...args: Array) { - this.throwMissingNativeModule(); + switch (type) { + case 'change': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + const changeHandler: AppStateValues => void = handler; + return emitter.addListener('appStateDidChange', appStateData => { + changeHandler(appStateData.app_state); + }); + case 'memoryWarning': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + const memoryWarningHandler: () => void = handler; + return emitter.addListener('memoryWarning', memoryWarningHandler); + case 'blur': + case 'focus': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + const focusOrBlurHandler: () => void = handler; + return emitter.addListener('appStateFocusChange', hasFocus => { + if (type === 'blur' && !hasFocus) { + focusOrBlurHandler(); + } + if (type === 'focus' && hasFocus) { + focusOrBlurHandler(); + } + }); } + throw new Error('Trying to subscribe to unknown event: ' + type); } - - // This module depends on the native `RCTAppState` module. If you don't - // include it, `AppState.isAvailable` will return `false`, and any method - // calls will throw. We reassign the class variable to keep the autodoc - // generator happy. - AppState = new MissingNativeAppStateShim(); -} else { - AppState = new AppState(); } -module.exports = AppState; +module.exports = (new AppState(): AppState); diff --git a/Libraries/AppState/NativeAppState.js b/Libraries/AppState/NativeAppState.js new file mode 100644 index 00000000000000..d546e70051d0d1 --- /dev/null +++ b/Libraries/AppState/NativeAppState.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getConstants: () => {| + initialAppState: string, + |}; + +getCurrentAppState: ( + success: (appState: {|app_state: string|}) => void, + error: (error: Object) => void, + ) => void; + + // Events + +addListener: (eventName: string) => void; + +removeListeners: (count: number) => void; +} + +export default (TurboModuleRegistry.getEnforcing('AppState'): Spec); diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js index bfc6862cb83551..0c25ba26811a46 100644 --- a/Libraries/BatchedBridge/BatchedBridge.js +++ b/Libraries/BatchedBridge/BatchedBridge.js @@ -1,30 +1,18 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule BatchedBridge - * @flow + * @format + * @flow strict */ -'use strict'; -const MessageQueue = require('MessageQueue'); +'use strict'; -// MessageQueue can install a global handler to catch all exceptions where JS users can register their own behavior -// This handler makes all exceptions to be handled inside MessageQueue rather than by the VM at its origin -// This makes stacktraces to be placed at MessageQueue rather than at where they were launched -// The parameter __fbUninstallRNGlobalErrorHandler is passed to MessageQueue to prevent the handler from being installed -// -// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for initialization -// If the Inspector isn't present it defaults to undefined and the global error handler is installed -// The Inspector can still call MessageQueue#uninstallGlobalErrorHandler to uninstalled on attach +const MessageQueue = require('./MessageQueue'); -const BatchedBridge = new MessageQueue( - // $FlowFixMe - typeof __fbUninstallRNGlobalErrorHandler !== 'undefined' && - __fbUninstallRNGlobalErrorHandler === true, // eslint-disable-line no-undef -); +const BatchedBridge: MessageQueue = new MessageQueue(); // Wire up the batched bridge on the global object so that we can call into it. // Ideally, this would be the inverse relationship. I.e. the native environment diff --git a/Libraries/BatchedBridge/MessageQueue.js b/Libraries/BatchedBridge/MessageQueue.js index 428d388173989e..2ae720810ac9e4 100644 --- a/Libraries/BatchedBridge/MessageQueue.js +++ b/Libraries/BatchedBridge/MessageQueue.js @@ -1,28 +1,28 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule MessageQueue - * @flow + * @flow strict * @format */ 'use strict'; -const ErrorUtils = require('ErrorUtils'); -const Systrace = require('Systrace'); - -const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -const invariant = require('fbjs/lib/invariant'); -const stringifySafe = require('stringifySafe'); +const Systrace = require('../Performance/Systrace'); +const deepFreezeAndThrowOnMutationInDev = require('../Utilities/deepFreezeAndThrowOnMutationInDev'); +const stringifySafe = require('../Utilities/stringifySafe').default; +const warnOnce = require('../Utilities/warnOnce'); +const ErrorUtils = require('../vendor/core/ErrorUtils'); +const invariant = require('invariant'); export type SpyData = { type: number, module: ?string, method: string | number, - args: any[], + args: mixed[], + ... }; const TO_JS = 0; @@ -38,40 +38,31 @@ const TRACE_TAG_REACT_APPS = 1 << 17; const DEBUG_INFO_LIMIT = 32; -// Work around an initialization order issue -let JSTimers = null; - class MessageQueue { - _lazyCallableModules: {[key: string]: (void) => Object}; - _queue: [number[], number[], any[], number]; - _successCallbacks: (?Function)[]; - _failureCallbacks: (?Function)[]; + _lazyCallableModules: {[key: string]: (void) => {...}, ...}; + _queue: [number[], number[], mixed[], number]; + _successCallbacks: Map void>; + _failureCallbacks: Map void>; _callID: number; - _inCall: number; _lastFlush: number; _eventLoopStartTime: number; + _reactNativeMicrotasksCallback: ?() => void; - _debugInfo: {[number]: [number, number]}; - _remoteModuleTable: {[number]: string}; - _remoteMethodTable: {[number]: string[]}; + _debugInfo: {[number]: [number, number], ...}; + _remoteModuleTable: {[number]: string, ...}; + _remoteMethodTable: {[number]: $ReadOnlyArray, ...}; __spy: ?(data: SpyData) => void; - __guard: (() => void) => void; - - constructor(shouldUninstallGlobalErrorHandler: boolean = false) { + constructor() { this._lazyCallableModules = {}; this._queue = [[], [], [], 0]; - this._successCallbacks = []; - this._failureCallbacks = []; + this._successCallbacks = new Map(); + this._failureCallbacks = new Map(); this._callID = 0; this._lastFlush = 0; - this._eventLoopStartTime = new Date().getTime(); - if (shouldUninstallGlobalErrorHandler) { - this.uninstallGlobalErrorHandler(); - } else { - this.installGlobalErrorHandler(); - } + this._eventLoopStartTime = Date.now(); + this._reactNativeMicrotasksCallback = null; if (__DEV__) { this._debugInfo = {}; @@ -79,16 +70,18 @@ class MessageQueue { this._remoteMethodTable = {}; } - (this: any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind( - this, - ); - (this: any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind( - this, - ); - (this: any).flushedQueue = this.flushedQueue.bind(this); - (this: any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind( - this, - ); + // $FlowFixMe[cannot-write] + this.callFunctionReturnFlushedQueue = + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this.callFunctionReturnFlushedQueue.bind(this); + // $FlowFixMe[cannot-write] + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this.flushedQueue = this.flushedQueue.bind(this); + + // $FlowFixMe[cannot-write] + this.invokeCallbackAndReturnFlushedQueue = + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this.invokeCallbackAndReturnFlushedQueue.bind(this); } /** @@ -100,7 +93,7 @@ class MessageQueue { MessageQueue.prototype.__spy = info => { console.log( `${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` + - `${info.module ? info.module + '.' : ''}${info.method}` + + `${info.module != null ? info.module + '.' : ''}${info.method}` + `(${JSON.stringify(info.args)})`, ); }; @@ -111,28 +104,22 @@ class MessageQueue { } } - callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) { - this.__guard(() => { - this.__callFunction(module, method, args); - }); - - return this.flushedQueue(); - } - - callFunctionReturnResultAndFlushedQueue( + callFunctionReturnFlushedQueue( module: string, method: string, - args: any[], - ) { - let result; + args: mixed[], + ): null | [Array, Array, Array, number] { this.__guard(() => { - result = this.__callFunction(module, method, args); + this.__callFunction(module, method, args); }); - return [result, this.flushedQueue()]; + return this.flushedQueue(); } - invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) { + invokeCallbackAndReturnFlushedQueue( + cbID: number, + args: mixed[], + ): null | [Array, Array, Array, number] { this.__guard(() => { this.__invokeCallback(cbID, args); }); @@ -140,9 +127,9 @@ class MessageQueue { return this.flushedQueue(); } - flushedQueue() { + flushedQueue(): null | [Array, Array, Array, number] { this.__guard(() => { - this.__callImmediates(); + this.__callReactNativeMicrotasks(); }); const queue = this._queue; @@ -150,44 +137,83 @@ class MessageQueue { return queue[0].length ? queue : null; } - getEventLoopRunningTime() { - return new Date().getTime() - this._eventLoopStartTime; + getEventLoopRunningTime(): number { + return Date.now() - this._eventLoopStartTime; } - registerCallableModule(name: string, module: Object) { + registerCallableModule(name: string, module: {...}) { this._lazyCallableModules[name] = () => module; } - registerLazyCallableModule(name: string, factory: void => Object) { - let module: Object; - let getValue: ?(void) => Object = factory; + registerLazyCallableModule(name: string, factory: void => interface {}) { + let module: interface {}; + let getValue: ?(void) => interface {} = factory; this._lazyCallableModules[name] = () => { if (getValue) { module = getValue(); getValue = null; } + /* $FlowFixMe[class-object-subtyping] added when improving typing for + * this parameters */ return module; }; } - getCallableModule(name: string) { + getCallableModule(name: string): {...} | null { const getValue = this._lazyCallableModules[name]; return getValue ? getValue() : null; } - enqueueNativeCall( + callNativeSyncHook( moduleID: number, methodID: number, - params: any[], - onFail: ?Function, - onSucc: ?Function, - ) { + params: mixed[], + onFail: ?(...mixed[]) => void, + onSucc: ?(...mixed[]) => void, + ): mixed { + if (__DEV__) { + invariant( + global.nativeCallSyncHook, + 'Calling synchronous methods on native ' + + 'modules is not supported in Chrome.\n\n Consider providing alternative ' + + 'methods to expose this method in debug mode, e.g. by exposing constants ' + + 'ahead-of-time.', + ); + } + this.processCallbacks(moduleID, methodID, params, onFail, onSucc); + return global.nativeCallSyncHook(moduleID, methodID, params); + } + + processCallbacks( + moduleID: number, + methodID: number, + params: mixed[], + onFail: ?(...mixed[]) => void, + onSucc: ?(...mixed[]) => void, + ): void { if (onFail || onSucc) { if (__DEV__) { this._debugInfo[this._callID] = [moduleID, methodID]; if (this._callID > DEBUG_INFO_LIMIT) { delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT]; } + if (this._successCallbacks.size > 500) { + const info: {[number]: {method: string, module: string}} = {}; + this._successCallbacks.forEach((_, callID) => { + const debug = this._debugInfo[callID]; + const module = debug && this._remoteModuleTable[debug[0]]; + const method = debug && this._remoteMethodTable[debug[0]][debug[1]]; + info[callID] = {module, method}; + }); + warnOnce( + 'excessive-number-of-pending-callbacks', + `Please report: Excessive number of pending callbacks: ${ + this._successCallbacks.size + }. Some pending callbacks that might have leaked by never being called from native code: ${stringifySafe( + info, + )}`, + ); + } } // Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit // to indicate fail (0) or success (1) @@ -195,10 +221,9 @@ class MessageQueue { onFail && params.push(this._callID << 1); // eslint-disable-next-line no-bitwise onSucc && params.push((this._callID << 1) | 1); - this._successCallbacks[this._callID] = onSucc; - this._failureCallbacks[this._callID] = onFail; + this._successCallbacks.set(this._callID, onSucc); + this._failureCallbacks.set(this._callID, onFail); } - if (__DEV__) { global.nativeTraceBeginAsyncFlow && global.nativeTraceBeginAsyncFlow( @@ -208,6 +233,16 @@ class MessageQueue { ); } this._callID++; + } + + enqueueNativeCall( + moduleID: number, + methodID: number, + params: mixed[], + onFail: ?(...mixed[]) => void, + onSucc: ?(...mixed[]) => void, + ) { + this.processCallbacks(moduleID, methodID, params, onFail, onSucc); this._queue[MODULE_IDS].push(moduleID); this._queue[METHOD_IDS].push(methodID); @@ -217,49 +252,69 @@ class MessageQueue { // folly-convertible. As a special case, if a prop value is a // function it is permitted here, and special-cased in the // conversion. - const isValidArgument = val => { - const t = typeof val; - if ( - t === 'undefined' || - t === 'null' || - t === 'boolean' || - t === 'number' || - t === 'string' - ) { - return true; - } - if (t === 'function' || t !== 'object') { - return false; - } - if (Array.isArray(val)) { - return val.every(isValidArgument); - } - for (const k in val) { - if (typeof val[k] !== 'function' && !isValidArgument(val[k])) { + const isValidArgument = (val: mixed) => { + switch (typeof val) { + case 'undefined': + case 'boolean': + case 'string': + return true; + case 'number': + return isFinite(val); + case 'object': + if (val == null) { + return true; + } + + if (Array.isArray(val)) { + return val.every(isValidArgument); + } + + for (const k in val) { + if (typeof val[k] !== 'function' && !isValidArgument(val[k])) { + return false; + } + } + + return true; + case 'function': return false; - } + default: + return false; + } + }; + + // Replacement allows normally non-JSON-convertible values to be + // seen. There is ambiguity with string values, but in context, + // it should at least be a strong hint. + const replacer = (key: string, val: $FlowFixMe) => { + const t = typeof val; + if (t === 'function') { + return '<>'; + } else if (t === 'number' && !isFinite(val)) { + return '<<' + val.toString() + '>>'; + } else { + return val; } - return true; }; + // Note that JSON.stringify invariant( isValidArgument(params), '%s is not usable as a native method argument', - params, + JSON.stringify(params, replacer), ); // The params object should not be mutated after being queued - deepFreezeAndThrowOnMutationInDev((params: any)); + deepFreezeAndThrowOnMutationInDev(params); } this._queue[PARAMS].push(params); - const now = new Date().getTime(); + const now = Date.now(); if ( global.nativeFlushQueueImmediate && - (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS || - this._inCall === 0) + now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ) { - var queue = this._queue; + const queue = this._queue; this._queue = [[], [], [], this._callID]; this._lastFlush = now; global.nativeFlushQueueImmediate(queue); @@ -282,79 +337,95 @@ class MessageQueue { } } - createDebugLookup(moduleID: number, name: string, methods: string[]) { + createDebugLookup( + moduleID: number, + name: string, + methods: ?$ReadOnlyArray, + ) { if (__DEV__) { this._remoteModuleTable[moduleID] = name; - this._remoteMethodTable[moduleID] = methods; + this._remoteMethodTable[moduleID] = methods || []; } } - uninstallGlobalErrorHandler() { - this.__guard = this.__guardUnsafe; - } - - installGlobalErrorHandler() { - this.__guard = this.__guardSafe; + // For JSTimers to register its callback. Otherwise a circular dependency + // between modules is introduced. Note that only one callback may be + // registered at a time. + setReactNativeMicrotasksCallback(fn: () => void) { + this._reactNativeMicrotasksCallback = fn; } /** * Private methods */ - // Lets exceptions propagate to be handled by the VM at the origin - __guardUnsafe(fn: () => void) { - this._inCall++; - fn(); - this._inCall--; - } - - __guardSafe(fn: () => void) { - this._inCall++; - try { + __guard(fn: () => void) { + if (this.__shouldPauseOnThrow()) { fn(); - } catch (error) { - ErrorUtils.reportFatalError(error); - } finally { - this._inCall--; + } else { + try { + fn(); + } catch (error) { + ErrorUtils.reportFatalError(error); + } } } - __callImmediates() { - Systrace.beginEvent('JSTimers.callImmediates()'); - if (!JSTimers) { - JSTimers = require('JSTimers'); + // MessageQueue installs a global handler to catch all exceptions where JS users can register their own behavior + // This handler makes all exceptions to be propagated from inside MessageQueue rather than by the VM at their origin + // This makes stacktraces to be placed at MessageQueue rather than at where they were launched + // The parameter DebuggerInternal.shouldPauseOnThrow is used to check before catching all exceptions and + // can be configured by the VM or any Inspector + __shouldPauseOnThrow(): boolean { + return ( + // $FlowFixMe[cannot-resolve-name] + typeof DebuggerInternal !== 'undefined' && + DebuggerInternal.shouldPauseOnThrow === true // eslint-disable-line no-undef + ); + } + + __callReactNativeMicrotasks() { + Systrace.beginEvent('JSTimers.callReactNativeMicrotasks()'); + if (this._reactNativeMicrotasksCallback != null) { + this._reactNativeMicrotasksCallback(); } - JSTimers.callImmediates(); Systrace.endEvent(); } - __callFunction(module: string, method: string, args: any[]): any { - this._lastFlush = new Date().getTime(); + __callFunction(module: string, method: string, args: mixed[]): void { + this._lastFlush = Date.now(); this._eventLoopStartTime = this._lastFlush; - Systrace.beginEvent(`${module}.${method}()`); + if (__DEV__ || this.__spy) { + Systrace.beginEvent(`${module}.${method}(${stringifySafe(args)})`); + } else { + Systrace.beginEvent(`${module}.${method}(...)`); + } if (this.__spy) { this.__spy({type: TO_JS, module, method, args}); } const moduleMethods = this.getCallableModule(module); - invariant( - !!moduleMethods, - 'Module %s is not a registered callable module (calling %s)', - module, - method, - ); - invariant( - !!moduleMethods[method], - 'Method %s does not exist on module %s', - method, - module, - ); - const result = moduleMethods[method].apply(moduleMethods, args); + if (!moduleMethods) { + const callableModuleNames = Object.keys(this._lazyCallableModules); + const n = callableModuleNames.length; + const callableModuleNameList = callableModuleNames.join(', '); + invariant( + false, + `Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}. + A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`, + ); + } + if (!moduleMethods[method]) { + invariant( + false, + `Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`, + ); + } + moduleMethods[method].apply(moduleMethods, args); Systrace.endEvent(); - return result; } - __invokeCallback(cbID: number, args: any[]) { - this._lastFlush = new Date().getTime(); + __invokeCallback(cbID: number, args: mixed[]) { + this._lastFlush = Date.now(); this._eventLoopStartTime = this._lastFlush; // The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left. @@ -363,22 +434,21 @@ class MessageQueue { // eslint-disable-next-line no-bitwise const isSuccess = cbID & 1; const callback = isSuccess - ? this._successCallbacks[callID] - : this._failureCallbacks[callID]; + ? this._successCallbacks.get(callID) + : this._failureCallbacks.get(callID); if (__DEV__) { const debug = this._debugInfo[callID]; const module = debug && this._remoteModuleTable[debug[0]]; const method = debug && this._remoteMethodTable[debug[0]][debug[1]]; - if (!callback) { - let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`; - if (method) { - errorMessage = - `The callback ${method}() exists in module ${module}, ` + - 'but only one callback may be registered to a function in a native module.'; - } - invariant(callback, errorMessage); - } + invariant( + callback, + `No callback found with cbID ${cbID} and callID ${callID} for ` + + (method + ? ` ${module}.${method} - most likely the callback was already invoked` + : `module ${module || ''}`) + + `. Args: '${stringifySafe(args)}'`, + ); const profileName = debug ? '' : cbID; @@ -394,7 +464,8 @@ class MessageQueue { return; } - this._successCallbacks[callID] = this._failureCallbacks[callID] = null; + this._successCallbacks.delete(callID); + this._failureCallbacks.delete(callID); callback(...args); if (__DEV__) { diff --git a/Libraries/BatchedBridge/NativeModules.js b/Libraries/BatchedBridge/NativeModules.js index 324c4f6e995bdd..30259e79b9fdec 100644 --- a/Libraries/BatchedBridge/NativeModules.js +++ b/Libraries/BatchedBridge/NativeModules.js @@ -1,68 +1,95 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule NativeModules - * @flow + * @format + * @flow strict */ -'use strict'; -const BatchedBridge = require('BatchedBridge'); +'use strict'; -const invariant = require('fbjs/lib/invariant'); +import type {ExtendedError} from '../Core/ExtendedError'; -import type {ExtendedError} from 'parseErrorStack'; +const BatchedBridge = require('./BatchedBridge'); +const invariant = require('invariant'); -type ModuleConfig = [ - string, /* name */ - ?Object, /* constants */ - Array, /* functions */ - Array, /* promise method IDs */ - Array, /* sync method IDs */ +export type ModuleConfig = [ + string /* name */, + ?{...} /* constants */, + ?$ReadOnlyArray /* functions */, + ?$ReadOnlyArray /* promise method IDs */, + ?$ReadOnlyArray /* sync method IDs */, ]; export type MethodType = 'async' | 'promise' | 'sync'; -function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} { +function genModule( + config: ?ModuleConfig, + moduleID: number, +): ?{ + name: string, + module?: {...}, + ... +} { if (!config) { return null; } const [moduleName, constants, methods, promiseMethods, syncMethods] = config; - invariant(!moduleName.startsWith('RCT') && !moduleName.startsWith('RK'), - 'Module name prefixes should\'ve been stripped by the native side ' + - 'but wasn\'t for ' + moduleName); + invariant( + !moduleName.startsWith('RCT') && !moduleName.startsWith('RK'), + "Module name prefixes should've been stripped by the native side " + + "but wasn't for " + + moduleName, + ); if (!constants && !methods) { // Module contents will be filled in lazily later - return { name: moduleName }; + return {name: moduleName}; } - const module = {}; - methods && methods.forEach((methodName, methodID) => { - const isPromise = promiseMethods && arrayContains(promiseMethods, methodID); - const isSync = syncMethods && arrayContains(syncMethods, methodID); - invariant(!isPromise || !isSync, 'Cannot have a method that is both async and a sync hook'); - const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async'; - module[methodName] = genMethod(moduleID, methodID, methodType); - }); + const module: {[string]: mixed} = {}; + methods && + methods.forEach((methodName, methodID) => { + const isPromise = + (promiseMethods && arrayContains(promiseMethods, methodID)) || false; + const isSync = + (syncMethods && arrayContains(syncMethods, methodID)) || false; + invariant( + !isPromise || !isSync, + 'Cannot have a method that is both async and a sync hook', + ); + const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async'; + module[methodName] = genMethod(moduleID, methodID, methodType); + }); + Object.assign(module, constants); + if (module.getConstants == null) { + module.getConstants = () => constants || Object.freeze({}); + } else { + console.warn( + `Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`, + ); + } + if (__DEV__) { BatchedBridge.createDebugLookup(moduleID, moduleName, methods); } - return { name: moduleName, module }; + return {name: moduleName, module}; } // export this method as a global so we can call it from native global.__fbGenNativeModule = genModule; -function loadModule(name: string, moduleID: number): ?Object { - invariant(global.nativeRequireModuleConfig, - 'Can\'t lazily create module without nativeRequireModuleConfig'); +function loadModule(name: string, moduleID: number): ?{...} { + invariant( + global.nativeRequireModuleConfig, + "Can't lazily create module without nativeRequireModuleConfig", + ); const config = global.nativeRequireModuleConfig(name); const info = genModule(config, moduleID); return info && info.module; @@ -71,84 +98,111 @@ function loadModule(name: string, moduleID: number): ?Object { function genMethod(moduleID: number, methodID: number, type: MethodType) { let fn = null; if (type === 'promise') { - fn = function(...args: Array) { + fn = function promiseMethodWrapper(...args: Array) { + // In case we reject, capture a useful stack trace here. + /* $FlowFixMe[class-object-subtyping] added when improving typing for + * this parameters */ + const enqueueingFrameError: ExtendedError = new Error(); return new Promise((resolve, reject) => { - BatchedBridge.enqueueNativeCall(moduleID, methodID, args, - (data) => resolve(data), - (errorData) => reject(createErrorFromErrorData(errorData))); + BatchedBridge.enqueueNativeCall( + moduleID, + methodID, + args, + data => resolve(data), + errorData => + reject( + updateErrorWithErrorData( + (errorData: $FlowFixMe), + enqueueingFrameError, + ), + ), + ); }); }; - } else if (type === 'sync') { - fn = function(...args: Array) { - if (__DEV__) { - invariant(global.nativeCallSyncHook, 'Calling synchronous methods on native ' + - 'modules is not supported in Chrome.\n\n Consider providing alternative ' + - 'methods to expose this method in debug mode, e.g. by exposing constants ' + - 'ahead-of-time.'); - } - return global.nativeCallSyncHook(moduleID, methodID, args); - }; } else { - fn = function(...args: Array) { + fn = function nonPromiseMethodWrapper(...args: Array) { const lastArg = args.length > 0 ? args[args.length - 1] : null; const secondLastArg = args.length > 1 ? args[args.length - 2] : null; const hasSuccessCallback = typeof lastArg === 'function'; const hasErrorCallback = typeof secondLastArg === 'function'; - hasErrorCallback && invariant( - hasSuccessCallback, - 'Cannot have a non-function arg after a function arg.' - ); - const onSuccess = hasSuccessCallback ? lastArg : null; - const onFail = hasErrorCallback ? secondLastArg : null; + hasErrorCallback && + invariant( + hasSuccessCallback, + 'Cannot have a non-function arg after a function arg.', + ); + // $FlowFixMe[incompatible-type] + const onSuccess: ?(mixed) => void = hasSuccessCallback ? lastArg : null; + // $FlowFixMe[incompatible-type] + const onFail: ?(mixed) => void = hasErrorCallback ? secondLastArg : null; const callbackCount = hasSuccessCallback + hasErrorCallback; - args = args.slice(0, args.length - callbackCount); - BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess); + const newArgs = args.slice(0, args.length - callbackCount); + if (type === 'sync') { + return BatchedBridge.callNativeSyncHook( + moduleID, + methodID, + newArgs, + onFail, + onSuccess, + ); + } else { + BatchedBridge.enqueueNativeCall( + moduleID, + methodID, + newArgs, + onFail, + onSuccess, + ); + } }; } fn.type = type; return fn; } -function arrayContains(array: Array, value: T): boolean { +function arrayContains(array: $ReadOnlyArray, value: T): boolean { return array.indexOf(value) !== -1; } -function createErrorFromErrorData(errorData: {message: string}): ExtendedError { - const { - message, - ...extraErrorInfo - } = errorData || {}; - const error : ExtendedError = new Error(message); - error.framesToPop = 1; - return Object.assign(error, extraErrorInfo); +function updateErrorWithErrorData( + errorData: {message: string, ...}, + error: ExtendedError, +): ExtendedError { + /* $FlowFixMe[class-object-subtyping] added when improving typing for this + * parameters */ + return Object.assign(error, errorData || {}); } -let NativeModules : {[moduleName: string]: Object} = {}; +let NativeModules: {[moduleName: string]: $FlowFixMe, ...} = {}; if (global.nativeModuleProxy) { NativeModules = global.nativeModuleProxy; -} else { +} else if (!global.nativeExtensions) { const bridgeConfig = global.__fbBatchedBridgeConfig; - invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules'); - - const defineLazyObjectProperty = require('defineLazyObjectProperty'); - (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { - // Initially this config will only contain the module name when running in JSC. The actual - // configuration of the module will be lazily loaded. - const info = genModule(config, moduleID); - if (!info) { - return; - } - - if (info.module) { - NativeModules[info.name] = info.module; - } - // If there's no module config, define a lazy getter - else { - defineLazyObjectProperty(NativeModules, info.name, { - get: () => loadModule(info.name, moduleID) - }); - } - }); + invariant( + bridgeConfig, + '__fbBatchedBridgeConfig is not set, cannot invoke native modules', + ); + + const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty'); + (bridgeConfig.remoteModuleConfig || []).forEach( + (config: ModuleConfig, moduleID: number) => { + // Initially this config will only contain the module name when running in JSC. The actual + // configuration of the module will be lazily loaded. + const info = genModule(config, moduleID); + if (!info) { + return; + } + + if (info.module) { + NativeModules[info.name] = info.module; + } + // If there's no module config, define a lazy getter + else { + defineLazyObjectProperty(NativeModules, info.name, { + get: () => loadModule(info.name, moduleID), + }); + } + }, + ); } module.exports = NativeModules; diff --git a/Libraries/BatchedBridge/__mocks__/MessageQueueTestConfig.js b/Libraries/BatchedBridge/__mocks__/MessageQueueTestConfig.js index 40ad81f5f5cb5f..0d70e66c2d76dc 100644 --- a/Libraries/BatchedBridge/__mocks__/MessageQueueTestConfig.js +++ b/Libraries/BatchedBridge/__mocks__/MessageQueueTestConfig.js @@ -1,18 +1,37 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * These don't actually exist anywhere in the code. + * @flow strict-local + * @format */ + +// These don't actually exist anywhere in the code. + 'use strict'; -var remoteModulesConfig = [ - ['RemoteModule1',null,['remoteMethod','promiseMethod'],[]], - ['RemoteModule2',null,['remoteMethod','promiseMethod'],[]], + +import type {ModuleConfig} from '../NativeModules'; + +const remoteModulesConfig: $ReadOnlyArray = [ + [ + 'RemoteModule1', + null, + ['remoteMethod', 'promiseMethod', 'promiseReturningMethod', 'syncMethod'], + [2 /* promiseReturningMethod */], + [3 /* syncMethod */], + ], + [ + 'RemoteModule2', + null, + ['remoteMethod', 'promiseMethod', 'promiseReturningMethod', 'syncMethod'], + [2 /* promiseReturningMethod */], + [3 /* syncMethod */], + ], ]; -var MessageQueueTestConfig = { +const MessageQueueTestConfig = { remoteModuleConfig: remoteModulesConfig, }; diff --git a/Libraries/BatchedBridge/__mocks__/MessageQueueTestModule.js b/Libraries/BatchedBridge/__mocks__/MessageQueueTestModule.js index 8c4f7d3c03063f..77b9421c59820b 100644 --- a/Libraries/BatchedBridge/__mocks__/MessageQueueTestModule.js +++ b/Libraries/BatchedBridge/__mocks__/MessageQueueTestModule.js @@ -1,22 +1,22 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format */ + 'use strict'; /** * Dummy module that only exists for the sake of proving that the message queue - * correctly dispatches to commonJS modules. The `testHook` is overriden by test + * correctly dispatches to commonJS modules. The `testHook` is overridden by test * cases. */ -var MessageQueueTestModule = { - testHook1: function() { - }, - testHook2: function() { - } +const MessageQueueTestModule = { + testHook1: function () {}, + testHook2: function () {}, }; module.exports = MessageQueueTestModule; diff --git a/Libraries/BatchedBridge/__tests__/MessageQueue-test.js b/Libraries/BatchedBridge/__tests__/MessageQueue-test.js index ecf60bcd0455cc..c94f580a1cbf42 100644 --- a/Libraries/BatchedBridge/__tests__/MessageQueue-test.js +++ b/Libraries/BatchedBridge/__tests__/MessageQueue-test.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,6 +7,7 @@ * @emails oncall+react_native * @format */ + 'use strict'; let MessageQueue; @@ -29,11 +30,11 @@ const assertQueue = (flushedQueue, index, moduleID, methodID, params) => { // // [ ] Local modules that throw exceptions are gracefully caught. In that case // local callbacks stored by IDs are cleaned up. -describe('MessageQueue', function() { - beforeEach(function() { +describe('MessageQueue', function () { + beforeEach(function () { jest.resetModules(); - MessageQueue = require('MessageQueue'); - MessageQueueTestModule = require('MessageQueueTestModule'); + MessageQueue = require('../MessageQueue'); + MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); queue = new MessageQueue(); queue.registerCallableModule( 'MessageQueueTestModule', @@ -80,13 +81,25 @@ describe('MessageQueue', function() { }); it('should throw when calling the same callback twice', () => { - queue.enqueueNativeCall(0, 1, [], () => {}, () => {}); + queue.enqueueNativeCall( + 0, + 1, + [], + () => {}, + () => {}, + ); queue.__invokeCallback(1, []); expect(() => queue.__invokeCallback(1, [])).toThrow(); }); it('should throw when calling both success and failure callback', () => { - queue.enqueueNativeCall(0, 1, [], () => {}, () => {}); + queue.enqueueNativeCall( + 0, + 1, + [], + () => {}, + () => {}, + ); queue.__invokeCallback(1, []); expect(() => queue.__invokeCallback(0, [])).toThrow(); }); @@ -95,7 +108,8 @@ describe('MessageQueue', function() { const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod'; expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow( - `Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`, + `Failed to call into JavaScript module method ${unknownModule}.${unknownMethod}(). Module has not been registered as callable. Registered callable JavaScript modules (n = 1): MessageQueueTestModule. + A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`, ); }); @@ -125,59 +139,27 @@ describe('MessageQueue', function() { expect(factory).toHaveBeenCalledTimes(1); }); - it('should catch all exceptions if the global error handler is installed', () => { - const errorMessage = 'intentional error'; - const errorModule = { - explode: function() { - throw new Error(errorMessage); - }, - }; - const name = 'errorModuleName'; - const factory = jest.fn(() => errorModule); - queue.__guardSafe = jest.fn(() => {}); - queue.__guardUnsafe = jest.fn(() => {}); - queue.installGlobalErrorHandler(); - queue.registerLazyCallableModule(name, factory); - queue.callFunctionReturnFlushedQueue(name, 'explode', []); - expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0); - expect(queue.__guardSafe).toHaveBeenCalledTimes(2); - }); - - it('should propagate exceptions if the global error handler is uninstalled', () => { - queue.uninstallGlobalErrorHandler(); - const errorMessage = 'intentional error'; - const errorModule = { - explode: function() { - throw new Error(errorMessage); - }, + it('should check if the global error handler is not overridden by the DebuggerInternal object', () => { + const dummyModule = { + dummy: function () {}, }; - const name = 'errorModuleName'; - const factory = jest.fn(() => errorModule); - queue.__guardUnsafe = jest.fn(() => {}); - queue.__guardSafe = jest.fn(() => {}); + const name = 'emptyModuleName'; + const factory = jest.fn(() => dummyModule); + queue.__shouldPauseOnThrow = jest.fn(() => false); queue.registerLazyCallableModule(name, factory); - queue.uninstallGlobalErrorHandler(); - queue.callFunctionReturnFlushedQueue(name, 'explode'); - expect(queue.__guardUnsafe).toHaveBeenCalledTimes(2); - expect(queue.__guardSafe).toHaveBeenCalledTimes(0); + queue.callFunctionReturnFlushedQueue(name, 'dummy', []); + expect(queue.__shouldPauseOnThrow).toHaveBeenCalledTimes(2); }); - it('should catch all exceptions if the global error handler is re-installed', () => { - const errorMessage = 'intentional error'; - const errorModule = { - explode: function() { - throw new Error(errorMessage); - }, + it('should check if the global error handler is overridden by the DebuggerInternal object', () => { + const dummyModule = { + dummy: function () {}, }; - const name = 'errorModuleName'; - const factory = jest.fn(() => errorModule); - queue.__guardUnsafe = jest.fn(() => {}); - queue.__guardSafe = jest.fn(() => {}); + const name = 'emptyModuleName'; + const factory = jest.fn(() => dummyModule); + queue.__shouldPauseOnThrow = jest.fn(() => true); queue.registerLazyCallableModule(name, factory); - queue.uninstallGlobalErrorHandler(); - queue.installGlobalErrorHandler(); - queue.callFunctionReturnFlushedQueue(name, 'explode'); - expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0); - expect(queue.__guardSafe).toHaveBeenCalledTimes(2); + queue.callFunctionReturnFlushedQueue(name, 'dummy', []); + expect(queue.__shouldPauseOnThrow).toHaveBeenCalledTimes(2); }); }); diff --git a/Libraries/BatchedBridge/__tests__/NativeModules-test.js b/Libraries/BatchedBridge/__tests__/NativeModules-test.js index b9cc2411d29512..f4d4aa7a93a6c8 100644 --- a/Libraries/BatchedBridge/__tests__/NativeModules-test.js +++ b/Libraries/BatchedBridge/__tests__/NativeModules-test.js @@ -1,19 +1,16 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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('BatchedBridge') - .unmock('defineLazyObjectProperty') - .unmock('MessageQueue') - .unmock('NativeModules'); +jest.unmock('../NativeModules'); let BatchedBridge; let NativeModules; @@ -39,13 +36,13 @@ const assertQueue = (flushedQueue, index, moduleID, methodID, params) => { // success callbacks are cleaned up. // // [ ] Remote invocation throws if not supplying an error callback. -describe('MessageQueue', function() { - beforeEach(function() { +describe('MessageQueue', function () { + beforeEach(function () { jest.resetModules(); - global.__fbBatchedBridgeConfig = require('MessageQueueTestConfig'); - BatchedBridge = require('BatchedBridge'); - NativeModules = require('NativeModules'); + global.__fbBatchedBridgeConfig = require('../__mocks__/MessageQueueTestConfig'); + BatchedBridge = require('../BatchedBridge'); + NativeModules = require('../NativeModules'); }); it('should generate native modules', () => { @@ -54,12 +51,17 @@ describe('MessageQueue', function() { assertQueue(flushedQueue, 0, 0, 0, ['foo']); }); - it('should make round trip and clear memory', function() { + it('should make round trip and clear memory', function () { const onFail = jest.fn(); const onSucc = jest.fn(); // Perform communication - NativeModules.RemoteModule1.promiseMethod('paloAlto', 'menloPark', onFail, onSucc); + NativeModules.RemoteModule1.promiseMethod( + 'paloAlto', + 'menloPark', + onFail, + onSucc, + ); NativeModules.RemoteModule2.promiseMethod('mac', 'windows', onFail, onSucc); const resultingRemoteInvocations = BatchedBridge.flushedQueue(); @@ -73,9 +75,10 @@ describe('MessageQueue', function() { expect(resultingRemoteInvocations[0][0]).toBe(0); // `RemoteModule1` expect(resultingRemoteInvocations[1][0]).toBe(1); // `promiseMethod` - expect([ // the arguments + expect([ + // the arguments resultingRemoteInvocations[2][0][0], - resultingRemoteInvocations[2][0][1] + resultingRemoteInvocations[2][0][1], ]).toEqual(['paloAlto', 'menloPark']); // Callbacks ids are tacked onto the end of the remote arguments. const firstFailCBID = resultingRemoteInvocations[2][0][2]; @@ -83,9 +86,10 @@ describe('MessageQueue', function() { expect(resultingRemoteInvocations[0][1]).toBe(1); // `RemoteModule2` expect(resultingRemoteInvocations[1][1]).toBe(1); // `promiseMethod` - expect([ // the arguments + expect([ + // the arguments resultingRemoteInvocations[2][1][0], - resultingRemoteInvocations[2][1][1] + resultingRemoteInvocations[2][1][1], ]).toEqual(['mac', 'windows']); const secondFailCBID = resultingRemoteInvocations[2][1][2]; const secondSuccCBID = resultingRemoteInvocations[2][1][3]; @@ -93,7 +97,7 @@ describe('MessageQueue', function() { // Handle the first remote invocation by signaling failure. BatchedBridge.__invokeCallback(firstFailCBID, ['firstFailure']); // The failure callback was already invoked, the success is no longer valid - expect(function() { + expect(function () { BatchedBridge.__invokeCallback(firstSuccCBID, ['firstSucc']); }).toThrow(); expect(onFail.mock.calls.length).toBe(1); @@ -102,10 +106,117 @@ describe('MessageQueue', function() { // Handle the second remote invocation by signaling success. BatchedBridge.__invokeCallback(secondSuccCBID, ['secondSucc']); // The success callback was already invoked, the fail cb is no longer valid - expect(function() { + expect(function () { BatchedBridge.__invokeCallback(secondFailCBID, ['secondFail']); }).toThrow(); expect(onFail.mock.calls.length).toBe(1); expect(onSucc.mock.calls.length).toBe(1); }); + + it('promise-returning methods (type=promise)', async function () { + // Perform communication + const promise1 = NativeModules.RemoteModule1.promiseReturningMethod( + 'paloAlto', + 'menloPark', + ); + const promise2 = NativeModules.RemoteModule2.promiseReturningMethod( + 'mac', + 'windows', + ); + + const resultingRemoteInvocations = BatchedBridge.flushedQueue(); + + // As always, the message queue has four fields + expect(resultingRemoteInvocations.length).toBe(4); + expect(resultingRemoteInvocations[MODULE_IDS].length).toBe(2); + expect(resultingRemoteInvocations[METHOD_IDS].length).toBe(2); + expect(resultingRemoteInvocations[PARAMS].length).toBe(2); + expect(typeof resultingRemoteInvocations[CALL_ID]).toEqual('number'); + + expect(resultingRemoteInvocations[0][0]).toBe(0); // `RemoteModule1` + expect(resultingRemoteInvocations[1][0]).toBe(2); // `promiseReturningMethod` + expect([ + // the arguments + resultingRemoteInvocations[2][0][0], + resultingRemoteInvocations[2][0][1], + ]).toEqual(['paloAlto', 'menloPark']); + // For promise-returning methods, the order of callbacks is flipped from + // regular async methods. + const firstSuccCBID = resultingRemoteInvocations[2][0][2]; + const firstFailCBID = resultingRemoteInvocations[2][0][3]; + + expect(resultingRemoteInvocations[0][1]).toBe(1); // `RemoteModule2` + expect(resultingRemoteInvocations[1][1]).toBe(2); // `promiseReturningMethod` + expect([ + // the arguments + resultingRemoteInvocations[2][1][0], + resultingRemoteInvocations[2][1][1], + ]).toEqual(['mac', 'windows']); + const secondSuccCBID = resultingRemoteInvocations[2][1][2]; + const secondFailCBID = resultingRemoteInvocations[2][1][3]; + + // Handle the first remote invocation by signaling failure. + BatchedBridge.__invokeCallback(firstFailCBID, [{message: 'firstFailure'}]); + // The failure callback was already invoked, the success is no longer valid + expect(function () { + BatchedBridge.__invokeCallback(firstSuccCBID, ['firstSucc']); + }).toThrow(); + await expect(promise1).rejects.toBeInstanceOf(Error); + await expect(promise1).rejects.toMatchObject({message: 'firstFailure'}); + + // Handle the second remote invocation by signaling success. + BatchedBridge.__invokeCallback(secondSuccCBID, ['secondSucc']); + // The success callback was already invoked, the fail cb is no longer valid + expect(function () { + BatchedBridge.__invokeCallback(secondFailCBID, ['secondFail']); + }).toThrow(); + await promise2; + }); + + describe('sync methods', () => { + afterEach(function () { + delete global.nativeCallSyncHook; + }); + + it('throwing an exception', function () { + global.nativeCallSyncHook = jest.fn(() => { + throw new Error('firstFailure'); + }); + + let error; + try { + NativeModules.RemoteModule1.syncMethod('paloAlto', 'menloPark'); + } catch (e) { + error = e; + } + + expect(global.nativeCallSyncHook).toBeCalledTimes(1); + expect(global.nativeCallSyncHook).toBeCalledWith( + 0, // `RemoteModule1` + 3, // `syncMethod` + ['paloAlto', 'menloPark'], + ); + expect(error).toBeInstanceOf(Error); + expect(error).toMatchObject({ + message: 'firstFailure', + }); + }); + + it('returning a value', function () { + global.nativeCallSyncHook = jest.fn(() => { + return 'secondSucc'; + }); + + const result = NativeModules.RemoteModule2.syncMethod('mac', 'windows'); + + expect(global.nativeCallSyncHook).toBeCalledTimes(1); + expect(global.nativeCallSyncHook).toBeCalledWith( + 1, // `RemoteModule2` + 3, // `syncMethod` + ['mac', 'windows'], + ); + + expect(result).toBe('secondSucc'); + }); + }); }); diff --git a/Libraries/Blob/Blob.js b/Libraries/Blob/Blob.js index eca13a6a309e8f..b279a030cff354 100644 --- a/Libraries/Blob/Blob.js +++ b/Libraries/Blob/Blob.js @@ -1,17 +1,16 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule Blob - * @flow + * @flow strict-local * @format */ 'use strict'; -import type {BlobData, BlobOptions} from 'BlobTypes'; +import type {BlobData, BlobOptions} from './BlobTypes'; /** * Opaque JS representation of some binary data in native. @@ -59,7 +58,7 @@ class Blob { * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob */ constructor(parts: Array = [], options?: BlobOptions) { - const BlobManager = require('BlobManager'); + const BlobManager = require('./BlobManager'); this.data = BlobManager.createFromParts(parts, options).data; } @@ -68,10 +67,12 @@ class Blob { * the data in the specified range of bytes of the source Blob. * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice */ + // $FlowFixMe[unsafe-getters-setters] set data(data: ?BlobData) { this._data = data; } + // $FlowFixMe[unsafe-getters-setters] get data(): BlobData { if (!this._data) { throw new Error('Blob has been closed and is no longer available'); @@ -81,11 +82,12 @@ class Blob { } slice(start?: number, end?: number): Blob { - const BlobManager = require('BlobManager'); + const BlobManager = require('./BlobManager'); let {offset, size} = this.data; if (typeof start === 'number') { if (start > size) { + // $FlowFixMe[reassign-const] start = size; } offset += start; @@ -93,6 +95,7 @@ class Blob { if (typeof end === 'number') { if (end < 0) { + // $FlowFixMe[reassign-const] end = this.size + end; } size = end - start; @@ -118,7 +121,7 @@ class Blob { * `new Blob([blob, ...])` actually copies the data in memory. */ close() { - const BlobManager = require('BlobManager'); + const BlobManager = require('./BlobManager'); BlobManager.release(this.data.blobId); this.data = null; } @@ -126,6 +129,7 @@ class Blob { /** * Size of the data contained in the Blob object, in bytes. */ + // $FlowFixMe[unsafe-getters-setters] get size(): number { return this.data.size; } @@ -134,6 +138,7 @@ class Blob { * String indicating the MIME type of the data contained in the Blob. * If the type is unknown, this string is empty. */ + // $FlowFixMe[unsafe-getters-setters] get type(): string { return this.data.type || ''; } diff --git a/Libraries/Blob/BlobManager.js b/Libraries/Blob/BlobManager.js index dd7cc00eabc652..0d4cee7282baaf 100644 --- a/Libraries/Blob/BlobManager.js +++ b/Libraries/Blob/BlobManager.js @@ -1,21 +1,19 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule BlobManager - * @flow + * @flow strict-local * @format */ -'use strict'; +const Blob = require('./Blob'); +const BlobRegistry = require('./BlobRegistry'); -const Blob = require('Blob'); -const BlobRegistry = require('BlobRegistry'); -const {BlobModule} = require('NativeModules'); - -import type {BlobData, BlobOptions} from 'BlobTypes'; +import type {BlobData, BlobOptions, BlobCollector} from './BlobTypes'; +import NativeBlobModule from './NativeBlobModule'; +import invariant from 'invariant'; /*eslint-disable no-bitwise */ /*eslint-disable eqeqeq */ @@ -32,6 +30,21 @@ function uuidv4(): string { }); } +// **Temporary workaround** +// TODO(#24654): Use turbomodules for the Blob module. +// Blob collector is a jsi::HostObject that is used by native to know +// when the a Blob instance is deallocated. This allows to free the +// underlying native resources. This is a hack to workaround the fact +// that the current bridge infra doesn't allow to track js objects +// deallocation. Ideally the whole Blob object should be a jsi::HostObject. +function createBlobCollector(blobId: string): BlobCollector | null { + if (global.__blobCollectorProvider == null) { + return null; + } else { + return global.__blobCollectorProvider(blobId); + } +} + /** * Module to manage blobs. Wrapper around the native blob module. */ @@ -39,7 +52,7 @@ class BlobManager { /** * If the native blob module is available. */ - static isAvailable = !!BlobModule; + static isAvailable: boolean = !!NativeBlobModule; /** * Create blob from existing array of blobs. @@ -48,6 +61,8 @@ class BlobManager { parts: Array, options?: BlobOptions, ): Blob { + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + const blobId = uuidv4(); const items = parts.map(part => { if ( @@ -78,7 +93,7 @@ class BlobManager { } }, 0); - BlobModule.createFromParts(items, blobId); + NativeBlobModule.createFromParts(items, blobId); return BlobManager.createFromOptions({ blobId, @@ -95,18 +110,31 @@ class BlobManager { */ static createFromOptions(options: BlobData): Blob { BlobRegistry.register(options.blobId); - return Object.assign(Object.create(Blob.prototype), {data: options}); + return Object.assign(Object.create(Blob.prototype), { + data: + // Reuse the collector instance when creating from an existing blob. + // This will make sure that the underlying resource is only deallocated + // when all blobs that refer to it are deallocated. + options.__collector == null + ? { + ...options, + __collector: createBlobCollector(options.blobId), + } + : options, + }); } /** * Deallocate resources for a blob. */ static release(blobId: string): void { + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + BlobRegistry.unregister(blobId); if (BlobRegistry.has(blobId)) { return; } - BlobModule.release(blobId); + NativeBlobModule.release(blobId); } /** @@ -114,7 +142,9 @@ class BlobManager { * requests and responses. */ static addNetworkingHandler(): void { - BlobModule.addNetworkingHandler(); + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + + NativeBlobModule.addNetworkingHandler(); } /** @@ -122,7 +152,9 @@ class BlobManager { * messages. */ static addWebSocketHandler(socketId: number): void { - BlobModule.addWebSocketHandler(socketId); + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + + NativeBlobModule.addWebSocketHandler(socketId); } /** @@ -130,14 +162,18 @@ class BlobManager { * binary messages. */ static removeWebSocketHandler(socketId: number): void { - BlobModule.removeWebSocketHandler(socketId); + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + + NativeBlobModule.removeWebSocketHandler(socketId); } /** * Send a blob message to a websocket. */ static sendOverSocket(blob: Blob, socketId: number): void { - BlobModule.sendOverSocket(blob.data, socketId); + invariant(NativeBlobModule, 'NativeBlobModule is available.'); + + NativeBlobModule.sendOverSocket(blob.data, socketId); } } diff --git a/Libraries/Blob/BlobRegistry.js b/Libraries/Blob/BlobRegistry.js index 330c1659ae4235..01bb0d11eff48c 100644 --- a/Libraries/Blob/BlobRegistry.js +++ b/Libraries/Blob/BlobRegistry.js @@ -1,15 +1,14 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule BlobRegistry - * @flow + * @flow strict * @format */ -const registry: {[key: string]: number} = {}; +const registry: {[key: string]: number, ...} = {}; const register = (id: string) => { if (registry[id]) { @@ -28,7 +27,7 @@ const unregister = (id: string) => { } }; -const has = (id: string) => { +const has = (id: string): number | boolean => { return registry[id] && registry[id] > 0; }; diff --git a/Libraries/Blob/BlobTypes.js b/Libraries/Blob/BlobTypes.js index 8e895d9006108f..2c3e00eb06e4dd 100644 --- a/Libraries/Blob/BlobTypes.js +++ b/Libraries/Blob/BlobTypes.js @@ -1,16 +1,17 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule BlobTypes - * @flow + * @flow strict * @format */ 'use strict'; +export opaque type BlobCollector = {...}; + export type BlobData = { blobId: string, offset: number, @@ -18,9 +19,12 @@ export type BlobData = { name?: string, type?: string, lastModified?: number, + __collector?: ?BlobCollector, + ... }; export type BlobOptions = { type: string, lastModified: number, + ... }; diff --git a/Libraries/Blob/File.js b/Libraries/Blob/File.js index f5b03d85f7f178..5aa2dd73c46f3d 100644 --- a/Libraries/Blob/File.js +++ b/Libraries/Blob/File.js @@ -1,20 +1,20 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule File * @flow * @format */ + 'use strict'; -const Blob = require('Blob'); +const Blob = require('./Blob'); -const invariant = require('fbjs/lib/invariant'); +const invariant = require('invariant'); -import type {BlobOptions} from 'BlobTypes'; +import type {BlobOptions} from './BlobTypes'; /** * The File interface provides information about files. diff --git a/Libraries/Blob/FileReader.js b/Libraries/Blob/FileReader.js index b3ed89dc9318e6..c0b7feaec713c4 100644 --- a/Libraries/Blob/FileReader.js +++ b/Libraries/Blob/FileReader.js @@ -1,19 +1,17 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule FileReader * @flow * @format */ -'use strict'; - +const Blob = require('./Blob'); const EventTarget = require('event-target-shim'); -const Blob = require('Blob'); -const {FileReaderModule} = require('NativeModules'); + +import NativeFileReaderModule from './NativeFileReaderModule'; type ReadyState = | 0 // EMPTY @@ -35,20 +33,19 @@ const EMPTY = 0; const LOADING = 1; const DONE = 2; -class FileReader extends EventTarget(...READER_EVENTS) { - static EMPTY = EMPTY; - static LOADING = LOADING; - static DONE = DONE; +class FileReader extends (EventTarget(...READER_EVENTS): any) { + static EMPTY: number = EMPTY; + static LOADING: number = LOADING; + static DONE: number = DONE; - EMPTY = EMPTY; - LOADING = LOADING; - DONE = DONE; + EMPTY: number = EMPTY; + LOADING: number = LOADING; + DONE: number = DONE; _readyState: ReadyState; _error: ?Error; _result: ?ReaderResult; _aborted: boolean = false; - _subscriptions: Array<*> = []; constructor() { super(); @@ -61,11 +58,6 @@ class FileReader extends EventTarget(...READER_EVENTS) { this._result = null; } - _clearSubscriptions(): void { - this._subscriptions.forEach(sub => sub.remove()); - this._subscriptions = []; - } - _setReadyState(newState: ReadyState) { this._readyState = newState; this.dispatchEvent({type: 'readystatechange'}); @@ -85,10 +77,16 @@ class FileReader extends EventTarget(...READER_EVENTS) { throw new Error('FileReader.readAsArrayBuffer is not implemented'); } - readAsDataURL(blob: Blob) { + readAsDataURL(blob: ?Blob) { this._aborted = false; - FileReaderModule.readAsDataURL(blob.data).then( + if (blob == null) { + throw new TypeError( + "Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'", + ); + } + + NativeFileReaderModule.readAsDataURL(blob.data).then( (text: string) => { if (this._aborted) { return; @@ -106,10 +104,16 @@ class FileReader extends EventTarget(...READER_EVENTS) { ); } - readAsText(blob: Blob, encoding: string = 'UTF-8') { + readAsText(blob: ?Blob, encoding: string = 'UTF-8') { this._aborted = false; - FileReaderModule.readAsText(blob.data, encoding).then( + if (blob == null) { + throw new TypeError( + "Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'", + ); + } + + NativeFileReaderModule.readAsText(blob.data, encoding).then( (text: string) => { if (this._aborted) { return; diff --git a/Libraries/Blob/NativeBlobModule.js b/Libraries/Blob/NativeBlobModule.js new file mode 100644 index 00000000000000..2b474d693527b6 --- /dev/null +++ b/Libraries/Blob/NativeBlobModule.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getConstants: () => {|BLOB_URI_SCHEME: ?string, BLOB_URI_HOST: ?string|}; + +addNetworkingHandler: () => void; + +addWebSocketHandler: (id: number) => void; + +removeWebSocketHandler: (id: number) => void; + +sendOverSocket: (blob: Object, socketID: number) => void; + +createFromParts: (parts: Array, withId: string) => void; + +release: (blobId: string) => void; +} + +const NativeModule = TurboModuleRegistry.get('BlobModule'); + +let constants = null; +let NativeBlobModule = null; + +if (NativeModule != null) { + NativeBlobModule = { + getConstants(): {|BLOB_URI_SCHEME: ?string, BLOB_URI_HOST: ?string|} { + if (constants == null) { + constants = NativeModule.getConstants(); + } + return constants; + }, + addNetworkingHandler(): void { + NativeModule.addNetworkingHandler(); + }, + addWebSocketHandler(id: number): void { + NativeModule.addWebSocketHandler(id); + }, + removeWebSocketHandler(id: number): void { + NativeModule.removeWebSocketHandler(id); + }, + sendOverSocket(blob: Object, socketID: number): void { + NativeModule.sendOverSocket(blob, socketID); + }, + createFromParts(parts: Array, withId: string): void { + NativeModule.createFromParts(parts, withId); + }, + release(blobId: string): void { + NativeModule.release(blobId); + }, + }; +} + +export default (NativeBlobModule: ?Spec); diff --git a/Libraries/Blob/NativeFileReaderModule.js b/Libraries/Blob/NativeFileReaderModule.js new file mode 100644 index 00000000000000..ac964b3af42ea7 --- /dev/null +++ b/Libraries/Blob/NativeFileReaderModule.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +readAsDataURL: (data: Object) => Promise; + +readAsText: (data: Object, encoding: string) => Promise; +} + +export default (TurboModuleRegistry.getEnforcing( + 'FileReaderModule', +): Spec); diff --git a/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj b/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj deleted file mode 100755 index 2ffded6f074d39..00000000000000 --- a/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj +++ /dev/null @@ -1,363 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; - 19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; - 19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; - 19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; }; - AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; - AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; - AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; - AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; - AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */; }; - ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */; }; - ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; - ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 358F4ED51D1E81A9004DF814 /* Copy Headers */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = include/RCTBlob; - dstSubfolderSpec = 16; - files = ( - 19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */, - AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */, - ); - name = "Copy Headers"; - runOnlyForDeploymentPostprocessing = 0; - }; - AD0871121E215B16007D136D /* Copy Headers */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = include/RCTBlob; - dstSubfolderSpec = 16; - files = ( - 19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */, - AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */, - ); - name = "Copy Headers"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; }; - AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = ""; }; - AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = ""; }; - ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTBlob-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTFileReaderModule.h; sourceTree = ""; }; - ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileReaderModule.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXGroup section */ - 358F4ECE1D1E81A9004DF814 = { - isa = PBXGroup; - children = ( - ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */, - ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */, - AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */, - AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */, - 358F4ED81D1E81A9004DF814 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 358F4ED81D1E81A9004DF814 /* Products */ = { - isa = PBXGroup; - children = ( - 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */, - ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - AD0871151E215EB7007D136D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */, - ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD0871171E215ECC007D136D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */, - AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 358F4ED61D1E81A9004DF814 /* RCTBlob */ = { - isa = PBXNativeTarget; - buildConfigurationList = 358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */; - buildPhases = ( - AD0871151E215EB7007D136D /* Headers */, - 358F4ED51D1E81A9004DF814 /* Copy Headers */, - 358F4ED31D1E81A9004DF814 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTBlob; - productName = SLKBlobs; - productReference = 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */; - productType = "com.apple.product-type.library.static"; - }; - ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */; - buildPhases = ( - AD0871171E215ECC007D136D /* Headers */, - AD0871121E215B16007D136D /* Copy Headers */, - ADD01A641E09402E00F6D226 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "RCTBlob-tvOS"; - productName = "RCTBlob-tvOS"; - productReference = ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 358F4ECF1D1E81A9004DF814 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0730; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 358F4ED61D1E81A9004DF814 = { - CreatedOnToolsVersion = 7.3; - }; - ADD01A671E09402E00F6D226 = { - CreatedOnToolsVersion = 8.2; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = 358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 358F4ECE1D1E81A9004DF814; - productRefGroup = 358F4ED81D1E81A9004DF814 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 358F4ED61D1E81A9004DF814 /* RCTBlob */, - ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 358F4ED31D1E81A9004DF814 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */, - AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - ADD01A641E09402E00F6D226 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */, - ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 358F4EDE1D1E81A9004DF814 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - 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", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 358F4EDF1D1E81A9004DF814 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - 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_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 358F4EE11D1E81A9004DF814 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 358F4EE21D1E81A9004DF814 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; - ADD01A6F1E09402E00F6D226 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 10.1; - }; - name = Debug; - }; - ADD01A701E09402E00F6D226 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 10.1; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 358F4EDE1D1E81A9004DF814 /* Debug */, - 358F4EDF1D1E81A9004DF814 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 358F4EE11D1E81A9004DF814 /* Debug */, - 358F4EE21D1E81A9004DF814 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - ADD01A6F1E09402E00F6D226 /* Debug */, - ADD01A701E09402E00F6D226 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 358F4ECF1D1E81A9004DF814 /* Project object */; -} diff --git a/Libraries/Blob/RCTBlobCollector.h b/Libraries/Blob/RCTBlobCollector.h new file mode 100644 index 00000000000000..fdd68112d6820a --- /dev/null +++ b/Libraries/Blob/RCTBlobCollector.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +using namespace facebook; + +@class RCTBlobManager; + +namespace facebook { +namespace react { + +class JSI_EXPORT RCTBlobCollector : public jsi::HostObject { + public: + RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId); + ~RCTBlobCollector(); + + static void install(RCTBlobManager *blobManager); + + private: + const std::string blobId_; + RCTBlobManager *blobManager_; +}; + +} // namespace react +} // namespace facebook diff --git a/Libraries/Blob/RCTBlobCollector.mm b/Libraries/Blob/RCTBlobCollector.mm new file mode 100644 index 00000000000000..b7387c445a5038 --- /dev/null +++ b/Libraries/Blob/RCTBlobCollector.mm @@ -0,0 +1,52 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTBlobCollector.h" + +#import +#import + +namespace facebook { +namespace react { + +RCTBlobCollector::RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId) +: blobId_(blobId), blobManager_(blobManager) {} + +RCTBlobCollector::~RCTBlobCollector() { + RCTBlobManager *blobManager = blobManager_; + NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()]; + dispatch_async([blobManager_ methodQueue], ^{ + [blobManager remove:blobId]; + }); +} + +void RCTBlobCollector::install(RCTBlobManager *blobManager) { + __weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge; + [cxxBridge dispatchBlock:^{ + if (!cxxBridge || cxxBridge.runtime == nullptr) { + return; + } + jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime; + runtime.global().setProperty( + runtime, + "__blobCollectorProvider", + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"), + 1, + [blobManager](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) { + auto blobId = args[0].asString(rt).utf8(rt); + auto blobCollector = std::make_shared(blobManager, blobId); + return jsi::Object::createFromHostObject(rt, blobCollector); + } + ) + ); + } queue:RCTJSThread]; +} + +} // namespace react +} // namespace facebook diff --git a/Libraries/Blob/RCTBlobManager.h b/Libraries/Blob/RCTBlobManager.h index 24b589ef987ae5..34cd8b82cf908d 100755 --- a/Libraries/Blob/RCTBlobManager.h +++ b/Libraries/Blob/RCTBlobManager.h @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -8,8 +8,9 @@ #import #import #import +#import -@interface RCTBlobManager : NSObject +@interface RCTBlobManager : NSObject - (NSString *)store:(NSData *)data; diff --git a/Libraries/Blob/RCTBlobManager.mm b/Libraries/Blob/RCTBlobManager.mm index 56235ee04b6dfe..567e16ffee56c1 100755 --- a/Libraries/Blob/RCTBlobManager.mm +++ b/Libraries/Blob/RCTBlobManager.mm @@ -1,22 +1,26 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -#import "RCTBlobManager.h" +#import #import +#import #import #import #import #import +#import "RCTBlobPlugins.h" +#import "RCTBlobCollector.h" + static NSString *const kBlobURIScheme = @"blob"; -@interface RCTBlobManager () +@interface RCTBlobManager () @end @@ -33,13 +37,15 @@ @implementation RCTBlobManager RCT_EXPORT_MODULE(BlobModule) @synthesize bridge = _bridge; +@synthesize moduleRegistry = _moduleRegistry; +@synthesize methodQueue = _methodQueue; -- (void)setBridge:(RCTBridge *)bridge +- (void)initialize { - _bridge = bridge; - std::lock_guard lock(_blobsMutex); _blobs = [NSMutableDictionary new]; + + facebook::react::RCTBlobCollector::install(self); } + (BOOL)requiresMainQueueSetup @@ -48,6 +54,11 @@ + (BOOL)requiresMainQueueSetup } - (NSDictionary *)constantsToExport +{ + return [self getConstants]; +} + +- (NSDictionary *)getConstants { return @{ @"BLOB_URI_SCHEME": kBlobURIScheme, @@ -127,31 +138,39 @@ - (void)remove:(NSString *)blobId RCT_EXPORT_METHOD(addNetworkingHandler) { - dispatch_async(_bridge.networking.methodQueue, ^{ - [self->_bridge.networking addRequestHandler:self]; - [self->_bridge.networking addResponseHandler:self]; + RCTNetworking *const networking = [_moduleRegistry moduleForName:"Networking"]; + + // TODO(T63516227): Why can methodQueue be nil here? + // We don't want to do anything when methodQueue is nil. + if (!networking.methodQueue) { + return; + } + + dispatch_async(networking.methodQueue, ^{ + [networking addRequestHandler:self]; + [networking addResponseHandler:self]; }); } -RCT_EXPORT_METHOD(addWebSocketHandler:(nonnull NSNumber *)socketID) +RCT_EXPORT_METHOD(addWebSocketHandler:(double)socketID) { - dispatch_async(_bridge.webSocketModule.methodQueue, ^{ - [self->_bridge.webSocketModule setContentHandler:self forSocketID:socketID]; + dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{ + [[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:self forSocketID:[NSNumber numberWithDouble:socketID]]; }); } -RCT_EXPORT_METHOD(removeWebSocketHandler:(nonnull NSNumber *)socketID) +RCT_EXPORT_METHOD(removeWebSocketHandler:(double)socketID) { - dispatch_async(_bridge.webSocketModule.methodQueue, ^{ - [self->_bridge.webSocketModule setContentHandler:nil forSocketID:socketID]; + dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{ + [[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:nil forSocketID:[NSNumber numberWithDouble:socketID]]; }); } // @lint-ignore FBOBJCUNTYPEDCOLLECTION1 -RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(nonnull NSNumber *)socketID) +RCT_EXPORT_METHOD(sendOverSocket:(NSDictionary *)blob socketID:(double)socketID) { - dispatch_async(_bridge.webSocketModule.methodQueue, ^{ - [self->_bridge.webSocketModule sendData:[self resolve:blob] forSocketID:socketID]; + dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{ + [[self->_moduleRegistry moduleForName:"WebSocketModule"] sendData:[self resolve:blob] forSocketID:[NSNumber numberWithDouble:socketID]]; }); } @@ -243,7 +262,7 @@ - (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]]; NSString *contentType = @"application/octet-stream"; - NSString *blobType = [RCTConvert NSString:blob[@"type"]]; + NSString *blobType = [RCTConvert NSString:RCTNilIfNull(blob[@"type"])]; if (blobType != nil && blobType.length > 0) { contentType = blob[@"type"]; } @@ -258,6 +277,9 @@ - (BOOL)canHandleNetworkingResponse:(NSString *)responseType - (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data { + // An empty body will have nil for data, in this case we need to return + // an empty blob as per the XMLHttpRequest spec. + data = data ?: [NSData new]; return @{ @"blobId": [self store:data], @"offset": @0, @@ -286,4 +308,13 @@ - (id)processWebsocketMessage:(id)message }; } +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + @end + +Class RCTBlobManagerCls(void) { + return RCTBlobManager.class; +} diff --git a/Libraries/Blob/RCTBlobPlugins.h b/Libraries/Blob/RCTBlobPlugins.h new file mode 100644 index 00000000000000..d1fc8cfdae1008 --- /dev/null +++ b/Libraries/Blob/RCTBlobPlugins.h @@ -0,0 +1,41 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by an internal plugin build system + */ + +#ifdef RN_DISABLE_OSS_PLUGIN_HEADER + +// FB Internal: FBRCTBlobPlugins.h is autogenerated by the build system. +#import + +#else + +// OSS-compatibility layer + +#import + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" + +#ifdef __cplusplus +extern "C" { +#endif + +// RCTTurboModuleManagerDelegate should call this to resolve module classes. +Class RCTBlobClassProvider(const char *name); + +// Lookup functions +Class RCTFileReaderModuleCls(void) __attribute__((used)); +Class RCTBlobManagerCls(void) __attribute__((used)); + +#ifdef __cplusplus +} +#endif + +#pragma GCC diagnostic pop + +#endif // RN_DISABLE_OSS_PLUGIN_HEADER diff --git a/Libraries/Blob/RCTBlobPlugins.mm b/Libraries/Blob/RCTBlobPlugins.mm new file mode 100644 index 00000000000000..db0dcd82559652 --- /dev/null +++ b/Libraries/Blob/RCTBlobPlugins.mm @@ -0,0 +1,34 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by an internal plugin build system + */ + +#ifndef RN_DISABLE_OSS_PLUGIN_HEADER + +// OSS-compatibility layer + +#import "RCTBlobPlugins.h" + +#import +#import + +Class RCTBlobClassProvider(const char *name) { + // Intentionally leak to avoid crashing after static destructors are run. + static const auto sCoreModuleClassMap = new const std::unordered_map{ + {"FileReaderModule", RCTFileReaderModuleCls}, + {"BlobModule", RCTBlobManagerCls}, + }; + + auto p = sCoreModuleClassMap->find(name); + if (p != sCoreModuleClassMap->end()) { + auto classFunc = p->second; + return classFunc(); + } + return nil; +} + +#endif // RN_DISABLE_OSS_PLUGIN_HEADER diff --git a/Libraries/Blob/RCTFileReaderModule.h b/Libraries/Blob/RCTFileReaderModule.h index 72d224b0d968cb..80d38089073fd6 100644 --- a/Libraries/Blob/RCTFileReaderModule.h +++ b/Libraries/Blob/RCTFileReaderModule.h @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. diff --git a/Libraries/Blob/RCTFileReaderModule.m b/Libraries/Blob/RCTFileReaderModule.m deleted file mode 100644 index 5059e0b84180ee..00000000000000 --- a/Libraries/Blob/RCTFileReaderModule.m +++ /dev/null @@ -1,69 +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. - */ - - -#import "RCTFileReaderModule.h" - -#import -#import - -#import "RCTBlobManager.h" - - -@implementation RCTFileReaderModule - -RCT_EXPORT_MODULE(FileReaderModule) - -@synthesize bridge = _bridge; - -RCT_EXPORT_METHOD(readAsText:(NSDictionary *)blob - encoding:(NSString *)encoding - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - RCTBlobManager *blobManager = [[self bridge] moduleForClass:[RCTBlobManager class]]; - NSData *data = [blobManager resolve:blob]; - - if (data == nil) { - reject(RCTErrorUnspecified, - [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); - } else { - NSStringEncoding stringEncoding; - - if (encoding == nil) { - stringEncoding = NSUTF8StringEncoding; - } else { - stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef) encoding)); - } - - NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding]; - - resolve(text); - } -} - -RCT_EXPORT_METHOD(readAsDataURL:(NSDictionary *)blob - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - RCTBlobManager *blobManager = [[self bridge] moduleForClass:[RCTBlobManager class]]; - NSData *data = [blobManager resolve:blob]; - - if (data == nil) { - reject(RCTErrorUnspecified, - [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); - } else { - NSString *type = [RCTConvert NSString:blob[@"type"]]; - NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@", - type != nil && [type length] > 0 ? type : @"application/octet-stream", - [data base64EncodedStringWithOptions:0]]; - - resolve(text); - } -} - -@end diff --git a/Libraries/Blob/RCTFileReaderModule.mm b/Libraries/Blob/RCTFileReaderModule.mm new file mode 100644 index 00000000000000..f52a68aba98c19 --- /dev/null +++ b/Libraries/Blob/RCTFileReaderModule.mm @@ -0,0 +1,88 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#import +#import +#import + +#import + +#import "RCTBlobPlugins.h" + +@interface RCTFileReaderModule() +@end + +@implementation RCTFileReaderModule + +RCT_EXPORT_MODULE(FileReaderModule) + +@synthesize moduleRegistry = _moduleRegistry; + +RCT_EXPORT_METHOD(readAsText:(NSDictionary *)blob + encoding:(NSString *)encoding + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; + dispatch_async(blobManager.methodQueue, ^{ + NSData *data = [blobManager resolve:blob]; + + if (data == nil) { + reject(RCTErrorUnspecified, + [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); + } else { + NSStringEncoding stringEncoding; + + if (encoding == nil) { + stringEncoding = NSUTF8StringEncoding; + } else { + stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef) encoding)); + } + + NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding]; + + resolve(text); + } + }); +} + +RCT_EXPORT_METHOD(readAsDataURL:(NSDictionary *)blob + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; + dispatch_async(blobManager.methodQueue, ^{ + NSData *data = [blobManager resolve:blob]; + + if (data == nil) { + reject(RCTErrorUnspecified, + [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); + } else { + NSString *type = [RCTConvert NSString:blob[@"type"]]; + NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@", + type != nil && [type length] > 0 ? type : @"application/octet-stream", + [data base64EncodedStringWithOptions:0]]; + + resolve(text); + } + }); +} + +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + +@end + +Class RCTFileReaderModuleCls(void) +{ + return RCTFileReaderModule.class; +} diff --git a/Libraries/Blob/React-RCTBlob.podspec b/Libraries/Blob/React-RCTBlob.podspec new file mode 100644 index 00000000000000..ee488e04aa142a --- /dev/null +++ b/Libraries/Blob/React-RCTBlob.podspec @@ -0,0 +1,49 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' +folly_version = '2021.07.22.00' + +Pod::Spec.new do |s| + s.name = "React-RCTBlob" + s.version = version + s.summary = "An API for displaying iOS action sheets and share sheets." + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Facebook, Inc. and its affiliates" + s.platforms = { :ios => "12.4" } + s.compiler_flags = folly_compiler_flags + ' -Wno-nullability-completeness' + s.source = source + s.source_files = "*.{h,m,mm}" + s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs" + s.header_dir = "RCTBlob" + s.pod_target_xcconfig = { + "USE_HEADERMAP" => "YES", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/RCT-Folly\" \"${PODS_ROOT}/Headers/Public/React-Codegen/react/renderer/components\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen/React_Codegen.framework/Headers\"" + } + + s.dependency "RCT-Folly", folly_version + s.dependency "React-Codegen", version + s.dependency "ReactCommon/turbomodule/core", version + s.dependency "React-jsi", version + s.dependency "React-Core/RCTBlobHeaders", version + s.dependency "React-Core/RCTWebSocket", version + s.dependency "React-RCTNetwork", version + s.dependency "React-jsi", version +end diff --git a/Libraries/Blob/URL.js b/Libraries/Blob/URL.js index 7349f7752f268c..77b4d7f94d6d7f 100644 --- a/Libraries/Blob/URL.js +++ b/Libraries/Blob/URL.js @@ -1,26 +1,28 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule URL * @format * @flow */ -'use strict'; +const Blob = require('./Blob'); -const Blob = require('Blob'); - -const {BlobModule} = require('NativeModules'); +import NativeBlobModule from './NativeBlobModule'; let BLOB_URL_PREFIX = null; -if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') { - BLOB_URL_PREFIX = BlobModule.BLOB_URI_SCHEME + ':'; - if (typeof BlobModule.BLOB_URI_HOST === 'string') { - BLOB_URL_PREFIX += `//${BlobModule.BLOB_URI_HOST}/`; +if ( + NativeBlobModule && + typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string' +) { + const constants = NativeBlobModule.getConstants(); + // $FlowFixMe[incompatible-type] asserted above + BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':'; + if (typeof constants.BLOB_URI_HOST === 'string') { + BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`; } } @@ -48,23 +50,180 @@ if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') { * * ``` */ -class URL { - constructor() { - throw new Error('Creating URL objects is not supported yet.'); + +// Small subset from whatwg-url: https://github.com/jsdom/whatwg-url/tree/master/src +// The reference code bloat comes from Unicode issues with URLs, so those won't work here. +export class URLSearchParams { + _searchParams = []; + + constructor(params: any) { + if (typeof params === 'object') { + Object.keys(params).forEach(key => this.append(key, params[key])); + } + } + + append(key: string, value: string) { + this._searchParams.push([key, value]); + } + + delete(name: string) { + throw new Error('URLSearchParams.delete is not implemented'); + } + + get(name: string) { + throw new Error('URLSearchParams.get is not implemented'); + } + + getAll(name: string) { + throw new Error('URLSearchParams.getAll is not implemented'); + } + + has(name: string) { + throw new Error('URLSearchParams.has is not implemented'); + } + + set(name: string, value: string) { + throw new Error('URLSearchParams.set is not implemented'); } - static createObjectURL(blob: Blob) { + sort() { + throw new Error('URLSearchParams.sort is not implemented'); + } + + // $FlowFixMe[unsupported-syntax] + [Symbol.iterator]() { + return this._searchParams[Symbol.iterator](); + } + + toString(): string { + if (this._searchParams.length === 0) { + return ''; + } + const last = this._searchParams.length - 1; + return this._searchParams.reduce((acc, curr, index) => { + return ( + acc + + encodeURIComponent(curr[0]) + + '=' + + encodeURIComponent(curr[1]) + + (index === last ? '' : '&') + ); + }, ''); + } +} + +function validateBaseUrl(url: string) { + // from this MIT-licensed gist: https://gist.github.com/dperini/729294 + return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test( + url, + ); +} + +export class URL { + _url: string; + _searchParamsInstance = null; + + static createObjectURL(blob: Blob): string { if (BLOB_URL_PREFIX === null) { throw new Error('Cannot create URL for blob!'); } - return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${ - blob.data.offset - }&size=${blob.size}`; + return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${blob.data.offset}&size=${blob.size}`; } static revokeObjectURL(url: string) { // Do nothing. } -} -module.exports = URL; + constructor(url: string, base: string | URL) { + let baseUrl = null; + if (!base || validateBaseUrl(url)) { + this._url = url; + if (!this._url.endsWith('/')) { + this._url += '/'; + } + } else { + if (typeof base === 'string') { + baseUrl = base; + if (!validateBaseUrl(baseUrl)) { + throw new TypeError(`Invalid base URL: ${baseUrl}`); + } + } else { + baseUrl = base.toString(); + } + if (baseUrl.endsWith('/')) { + baseUrl = baseUrl.slice(0, baseUrl.length - 1); + } + if (!url.startsWith('/')) { + url = `/${url}`; + } + if (baseUrl.endsWith(url)) { + url = ''; + } + this._url = `${baseUrl}${url}`; + } + } + + get hash(): string { + throw new Error('URL.hash is not implemented'); + } + + get host(): string { + throw new Error('URL.host is not implemented'); + } + + get hostname(): string { + throw new Error('URL.hostname is not implemented'); + } + + get href(): string { + return this.toString(); + } + + get origin(): string { + throw new Error('URL.origin is not implemented'); + } + + get password(): string { + throw new Error('URL.password is not implemented'); + } + + get pathname(): string { + throw new Error('URL.pathname not implemented'); + } + + get port(): string { + throw new Error('URL.port is not implemented'); + } + + get protocol(): string { + throw new Error('URL.protocol is not implemented'); + } + + get search(): string { + throw new Error('URL.search is not implemented'); + } + + get searchParams(): URLSearchParams { + if (this._searchParamsInstance == null) { + this._searchParamsInstance = new URLSearchParams(); + } + return this._searchParamsInstance; + } + + toJSON(): string { + return this.toString(); + } + + toString(): string { + if (this._searchParamsInstance === null) { + return this._url; + } + const instanceString = this._searchParamsInstance.toString(); + const separator = this._url.indexOf('?') > -1 ? '&' : '?'; + return this._url + separator + instanceString; + } + + get username(): string { + throw new Error('URL.username is not implemented'); + } +} diff --git a/Libraries/Blob/__mocks__/BlobModule.js b/Libraries/Blob/__mocks__/BlobModule.js index 2542c3a9339adc..99e5c405411bc2 100644 --- a/Libraries/Blob/__mocks__/BlobModule.js +++ b/Libraries/Blob/__mocks__/BlobModule.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ + const BlobModule = { createFromParts() {}, release() {}, diff --git a/Libraries/Blob/__mocks__/FileReaderModule.js b/Libraries/Blob/__mocks__/FileReaderModule.js index f5ccc39d36e200..1cc016c82abbff 100644 --- a/Libraries/Blob/__mocks__/FileReaderModule.js +++ b/Libraries/Blob/__mocks__/FileReaderModule.js @@ -1,17 +1,18 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ + const FileReaderModule = { - async readAsText() { + async readAsText(): Promise { return ''; }, - async readAsDataURL() { + async readAsDataURL(): Promise { return 'data:text/plain;base64,NDI='; }, }; diff --git a/Libraries/Blob/__tests__/Blob-test.js b/Libraries/Blob/__tests__/Blob-test.js index 8d37915720c9b3..5e406f024dd084 100644 --- a/Libraries/Blob/__tests__/Blob-test.js +++ b/Libraries/Blob/__tests__/Blob-test.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,15 +7,16 @@ * @format * @emails oncall+react_native */ + 'use strict'; -jest.setMock('NativeModules', { +jest.setMock('../../BatchedBridge/NativeModules', { BlobModule: require('../__mocks__/BlobModule'), }); -var Blob = require('Blob'); +const Blob = require('../Blob'); -describe('Blob', function() { +describe('Blob', function () { it('should create empty blob', () => { const blob = new Blob(); expect(blob).toBeInstanceOf(Blob); diff --git a/Libraries/Blob/__tests__/BlobManager-test.js b/Libraries/Blob/__tests__/BlobManager-test.js index 2ed118b446be1c..e03dd87d9d1c85 100644 --- a/Libraries/Blob/__tests__/BlobManager-test.js +++ b/Libraries/Blob/__tests__/BlobManager-test.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,16 +7,17 @@ * @format * @emails oncall+react_native */ + 'use strict'; -jest.setMock('NativeModules', { +jest.setMock('../../BatchedBridge/NativeModules', { BlobModule: require('../__mocks__/BlobModule'), }); -var Blob = require('Blob'); -var BlobManager = require('BlobManager'); +const Blob = require('../Blob'); +const BlobManager = require('../BlobManager'); -describe('BlobManager', function() { +describe('BlobManager', function () { it('should create blob from parts', () => { const blob = BlobManager.createFromParts([], {type: 'text/html'}); expect(blob).toBeInstanceOf(Blob); diff --git a/Libraries/Blob/__tests__/File-test.js b/Libraries/Blob/__tests__/File-test.js index c10f90c18766ae..21b50650a99879 100644 --- a/Libraries/Blob/__tests__/File-test.js +++ b/Libraries/Blob/__tests__/File-test.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,17 +7,18 @@ * @format * @emails oncall+react_native */ + 'use strict'; -jest.setMock('NativeModules', { +jest.setMock('../../BatchedBridge/NativeModules', { BlobModule: require('../__mocks__/BlobModule'), }); -const Blob = require('Blob'); -const File = require('File'); +const Blob = require('../Blob'); +const File = require('../File'); -describe('babel 7 smoke test', function() { - it('should be able to extend a class with native name', function() { +describe('babel 7 smoke test', function () { + it('should be able to extend a class with native name', function () { let called = false; class Array { constructor() { @@ -39,14 +40,14 @@ describe('babel 7 smoke test', function() { }); }); -describe('Blob', function() { - it('regression caused by circular dep && babel 7', function() { +describe('Blob', function () { + it('regression caused by circular dep && babel 7', function () { const blob = new Blob([], {type: 'image/jpeg'}); expect(blob).toBeInstanceOf(Blob); }); }); -describe('File', function() { +describe('File', function () { it('should create empty file', () => { const file = new File([], 'test.jpg'); expect(file).toBeInstanceOf(File); diff --git a/Libraries/Blob/__tests__/FileReader-test.js b/Libraries/Blob/__tests__/FileReader-test.js index a278a4918a9d10..8ae1c81a31f8a8 100644 --- a/Libraries/Blob/__tests__/FileReader-test.js +++ b/Libraries/Blob/__tests__/FileReader-test.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,17 +7,18 @@ * @format * @emails oncall+react_native */ + 'use strict'; -jest.unmock('event-target-shim').setMock('NativeModules', { +jest.unmock('event-target-shim').setMock('../../BatchedBridge/NativeModules', { BlobModule: require('../__mocks__/BlobModule'), FileReaderModule: require('../__mocks__/FileReaderModule'), }); -var Blob = require('Blob'); -var FileReader = require('FileReader'); +const Blob = require('../Blob'); +const FileReader = require('../FileReader'); -describe('FileReader', function() { +describe('FileReader', function () { it('should read blob as text', async () => { const e = await new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/Libraries/Blob/__tests__/URL-test.js b/Libraries/Blob/__tests__/URL-test.js new file mode 100644 index 00000000000000..28e89950b267db --- /dev/null +++ b/Libraries/Blob/__tests__/URL-test.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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'; + +const URL = require('../URL').URL; + +describe('URL', function () { + it('should pass Mozilla Dev Network examples', () => { + const a = new URL('/', 'https://developer.mozilla.org'); + expect(a.href).toBe('https://developer.mozilla.org/'); + const b = new URL('https://developer.mozilla.org'); + expect(b.href).toBe('https://developer.mozilla.org/'); + const c = new URL('en-US/docs', b); + expect(c.href).toBe('https://developer.mozilla.org/en-US/docs'); + const d = new URL('/en-US/docs', b); + expect(d.href).toBe('https://developer.mozilla.org/en-US/docs'); + const f = new URL('/en-US/docs', d); + expect(f.href).toBe('https://developer.mozilla.org/en-US/docs'); + // from original test suite, but requires complex implementation + // const g = new URL( + // '/en-US/docs', + // 'https://developer.mozilla.org/fr-FR/toto', + // ); + // expect(g.href).toBe('https://developer.mozilla.org/en-US/docs'); + const h = new URL('/en-US/docs', a); + expect(h.href).toBe('https://developer.mozilla.org/en-US/docs'); + const i = new URL('http://github.com', 'http://google.com'); + expect(i.href).toBe('http://github.com/'); + // Support Bare Hosts + const j = new URL('home', 'http://localhost'); + expect(j.href).toBe('http://localhost/home'); + // Insert / between Base and Path if missing + const k = new URL('en-US/docs', 'https://developer.mozilla.org'); + expect(k.href).toBe('https://developer.mozilla.org/en-US/docs'); + }); +}); diff --git a/Libraries/BugReporting/BugReporting.js b/Libraries/BugReporting/BugReporting.js index 11a59e6d5222ca..2ee616af829c50 100644 --- a/Libraries/BugReporting/BugReporting.js +++ b/Libraries/BugReporting/BugReporting.js @@ -1,26 +1,30 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule BugReporting - * @flow + * @format + * @flow strict-local */ -'use strict'; -const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -const Map = require('Map'); -const infoLog = require('infoLog'); +import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; +import NativeRedBox from '../NativeModules/specs/NativeRedBox'; +import {type EventSubscription} from '../vendor/emitter/EventEmitter'; +import NativeBugReporting from './NativeBugReporting'; -import type EmitterSubscription from 'EmitterSubscription'; - -type ExtraData = { [key: string]: string }; +type ExtraData = {[key: string]: string, ...}; type SourceCallback = () => string; -type DebugData = { extras: ExtraData, files: ExtraData }; +type DebugData = { + extras: ExtraData, + files: ExtraData, + ... +}; function defaultExtras() { - BugReporting.addFileSource('react_hierarchy.txt', () => require('dumpReactTree')()); + BugReporting.addFileSource('react_hierarchy.txt', () => + require('./dumpReactTree')(), + ); } /** @@ -32,19 +36,27 @@ function defaultExtras() { class BugReporting { static _extraSources: Map = new Map(); static _fileSources: Map = new Map(); - static _subscription: ?EmitterSubscription = null; - static _redboxSubscription: ?EmitterSubscription = null; + static _subscription: ?EventSubscription = null; + static _redboxSubscription: ?EventSubscription = null; static _maybeInit() { if (!BugReporting._subscription) { - BugReporting._subscription = RCTDeviceEventEmitter - .addListener('collectBugExtraData', BugReporting.collectExtraData, null); + BugReporting._subscription = RCTDeviceEventEmitter.addListener( + 'collectBugExtraData', + // $FlowFixMe[method-unbinding] + BugReporting.collectExtraData, + null, + ); defaultExtras(); } if (!BugReporting._redboxSubscription) { - BugReporting._redboxSubscription = RCTDeviceEventEmitter - .addListener('collectRedBoxExtraData', BugReporting.collectExtraData, null); + BugReporting._redboxSubscription = RCTDeviceEventEmitter.addListener( + 'collectRedBoxExtraData', + // $FlowFixMe[method-unbinding] + BugReporting.collectExtraData, + null, + ); } } @@ -56,7 +68,10 @@ class BugReporting { * * Conflicts trample with a warning. */ - static addSource(key: string, callback: SourceCallback): {remove: () => void} { + static addSource( + key: string, + callback: SourceCallback, + ): {remove: () => void, ...} { return this._addSource(key, callback, BugReporting._extraSources); } @@ -68,17 +83,30 @@ class BugReporting { * * Conflicts trample with a warning. */ - static addFileSource(key: string, callback: SourceCallback): {remove: () => void} { + static addFileSource( + key: string, + callback: SourceCallback, + ): {remove: () => void, ...} { return this._addSource(key, callback, BugReporting._fileSources); } - static _addSource(key: string, callback: SourceCallback, source: Map): {remove: () => void} { + static _addSource( + key: string, + callback: SourceCallback, + source: Map, + ): {remove: () => void, ...} { BugReporting._maybeInit(); if (source.has(key)) { - console.warn(`BugReporting.add* called multiple times for same key '${key}'`); + console.warn( + `BugReporting.add* called multiple times for same key '${key}'`, + ); } source.set(key, callback); - return {remove: () => { source.delete(key); }}; + return { + remove: () => { + source.delete(key); + }, + }; } /** @@ -96,18 +124,16 @@ class BugReporting { for (const [key, callback] of BugReporting._fileSources) { fileData[key] = callback(); } - infoLog('BugReporting extraData:', extraData); - const BugReportingNativeModule = require('NativeModules').BugReporting; - BugReportingNativeModule && - BugReportingNativeModule.setExtraData && - BugReportingNativeModule.setExtraData(extraData, fileData); - const RedBoxNativeModule = require('NativeModules').RedBox; - RedBoxNativeModule && - RedBoxNativeModule.setExtraData && - RedBoxNativeModule.setExtraData(extraData, 'From BugReporting.js'); + if (NativeBugReporting != null && NativeBugReporting.setExtraData != null) { + NativeBugReporting.setExtraData(extraData, fileData); + } + + if (NativeRedBox != null && NativeRedBox.setExtraData != null) { + NativeRedBox.setExtraData(extraData, 'From BugReporting.js'); + } - return { extras: extraData, files: fileData }; + return {extras: extraData, files: fileData}; } } diff --git a/Libraries/BugReporting/NativeBugReporting.js b/Libraries/BugReporting/NativeBugReporting.js new file mode 100644 index 00000000000000..13a51a585c0b25 --- /dev/null +++ b/Libraries/BugReporting/NativeBugReporting.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +startReportAProblemFlow: () => void; + +setExtraData: (extraData: Object, extraFiles: Object) => void; + +setCategoryID: (categoryID: string) => void; +} + +export default (TurboModuleRegistry.get('BugReporting'): ?Spec); diff --git a/Libraries/BugReporting/dumpReactTree.js b/Libraries/BugReporting/dumpReactTree.js index f8a137ceff7f2c..6673657d42a894 100644 --- a/Libraries/BugReporting/dumpReactTree.js +++ b/Libraries/BugReporting/dumpReactTree.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule dumpReactTree - * @flow + * @format + * @flow strict */ + 'use strict'; /* @@ -22,7 +23,7 @@ const MAX_STRING_LENGTH = 50; * it best to get the content but ultimately relies on implementation details * of React and will fail in future versions. */ -function dumpReactTree() { +function dumpReactTree(): string { try { return getReactTree(); } catch (e) { @@ -36,7 +37,7 @@ function getReactTree() { 'React tree dumps have been temporarily disabled while React is ' + 'upgraded to Fiber.' ); -/* + /* let output = ''; const rootIds = Object.getOwnPropertyNames(ReactNativeMount._instancesByContainerID); for (const rootId of rootIds) { @@ -50,14 +51,14 @@ function getReactTree() { } /* -function dumpNode(node: Object, identation: number) { +function dumpNode(node: Object, indentation: number) { const data = getReactData(node); if (data.nodeType === 'Text') { - return indent(identation) + data.text + '\n'; + return indent(indentation) + data.text + '\n'; } else if (data.nodeType === 'Empty') { return ''; } - let output = indent(identation) + `<${data.name}`; + let output = indent(indentation) + `<${data.name}`; if (data.nodeType === 'Composite') { for (const propName of Object.getOwnPropertyNames(data.props || {})) { if (isNormalProp(propName)) { @@ -75,11 +76,11 @@ function dumpNode(node: Object, identation: number) { } let childOutput = ''; for (const child of data.children || []) { - childOutput += dumpNode(child, identation + 1); + childOutput += dumpNode(child, indentation + 1); } if (childOutput) { - output += '>\n' + childOutput + indent(identation) + `\n`; + output += '>\n' + childOutput + indent(indentation) + `\n`; } else { output += ' />\n'; } diff --git a/Libraries/BugReporting/getReactData.js b/Libraries/BugReporting/getReactData.js index 53c8fb6ccfc110..4440cc4e0d0e60 100644 --- a/Libraries/BugReporting/getReactData.js +++ b/Libraries/BugReporting/getReactData.js @@ -1,38 +1,42 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule getReactData + * @format * @flow */ + 'use strict'; /** * Convert a react internal instance to a sanitized data object. * * This is shamelessly stolen from react-devtools: - * https://github.com/facebook/react-devtools/blob/master/backend/getData.js + * https://github.com/facebook/react-devtools/blob/HEAD/backend/getData.js */ function getData(element: Object): Object { - var children = null; - var props = null; - var state = null; - var context = null; - var updater = null; - var name = null; - var type = null; - var text = null; - var publicInstance = null; - var nodeType = 'Native'; + let children = null; + let props = null; + let state = null; + let context = null; + let updater = null; + let name = null; + let type = null; + let text = null; + let publicInstance = null; + let nodeType = 'Native'; // If the parent is a native node without rendered children, but with // multiple string children, then the `element` that gets passed in here is // a plain value -- a string or number. if (typeof element !== 'object') { nodeType = 'Text'; text = element + ''; - } else if (element._currentElement === null || element._currentElement === false) { + } else if ( + element._currentElement === null || + element._currentElement === false + ) { nodeType = 'Empty'; } else if (element._renderedComponent) { nodeType = 'NativeWrapper'; @@ -66,7 +70,11 @@ function getData(element: Object): Object { name = element.getName(); // 0.14 top-level wrapper // TODO(jared): The backend should just act as if these don't exist. - if (element._renderedComponent && element._currentElement.props === element._renderedComponent._currentElement) { + if ( + element._renderedComponent && + element._currentElement.props === + element._renderedComponent._currentElement + ) { nodeType = 'Wrapper'; } if (name === null) { @@ -81,7 +89,7 @@ function getData(element: Object): Object { } if (element._instance) { - var inst = element._instance; + const inst = element._instance; updater = { setState: inst.setState && inst.setState.bind(inst), forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), @@ -113,8 +121,12 @@ function getData(element: Object): Object { }; } -function setInProps(internalInst, path: Array, value: any) { - var element = internalInst._currentElement; +function setInProps( + internalInst: any, + path: Array, + value: any, +) { + const element = internalInst._currentElement; internalInst._currentElement = { ...element, props: copyWithSet(element.props, path, value), @@ -122,44 +134,53 @@ function setInProps(internalInst, path: Array, value: any) { internalInst._instance.forceUpdate(); } -function setInState(inst, path: Array, value: any) { +function setInState(inst: any, path: Array, value: any) { setIn(inst.state, path, value); inst.forceUpdate(); } -function setInContext(inst, path: Array, value: any) { +function setInContext(inst: any, path: Array, value: any) { setIn(inst.context, path, value); inst.forceUpdate(); } function setIn(obj: Object, path: Array, value: any) { - var last = path.pop(); - var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); + const last = path.pop(); + const parent = path.reduce((obj_, attr) => (obj_ ? obj_[attr] : null), obj); if (parent) { parent[last] = value; } } -function childrenList(children) { - var res = []; - for (var name in children) { +function childrenList(children: any) { + const res = []; + for (const name in children) { res.push(children[name]); } return res; } -function copyWithSetImpl(obj, path, idx, value) { +function copyWithSetImpl( + obj: any | Array, + path: Array, + idx: number, + value: any, +) { if (idx >= path.length) { return value; } - var key = path[idx]; - var updated = Array.isArray(obj) ? obj.slice() : {...obj}; - // $FlowFixMe number or string is fine here + const key = path[idx]; + const updated = Array.isArray(obj) ? obj.slice() : {...obj}; + // $FlowFixMe[incompatible-use] number or string is fine here updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value); return updated; } -function copyWithSet(obj: Object | Array, path: Array, value: any): Object | Array { +function copyWithSet( + obj: Object | Array, + path: Array, + value: any, +): Object | Array { return copyWithSetImpl(obj, path, 0, value); } diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js deleted file mode 100644 index 3bc48664f35db1..00000000000000 --- a/Libraries/CameraRoll/CameraRoll.js +++ /dev/null @@ -1,245 +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 CameraRoll - * @flow - * @format - */ -'use strict'; - -const PropTypes = require('prop-types'); -const {checkPropTypes} = PropTypes; -const RCTCameraRollManager = require('NativeModules').CameraRollManager; - -const createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); -const invariant = require('fbjs/lib/invariant'); - -const GROUP_TYPES_OPTIONS = { - Album: 'Album', - All: 'All', - Event: 'Event', - Faces: 'Faces', - Library: 'Library', - PhotoStream: 'PhotoStream', - SavedPhotos: 'SavedPhotos', // default -}; - -const ASSET_TYPE_OPTIONS = { - All: 'All', - Videos: 'Videos', - Photos: 'Photos', -}; - -type GetPhotosParams = { - first: number, - after?: string, - groupTypes?: $Keys, - groupName?: string, - assetType?: $Keys, - mimeTypes?: Array, -}; - -/** - * Shape of the param arg for the `getPhotos` function. - */ -const getPhotosParamChecker = createStrictShapeTypeChecker({ - /** - * The number of photos wanted in reverse order of the photo application - * (i.e. most recent first for SavedPhotos). - */ - first: PropTypes.number.isRequired, - - /** - * A cursor that matches `page_info { end_cursor }` returned from a previous - * call to `getPhotos` - */ - after: PropTypes.string, - - /** - * Specifies which group types to filter the results to. - */ - groupTypes: PropTypes.oneOf(Object.keys(GROUP_TYPES_OPTIONS)), - - /** - * Specifies filter on group names, like 'Recent Photos' or custom album - * titles. - */ - groupName: PropTypes.string, - - /** - * Specifies filter on asset type - */ - assetType: PropTypes.oneOf(Object.keys(ASSET_TYPE_OPTIONS)), - - /** - * Filter by mimetype (e.g. image/jpeg). - */ - mimeTypes: PropTypes.arrayOf(PropTypes.string), -}); - -type GetPhotosReturn = Promise<{ - edges: Array<{ - node: { - type: string, - group_name: string, - image: { - uri: string, - height: number, - width: number, - isStored?: boolean, - playableDuration: number, - }, - timestamp: number, - location?: { - latitude?: number, - longitude?: number, - altitude?: number, - heading?: number, - speed?: number, - }, - }, - }>, - page_info: { - has_next_page: boolean, - start_cursor?: string, - end_cursor?: string, - }, -}>; - -/** - * Shape of the return value of the `getPhotos` function. - */ -const getPhotosReturnChecker = createStrictShapeTypeChecker({ - edges: PropTypes.arrayOf( - /* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.66 was deployed. To see the error delete this - * comment and run Flow. */ - createStrictShapeTypeChecker({ - node: createStrictShapeTypeChecker({ - type: PropTypes.string.isRequired, - group_name: PropTypes.string.isRequired, - image: createStrictShapeTypeChecker({ - uri: PropTypes.string.isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - isStored: PropTypes.bool, - playableDuration: PropTypes.number.isRequired, - }).isRequired, - timestamp: PropTypes.number.isRequired, - location: createStrictShapeTypeChecker({ - latitude: PropTypes.number, - longitude: PropTypes.number, - altitude: PropTypes.number, - heading: PropTypes.number, - speed: PropTypes.number, - }), - }).isRequired, - }), - ).isRequired, - page_info: createStrictShapeTypeChecker({ - has_next_page: PropTypes.bool.isRequired, - start_cursor: PropTypes.string, - end_cursor: PropTypes.string, - }).isRequired, -}); - -/** - * `CameraRoll` provides access to the local camera roll or photo library. - * - * See https://facebook.github.io/react-native/docs/cameraroll.html - */ -class CameraRoll { - static GroupTypesOptions: Object = GROUP_TYPES_OPTIONS; - static AssetTypeOptions: Object = ASSET_TYPE_OPTIONS; - - /** - * `CameraRoll.saveImageWithTag()` is deprecated. Use `CameraRoll.saveToCameraRoll()` instead. - */ - static saveImageWithTag(tag: string): Promise { - console.warn( - '`CameraRoll.saveImageWithTag()` is deprecated. Use `CameraRoll.saveToCameraRoll()` instead.', - ); - return this.saveToCameraRoll(tag, 'photo'); - } - - static deletePhotos(photos: Array) { - return RCTCameraRollManager.deletePhotos(photos); - } - - /** - * Saves the photo or video to the camera roll or photo library. - * - * See https://facebook.github.io/react-native/docs/cameraroll.html#savetocameraroll - */ - static saveToCameraRoll( - tag: string, - type?: 'photo' | 'video', - ): Promise { - invariant( - typeof tag === 'string', - 'CameraRoll.saveToCameraRoll must be a valid string.', - ); - - invariant( - type === 'photo' || type === 'video' || type === undefined, - `The second argument to saveToCameraRoll must be 'photo' or 'video'. You passed ${type || - 'unknown'}`, - ); - - let mediaType = 'photo'; - if (type) { - mediaType = type; - } else if (['mov', 'mp4'].indexOf(tag.split('.').slice(-1)[0]) >= 0) { - mediaType = 'video'; - } - - return RCTCameraRollManager.saveToCameraRoll(tag, mediaType); - } - - /** - * Returns a Promise with photo identifier objects from the local camera - * roll of the device matching shape defined by `getPhotosReturnChecker`. - * - * See https://facebook.github.io/react-native/docs/cameraroll.html#getphotos - */ - static getPhotos(params: GetPhotosParams): GetPhotosReturn { - if (__DEV__) { - checkPropTypes( - {params: getPhotosParamChecker}, - {params}, - 'params', - 'CameraRoll.getPhotos', - ); - } - if (arguments.length > 1) { - console.warn( - 'CameraRoll.getPhotos(tag, success, error) is deprecated. Use the returned Promise instead', - ); - let successCallback = arguments[1]; - if (__DEV__) { - const callback = arguments[1]; - successCallback = response => { - checkPropTypes( - {response: getPhotosReturnChecker}, - {response}, - 'response', - 'CameraRoll.getPhotos callback', - ); - callback(response); - }; - } - const errorCallback = arguments[2] || (() => {}); - RCTCameraRollManager.getPhotos(params).then( - successCallback, - errorCallback, - ); - } - // TODO: Add the __DEV__ check back in to verify the Promise result - return RCTCameraRollManager.getPhotos(params); - } -} - -module.exports = CameraRoll; diff --git a/Libraries/CameraRoll/ImagePickerIOS.js b/Libraries/CameraRoll/ImagePickerIOS.js deleted file mode 100644 index 24d1309d2e1b91..00000000000000 --- a/Libraries/CameraRoll/ImagePickerIOS.js +++ /dev/null @@ -1,38 +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 ImagePickerIOS - * @flow - */ -'use strict'; - -var RCTImagePicker = require('NativeModules').ImagePickerIOS; - -var ImagePickerIOS = { - canRecordVideos: function(callback: Function) { - return RCTImagePicker.canRecordVideos(callback); - }, - canUseCamera: function(callback: Function) { - return RCTImagePicker.canUseCamera(callback); - }, - openCameraDialog: function(config: Object, successCallback: Function, cancelCallback: Function) { - config = { - videoMode: false, - ...config, - }; - return RCTImagePicker.openCameraDialog(config, successCallback, cancelCallback); - }, - openSelectDialog: function(config: Object, successCallback: Function, cancelCallback: Function) { - config = { - showImages: true, - showVideos: false, - ...config, - }; - return RCTImagePicker.openSelectDialog(config, successCallback, cancelCallback); - }, -}; - -module.exports = ImagePickerIOS; diff --git a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h deleted file mode 100644 index 8189bb56e4ea8c..00000000000000 --- a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h +++ /dev/null @@ -1,24 +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. - */ - -#import -#import - -@class ALAssetsLibrary; - -@interface RCTAssetsLibraryRequestHandler : NSObject - -@end - -@interface RCTBridge (RCTAssetsLibraryImageLoader) - -/** - * The shared asset library instance. - */ -@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary; - -@end diff --git a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m deleted file mode 100644 index e9637d7c4c16b5..00000000000000 --- a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m +++ /dev/null @@ -1,115 +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. - */ - -#import "RCTAssetsLibraryRequestHandler.h" - -#import - -#import -#import - -#import -#import - -@implementation RCTAssetsLibraryRequestHandler -{ - ALAssetsLibrary *_assetsLibrary; -} - -RCT_EXPORT_MODULE() - -@synthesize bridge = _bridge; - -- (ALAssetsLibrary *)assetsLibrary -{ - return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]); -} - -#pragma mark - RCTURLRequestHandler - -- (BOOL)canHandleRequest:(NSURLRequest *)request -{ - return [request.URL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame; -} - -- (id)sendRequest:(NSURLRequest *)request - withDelegate:(id)delegate -{ - __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); - void (^cancellationBlock)(void) = ^{ - atomic_store(&cancelled, YES); - }; - - [[self assetsLibrary] assetForURL:request.URL resultBlock:^(ALAsset *asset) { - if (atomic_load(&cancelled)) { - return; - } - - if (asset) { - - ALAssetRepresentation *representation = [asset defaultRepresentation]; - NSInteger length = (NSInteger)representation.size; - CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(representation.UTI), kUTTagClassMIMEType); - - NSURLResponse *response = - [[NSURLResponse alloc] initWithURL:request.URL - MIMEType:(__bridge NSString *)(MIMEType) - expectedContentLength:length - textEncodingName:nil]; - - [delegate URLRequest:cancellationBlock didReceiveResponse:response]; - - NSError *error = nil; - uint8_t *buffer = (uint8_t *)malloc((size_t)length); - if ([representation getBytes:buffer - fromOffset:0 - length:length - error:&error]) { - - NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer - length:length - freeWhenDone:YES]; - - [delegate URLRequest:cancellationBlock didReceiveData:data]; - [delegate URLRequest:cancellationBlock didCompleteWithError:nil]; - - } else { - free(buffer); - [delegate URLRequest:cancellationBlock didCompleteWithError:error]; - } - - } else { - NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset" - " at URL %@ with no error message.", request.URL]; - NSError *error = RCTErrorWithMessage(errorMessage); - [delegate URLRequest:cancellationBlock didCompleteWithError:error]; - } - } failureBlock:^(NSError *loadError) { - if (atomic_load(&cancelled)) { - return; - } - [delegate URLRequest:cancellationBlock didCompleteWithError:loadError]; - }]; - - return cancellationBlock; -} - -- (void)cancelRequest:(id)requestToken -{ - ((void (^)(void))requestToken)(); -} - -@end - -@implementation RCTBridge (RCTAssetsLibraryImageLoader) - -- (ALAssetsLibrary *)assetsLibrary -{ - return [[self moduleForClass:[RCTAssetsLibraryRequestHandler class]] assetsLibrary]; -} - -@end diff --git a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj b/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj deleted file mode 100644 index defe37b47f48d6..00000000000000 --- a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj +++ /dev/null @@ -1,283 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; }; - 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; - 8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */; }; - 8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 58B5115B1A9E6B3D00147676 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTImagePickerManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = ""; }; - 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTCameraRollManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = ""; }; - 58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTCameraRoll.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTAssetsLibraryRequestHandler.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryRequestHandler.m; sourceTree = ""; }; - 8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTPhotoLibraryImageLoader.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 58B5115A1A9E6B3D00147676 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 58B511541A9E6B3D00147676 = { - isa = PBXGroup; - children = ( - 8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */, - 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */, - 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */, - 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */, - 137620331B31C53500677FF0 /* RCTImagePickerManager.h */, - 137620341B31C53500677FF0 /* RCTImagePickerManager.m */, - 8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */, - 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */, - 58B5115E1A9E6B3D00147676 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 58B5115E1A9E6B3D00147676 /* Products */ = { - isa = PBXGroup; - children = ( - 58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 58B5115C1A9E6B3D00147676 /* RCTCameraRoll */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */; - buildPhases = ( - 58B511591A9E6B3D00147676 /* Sources */, - 58B5115A1A9E6B3D00147676 /* Frameworks */, - 58B5115B1A9E6B3D00147676 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTCameraRoll; - productName = RCTNetworkImage; - productReference = 58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511551A9E6B3D00147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 58B5115C1A9E6B3D00147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 58B511541A9E6B3D00147676; - productRefGroup = 58B5115E1A9E6B3D00147676 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B5115C1A9E6B3D00147676 /* RCTCameraRoll */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511591A9E6B3D00147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */, - 8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */, - 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */, - 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B5116F1A9E6B3D00147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Debug; - }; - 58B511701A9E6B3D00147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - 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_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - VALIDATE_PRODUCT = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Release; - }; - 58B511721A9E6B3D00147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - ); - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTCameraRoll; - RUN_CLANG_STATIC_ANALYZER = YES; - }; - name = Debug; - }; - 58B511731A9E6B3D00147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - ); - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTCameraRoll; - RUN_CLANG_STATIC_ANALYZER = NO; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B5116F1A9E6B3D00147676 /* Debug */, - 58B511701A9E6B3D00147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511721A9E6B3D00147676 /* Debug */, - 58B511731A9E6B3D00147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511551A9E6B3D00147676 /* Project object */; -} diff --git a/Libraries/CameraRoll/RCTCameraRollManager.h b/Libraries/CameraRoll/RCTCameraRollManager.h deleted file mode 100644 index b8b2c7d2ad63c7..00000000000000 --- a/Libraries/CameraRoll/RCTCameraRollManager.h +++ /dev/null @@ -1,22 +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. - */ - -#import - -#import -#import - -@interface RCTConvert (ALAssetGroup) - -+ (ALAssetsGroupType)ALAssetsGroupType:(id)json; -+ (ALAssetsFilter *)ALAssetsFilter:(id)json; - -@end - -@interface RCTCameraRollManager : NSObject - -@end diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m deleted file mode 100644 index b5d9a85dadb44b..00000000000000 --- a/Libraries/CameraRoll/RCTCameraRollManager.m +++ /dev/null @@ -1,266 +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. - */ - -#import "RCTCameraRollManager.h" - -#import -#import -#import -#import - -#import -#import -#import -#import -#import - -#import "RCTAssetsLibraryRequestHandler.h" - -@implementation RCTConvert (ALAssetGroup) - -RCT_ENUM_CONVERTER(ALAssetsGroupType, (@{ - - // New values - @"album": @(ALAssetsGroupAlbum), - @"all": @(ALAssetsGroupAll), - @"event": @(ALAssetsGroupEvent), - @"faces": @(ALAssetsGroupFaces), - @"library": @(ALAssetsGroupLibrary), - @"photo-stream": @(ALAssetsGroupPhotoStream), - @"saved-photos": @(ALAssetsGroupSavedPhotos), - - // Legacy values - @"Album": @(ALAssetsGroupAlbum), - @"All": @(ALAssetsGroupAll), - @"Event": @(ALAssetsGroupEvent), - @"Faces": @(ALAssetsGroupFaces), - @"Library": @(ALAssetsGroupLibrary), - @"PhotoStream": @(ALAssetsGroupPhotoStream), - @"SavedPhotos": @(ALAssetsGroupSavedPhotos), - -}), ALAssetsGroupSavedPhotos, integerValue) - -+ (ALAssetsFilter *)ALAssetsFilter:(id)json -{ - static NSDictionary *options; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - options = @{ - - // New values - @"photos": [ALAssetsFilter allPhotos], - @"videos": [ALAssetsFilter allVideos], - @"all": [ALAssetsFilter allAssets], - - // Legacy values - @"Photos": [ALAssetsFilter allPhotos], - @"Videos": [ALAssetsFilter allVideos], - @"All": [ALAssetsFilter allAssets], - }; - }); - - ALAssetsFilter *filter = options[json ?: @"photos"]; - if (!filter) { - RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos'," - "'videos' or 'all'.", json); - } - return filter ?: [ALAssetsFilter allPhotos]; -} - -@end - -@implementation RCTCameraRollManager - -RCT_EXPORT_MODULE() - -@synthesize bridge = _bridge; - -static NSString *const kErrorUnableToLoad = @"E_UNABLE_TO_LOAD"; -static NSString *const kErrorUnableToSave = @"E_UNABLE_TO_SAVE"; - -RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request - type:(NSString *)type - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - if ([type isEqualToString:@"video"]) { - // It's unclear if writeVideoAtPathToSavedPhotosAlbum is thread-safe - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_bridge.assetsLibrary writeVideoAtPathToSavedPhotosAlbum:request.URL completionBlock:^(NSURL *assetURL, NSError *saveError) { - if (saveError) { - reject(kErrorUnableToSave, nil, saveError); - } else { - resolve(assetURL.absoluteString); - } - }]; - }); - } else { - [_bridge.imageLoader loadImageWithURLRequest:request - callback:^(NSError *loadError, UIImage *loadedImage) { - if (loadError) { - reject(kErrorUnableToLoad, nil, loadError); - return; - } - // It's unclear if writeImageToSavedPhotosAlbum is thread-safe - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) { - if (saveError) { - RCTLogWarn(@"Error saving cropped image: %@", saveError); - reject(kErrorUnableToSave, nil, saveError); - } else { - resolve(assetURL.absoluteString); - } - }]; - }); - }]; - } -} - -static void RCTResolvePromise(RCTPromiseResolveBlock resolve, - NSArray *> *assets, - BOOL hasNextPage) -{ - if (!assets.count) { - resolve(@{ - @"edges": assets, - @"page_info": @{ - @"has_next_page": @NO, - } - }); - return; - } - resolve(@{ - @"edges": assets, - @"page_info": @{ - @"start_cursor": assets[0][@"node"][@"image"][@"uri"], - @"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"], - @"has_next_page": @(hasNextPage), - } - }); -} - -RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - checkPhotoLibraryConfig(); - - NSUInteger first = [RCTConvert NSInteger:params[@"first"]]; - NSString *afterCursor = [RCTConvert NSString:params[@"after"]]; - NSString *groupName = [RCTConvert NSString:params[@"groupName"]]; - ALAssetsFilter *assetType = [RCTConvert ALAssetsFilter:params[@"assetType"]]; - ALAssetsGroupType groupTypes = [RCTConvert ALAssetsGroupType:params[@"groupTypes"]]; - - BOOL __block foundAfter = NO; - BOOL __block hasNextPage = NO; - BOOL __block resolvedPromise = NO; - NSMutableArray *> *assets = [NSMutableArray new]; - - [_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { - if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { - - [group setAssetsFilter:assetType]; - [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) { - if (result) { - NSString *uri = ((NSURL *)[result valueForProperty:ALAssetPropertyAssetURL]).absoluteString; - if (afterCursor && !foundAfter) { - if ([afterCursor isEqualToString:uri]) { - foundAfter = YES; - } - return; // Skip until we get to the first one - } - if (first == assets.count) { - *stopAssets = YES; - *stopGroups = YES; - hasNextPage = YES; - RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results."); - RCTResolvePromise(resolve, assets, hasNextPage); - resolvedPromise = YES; - return; - } - CGSize dimensions = [result defaultRepresentation].dimensions; - CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation]; - NSDate *date = [result valueForProperty:ALAssetPropertyDate]; - NSString *filename = [result defaultRepresentation].filename; - int64_t duration = 0; - if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) { - duration = [[result valueForProperty:ALAssetPropertyDuration] intValue]; - } - - [assets addObject:@{ - @"node": @{ - @"type": [result valueForProperty:ALAssetPropertyType], - @"group_name": [group valueForProperty:ALAssetsGroupPropertyName], - @"image": @{ - @"uri": uri, - @"filename" : filename ?: [NSNull null], - @"height": @(dimensions.height), - @"width": @(dimensions.width), - @"isStored": @YES, - @"playableDuration": @(duration), - }, - @"timestamp": @(date.timeIntervalSince1970), - @"location": loc ? @{ - @"latitude": @(loc.coordinate.latitude), - @"longitude": @(loc.coordinate.longitude), - @"altitude": @(loc.altitude), - @"heading": @(loc.course), - @"speed": @(loc.speed), - } : @{}, - } - }]; - } - }]; - } - - if (!group) { - // Sometimes the enumeration continues even if we set stop above, so we guard against resolving the promise - // multiple times here. - if (!resolvedPromise) { - RCTResolvePromise(resolve, assets, hasNextPage); - resolvedPromise = YES; - } - } - } failureBlock:^(NSError *error) { - if (error.code != ALAssetsLibraryAccessUserDeniedError) { - RCTLogError(@"Failure while iterating through asset groups %@", error); - } - reject(kErrorUnableToLoad, nil, error); - }]; -} - -RCT_EXPORT_METHOD(deletePhotos:(NSArray*)assets - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - NSArray *assets_ = [RCTConvert NSURLArray:assets]; - [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ - PHFetchResult *fetched = - [PHAsset fetchAssetsWithALAssetURLs:assets_ options:nil]; - [PHAssetChangeRequest deleteAssets:fetched]; - } - completionHandler:^(BOOL success, NSError *error) { - if (success == YES) { - resolve(@(success)); - } - else { - reject(@"Couldn't delete", @"Couldn't delete assets", error); - } - } - ]; -} - -static void checkPhotoLibraryConfig() -{ -#if RCT_DEV - if (![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSPhotoLibraryUsageDescription"]) { - RCTLogError(@"NSPhotoLibraryUsageDescription key must be present in Info.plist to use camera roll."); - } -#endif -} - -@end diff --git a/Libraries/CameraRoll/RCTImagePickerManager.h b/Libraries/CameraRoll/RCTImagePickerManager.h deleted file mode 100644 index 825e7721a46c01..00000000000000 --- a/Libraries/CameraRoll/RCTImagePickerManager.h +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2013-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. - * - */ - -#import - -@interface RCTImagePickerManager : NSObject - -@end diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m deleted file mode 100644 index 1ee31752f2a992..00000000000000 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2013-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. - * - */ - -#import "RCTImagePickerManager.h" - -#import -#import - -#import -#import -#import -#import - -@interface RCTImagePickerManager () - -@end - -@implementation RCTImagePickerManager -{ - NSMutableArray *_pickers; - NSMutableArray *_pickerCallbacks; - NSMutableArray *_pickerCancelCallbacks; -} - -RCT_EXPORT_MODULE(ImagePickerIOS); - -@synthesize bridge = _bridge; - -- (dispatch_queue_t)methodQueue -{ - return dispatch_get_main_queue(); -} - -RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback) -{ - NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; - callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]); -} - -RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback) -{ - callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]); -} - -RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config - successCallback:(RCTResponseSenderBlock)callback - cancelCallback:(RCTResponseSenderBlock)cancelCallback) -{ - if (RCTRunningInAppExtension()) { - cancelCallback(@[@"Camera access is unavailable in an app extension"]); - return; - } - - UIImagePickerController *imagePicker = [UIImagePickerController new]; - imagePicker.delegate = self; - imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; - - if ([RCTConvert BOOL:config[@"videoMode"]]) { - imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; - } - - [self _presentPicker:imagePicker - successCallback:callback - cancelCallback:cancelCallback]; -} - -RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config - successCallback:(RCTResponseSenderBlock)callback - cancelCallback:(RCTResponseSenderBlock)cancelCallback) -{ - if (RCTRunningInAppExtension()) { - cancelCallback(@[@"Image picker is currently unavailable in an app extension"]); - return; - } - - UIImagePickerController *imagePicker = [UIImagePickerController new]; - imagePicker.delegate = self; - imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - - NSMutableArray *allowedTypes = [NSMutableArray new]; - if ([RCTConvert BOOL:config[@"showImages"]]) { - [allowedTypes addObject:(NSString *)kUTTypeImage]; - } - if ([RCTConvert BOOL:config[@"showVideos"]]) { - [allowedTypes addObject:(NSString *)kUTTypeMovie]; - } - - imagePicker.mediaTypes = allowedTypes; - - [self _presentPicker:imagePicker - successCallback:callback - cancelCallback:cancelCallback]; -} - -- (void)imagePickerController:(UIImagePickerController *)picker -didFinishPickingMediaWithInfo:(NSDictionary *)info -{ - NSString *mediaType = info[UIImagePickerControllerMediaType]; - BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie]; - NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL; - NSURL *imageURL = info[key]; - UIImage *image = info[UIImagePickerControllerOriginalImage]; - NSNumber *width = 0; - NSNumber *height = 0; - if (image) { - height = @(image.size.height); - width = @(image.size.width); - } - if (imageURL) { - [self _dismissPicker:picker args:@[imageURL.absoluteString, RCTNullIfNil(height), RCTNullIfNil(width)]]; - return; - } - - // This is a newly taken image, and doesn't have a URL yet. - // We need to save it to the image store first. - UIImage *originalImage = info[UIImagePickerControllerOriginalImage]; - - // WARNING: Using ImageStoreManager may cause a memory leak because the - // image isn't automatically removed from store once we're done using it. - [_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) { - [self _dismissPicker:picker args:tempImageTag ? @[tempImageTag, height, width] : nil]; - }]; -} - -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker -{ - [self _dismissPicker:picker args:nil]; -} - -- (void)_presentPicker:(UIImagePickerController *)imagePicker - successCallback:(RCTResponseSenderBlock)callback - cancelCallback:(RCTResponseSenderBlock)cancelCallback -{ - if (!_pickers) { - _pickers = [NSMutableArray new]; - _pickerCallbacks = [NSMutableArray new]; - _pickerCancelCallbacks = [NSMutableArray new]; - } - - [_pickers addObject:imagePicker]; - [_pickerCallbacks addObject:callback]; - [_pickerCancelCallbacks addObject:cancelCallback]; - - UIViewController *rootViewController = RCTPresentedViewController(); - [rootViewController presentViewController:imagePicker animated:YES completion:nil]; -} - -- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args -{ - NSUInteger index = [_pickers indexOfObject:picker]; - RCTResponseSenderBlock successCallback = _pickerCallbacks[index]; - RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index]; - - [_pickers removeObjectAtIndex:index]; - [_pickerCallbacks removeObjectAtIndex:index]; - [_pickerCancelCallbacks removeObjectAtIndex:index]; - - UIViewController *rootViewController = RCTPresentedViewController(); - [rootViewController dismissViewControllerAnimated:YES completion:nil]; - - if (args) { - successCallback(args); - } else { - cancelCallback(@[]); - } -} - -@end diff --git a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.h b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.h deleted file mode 100644 index 0a45f184dab73c..00000000000000 --- a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.h +++ /dev/null @@ -1,12 +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. - */ - -#import - -@interface RCTPhotoLibraryImageLoader : NSObject - -@end diff --git a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m deleted file mode 100644 index ac27231adc9e9a..00000000000000 --- a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m +++ /dev/null @@ -1,111 +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. - */ - -#import "RCTPhotoLibraryImageLoader.h" - -#import - -#import - -@implementation RCTPhotoLibraryImageLoader - -RCT_EXPORT_MODULE() - -@synthesize bridge = _bridge; - -#pragma mark - RCTImageLoader - -- (BOOL)canLoadImageURL:(NSURL *)requestURL -{ - if (![PHAsset class]) { - return NO; - } - return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame || - [requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame; -} - -- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - progressHandler:(RCTImageLoaderProgressBlock)progressHandler - partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler - completionHandler:(RCTImageLoaderCompletionBlock)completionHandler -{ - // Using PhotoKit for iOS 8+ - // The 'ph://' prefix is used by FBMediaKit to differentiate between - // assets-library. It is prepended to the local ID so that it is in the - // form of an, NSURL which is what assets-library uses. - NSString *assetID = @""; - PHFetchResult *results; - if (!imageURL) { - completionHandler(RCTErrorWithMessage(@"Cannot load a photo library asset with no URL"), nil); - return ^{}; - } else if ([imageURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) { - assetID = [imageURL absoluteString]; - results = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil]; - } else { - assetID = [imageURL.absoluteString substringFromIndex:@"ph://".length]; - results = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetID] options:nil]; - } - if (results.count == 0) { - NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", assetID]; - completionHandler(RCTErrorWithMessage(errorText), nil); - return ^{}; - } - - PHAsset *asset = [results firstObject]; - PHImageRequestOptions *imageOptions = [PHImageRequestOptions new]; - - // Allow PhotoKit to fetch images from iCloud - imageOptions.networkAccessAllowed = YES; - - if (progressHandler) { - imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { - static const double multiplier = 1e6; - progressHandler(progress * multiplier, multiplier); - }; - } - - // Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic - // which means it may call back multiple times - we probably don't want that - imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - - BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero); - CGSize targetSize; - if (useMaximumSize) { - targetSize = PHImageManagerMaximumSize; - imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone; - } else { - targetSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); - imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast; - } - - PHImageContentMode contentMode = PHImageContentModeAspectFill; - if (resizeMode == RCTResizeModeContain) { - contentMode = PHImageContentModeAspectFit; - } - - PHImageRequestID requestID = - [[PHImageManager defaultManager] requestImageForAsset:asset - targetSize:targetSize - contentMode:contentMode - options:imageOptions - resultHandler:^(UIImage *result, NSDictionary *info) { - if (result) { - completionHandler(nil, result); - } else { - completionHandler(info[PHImageErrorKey], nil); - } - }]; - - return ^{ - [[PHImageManager defaultManager] cancelImageRequest:requestID]; - }; -} - -@end diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js deleted file mode 100644 index afc4abe1533a72..00000000000000 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js +++ /dev/null @@ -1,74 +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 AccessibilityInfo - * @flow - */ -'use strict'; - -const NativeModules = require('NativeModules'); -const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); - -const RCTAccessibilityInfo = NativeModules.AccessibilityInfo; - -const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange'; - -type ChangeEventName = $Enum<{ - change: string, -}>; - -const _subscriptions = new Map(); - -/** - * Sometimes it's useful to know whether or not the device has a screen reader - * that is currently active. The `AccessibilityInfo` API is designed for this - * purpose. You can use it to query the current state of the screen reader as - * well as to register to be notified when the state of the screen reader - * changes. - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html - */ - -const AccessibilityInfo = { - - fetch: function(): Promise { - return new Promise((resolve, reject) => { - RCTAccessibilityInfo.isTouchExplorationEnabled( - function(resp) { - resolve(resp); - } - ); - }); - }, - - addEventListener: function ( - eventName: ChangeEventName, - handler: Function - ): void { - const listener = RCTDeviceEventEmitter.addListener( - TOUCH_EXPLORATION_EVENT, - (enabled) => { - handler(enabled); - } - ); - _subscriptions.set(handler, listener); - }, - - removeEventListener: function( - eventName: ChangeEventName, - handler: Function - ): void { - const listener = _subscriptions.get(handler); - if (!listener) { - return; - } - listener.remove(); - _subscriptions.delete(handler); - }, - -}; - -module.exports = AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.flow.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.flow.js new file mode 100644 index 00000000000000..60c3ec53cd3f44 --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.flow.js @@ -0,0 +1,208 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import type {EventSubscription} from '../../vendor/emitter/EventEmitter'; +import type {ElementRef} from 'react'; + +// Events that are only supported on Android. +type AccessibilityEventDefinitionsAndroid = { + accessibilityServiceChanged: [boolean], +}; + +// Events that are only supported on iOS. +type AccessibilityEventDefinitionsIOS = { + announcementFinished: [{announcement: string, success: boolean}], + boldTextChanged: [boolean], + grayscaleChanged: [boolean], + invertColorsChanged: [boolean], + reduceTransparencyChanged: [boolean], +}; + +type AccessibilityEventDefinitions = { + ...AccessibilityEventDefinitionsAndroid, + ...AccessibilityEventDefinitionsIOS, + change: [boolean], // screenReaderChanged + reduceMotionChanged: [boolean], + screenReaderChanged: [boolean], +}; + +type AccessibilityEventTypes = 'click' | 'focus'; +/** + * Sometimes it's useful to know whether or not the device has a screen reader + * that is currently active. The `AccessibilityInfo` API is designed for this + * purpose. You can use it to query the current state of the screen reader as + * well as to register to be notified when the state of the screen reader + * changes. + * + * See https://reactnative.dev/docs/accessibilityinfo + */ +export interface AccessibilityInfo { + /** + * Query whether bold text is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when bold text is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isBoldTextEnabled + */ + isBoldTextEnabled: () => Promise; + + /** + * Query whether grayscale is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when grayscale is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isGrayscaleEnabled + */ + isGrayscaleEnabled: () => Promise; + + /** + * Query whether inverted colors are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isInvertColorsEnabled + */ + isInvertColorsEnabled: () => Promise; + + /** + * Query whether reduced motion is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce motion is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isReduceMotionEnabled + */ + isReduceMotionEnabled: () => Promise; + + /** + * Query whether reduce motion and prefer cross-fade transitions settings are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when prefer cross-fade transitions is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#prefersCrossFadeTransitions + */ + prefersCrossFadeTransitions: () => Promise; + + /** + * Query whether reduced transparency is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce transparency is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isReduceTransparencyEnabled + */ + isReduceTransparencyEnabled: () => Promise; + + /** + * Query whether a screen reader is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a screen reader is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isScreenReaderEnabled + */ + isScreenReaderEnabled: () => Promise; + + /** + * Query whether Accessibility Service is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when any service is enabled and `false` otherwise. + * + * @platform android + * + * See https://reactnative.dev/docs/accessibilityinfo/#isaccessibilityserviceenabled-android + */ + isAccessibilityServiceEnabled: () => Promise; + + /** + * Add an event handler. Supported events: + * + * - `reduceMotionChanged`: Fires when the state of the reduce motion toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a reduce + * motion is enabled (or when "Transition Animation Scale" in "Developer options" is + * "Animation off") and `false` otherwise. + * - `screenReaderChanged`: Fires when the state of the screen reader changes. The argument + * to the event handler is a boolean. The boolean is `true` when a screen + * reader is enabled and `false` otherwise. + * + * These events are only supported on iOS: + * + * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a bold text + * is enabled and `false` otherwise. + * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a gray scale + * is enabled and `false` otherwise. + * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle + * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert + * colors is enabled and `false` otherwise. + * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency + * toggle changes. The argument to the event handler is a boolean. The boolean is `true` + * when a reduce transparency is enabled and `false` otherwise. + * - `announcementFinished`: iOS-only event. Fires when the screen reader has + * finished making an announcement. The argument to the event handler is a + * dictionary with these keys: + * - `announcement`: The string announced by the screen reader. + * - `success`: A boolean indicating whether the announcement was + * successfully made. + * + * See https://reactnative.dev/docs/accessibilityinfo#addeventlistener + */ + addEventListener>( + eventName: K, + handler: (...$ElementType) => void, + ): EventSubscription; + + /** + * Set accessibility focus to a React component. + * + * See https://reactnative.dev/docs/accessibilityinfo#setaccessibilityfocus + */ + setAccessibilityFocus: (reactTag: number) => void; + + /** + * Send a named accessibility event to a HostComponent. + */ + sendAccessibilityEvent: ( + handle: ElementRef>, + eventType: AccessibilityEventTypes, + ) => void; + + /** + * Post a string to be announced by the screen reader. + * + * See https://reactnative.dev/docs/accessibilityinfo#announceforaccessibility + */ + announceForAccessibility: (announcement: string) => void; + + /** + * Post a string to be announced by the screen reader. + * - `announcement`: The string announced by the screen reader. + * - `options`: An object that configures the reading options. + * - `queue`: The announcement will be queued behind existing announcements. iOS only. + */ + announceForAccessibilityWithOptions: ( + announcement: string, + options: {queue?: boolean}, + ) => void; + + /** + * Get the recommended timeout for changes to the UI needed by this user. + * + * See https://reactnative.dev/docs/accessibilityinfo#getrecommendedtimeoutmillis + */ + getRecommendedTimeoutMillis: (originalTimeout: number) => Promise; +} diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js deleted file mode 100644 index 6e39b5e477d5fb..00000000000000 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ /dev/null @@ -1,140 +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 AccessibilityInfo - * @flow - */ -'use strict'; - -const NativeModules = require('NativeModules'); -const Promise = require('Promise'); -const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); - -const AccessibilityManager = NativeModules.AccessibilityManager; - -const VOICE_OVER_EVENT = 'voiceOverDidChange'; -const ANNOUNCEMENT_DID_FINISH_EVENT = 'announcementDidFinish'; - -type ChangeEventName = $Enum<{ - change: string, - announcementFinished: string -}>; - -const _subscriptions = new Map(); - -/** - * Sometimes it's useful to know whether or not the device has a screen reader - * that is currently active. The `AccessibilityInfo` API is designed for this - * purpose. You can use it to query the current state of the screen reader as - * well as to register to be notified when the state of the screen reader - * changes. - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html - */ -const AccessibilityInfo = { - - /** - * Query whether a screen reader is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when a screen reader is enabledand `false` otherwise. - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#fetch - */ - fetch: function(): Promise { - return new Promise((resolve, reject) => { - AccessibilityManager.getCurrentVoiceOverState( - resolve, - reject - ); - }); - }, - - /** - * Add an event handler. Supported events: - * - * - `change`: Fires when the state of the screen reader changes. The argument - * to the event handler is a boolean. The boolean is `true` when a screen - * reader is enabled and `false` otherwise. - * - `announcementFinished`: iOS-only event. Fires when the screen reader has - * finished making an announcement. The argument to the event handler is a - * dictionary with these keys: - * - `announcement`: The string announced by the screen reader. - * - `success`: A boolean indicating whether the announcement was - * successfully made. - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#addeventlistener - */ - addEventListener: function ( - eventName: ChangeEventName, - handler: Function - ): Object { - let listener; - - if (eventName === 'change') { - listener = RCTDeviceEventEmitter.addListener( - VOICE_OVER_EVENT, - handler - ); - } else if (eventName === 'announcementFinished') { - listener = RCTDeviceEventEmitter.addListener( - ANNOUNCEMENT_DID_FINISH_EVENT, - handler - ); - } - - _subscriptions.set(handler, listener); - return { - remove: AccessibilityInfo.removeEventListener.bind(null, eventName, handler), - }; - }, - - /** - * Set accessibility focus to a react component. - * - * @platform ios - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#setaccessibilityfocus - */ - setAccessibilityFocus: function( - reactTag: number - ): void { - AccessibilityManager.setAccessibilityFocus(reactTag); - }, - - /** - * Post a string to be announced by the screen reader. - * - * @platform ios - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#announceforaccessibility - */ - announceForAccessibility: function( - announcement: string - ): void { - AccessibilityManager.announceForAccessibility(announcement); - }, - - /** - * Remove an event handler. - * - * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#removeeventlistener - */ - removeEventListener: function( - eventName: ChangeEventName, - handler: Function - ): void { - const listener = _subscriptions.get(handler); - if (!listener) { - return; - } - listener.remove(); - _subscriptions.delete(handler); - }, - -}; - -module.exports = AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js new file mode 100644 index 00000000000000..1f116ddcf1795e --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js @@ -0,0 +1,420 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter'; +import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative'; +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import Platform from '../../Utilities/Platform'; +import type {EventSubscription} from '../../vendor/emitter/EventEmitter'; +import NativeAccessibilityInfoAndroid from './NativeAccessibilityInfo'; +import NativeAccessibilityManagerIOS from './NativeAccessibilityManager'; +import legacySendAccessibilityEvent from './legacySendAccessibilityEvent'; +import type {ElementRef} from 'react'; +import type {AccessibilityInfo as AccessibilityInfoType} from './AccessibilityInfo.flow'; + +// Events that are only supported on Android. +type AccessibilityEventDefinitionsAndroid = { + accessibilityServiceChanged: [boolean], +}; + +// Events that are only supported on iOS. +type AccessibilityEventDefinitionsIOS = { + announcementFinished: [{announcement: string, success: boolean}], + boldTextChanged: [boolean], + grayscaleChanged: [boolean], + invertColorsChanged: [boolean], + reduceTransparencyChanged: [boolean], +}; + +type AccessibilityEventDefinitions = { + ...AccessibilityEventDefinitionsAndroid, + ...AccessibilityEventDefinitionsIOS, + change: [boolean], // screenReaderChanged + reduceMotionChanged: [boolean], + screenReaderChanged: [boolean], +}; + +type AccessibilityEventTypes = 'click' | 'focus'; + +// Mapping of public event names to platform-specific event names. +const EventNames: Map< + $Keys, + string, +> = Platform.OS === 'android' + ? new Map([ + ['change', 'touchExplorationDidChange'], + ['reduceMotionChanged', 'reduceMotionDidChange'], + ['screenReaderChanged', 'touchExplorationDidChange'], + ['accessibilityServiceChanged', 'accessibilityServiceDidChange'], + ]) + : new Map([ + ['announcementFinished', 'announcementFinished'], + ['boldTextChanged', 'boldTextChanged'], + ['change', 'screenReaderChanged'], + ['grayscaleChanged', 'grayscaleChanged'], + ['invertColorsChanged', 'invertColorsChanged'], + ['reduceMotionChanged', 'reduceMotionChanged'], + ['reduceTransparencyChanged', 'reduceTransparencyChanged'], + ['screenReaderChanged', 'screenReaderChanged'], + ]); + +/** + * Sometimes it's useful to know whether or not the device has a screen reader + * that is currently active. The `AccessibilityInfo` API is designed for this + * purpose. You can use it to query the current state of the screen reader as + * well as to register to be notified when the state of the screen reader + * changes. + * + * See https://reactnative.dev/docs/accessibilityinfo + */ +const AccessibilityInfo: AccessibilityInfoType = { + /** + * Query whether bold text is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when bold text is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isBoldTextEnabled + */ + isBoldTextEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentBoldTextState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether grayscale is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when grayscale is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isGrayscaleEnabled + */ + isGrayscaleEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentGrayscaleState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether inverted colors are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isInvertColorsEnabled + */ + isInvertColorsEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentInvertColorsState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether reduced motion is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce motion is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isReduceMotionEnabled + */ + isReduceMotionEnabled(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid != null) { + NativeAccessibilityInfoAndroid.isReduceMotionEnabled(resolve); + } else { + reject(null); + } + } else { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentReduceMotionState( + resolve, + reject, + ); + } else { + reject(null); + } + } + }); + }, + + /** + * Query whether reduce motion and prefer cross-fade transitions settings are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when prefer cross-fade transitions is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#prefersCrossFadeTransitions + */ + prefersCrossFadeTransitions(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + if ( + NativeAccessibilityManagerIOS?.getCurrentPrefersCrossFadeTransitionsState != + null + ) { + NativeAccessibilityManagerIOS.getCurrentPrefersCrossFadeTransitionsState( + resolve, + reject, + ); + } else { + reject(null); + } + } + }); + }, + + /** + * Query whether reduced transparency is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce transparency is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isReduceTransparencyEnabled + */ + isReduceTransparencyEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentReduceTransparencyState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether a screen reader is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a screen reader is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo#isScreenReaderEnabled + */ + isScreenReaderEnabled(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid != null) { + NativeAccessibilityInfoAndroid.isTouchExplorationEnabled(resolve); + } else { + reject(null); + } + } else { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentVoiceOverState( + resolve, + reject, + ); + } else { + reject(null); + } + } + }); + }, + + /** + * Query whether Accessibility Service is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when any service is enabled and `false` otherwise. + * + * @platform android + * + * See https://reactnative.dev/docs/accessibilityinfo/#isaccessibilityserviceenabled-android + */ + isAccessibilityServiceEnabled(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if ( + NativeAccessibilityInfoAndroid != null && + NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled != null + ) { + NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled(resolve); + } else { + reject(null); + } + } else { + reject(null); + } + }); + }, + + /** + * Add an event handler. Supported events: + * + * - `reduceMotionChanged`: Fires when the state of the reduce motion toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a reduce + * motion is enabled (or when "Transition Animation Scale" in "Developer options" is + * "Animation off") and `false` otherwise. + * - `screenReaderChanged`: Fires when the state of the screen reader changes. The argument + * to the event handler is a boolean. The boolean is `true` when a screen + * reader is enabled and `false` otherwise. + * + * These events are only supported on iOS: + * + * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a bold text + * is enabled and `false` otherwise. + * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a gray scale + * is enabled and `false` otherwise. + * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle + * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert + * colors is enabled and `false` otherwise. + * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency + * toggle changes. The argument to the event handler is a boolean. The boolean is `true` + * when a reduce transparency is enabled and `false` otherwise. + * - `announcementFinished`: iOS-only event. Fires when the screen reader has + * finished making an announcement. The argument to the event handler is a + * dictionary with these keys: + * - `announcement`: The string announced by the screen reader. + * - `success`: A boolean indicating whether the announcement was + * successfully made. + * + * See https://reactnative.dev/docs/accessibilityinfo#addeventlistener + */ + addEventListener>( + eventName: K, + // $FlowIssue[incompatible-type] - Flow bug with unions and generics (T128099423) + handler: (...$ElementType) => void, + ): EventSubscription { + const deviceEventName = EventNames.get(eventName); + return deviceEventName == null + ? {remove(): void {}} + : RCTDeviceEventEmitter.addListener(deviceEventName, handler); + }, + + /** + * Set accessibility focus to a React component. + * + * See https://reactnative.dev/docs/accessibilityinfo#setaccessibilityfocus + */ + setAccessibilityFocus(reactTag: number): void { + legacySendAccessibilityEvent(reactTag, 'focus'); + }, + + /** + * Send a named accessibility event to a HostComponent. + */ + sendAccessibilityEvent( + handle: ElementRef>, + eventType: AccessibilityEventTypes, + ) { + // iOS only supports 'focus' event types + if (Platform.OS === 'ios' && eventType === 'click') { + return; + } + // route through React renderer to distinguish between Fabric and non-Fabric handles + sendAccessibilityEvent(handle, eventType); + }, + + /** + * Post a string to be announced by the screen reader. + * + * See https://reactnative.dev/docs/accessibilityinfo#announceforaccessibility + */ + announceForAccessibility(announcement: string): void { + if (Platform.OS === 'android') { + NativeAccessibilityInfoAndroid?.announceForAccessibility(announcement); + } else { + NativeAccessibilityManagerIOS?.announceForAccessibility(announcement); + } + }, + + /** + * Post a string to be announced by the screen reader. + * - `announcement`: The string announced by the screen reader. + * - `options`: An object that configures the reading options. + * - `queue`: The announcement will be queued behind existing announcements. iOS only. + */ + announceForAccessibilityWithOptions( + announcement: string, + options: {queue?: boolean}, + ): void { + if (Platform.OS === 'android') { + NativeAccessibilityInfoAndroid?.announceForAccessibility(announcement); + } else { + if (NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions) { + NativeAccessibilityManagerIOS?.announceForAccessibilityWithOptions( + announcement, + options, + ); + } else { + NativeAccessibilityManagerIOS?.announceForAccessibility(announcement); + } + } + }, + + /** + * Get the recommended timeout for changes to the UI needed by this user. + * + * See https://reactnative.dev/docs/accessibilityinfo#getrecommendedtimeoutmillis + */ + getRecommendedTimeoutMillis(originalTimeout: number): Promise { + if (Platform.OS === 'android') { + return new Promise((resolve, reject) => { + if (NativeAccessibilityInfoAndroid?.getRecommendedTimeoutMillis) { + NativeAccessibilityInfoAndroid.getRecommendedTimeoutMillis( + originalTimeout, + resolve, + ); + } else { + resolve(originalTimeout); + } + }); + } else { + return Promise.resolve(originalTimeout); + } + }, +}; + +export default AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js b/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js new file mode 100644 index 00000000000000..beee9e28517569 --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +isReduceMotionEnabled: ( + onSuccess: (isReduceMotionEnabled: boolean) => void, + ) => void; + +isTouchExplorationEnabled: ( + onSuccess: (isScreenReaderEnabled: boolean) => void, + ) => void; + +isAccessibilityServiceEnabled?: ?( + onSuccess: (isAccessibilityServiceEnabled: boolean) => void, + ) => void; + +setAccessibilityFocus: (reactTag: number) => void; + +announceForAccessibility: (announcement: string) => void; + +getRecommendedTimeoutMillis?: ( + mSec: number, + onSuccess: (recommendedTimeoutMillis: number) => void, + ) => void; +} + +export default (TurboModuleRegistry.get('AccessibilityInfo'): ?Spec); diff --git a/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js new file mode 100644 index 00000000000000..a8dfc7147e1ffb --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getCurrentBoldTextState: ( + onSuccess: (isBoldTextEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentGrayscaleState: ( + onSuccess: (isGrayscaleEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentInvertColorsState: ( + onSuccess: (isInvertColorsEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentReduceMotionState: ( + onSuccess: (isReduceMotionEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentPrefersCrossFadeTransitionsState?: ( + onSuccess: (prefersCrossFadeTransitions: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentReduceTransparencyState: ( + onSuccess: (isReduceTransparencyEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +getCurrentVoiceOverState: ( + onSuccess: (isScreenReaderEnabled: boolean) => void, + onError: (error: Object) => void, + ) => void; + +setAccessibilityContentSizeMultipliers: (JSMultipliers: {| + +extraSmall?: ?number, + +small?: ?number, + +medium?: ?number, + +large?: ?number, + +extraLarge?: ?number, + +extraExtraLarge?: ?number, + +extraExtraExtraLarge?: ?number, + +accessibilityMedium?: ?number, + +accessibilityLarge?: ?number, + +accessibilityExtraLarge?: ?number, + +accessibilityExtraExtraLarge?: ?number, + +accessibilityExtraExtraExtraLarge?: ?number, + |}) => void; + +setAccessibilityFocus: (reactTag: number) => void; + +announceForAccessibility: (announcement: string) => void; + +announceForAccessibilityWithOptions?: ( + announcement: string, + options: {queue?: boolean}, + ) => void; +} + +export default (TurboModuleRegistry.get('AccessibilityManager'): ?Spec); diff --git a/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js new file mode 100644 index 00000000000000..09556be26a4644 --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +import UIManager from '../../ReactNative/UIManager'; + +/** + * This is a function exposed to the React Renderer that can be used by the + * pre-Fabric renderer to emit accessibility events to pre-Fabric nodes. + */ +function legacySendAccessibilityEvent( + reactTag: number, + eventType: string, +): void { + if (eventType === 'focus') { + UIManager.sendAccessibilityEvent( + reactTag, + UIManager.getConstants().AccessibilityEventTypes.typeViewFocused, + ); + } + if (eventType === 'click') { + UIManager.sendAccessibilityEvent( + reactTag, + UIManager.getConstants().AccessibilityEventTypes.typeViewClicked, + ); + } +} + +module.exports = legacySendAccessibilityEvent; diff --git a/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.ios.js b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.ios.js new file mode 100644 index 00000000000000..17091e354ff569 --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.ios.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +import NativeAccessibilityManager from './NativeAccessibilityManager'; + +/** + * This is a function exposed to the React Renderer that can be used by the + * pre-Fabric renderer to emit accessibility events to pre-Fabric nodes. + */ +function legacySendAccessibilityEvent( + reactTag: number, + eventType: string, +): void { + if (eventType === 'focus' && NativeAccessibilityManager) { + NativeAccessibilityManager.setAccessibilityFocus(reactTag); + } +} + +module.exports = legacySendAccessibilityEvent; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index a520b657904738..eb8b733e15a2cd 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -1,133 +1,193 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule ActivityIndicator + * @format * @flow + * @generate-docs */ -'use strict'; -const ColorPropType = require('ColorPropType'); -const NativeMethodsMixin = require('NativeMethodsMixin'); -const Platform = require('Platform'); -const ProgressBarAndroid = require('ProgressBarAndroid'); -const PropTypes = require('prop-types'); -const React = require('React'); -const StyleSheet = require('StyleSheet'); -const View = require('View'); -const ViewPropTypes = require('ViewPropTypes'); +'use strict'; +import * as React from 'react'; +import Platform from '../../Utilities/Platform'; +import StyleSheet, {type ColorValue} from '../../StyleSheet/StyleSheet'; +import View from '../View/View'; +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import type {ViewProps} from '../View/ViewPropTypes'; -const createReactClass = require('create-react-class'); -const requireNativeComponent = require('requireNativeComponent'); -let RCTActivityIndicator; +const PlatformActivityIndicator = + Platform.OS === 'android' + ? require('../ProgressBarAndroid/ProgressBarAndroid') + : require('./ActivityIndicatorViewNativeComponent').default; const GRAY = '#999999'; type IndicatorSize = number | 'small' | 'large'; -type DefaultProps = { - animating: boolean, - color: any, - hidesWhenStopped: boolean, - size: IndicatorSize, -} +type IOSProps = $ReadOnly<{| + /** + Whether the indicator should hide when not animating. + + @platform ios + */ + hidesWhenStopped?: ?boolean, +|}>; +type Props = $ReadOnly<{| + ...ViewProps, + ...IOSProps, + + /** + Whether to show the indicator (`true`) or hide it (`false`). + */ + animating?: ?boolean, + + /** + The foreground color of the spinner. + + @default {@platform android} `null` (system accent default color) + @default {@platform ios} '#999999' + */ + color?: ?ColorValue, + + /** + Size of the indicator. + + @type enum(`'small'`, `'large'`) + @type {@platform android} number + */ + size?: ?IndicatorSize, +|}>; + +const ActivityIndicator = ( + { + animating = true, + color = Platform.OS === 'ios' ? GRAY : null, + hidesWhenStopped = true, + onLayout, + size = 'small', + style, + ...restProps + }: Props, + forwardedRef?: any, +) => { + let sizeStyle; + let sizeProp; + + switch (size) { + case 'small': + sizeStyle = styles.sizeSmall; + sizeProp = 'small'; + break; + case 'large': + sizeStyle = styles.sizeLarge; + sizeProp = 'large'; + break; + default: + sizeStyle = {height: size, width: size}; + break; + } + + const nativeProps = { + animating, + color, + hidesWhenStopped, + ...restProps, + ref: forwardedRef, + style: sizeStyle, + size: sizeProp, + }; + + const androidProps = { + styleAttr: 'Normal', + indeterminate: true, + }; + + return ( + + {Platform.OS === 'android' ? ( + // $FlowFixMe[prop-missing] Flow doesn't know when this is the android component + + ) : ( + /* $FlowFixMe[prop-missing] (>=0.106.0 site=react_native_android_fb) This comment + * suppresses an error found when Flow v0.106 was deployed. To see the + * error, delete this comment and run Flow. */ + + )} + + ); +}; /** - * Displays a circular loading indicator. - * - * See http://facebook.github.io/react-native/docs/activityindicator.html - */ -const ActivityIndicator = createReactClass({ - displayName: 'ActivityIndicator', - mixins: [NativeMethodsMixin], - - propTypes: { - ...ViewPropTypes, - /** - * Whether to show the indicator (true, the default) or hide it (false). - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#animating - */ - animating: PropTypes.bool, - /** - * The foreground color of the spinner (default is gray). - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#color - */ - color: ColorPropType, - /** - * Size of the indicator (default is 'small'). - * Passing a number to the size prop is only supported on Android. - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#size - */ - size: PropTypes.oneOfType([ - PropTypes.oneOf([ 'small', 'large' ]), - PropTypes.number, - ]), - /** - * Whether the indicator should hide when not animating (true by default). - * - * @platform ios - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#hideswhenstopped - */ - hidesWhenStopped: PropTypes.bool, - }, + Displays a circular loading indicator. - getDefaultProps(): DefaultProps { - return { - animating: true, - color: Platform.OS === 'ios' ? GRAY : undefined, - hidesWhenStopped: true, - size: 'small', - }; - }, + ```SnackPlayer name=ActivityIndicator%20Function%20Component%20Example + import React from "react"; + import { ActivityIndicator, StyleSheet, Text, View } from "react-native"; + + const App = () => ( + + + + + + + ); - render() { - const {onLayout, style, ...props} = this.props; - let sizeStyle; - - switch (props.size) { - case 'small': - sizeStyle = styles.sizeSmall; - break; - case 'large': - sizeStyle = styles.sizeLarge; - break; - default: - sizeStyle = {height: props.size, width: props.size}; - break; + const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center" + }, + horizontal: { + flexDirection: "row", + justifyContent: "space-around", + padding: 10 } + }); + export default App; + ``` - const nativeProps = { - ...props, - style: sizeStyle, - styleAttr: 'Normal', - indeterminate: true, - }; - - return ( - - {Platform.OS === 'ios' ? ( - - ) : ( - - )} - - ); + ```SnackPlayer name=ActivityIndicator%20Class%20Component%20Example + import React, { Component } from "react"; + import { ActivityIndicator, StyleSheet, Text, View } from "react-native"; + + class App extends Component { + render() { + return ( + + + + + + + ); + } } -}); -if (Platform.OS === 'ios') { - RCTActivityIndicator = requireNativeComponent( - 'RCTActivityIndicatorView', - ActivityIndicator, - { nativeOnly: { activityIndicatorViewStyle: true } } - ); -} + const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center" + }, + horizontal: { + flexDirection: "row", + justifyContent: "space-around", + padding: 10 + } + }); + export default App; + ``` +*/ + +const ActivityIndicatorWithRef: React.AbstractComponent< + Props, + HostComponent, +> = React.forwardRef(ActivityIndicator); +ActivityIndicatorWithRef.displayName = 'ActivityIndicator'; const styles = StyleSheet.create({ container: { @@ -144,4 +204,4 @@ const styles = StyleSheet.create({ }, }); -module.exports = ActivityIndicator; +module.exports = ActivityIndicatorWithRef; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js b/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js new file mode 100644 index 00000000000000..548069e885d653 --- /dev/null +++ b/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +import type {WithDefault} from '../../Types/CodegenTypes'; + +import type {ColorValue} from '../../StyleSheet/StyleSheet'; +import type {ViewProps} from '../View/ViewPropTypes'; + +import codegenNativeComponent from '../../Utilities/codegenNativeComponent'; +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + + /** + * Whether the indicator should hide when not animating (true by default). + * + * See https://reactnative.dev/docs/activityindicator#hideswhenstopped + */ + hidesWhenStopped?: WithDefault, + + /** + * Whether to show the indicator (true, the default) or hide it (false). + * + * See https://reactnative.dev/docs/activityindicator#animating + */ + animating?: WithDefault, + + /** + * The foreground color of the spinner (default is gray). + * + * See https://reactnative.dev/docs/activityindicator#color + */ + color?: ?ColorValue, + + /** + * Size of the indicator (default is 'small'). + * Passing a number to the size prop is only supported on Android. + * + * See https://reactnative.dev/docs/activityindicator#size + */ + size?: WithDefault<'small' | 'large', 'small'>, +|}>; + +export default (codegenNativeComponent('ActivityIndicatorView', { + paperComponentName: 'RCTActivityIndicatorView', +}): HostComponent); diff --git a/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js b/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js new file mode 100644 index 00000000000000..91cb7bd6e29291 --- /dev/null +++ b/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + * @flow strict-local + */ + +'use strict'; + +const React = require('react'); +const ActivityIndicator = require('../ActivityIndicator'); + +const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); + +describe('', () => { + it('should set displayName to prevent regressions', () => { + expect(ActivityIndicator.displayName).toBe('ActivityIndicator'); + }); + + it('should render as expected', () => { + ReactNativeTestTools.expectRendersMatchingSnapshot( + 'ActivityIndicator', + () => , + () => { + jest.dontMock('../ActivityIndicator'); + }, + ); + }); +}); diff --git a/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap b/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap new file mode 100644 index 00000000000000..4326d7f8ae20f3 --- /dev/null +++ b/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render as expected: should deep render when mocked (please verify output manually) 1`] = ` + +`; + +exports[` should render as expected: should deep render when not mocked (please verify output manually) 1`] = ` + + + +`; + +exports[` should render as expected: should shallow render as when mocked 1`] = ` + +`; + +exports[` should render as expected: should shallow render as when not mocked 1`] = ` + +`; diff --git a/Libraries/Components/AppleTV/TVEventHandler.js b/Libraries/Components/AppleTV/TVEventHandler.js deleted file mode 100644 index 363d50d903d876..00000000000000 --- a/Libraries/Components/AppleTV/TVEventHandler.js +++ /dev/null @@ -1,47 +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 TVEventHandler - * @flow - */ -'use strict'; - -const Platform = require('Platform'); -const TVNavigationEventEmitter = require('NativeModules').TVNavigationEventEmitter; -const NativeEventEmitter = require('NativeEventEmitter'); - -function TVEventHandler() { - this.__nativeTVNavigationEventListener = null; - this.__nativeTVNavigationEventEmitter = null; -} - -TVEventHandler.prototype.enable = function(component: ?any, callback: Function) { - if (Platform.OS === 'ios' && !TVNavigationEventEmitter) { - return; - } - - this.__nativeTVNavigationEventEmitter = new NativeEventEmitter(TVNavigationEventEmitter); - this.__nativeTVNavigationEventListener = this.__nativeTVNavigationEventEmitter.addListener( - 'onHWKeyEvent', - (data) => { - if (callback) { - callback(component, data); - } - } - ); -}; - -TVEventHandler.prototype.disable = function() { - if (this.__nativeTVNavigationEventListener) { - this.__nativeTVNavigationEventListener.remove(); - delete this.__nativeTVNavigationEventListener; - } - if (this.__nativeTVNavigationEventEmitter) { - delete this.__nativeTVNavigationEventEmitter; - } -}; - -module.exports = TVEventHandler; diff --git a/Libraries/Components/AppleTV/TVViewPropTypes.js b/Libraries/Components/AppleTV/TVViewPropTypes.js deleted file mode 100644 index 2b6a6d4e53dcd1..00000000000000 --- a/Libraries/Components/AppleTV/TVViewPropTypes.js +++ /dev/null @@ -1,84 +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 TVViewPropTypes - * @flow - */ -'use strict'; -const PropTypes = require('prop-types'); - -/** - * Additional View properties for Apple TV - */ -const TVViewPropTypes = { - /** - * When set to true, this view will be focusable - * and navigable using the TV remote. - */ - isTVSelectable: PropTypes.bool, - - /** - * May be set to true to force the TV focus engine to move focus to this view. - */ - hasTVPreferredFocus: PropTypes.bool, - - /** - * *(Apple TV only)* Object with properties to control Apple TV parallax effects. - * - * enabled: If true, parallax effects are enabled. Defaults to true. - * shiftDistanceX: Defaults to 2.0. - * shiftDistanceY: Defaults to 2.0. - * tiltAngle: Defaults to 0.05. - * magnification: Defaults to 1.0. - * pressMagnification: Defaults to 1.0. - * pressDuration: Defaults to 0.3. - * pressDelay: Defaults to 0.0. - * - * @platform ios - */ - tvParallaxProperties: PropTypes.object, - - /** - * *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. - * - * @platform ios - */ - tvParallaxShiftDistanceX: PropTypes.number, - - /** - * *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 2.0. - * - * @platform ios - */ - tvParallaxShiftDistanceY: PropTypes.number, - - /** - * *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 0.05. - * - * @platform ios - */ - tvParallaxTiltAngle: PropTypes.number, - - /** - * *(Apple TV only)* May be used to change the appearance of the Apple TV parallax effect when this view goes in or out of focus. Defaults to 1.0. - * - * @platform ios - */ - tvParallaxMagnification: PropTypes.number, - -}; - -export type TVViewProps = { - isTVSelectable?: bool, - hasTVPreferredFocus?: bool, - tvParallaxProperties?: Object, - tvParallaxShiftDistanceX?: number, - tvParallaxShiftDistanceY?: number, - tvParallaxTiltAngle?: number, - tvParallaxMagnification?: number, -}; - -module.exports = TVViewPropTypes; diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js index 4370f3571c103c..89ec384fe2c1b7 100644 --- a/Libraries/Components/Button.js +++ b/Libraries/Components/Button.js @@ -1,104 +1,285 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule Button + * @format * @flow + * @generate-docs */ -'use strict'; -const ColorPropType = require('ColorPropType'); -const Platform = require('Platform'); -const React = require('React'); -const PropTypes = require('prop-types'); -const StyleSheet = require('StyleSheet'); -const Text = require('Text'); -const TouchableNativeFeedback = require('TouchableNativeFeedback'); -const TouchableOpacity = require('TouchableOpacity'); -const View = require('View'); +'use strict'; -const invariant = require('fbjs/lib/invariant'); +import * as React from 'react'; +import Platform from '../Utilities/Platform'; +import StyleSheet, {type ColorValue} from '../StyleSheet/StyleSheet'; +import Text from '../Text/Text'; +import TouchableNativeFeedback from './Touchable/TouchableNativeFeedback'; +import TouchableOpacity from './Touchable/TouchableOpacity'; +import View from './View/View'; +import invariant from 'invariant'; -/** - * A basic button component that should render nicely on any platform. Supports - * a minimal level of customization. - * - *
- * - * If this button doesn't look right for your app, you can build your own - * button using [TouchableOpacity](docs/touchableopacity.html) - * or [TouchableNativeFeedback](docs/touchablenativefeedback.html). - * For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js). - * Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button). - * - * Example usage: - * - * ``` - * import { Button } from 'react-native'; - * ... - * - *