mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91a3a548e2 | ||
|
|
2990a1b4b8 | ||
|
|
a0ae706fab | ||
|
|
0b6e5b4f91 | ||
|
|
b12a7f9e68 | ||
|
|
edcb3ca996 | ||
|
|
b55518c4bf | ||
|
|
40a3ae3613 | ||
|
|
782e0bff4a | ||
|
|
e22ed7d676 | ||
|
|
4575ef516f | ||
|
|
4f7eb6197f | ||
|
|
36153e2557 | ||
|
|
d2d244cb8f | ||
|
|
af9225ec5f | ||
|
|
4555fc7865 | ||
|
|
989546f97e | ||
|
|
ec3688f95a | ||
|
|
25e2cbb441 | ||
|
|
1fad3af37c | ||
|
|
72e1eda073 | ||
|
|
dfda480a98 | ||
|
|
a5ddc2e165 | ||
|
|
62ef6f5fa1 | ||
|
|
b5f1b26a8f | ||
|
|
48113b5635 | ||
|
|
faaf2560cd | ||
|
|
387ee70e72 | ||
|
|
3fd3fe04da | ||
|
|
fdd7848454 | ||
|
|
1f48b7b0a2 | ||
|
|
6268836c74 | ||
|
|
9efcaffe17 | ||
|
|
8400590abe | ||
|
|
a5c44e622e | ||
|
|
44ee801c29 | ||
|
|
5a8033df98 | ||
|
|
51f5ea15aa | ||
|
|
a4a6e23dec | ||
|
|
e645629323 | ||
|
|
b3f19d7f1f | ||
|
|
cb28a269f0 | ||
|
|
4163386aee | ||
|
|
ccd1708096 | ||
|
|
4e34e6c4fb | ||
|
|
3e69592c5a | ||
|
|
5fd04faf1e | ||
|
|
a761b79ad8 | ||
|
|
d7d33c7ef3 | ||
|
|
7b33f20546 | ||
|
|
4b370ceeb7 | ||
|
|
63e4bd655a | ||
|
|
762db49ded | ||
|
|
65e4d98a71 | ||
|
|
66b82aec55 | ||
|
|
2cd3173991 | ||
|
|
61f0d1a7d1 | ||
|
|
57cc524e93 | ||
|
|
bca4cf023a | ||
|
|
f5d371a2f9 | ||
|
|
b1c30f8f8a | ||
|
|
54f5c8c26c | ||
|
|
48f9733867 | ||
|
|
43d67cd1ec | ||
|
|
805afe4847 | ||
|
|
0924a28b19 | ||
|
|
1b2b2198e1 | ||
|
|
319e4df458 | ||
|
|
71548ffd69 | ||
|
|
58eb8b13a0 | ||
|
|
5ba5ff6eba | ||
|
|
a2a7032477 | ||
|
|
f06df0ef2b | ||
|
|
0fd6ade862 |
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"module:metro-react-native-babel-preset"
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-transform-flow-enums"
|
||||
]
|
||||
}
|
||||
@@ -2,10 +2,6 @@
|
||||
[android]
|
||||
target = android-31
|
||||
|
||||
[kotlin]
|
||||
compile_against_abis = True
|
||||
kotlin_version = 1.6.10
|
||||
|
||||
[download]
|
||||
max_number_of_retries = 3
|
||||
|
||||
@@ -15,6 +11,3 @@
|
||||
|
||||
[alias]
|
||||
rntester = //packages/rn-tester/android/app:app
|
||||
|
||||
[buildfile]
|
||||
includes = //tools/build_defs/oss/preload.bzl
|
||||
|
||||
@@ -14,31 +14,41 @@
|
||||
# and build a Android application that can be used to run the
|
||||
# tests specified in the scripts/ directory.
|
||||
#
|
||||
FROM reactnativecommunity/react-native-android:7.0
|
||||
FROM reactnativecommunity/react-native-android:5.2
|
||||
|
||||
LABEL Description="React Native Android Test Image"
|
||||
LABEL maintainer="Héctor Ramos <hector@fb.com>"
|
||||
|
||||
# set default environment variables
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dfile.encoding=utf-8 -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
|
||||
ENV KOTLIN_HOME="packages/react-native/third-party/kotlin"
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
|
||||
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
|
||||
|
||||
ADD .buckconfig /app/.buckconfig
|
||||
ADD .buckjavaargs /app/.buckjavaargs
|
||||
ADD BUCK /app/BUCK
|
||||
ADD packages/react-native/Libraries /app/packages/react-native/Libraries
|
||||
ADD packages/react-native/ReactAndroid /app/packages/react-native/ReactAndroid
|
||||
ADD packages/react-native/ReactCommon /app/packages/react-native/ReactCommon
|
||||
ADD packages/react-native/React /app/packages/react-native/React
|
||||
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 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 :packages:react-native:ReactAndroid:assembleDebug
|
||||
RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
|
||||
|
||||
RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1
|
||||
|
||||
@@ -33,7 +33,7 @@ node cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/a
|
||||
|
||||
# build test APK
|
||||
# shellcheck disable=SC1091
|
||||
source ./scripts/android-setup.sh && NO_BUCKD=1 scripts/retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1
|
||||
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 "$*"
|
||||
|
||||
@@ -141,7 +141,7 @@ function e2e_suite() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! (cd packages/react-native && FILENAME=$(npm pack | tail -1) && mv "$FILENAME" ../../)
|
||||
if ! npm pack
|
||||
then
|
||||
echo "Failed to pack react-native"
|
||||
return 1
|
||||
|
||||
+344
-1367
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
storage: ./storage
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
max_fails: 40
|
||||
maxage: 30m
|
||||
timeout: 60s
|
||||
fail_timeout: 10m
|
||||
cache: false
|
||||
agent_options:
|
||||
keepAlive: true
|
||||
maxSockets: 40
|
||||
maxFreeSockets: 10
|
||||
packages:
|
||||
# Get @react-native/normalize-color from npm registry, since its used in deprecated-react-native-prop-types package
|
||||
'@react-native/normalize-color':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: npmjs
|
||||
# Group and isolate all local packages, avoid being proxy from outside
|
||||
'@react-native/*':
|
||||
access: $all
|
||||
publish: $all
|
||||
'@*/*':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: npmjs
|
||||
'**':
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
logs:
|
||||
- {type: file, path: verdaccio.log, format: json, level: warn}
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
**/main.js
|
||||
**/staticBundle.js
|
||||
bots/node_modules
|
||||
docs/generatedComponentApiDocs.js
|
||||
packages/react-native/flow/
|
||||
packages/react-native/Libraries/Renderer/*
|
||||
packages/react-native/Libraries/vendor/**/*
|
||||
flow/
|
||||
Libraries/Renderer/*
|
||||
Libraries/vendor/**/*
|
||||
node_modules/
|
||||
packages/*/node_modules
|
||||
packages/react-native-codegen/lib
|
||||
tools/eslint/rules/sort-imports.js
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"root": true,
|
||||
|
||||
"extends": [
|
||||
"./packages/eslint-config-react-native-community/index.js"
|
||||
],
|
||||
|
||||
"plugins": [
|
||||
"@react-native/eslint-plugin-specs"
|
||||
],
|
||||
|
||||
"overrides": [
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const path = require('node:path');
|
||||
|
||||
require('eslint-plugin-lint').load(path.join(__dirname, 'tools/eslint/rules'));
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
extends: ['@react-native'],
|
||||
|
||||
plugins: ['@react-native/eslint-plugin-specs', 'lint'],
|
||||
|
||||
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,
|
||||
// flow handles this check for us, so it's not required
|
||||
'no-undef': 0,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['packages/react-native/Libraries/**/*.js'],
|
||||
rules: {
|
||||
'@react-native/platform-colors': 2,
|
||||
'@react-native/specs/react-native-modules': 2,
|
||||
'lint/no-haste-imports': 2,
|
||||
'lint/no-react-native-imports': 2,
|
||||
'lint/require-extends-error': 2,
|
||||
'lint/sort-imports': 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-native/flow-typed/**/*.js'],
|
||||
rules: {
|
||||
'lint/valid-flow-typed-signature': 2,
|
||||
'no-unused-vars': 0,
|
||||
quotes: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__fixtures__/**/*.js',
|
||||
'**/__mocks__/**/*.js',
|
||||
'**/__tests__/**/*.js',
|
||||
'packages/react-native/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,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'react-native/no-inline-styles': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'no-self-compare': 'off',
|
||||
'react/self-closing-comp': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
plugins: ['redundant-undefined'],
|
||||
rules: {
|
||||
'redundant-undefined/redundant-undefined': [
|
||||
'error',
|
||||
{followExactOptionalPropertyTypes: true},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
+10
-12
@@ -3,16 +3,16 @@
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore templates for 'react-native init'
|
||||
<PROJECT_ROOT>/packages/react-native/template/.*
|
||||
<PROJECT_ROOT>/template/.*
|
||||
|
||||
; Ignore the Dangerfile
|
||||
<PROJECT_ROOT>/packages/react-native-bots/dangerfile.js
|
||||
<PROJECT_ROOT>/bots/dangerfile.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/packages/react-native/Libraries/Utilities/LoadingView.js
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
|
||||
|
||||
@@ -25,13 +25,10 @@
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
packages/react-native/interface.js
|
||||
packages/react-native/flow/
|
||||
packages/react-native/flow-typed/
|
||||
interface.js
|
||||
flow/
|
||||
|
||||
[options]
|
||||
enums=true
|
||||
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
@@ -44,9 +41,9 @@ module.file_ext=.ios.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/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\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\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\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
@@ -63,6 +60,7 @@ nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
unsafe-getters-setters=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -74,4 +72,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.202.0
|
||||
^0.170.0
|
||||
|
||||
+10
-12
@@ -3,16 +3,16 @@
|
||||
.*/*[.]ios.js
|
||||
|
||||
; Ignore templates for 'react-native init'
|
||||
<PROJECT_ROOT>/packages/react-native/template/.*
|
||||
<PROJECT_ROOT>/template/.*
|
||||
|
||||
; Ignore the Dangerfile
|
||||
<PROJECT_ROOT>/packages/react-native-bots/dangerfile.js
|
||||
<PROJECT_ROOT>/bots/dangerfile.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/packages/react-native/Libraries/Utilities/LoadingView.js
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
|
||||
|
||||
@@ -25,13 +25,10 @@
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
packages/react-native/interface.js
|
||||
packages/react-native/flow/
|
||||
packages/react-native/flow-typed/
|
||||
interface.js
|
||||
flow/
|
||||
|
||||
[options]
|
||||
enums=true
|
||||
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
@@ -44,9 +41,9 @@ module.file_ext=.android.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/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\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\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\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
@@ -63,6 +60,7 @@ nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
unsafe-getters-setters=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -74,4 +72,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.202.0
|
||||
^0.170.0
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
# GitHub Settings, Bots
|
||||
/.github/ @hramos
|
||||
/packages/react-native-bots @hramos
|
||||
/bots @hramos
|
||||
|
||||
# Continuous Integration
|
||||
/.circleci/ @hramos
|
||||
|
||||
@@ -10,15 +10,15 @@ body:
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a clear and concise description of what the bug is. Include screenshots if needed. Test using the [latest React Native release](https://github.com/facebook/react-native/releases/latest) to make sure your issue has not already been fixed.
|
||||
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: React Native Version
|
||||
description: What is the latest version of react-native that this issue reproduces on? Please only list the highest version you tested. Bear in mind that only issues on [supported versions](https://github.com/reactwg/react-native-releases#which-versions-are-currently-supported) will be looked into.
|
||||
placeholder: ex. 0.71.0
|
||||
label: Version
|
||||
description: What react-native version does this appear on?
|
||||
placeholder: ex. 0.66.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -40,9 +40,8 @@ body:
|
||||
attributes:
|
||||
label: Snack, code example, screenshot, or link to a repository
|
||||
description: |
|
||||
Please provide a Snack (https://snack.expo.dev/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
|
||||
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
|
||||
required: false
|
||||
|
||||
@@ -6,9 +6,6 @@ contact_links:
|
||||
- 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.
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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: React Native Version
|
||||
description: What is the latest version of react-native that this issue reproduces on? Please test against the latest stable version, and list only the highest version you tested. Bug reports against older versions are more likely to stall.
|
||||
placeholder: ex. 0.71.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.dev/), 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
|
||||
@@ -1,11 +1,11 @@
|
||||
name: ⬆️ Upgrade - Build Regression
|
||||
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 upgraded 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).
|
||||
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:
|
||||
|
||||
@@ -6,16 +6,12 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry.
|
||||
|
||||
Pick one each for the category and type tags:
|
||||
|
||||
[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message
|
||||
|
||||
For more details, see:
|
||||
https://reactnative.dev/contributing/changelogs-in-pull-requests
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
|
||||
https://github.com/facebook/react-native/wiki/Changelog
|
||||
-->
|
||||
|
||||
[CATEGORY] [TYPE] - Message
|
||||
|
||||
## Test Plan
|
||||
|
||||
<!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. -->
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<!-- Template for pre-release GitHub release -->
|
||||
|
||||
<!-- TODO Post about this release to https://github.com/reactwg/react-native-releases/discussions) -->
|
||||
|
||||
- <!-- TODO List out notable picks for this patch -->
|
||||
|
||||
---
|
||||
|
||||
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)
|
||||
@@ -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: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Required Fields
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a <a href="https://github.com/facebook/react-native/issues/new?template=bug_report.md">new issue is created</a>. 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.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Environment Info":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Environment Information
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Verify on Latest Version":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Using Old Version
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like you are using an older version of React Native. Please <a href="https://reactnative.dev/docs/upgrading">upgrade</a> 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.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Repro":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Reproducible Example
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like your issue is missing a reproducible example. Please provide a <a href="https://snack.expo.io">Snack</a> or a repository that demonstrates the issue you are reporting in a <a href="https://stackoverflow.com/help/minimal-reproducible-example">minimal, complete, and reproducible</a> manner.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
@@ -1,137 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
module.exports = async (github, context, label) => {
|
||||
const closeIssue = async () => {
|
||||
await github.rest.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed',
|
||||
});
|
||||
};
|
||||
|
||||
const addComment = async comment => {
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment,
|
||||
});
|
||||
};
|
||||
|
||||
const requestAuthorFeedback = async () => {
|
||||
// Remove the triage label if it exists (ignore the 404 if not; it's not expected to always be there)
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'Needs: Triage :mag:',
|
||||
});
|
||||
} catch {}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['Needs: Author Feedback'],
|
||||
});
|
||||
};
|
||||
|
||||
switch (label) {
|
||||
case 'Type: Invalid':
|
||||
await addComment(
|
||||
`| :warning: | Issue is Invalid |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | This issue doesn't match any of the expected types for this repository - closing. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Question':
|
||||
await addComment(
|
||||
`| :warning: | Issue is a Question |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | 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/). |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Docs':
|
||||
await addComment(
|
||||
`| :warning: | Documentation Issue |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Resolution: For Stack Overflow':
|
||||
await addComment(
|
||||
`| :warning: | Issue is a Question |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | 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. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Type: Expo':
|
||||
await addComment(
|
||||
`| :warning: | Issue is Related to Expo |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open. |`,
|
||||
);
|
||||
await closeIssue();
|
||||
return;
|
||||
case 'Needs: Issue Template':
|
||||
await addComment(
|
||||
`| :warning: | Missing Required Fields |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?template=bug_report.md). 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. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Needs: Environment Info':
|
||||
await addComment(
|
||||
`| :warning: | Missing Environment Information |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Needs: Verify on Latest Version':
|
||||
await addComment(
|
||||
`| :warning: | Newer Version of React Native is Available! |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | You are on a supported minor version, but it looks like there's a newer patch available. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, 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 most recent releases. |`,
|
||||
);
|
||||
return;
|
||||
case 'Needs: Version Info':
|
||||
await addComment(
|
||||
`| :warning: | Add or Reformat Version Info |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.70.2 |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Needs: Repro':
|
||||
await addComment(
|
||||
`| :warning: | Missing Reproducible Example |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue is missing a reproducible example. Please provide a [Snack](https://snack.expo.dev) or a repository that demonstrates the issue you are reporting in a [minimal, complete, and reproducible](https://stackoverflow.com/help/minimal-reproducible-example) manner. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
case 'Type: Unsupported Version':
|
||||
await addComment(
|
||||
`| :warning: | Unsupported Version of React Native |\n` +
|
||||
`| --- | --- |\n` +
|
||||
`| :information_source: | It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy). Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
|
||||
);
|
||||
await requestAuthorFeedback();
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -4,16 +4,10 @@ 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
|
||||
if: github.repository == 'facebook/react-native'
|
||||
|
||||
steps:
|
||||
- uses: react-native-community/actions-apply-version-label@v0.0.3
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Automatic Rebase
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
rebase:
|
||||
permissions:
|
||||
contents: write # for cirrus-actions/rebase to push code to rebase
|
||||
pull-requests: read # for cirrus-actions/rebase to get info about PR
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Label closed PR as merged and leave a comment
|
||||
on:
|
||||
push
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment-and-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
if(!context.payload.commits || !context.payload.commits.length) return;
|
||||
const sha = context.payload.commits[0].id;
|
||||
|
||||
const {commit, author} = (await github.rest.repos.getCommit({
|
||||
ref: sha,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})).data;
|
||||
|
||||
// Looking at the commit message, checks which PR number, if any, was closed by this commit
|
||||
const getClosedPrIfExists = (commit) => {
|
||||
if(!commit || !commit.message) return;
|
||||
const prClosingRegex = /Closes https:\/\/github.com\/facebook\/react-native\/pull\/([0-9]+)|Pull Request resolved: https:\/\/github.com\/facebook\/react-native\/pull\/([0-9]+)/;
|
||||
const prClosingMatch = commit.message.match(prClosingRegex);
|
||||
if(!prClosingMatch || (!prClosingMatch[1] && ! prClosingMatch[2])) return;
|
||||
return prClosingMatch[1] ?? prClosingMatch[2];
|
||||
};
|
||||
|
||||
const closedPrNumber = getClosedPrIfExists(commit);
|
||||
if(!closedPrNumber) return;
|
||||
|
||||
const pr = (await github.rest.pulls.get({
|
||||
pull_number: closedPrNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})).data;
|
||||
|
||||
// If the PR has already been processed (labeled as Merged), skip it
|
||||
const mergedLabel = "Merged";
|
||||
if(pr.labels && pr.labels.some(label => label.name === mergedLabel)) return;
|
||||
|
||||
const authorName = author?.login ? `@${author.login}` : commit.author.name;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: closedPrNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `This pull request was successfully merged by ${authorName} in **${sha}**.\n\n<sup>[When will my fix make it into a release?](https://github.com/facebook/react-native/wiki/Release-FAQ#when-will-my-fix-make-it-into-a-release) | [Upcoming Releases](https://github.com/reactwg/react-native-releases/discussions/categories/releases)</sup>`
|
||||
});
|
||||
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: closedPrNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [mergedLabel]
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Run Danger on PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
checks: write
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run Yarn Install on Root
|
||||
run: yarn install
|
||||
working-directory: .
|
||||
- name: Danger
|
||||
run: yarn danger ci --use-github-checks --failOnErrors
|
||||
working-directory: packages/react-native-bots
|
||||
env:
|
||||
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -4,19 +4,12 @@ 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
|
||||
if: github.repository == 'facebook/react-native'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: Apply Needs Attention Label
|
||||
uses: hramos/needs-attention@v1
|
||||
with:
|
||||
|
||||
@@ -4,43 +4,13 @@ on:
|
||||
issues:
|
||||
types: labeled
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
# Runs automatic checks on issues labeled with "Needs: Triage",
|
||||
# then invokes actOnLabel to react to any added labels
|
||||
triage-issue:
|
||||
respondToIssueBasedOnLabel:
|
||||
name: Respond to Issue Based on Label
|
||||
runs-on: ubuntu-latest
|
||||
if: "${{ github.repository == 'facebook/react-native' && contains(github.event.label.name, 'Needs: Triage :mag:') }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/checkout@v2
|
||||
- name: Respond to Issue Based on Label
|
||||
uses: hramos/respond-to-issue-based-on-label@v2
|
||||
with:
|
||||
script: |
|
||||
const verifyVersion = require('./.github/workflows/verifyVersion.js')
|
||||
const labelToAdd = await verifyVersion(github, context);
|
||||
|
||||
if(labelToAdd) {
|
||||
await github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [labelToAdd]
|
||||
})
|
||||
|
||||
const actOnLabel = require('./.github/workflows/actOnLabel.js')
|
||||
await actOnLabel(github, context, labelToAdd)
|
||||
}
|
||||
# Reacts to the label that triggered this workflow (added manually or via other workflows)
|
||||
act-on-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const actOnLabel = require('./.github/workflows/actOnLabel.js')
|
||||
await actOnLabel(github, context, context.payload.label.name)
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,77 +1,19 @@
|
||||
name: Mark stale issues and pull requests
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0,6,12,18 * * *"
|
||||
- cron: "30 1 * * *"
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v4
|
||||
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.'
|
||||
days-before-stale: 365
|
||||
stale-issue-message: 'This issue is stale because it has been open 365 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 365 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.'
|
||||
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
stale-asc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
ascending: true
|
||||
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.'
|
||||
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
stale-needs-author-feedback:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
any-of-labels: 'Needs: Author Feedback'
|
||||
days-before-stale: 24
|
||||
stale-issue-message: "This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days."
|
||||
stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days"
|
||||
close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days."
|
||||
close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days."
|
||||
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
stale-needs-author-feedback-asc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'facebook/react-native'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
ascending: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
any-of-labels: 'Needs: Author Feedback'
|
||||
days-before-stale: 24
|
||||
stale-issue-message: "This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days."
|
||||
stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days"
|
||||
close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days."
|
||||
close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days."
|
||||
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Test Docker Android Image
|
||||
# This workflow is triggered on commits to main and pull requests.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [ synchronize ]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test-docker-android:
|
||||
name: Test Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Docker image with Android test app
|
||||
run: npm run docker-build-android
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
module.exports = async (github, context) => {
|
||||
const issue = context.payload.issue;
|
||||
|
||||
// Ignore issues using upgrade template (they use a special label)
|
||||
if (issue.labels.find(label => label.name == 'Type: Upgrade Issue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issueVersionUnparsed =
|
||||
getReactNativeVersionFromIssueBodyIfExists(issue);
|
||||
const issueVersion = parseVersionFromString(issueVersionUnparsed);
|
||||
|
||||
// Nightly versions are always supported
|
||||
if (reportedVersionIsNightly(issueVersionUnparsed, issueVersion)) return;
|
||||
|
||||
if (!issueVersion) {
|
||||
return 'Needs: Version Info';
|
||||
}
|
||||
|
||||
// Ensure the version matches one we support
|
||||
const recentReleases = (
|
||||
await github.rest.repos.listReleases({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
).data.map(release => release.name);
|
||||
|
||||
const latestRelease = (
|
||||
await github.rest.repos.getLatestRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
).data;
|
||||
const latestVersion = parseVersionFromString(latestRelease.name);
|
||||
|
||||
if (!isVersionSupported(issueVersion, latestVersion)) {
|
||||
return 'Type: Unsupported Version';
|
||||
}
|
||||
|
||||
// We want to encourage users to repro the issue on the highest available patch for the given minor.
|
||||
const latestPatchForVersion = getLatestPatchForVersion(
|
||||
issueVersion,
|
||||
recentReleases,
|
||||
);
|
||||
if (latestPatchForVersion > issueVersion.patch) {
|
||||
return 'Needs: Verify on Latest Version';
|
||||
}
|
||||
};
|
||||
|
||||
// We support N-2 minor versions, and the latest major.
|
||||
function isVersionSupported(actualVersion, latestVersion) {
|
||||
return (
|
||||
actualVersion.major >= latestVersion.major &&
|
||||
actualVersion.minor >= latestVersion.minor - 2
|
||||
);
|
||||
}
|
||||
|
||||
// Assumes that releases are sorted in the order of recency (i.e. most recent releases are earlier in the list)
|
||||
// This enables us to stop looking as soon as we find the first release with a matching major/minor version, since
|
||||
// we know it's the most recent release, therefore the highest patch available.
|
||||
function getLatestPatchForVersion(version, releases) {
|
||||
for (releaseName of releases) {
|
||||
const release = parseVersionFromString(releaseName);
|
||||
if (
|
||||
release &&
|
||||
release.major == version.major &&
|
||||
release.minor == version.minor
|
||||
) {
|
||||
return release.patch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getReactNativeVersionFromIssueBodyIfExists(issue) {
|
||||
if (!issue || !issue.body) return;
|
||||
const rnVersionRegex = /React Native Version[\r\n]+(?<version>.+)[\r\n]*/;
|
||||
const rnVersionMatch = issue.body.match(rnVersionRegex);
|
||||
if (!rnVersionMatch || !rnVersionMatch.groups.version) return;
|
||||
return rnVersionMatch.groups.version;
|
||||
}
|
||||
|
||||
function reportedVersionIsNightly(unparsedVersionString, version) {
|
||||
if (!unparsedVersionString && !version) return false;
|
||||
const nightlyRegex = /nightly/i;
|
||||
const nightlyMatch = unparsedVersionString.match(nightlyRegex);
|
||||
const versionIsNightly = nightlyMatch && nightlyMatch[0];
|
||||
|
||||
const versionIsZero =
|
||||
version && version.major == 0 && version.minor == 0 && version.patch == 0;
|
||||
|
||||
return versionIsZero || versionIsNightly;
|
||||
}
|
||||
|
||||
function parseVersionFromString(version) {
|
||||
if (!version) return;
|
||||
// This will match the standard x.x.x semver format, as well as the non-standard prerelease x.x.x-rc.x
|
||||
const semverRegex =
|
||||
/(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-[rR]{1}[cC]{1}\.(?<prerelease>\d+))?/;
|
||||
const versionMatch = version.match(semverRegex);
|
||||
if (!versionMatch) return;
|
||||
const {major, minor, patch, prerelease} = versionMatch.groups;
|
||||
return {
|
||||
major: parseInt(major),
|
||||
minor: parseInt(minor),
|
||||
patch: parseInt(patch),
|
||||
prerelease: parseInt(prerelease),
|
||||
};
|
||||
}
|
||||
+28
-54
@@ -20,36 +20,30 @@ DerivedData
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
**/.xcode.env.local
|
||||
|
||||
# Gradle
|
||||
/build/
|
||||
/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
|
||||
/packages/react-native/ReactAndroid/build/
|
||||
/packages/react-native/ReactAndroid/.cxx/
|
||||
/packages/react-native/ReactAndroid/gradle/
|
||||
/packages/react-native/ReactAndroid/gradlew
|
||||
/packages/react-native/ReactAndroid/gradlew.bat
|
||||
/packages/react-native/ReactAndroid/external-artifacts/build/
|
||||
/packages/react-native/ReactAndroid/external-artifacts/artifacts/
|
||||
/packages/react-native/ReactAndroid/hermes-engine/build/
|
||||
/packages/react-native/ReactAndroid/hermes-engine/.cxx/
|
||||
/packages/react-native/template/android/app/build/
|
||||
/packages/react-native/template/android/build/
|
||||
/ReactAndroid/build/
|
||||
/ReactAndroid/.cxx/
|
||||
/ReactAndroid/gradle/
|
||||
/ReactAndroid/gradlew
|
||||
/ReactAndroid/gradlew.bat
|
||||
/template/android/app/build/
|
||||
/template/android/build/
|
||||
|
||||
# Buck
|
||||
.buckd
|
||||
buck-out
|
||||
/.lsp.buckd
|
||||
/.lsp-buck-out
|
||||
/packages/react-native/ReactAndroid/src/main/jni/prebuilt/lib/
|
||||
/packages/react-native/ReactAndroid/src/main/gen
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/
|
||||
/ReactAndroid/src/main/gen
|
||||
|
||||
# Android Studio
|
||||
.project
|
||||
@@ -64,76 +58,59 @@ buck-out
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
/packages/react-native/android/*
|
||||
!/packages/react-native/android/README.md
|
||||
/android/
|
||||
|
||||
# Node
|
||||
node_modules
|
||||
*.log
|
||||
.nvm
|
||||
/bots/node_modules/
|
||||
package-lock.json
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
# Test generated files
|
||||
/packages/react-native/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
|
||||
*.js.meta
|
||||
|
||||
/coverage
|
||||
/third-party
|
||||
|
||||
# Test Reports
|
||||
/reports
|
||||
|
||||
# Stack Dumps generated when programs crash (Ex. bash.exe.stackdump on Win)
|
||||
*.stackdump
|
||||
|
||||
# Root dir shouldn't have Xcode project
|
||||
/*.xcodeproj
|
||||
|
||||
# ReactCommon subdir shouldn't have Xcode project
|
||||
/packages/react-native/ReactCommon/**/*.xcodeproj
|
||||
/ReactCommon/**/*.xcodeproj
|
||||
/packages/rn-tester/build
|
||||
/packages/rn-tester/android/app/build/*
|
||||
|
||||
# Libs that shouldn't have Xcode project
|
||||
/packages/react-native/Libraries/FBLazyVector/**/*.xcodeproj
|
||||
/packages/react-native/Libraries/RCTRequired/**/*.xcodeproj
|
||||
/packages/react-native/React/CoreModules/**/*.xcodeproj
|
||||
/packages/react-native/React/FBReactNativeSpec/**/*.xcodeproj
|
||||
/Libraries/FBLazyVector/**/*.xcodeproj
|
||||
/Libraries/RCTRequired/**/*.xcodeproj
|
||||
/React/CoreModules/**/*.xcodeproj
|
||||
/React/FBReactNativeSpec/**/*.xcodeproj
|
||||
/packages/react-native-codegen/**/*.xcodeproj
|
||||
|
||||
# Ruby Gems (Bundler)
|
||||
/packages/react-native/vendor
|
||||
/packages/react-native/template/vendor
|
||||
.ruby-version
|
||||
/**/.ruby-version
|
||||
./vendor/
|
||||
vendor/
|
||||
/vendor
|
||||
/template/vendor
|
||||
|
||||
# iOS / CocoaPods
|
||||
/packages/react-native/template/ios/build/
|
||||
/packages/react-native/template/ios/Pods/
|
||||
/packages/react-native/template/ios/Podfile.lock
|
||||
/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_hermes__
|
||||
!/packages/rn-tester/Pods/__offline_mirrors_jsc__
|
||||
!/packages/rn-tester/Pods/__offline_mirrors__
|
||||
|
||||
# @react-native/codegen
|
||||
/packages/react-native/React/FBReactNativeSpec/FBReactNativeSpec
|
||||
# react-native-codegen
|
||||
/React/FBReactNativeSpec/FBReactNativeSpec
|
||||
/packages/react-native-codegen/lib
|
||||
/packages/react-native-codegen/tmp/
|
||||
/packages/react-native/ReactCommon/react/renderer/components/rncore/
|
||||
/ReactCommon/react/renderer/components/rncore/
|
||||
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
|
||||
/**/RCTThirdPartyFabricComponentsProvider.*
|
||||
|
||||
|
||||
# Additional SDKs
|
||||
/packages/react-native/sdks/download
|
||||
/packages/react-native/sdks/hermes
|
||||
/packages/react-native/sdks/hermesc
|
||||
|
||||
# Visual studio
|
||||
.vscode
|
||||
@@ -141,6 +118,3 @@ vendor/
|
||||
|
||||
# Android memory profiler files
|
||||
*.hprof
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
2.7.4
|
||||
-1521
File diff suppressed because it is too large
Load Diff
+110
-4
@@ -1,7 +1,113 @@
|
||||
# Contributing to React Native
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
To learn more about how to contribute check out the Contributing section on the React Native website:
|
||||
* https://reactnative.dev/contributing/overview
|
||||
The [Open Source Guides](https://opensource.guide/) website has a collection of resources for individuals, communities, and companies who want to learn how to run and contribute to an open source project. Contributors and people new to open source alike will find the following guides especially useful:
|
||||
|
||||
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
* [Building Welcoming Communities](https://opensource.guide/building-community/)
|
||||
|
||||
|
||||
### [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md)
|
||||
|
||||
As a reminder, all contributors are expected to adhere to the [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
If you are eager to start contributing code right away, we have a list of [good first issues](https://github.com/facebook/react-native/labels/good%20first%20issue) that contain bugs which have a relatively limited scope. As you gain more experience and demonstrate a commitment to evolving React Native, you may be granted issue management permissions in the repository.
|
||||
|
||||
There are other ways you can contribute without writing a single line of code. Here are a few things you can do to help out:
|
||||
|
||||
1. **Replying and handling open issues.** We get a lot of issues every day, and some of them may lack necessary information. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information, or pointing them to existing issues that match their description of the problem. We cover more about this process in the [Issue Triage wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
|
||||
2. **Reviewing pull requests for the docs.** Reviewing [documentation updates](https://github.com/facebook/react-native-website/pulls) can be as simple as checking for spelling and grammar. If you encounter situations that can be explained better in the docs, click **Edit** at the top of most docs pages to get started with your own contribution.
|
||||
3. **Help people write test plans.** Some pull requests sent to the main repository may lack a proper test plan. These help reviewers understand how the change was tested, and can speed up the time it takes for a contribution to be accepted.
|
||||
|
||||
Each of these tasks is highly impactful, and maintainers will greatly appreciate your help.
|
||||
|
||||
### Our Development Process
|
||||
|
||||
We use GitHub issues and pull requests to keep track of bug reports and contributions from the community. All changes from engineers at Facebook will sync to [GitHub](https://github.com/facebook/react-native) through a bridge with Facebook's internal source control. Changes from the community are handled through GitHub pull requests. Once a change made on GitHub is approved, it will first be imported into Facebook's internal source control and tested against Facebook's codebase. Once merged at Facebook, the change will eventually sync back to GitHub as a single commit once it has passed Facebook's internal tests.
|
||||
|
||||
You can learn more about the contribution process in the following documents:
|
||||
|
||||
* [Issues](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues)
|
||||
* [Pull Requests](https://github.com/facebook/react-native/wiki/Managing-Pull-Requests)
|
||||
|
||||
We also have a thriving community of contributors who would be happy to help you get set up. You can reach out to us through [@ReactNative](http://twitter.com/reactnative) (the React Native team) and [@ReactNativeComm](http://twitter.com/reactnativecomm) (the React Native Community organization).
|
||||
|
||||
### Repositories
|
||||
|
||||
The main repository, <https://github.com/facebook/react-native>, contains the React Native framework itself and it is here where we keep track of bug reports and manage pull requests.
|
||||
|
||||
There are a few other repositories you might want to familiarize yourself with:
|
||||
|
||||
* **React Native website** which contains the source code for the website, including the documentation, located at <https://github.com/facebook/react-native-website>
|
||||
* **Releases** Conversations for new releases are happening [in this discussion repo](https://github.com/reactwg/react-native-releases/discussions).
|
||||
* **Changelog** The changelog can be found [here](https://github.com/facebook/react-native/blob/main/CHANGELOG.md).
|
||||
* **Discussions** about the future of React Native take place in the <https://github.com/react-native-community/discussions-and-proposals> repository.
|
||||
* **High-quality plugins** for React Native can be found throughout the [React Native Community GitHub Organization](http://github.com/react-native-community/).
|
||||
|
||||
Browsing through these repositories should provide some insight into how the React Native open source project is managed.
|
||||
|
||||
## GitHub Issues
|
||||
|
||||
We use GitHub issues to track bugs exclusively. We have documented our issue handling processes in the [Issues wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
|
||||
|
||||
### Security Bugs
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue.
|
||||
|
||||
## Helping with Documentation
|
||||
|
||||
The React Native documentation is hosted as part of the React Native website repository at https://github.com/facebook/react-native-website. The website itself is located at <https://reactnative.dev/> and it is built using [Docusaurus](https://docusaurus.io/). If there's anything you'd like to change in the docs, you can get started by clicking on the "Edit" button located on the upper right of most pages in the website.
|
||||
|
||||
If you are adding new functionality or introducing a change in behavior, we will ask you to update the documentation to reflect your changes.
|
||||
|
||||
### Contributing to the Blog
|
||||
|
||||
The React Native blog is generated [from the Markdown sources for the blog](https://github.com/facebook/react-native-website/tree/HEAD/website/blog).
|
||||
|
||||
Please open an issue in the https://github.com/facebook/react-native-website repository or tag us on [@ReactNative on Twitter](http://twitter.com/reactnative) and get the go-ahead from a maintainer before writing an article intended for the React Native blog. In most cases, you might want to share your article on your own blog or writing medium instead. It's worth asking, though, in case we find your article is a good fit for the blog.
|
||||
|
||||
We recommend referring to the [CONTRIBUTING](https://github.com/facebook/react-native-website/blob/HEAD/CONTRIBUTING.md) document for the `react-native-website` repository to learn more about contributing to the website in general.
|
||||
|
||||
## Contributing Code
|
||||
|
||||
Code-level contributions to React Native generally come in the form of [pull requests](https://help.github.com/en/articles/about-pull-requests). These are done by forking the repo and making changes locally. Directly in the repo, there is the [`rn-tester` app](/packages/rn-tester) that you can install on your device (or simulators) and use to test the changes you're making to React Native sources.
|
||||
|
||||
The process of proposing a change to React Native can be summarized as follows:
|
||||
|
||||
1. Fork the React Native repository and create your branch from `main`.
|
||||
2. Make the desired changes to React Native sources. Use the `packages/rn-tester` app to test them out.
|
||||
3. If you've added code that should be tested, add tests.
|
||||
4. If you've changed APIs, update the documentation, which lives in [another repo](https://github.com/facebook/react-native-website/).
|
||||
5. Ensure the test suite passes, either locally or on CI once you opened a pull request.
|
||||
6. Make sure your code lints (for example via `yarn lint --fix`).
|
||||
7. Push the changes to your fork.
|
||||
8. Create a pull request to the React Native repository.
|
||||
9. Review and address comments on your pull request.
|
||||
1. A bot may comment with suggestions. Generally we ask you to resolve these first before a maintainer will review your code.
|
||||
2. If changes are requested and addressed, please [request review](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to notify reviewers to take another look.
|
||||
10. If you haven't already, please complete the [Contributor License Agreement](https://github.com/facebook/react-native/wiki/Contributor-License-Agreement) ("CLA"). **[Complete your CLA here.](https://code.facebook.com/cla)**
|
||||
|
||||
If all goes well, your pull request will be merged. If it is not merged, maintainers will do their best to explain the reason why.
|
||||
|
||||
### Step-by-step Guide
|
||||
|
||||
Whenever you are ready to contribute code, check out our [step-by-step guide to sending your first pull request](https://github.com/facebook/react-native/wiki/How-to-Open-a-Pull-Request), or read the [How to Contribute Code](https://github.com/facebook/react-native/wiki/How-to-Contribute-Code) wiki for more details.
|
||||
|
||||
### Tests
|
||||
|
||||
Tests help us prevent regressions from being introduced to the codebase. The GitHub repository is continuously tested using Circle and Appveyor, the results of which are available through the Checks functionality on [commits](https://github.com/facebook/react-native/commits/HEAD) and pull requests. You can learn more about running and writing tests in the [Tests wiki](http://github.com/facebook/react-native/wiki/Tests).
|
||||
|
||||
## Community Contributions
|
||||
|
||||
Contributions to React Native are not limited to GitHub. You can help others by sharing your experience using React Native, whether that is through blog posts, presenting talks at conferences, or simply sharing your thoughts on Twitter and tagging @ReactNative.
|
||||
|
||||
## Where to Get Help
|
||||
|
||||
As you work on React Native, it is natural that sooner or later you may require help. In addition to the resources listed in [SUPPORT](.github/SUPPORT.md), people interested in contributing may take advantage of the following:
|
||||
|
||||
* **Twitter**. The React Native team at Facebook has its own account at [@reactnative](https://twitter.com/reactnative), and the React Native Community uses [@reactnativecomm](https://twitter.com/reactnativecomm). If you feel stuck, or need help contributing, please do not hesitate to reach out.
|
||||
* **Proposals Repository**. If you are considering working on a feature large in scope, consider [creating a proposal first](https://github.com/react-native-community/discussions-and-proposals). The community can help you figure out the right approach, and we'd be happy to help.
|
||||
* **React Native Community Discord**. While we try to hold most discussions in public, sometimes it can be beneficial to have conversations in real time with other contributors. People who have demonstrated a commitment to moving React Native forward through sustained contributions to the project may eventually be invited to join the React Native Community Discord.
|
||||
|
||||
+14
-29
@@ -1,36 +1,27 @@
|
||||
# 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 had not been written down before.
|
||||
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 significantly invested in React Native and take responsibility for the React Native vision and community.
|
||||
* **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 demonstrate ownership. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Examples of this may include large scale contributions to `react-native` or owning essential tools or libraries.
|
||||
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.
|
||||
|
||||
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, Facebook, Infinite Red, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include:
|
||||
|
||||
Baseline responsibilities of partners include:
|
||||
* Attending monthly meeting
|
||||
* Contributing to the release process. Examples include being a [community releaser](https://reactnative.dev/contributing/release-roles-responsibilities#release-role-2-community-releaser), testing new releases, technical support for release issues.
|
||||
* Engage on the core contributor Discord
|
||||
* **[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.io/):** Builds [expo](https://github.com/expo/expo) on top of React Native to simplify app development
|
||||
* **[Facebook](https://opensource.facebook.com):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/)
|
||||
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/)
|
||||
* **[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.
|
||||
|
||||
Our current partners and other areas of ownership:
|
||||
* **[Coinbase](https://www.coinbase.com/):** Publishes [posts](https://blog.coinbase.com/tagged/react-native) advocating React Native usage. Supports `@react-native-community/datetimepicker` and other community modules to migrate to the new architecture. Supports releases in testing and feedback.
|
||||
* **[Callstack](https://callstack.com/):** Maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and [other community libraries](https://github.com/callstack), organizes [React Native EU](https://react-native.eu/) and hosts [The React Native Show podcast](https://www.callstack.com/podcast-react-native-show)
|
||||
* **[Expo](https://expo.dev/):** Builds [Expo Go and SDK](https://github.com/expo/expo), [Snack](https://snack.expo.dev/), and [Expo Application Services](https://expo.dev/eas). Maintains [React Native Directory](https://reactnative.directory/), stewards [React Navigation](https://reactnavigation.org/) along with other partners.
|
||||
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/), hosts the [React Native Radio podcast](https://reactnativeradio.com), publishes the [React Native Newsletter](https://reactnativenewsletter.com)
|
||||
* **[Meta](https://opensource.fb.com/):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/)
|
||||
* **[Microsoft](https://twitter.com/ReactNativeMSFT):** 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; maintains [rnx-kit](https://github.com/microsoft/rnx-kit), [react-native-test-app](https://github.com/microsoft/react-native-test-app) and coordinates cross-companies efforts such as the [bundle working group](https://github.com/microsoft/rnx-kit/discussions/categories/bundle-working-group).
|
||||
* **[Shopify](https://www.shopify.com/):** Maintains React Native open source libraries such as [flash-list](https://github.com/Shopify/flash-list) or [@shopify/react-native-skia](https://github.com/Shopify/react-native-skia) and sponsors Software Mansion.
|
||||
* **[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/)
|
||||
* **[Wix.com](https://wix.engineering/open-source):** Maintains a variety of React Native open source projects ([see all](https://github.com/orgs/wix/repositories?q=react-native)), including: [Detox](https://wix.github.io/Detox/) end-to-end testing library for React Native apps, [RN UILib](https://wix.github.io/react-native-ui-lib/), [RN Navigation](https://wix.github.io/react-native-navigation/), [RN Calendars](https://wix.github.io/react-native-calendars/) and [RN Notifications](https://github.com/wix/react-native-notifications).
|
||||
|
||||
This list may fluctuate in response to newcomers who meet our partner definition. 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:
|
||||
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
|
||||
|
||||
@@ -47,19 +38,13 @@ Core contributors are individuals who contribute to the React Native project. A
|
||||
|
||||
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 (~6 months or more), a core contributor may be contacted to understand if they’re still interested in being part of the program.
|
||||
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.
|
||||
|
||||
You can use this [form](https://forms.gle/4jpA4QeNUvAUDnNe8) to either:
|
||||
* Apply yourself to become a Core Contributor. Make sure to include a list of valuable contributions you did to the React Native repository and ecosystem.
|
||||
* Nominate someone to become a Core Contributor.
|
||||
|
||||
As a core contributor, you will have access to the core contributor Discord which is used for light-weight coordination and discussion.
|
||||
|
||||
**We aim to make contributing to React Native as easy and transparent as possible.** We have discussion groups dedicated to the [new architecture rollout](https://github.com/reactwg/react-native-new-architecture), [releases](https://github.com/reactwg/react-native-releases), and [general questions and proposals](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.
|
||||
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.
|
||||
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. Facebook engineers will provide guidance to help build a vibrant community of people and components that make React Native better.
|
||||
|
||||
This structure has multiple benefits:
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby ">= 2.6.10"
|
||||
ruby '2.7.4'
|
||||
|
||||
gem 'cocoapods', '~> 1.12'
|
||||
gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
|
||||
gem 'cocoapods', '~> 1.11', '>= 1.11.2'
|
||||
|
||||
+24
-23
@@ -3,27 +3,28 @@ GEM
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (7.0.8)
|
||||
activesupport (6.1.7.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
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.14.3)
|
||||
cocoapods (1.11.3)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.14.3)
|
||||
cocoapods-core (= 1.11.3)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 2.1, < 3.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.6.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
@@ -31,10 +32,10 @@ GEM
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.23.0, < 2.0)
|
||||
cocoapods-core (1.14.3)
|
||||
activesupport (>= 5.0, < 8)
|
||||
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)
|
||||
@@ -44,7 +45,7 @@ GEM
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (2.1)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
@@ -53,47 +54,47 @@ GEM
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
concurrent-ruby (1.2.0)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
ffi (1.16.3)
|
||||
ffi (1.15.5)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.7.1)
|
||||
minitest (5.21.2)
|
||||
json (2.6.3)
|
||||
minitest (5.17.0)
|
||||
molinillo (0.8.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
netrc (0.11.0)
|
||||
public_suffix (4.0.7)
|
||||
rexml (3.2.6)
|
||||
rexml (3.2.5)
|
||||
ruby-macho (2.5.1)
|
||||
typhoeus (1.4.1)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
xcodeproj (1.23.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.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (>= 6.1.7.3, < 7.1.0)
|
||||
cocoapods (~> 1.12)
|
||||
cocoapods (~> 1.11, >= 1.11.2)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.0.6p216
|
||||
ruby 2.7.4p191
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.13
|
||||
2.2.27
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as React from 'react';
|
||||
const {TestModule} = NativeModules;
|
||||
|
||||
class AccessibilityManagerTest extends React.Component<{...}> {
|
||||
componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
invariant(
|
||||
NativeAccessibilityManager,
|
||||
"NativeAccessibilityManager doesn't exist",
|
||||
|
||||
@@ -38,6 +38,7 @@ class AppEventsTest extends React.Component<{...}, State> {
|
||||
NativeAppEventEmitter.addListener('testEvent', this.receiveEvent);
|
||||
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});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
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
|
||||
let done = (result: ?boolean) => {};
|
||||
let updateMessage = (message: string) => {};
|
||||
|
||||
function runTestCase(description: string, fn) {
|
||||
updateMessage(description);
|
||||
fn();
|
||||
}
|
||||
|
||||
function expectTrue(condition: boolean, message: string) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Type-safe wrapper around JSON.stringify
|
||||
function stringify(
|
||||
value:
|
||||
| void
|
||||
| null
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| {...}
|
||||
| $ReadOnlyArray<mixed>,
|
||||
): string {
|
||||
if (typeof value === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function expectEqual(lhs, rhs, testname: string) {
|
||||
expectTrue(
|
||||
!deepDiffer(lhs, rhs),
|
||||
'Error in test ' +
|
||||
testname +
|
||||
': expected\n' +
|
||||
stringify(rhs) +
|
||||
'\ngot\n' +
|
||||
stringify(lhs),
|
||||
);
|
||||
}
|
||||
|
||||
function expectAsyncNoError(place, err) {
|
||||
if (err instanceof Error) {
|
||||
err = err.message;
|
||||
}
|
||||
expectTrue(
|
||||
err === null,
|
||||
'Unexpected error in ' + place + ': ' + stringify(err),
|
||||
);
|
||||
}
|
||||
|
||||
function testSetAndGet() {
|
||||
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 ' + String(result));
|
||||
runTestCase('should get null for missing key', testMissingGet);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testMissingGet() {
|
||||
AsyncStorage.getItem(KEY_2, (err, result) => {
|
||||
expectAsyncNoError('testMissingGet/setItem', err);
|
||||
expectEqual(result, null, 'testMissingGet');
|
||||
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.getItem(KEY_1, (err, result) => {
|
||||
expectAsyncNoError('testSetTwice/setItem', err);
|
||||
expectEqual(result, VAL_1, 'testSetTwice');
|
||||
updateMessage('setTwice worked as expected');
|
||||
runTestCase('test removeItem', testRemoveItem);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testRemoveItem() {
|
||||
AsyncStorage.setItem(KEY_1, VAL_1, () => {
|
||||
AsyncStorage.setItem(KEY_2, VAL_2, () => {
|
||||
AsyncStorage.getAllKeys((err, result) => {
|
||||
expectAsyncNoError('testRemoveItem/getAllKeys', err);
|
||||
expectTrue(
|
||||
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 => {
|
||||
expectAsyncNoError('testRemoveItem/removeItem', err2);
|
||||
updateMessage('delete successful ');
|
||||
AsyncStorage.getItem(KEY_1, (err3, result2) => {
|
||||
expectAsyncNoError('testRemoveItem/getItem', err3);
|
||||
expectEqual(
|
||||
result2,
|
||||
null,
|
||||
'testRemoveItem: key_1 present after delete',
|
||||
);
|
||||
updateMessage('key properly removed ');
|
||||
AsyncStorage.getAllKeys((err4, result3) => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testMerge() {
|
||||
AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => {
|
||||
expectAsyncNoError('testMerge/setItem', err1);
|
||||
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(nullthrows(result)),
|
||||
VAL_MERGE_EXPECT,
|
||||
'testMerge',
|
||||
);
|
||||
updateMessage('objects deeply merged\nDone!');
|
||||
runTestCase('multi set and get', testOptimizedMultiGet);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testOptimizedMultiGet() {
|
||||
let batch = [
|
||||
[KEY_1, VAL_1],
|
||||
[KEY_2, VAL_2],
|
||||
];
|
||||
let keys = batch.map(([key, value]) => key);
|
||||
AsyncStorage.multiSet(batch, err1 => {
|
||||
// yes, twice on purpose
|
||||
[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 ' + stringify(result),
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 => {
|
||||
this.setState({messages: this.state.messages.concat('\n' + msg)});
|
||||
DEBUG && console.log(msg);
|
||||
};
|
||||
AsyncStorage.clear(testSetAndGet);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>
|
||||
{
|
||||
/* $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}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
padding: 40,
|
||||
},
|
||||
});
|
||||
|
||||
AsyncStorageTest.displayName = 'AsyncStorageTest';
|
||||
|
||||
module.exports = AsyncStorageTest;
|
||||
@@ -1,4 +1,4 @@
|
||||
load("@fbsource//tools/build_defs:js_library_glob.bzl", "js_library_glob")
|
||||
load("@fbsource//tools/build_defs:js_glob.bzl", "js_glob")
|
||||
load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library")
|
||||
|
||||
# This file was generated by running
|
||||
@@ -6,7 +6,7 @@ load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library")
|
||||
|
||||
rn_library(
|
||||
name = "IntegrationTests",
|
||||
srcs = js_library_glob(
|
||||
srcs = js_glob(
|
||||
[
|
||||
"**/*",
|
||||
],
|
||||
@@ -17,9 +17,7 @@ rn_library(
|
||||
"websocket_integration_test_server.js",
|
||||
],
|
||||
),
|
||||
labels = [
|
||||
"pfh:ReactNative_CommonInfrastructurePlaceholder",
|
||||
],
|
||||
labels = ["supermodule:xplat/default/public.react_native.tests"],
|
||||
skip_processors = True,
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
|
||||
@@ -20,7 +20,7 @@ const {View} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
if (typeof global.globalEvalWithSourceUrl !== 'function') {
|
||||
throw new Error(
|
||||
'Expected to find globalEvalWithSourceUrl function on global object but found ' +
|
||||
|
||||
@@ -16,7 +16,7 @@ const {Image} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class ImageSnapshotTest extends React.Component<{...}> {
|
||||
componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ class IntegrationTestHarnessTest extends React.Component<Props, State> {
|
||||
* native_oss) This comment suppresses an error found when Flow v0.54
|
||||
* was deployed. To see the error delete this comment and run Flow.
|
||||
*/
|
||||
// $FlowFixMe[unsafe-addition]
|
||||
this.constructor.displayName + ': '
|
||||
}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
|
||||
@@ -20,6 +20,7 @@ const {AppRegistry, ScrollView, StyleSheet, Text, TouchableOpacity, View} =
|
||||
const TESTS = [
|
||||
require('./IntegrationTestHarnessTest'),
|
||||
require('./TimersTest'),
|
||||
require('./AsyncStorageTest'),
|
||||
require('./LayoutEventsTest'),
|
||||
require('./AppEventsTest'),
|
||||
require('./SimpleSnapshotTest'),
|
||||
@@ -45,11 +46,11 @@ require('./LoggingTestModule');
|
||||
type Test = any;
|
||||
|
||||
class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
state: {test: ?Test} = {
|
||||
state = {
|
||||
test: (null: ?Test),
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
if (this.state.test) {
|
||||
return (
|
||||
<ScrollView>
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer');
|
||||
|
||||
function debug(...args: Array<void | Layout | string>) {
|
||||
function debug(...args) {
|
||||
// console.log.apply(null, arguments);
|
||||
}
|
||||
|
||||
@@ -119,19 +119,16 @@ class LayoutEventsTest extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
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: (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: (e: LayoutEvent) => void = (e: LayoutEvent) => {
|
||||
// $FlowFixMe[incompatible-call]
|
||||
debug('received image layout event\n', e.nativeEvent);
|
||||
this.setState({imageLayout: e.nativeEvent.layout}, this.checkLayout);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ const {StyleSheet, View} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class SimpleSnapshotTest extends React.Component<{...}> {
|
||||
componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const {View} = ReactNative;
|
||||
const {TestModule, RNTesterTestModule} = ReactNative.NativeModules;
|
||||
|
||||
class SyncMethodTest extends React.Component<{...}> {
|
||||
componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
if (
|
||||
RNTesterTestModule.echoString('test string value') !== 'test string value'
|
||||
) {
|
||||
|
||||
@@ -39,7 +39,7 @@ class TimersTest extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
setTimeout(fn: () => void, time: number): TimeoutID {
|
||||
const id: TimeoutID = setTimeout(() => {
|
||||
const id = setTimeout(() => {
|
||||
this._timeoutIDs.delete(id);
|
||||
fn();
|
||||
}, time);
|
||||
@@ -70,7 +70,7 @@ class TimersTest extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
setImmediate(fn: () => void): ImmediateID {
|
||||
const id: any = setImmediate(() => {
|
||||
const id = setImmediate(() => {
|
||||
this._immediateIDs.delete(id);
|
||||
fn();
|
||||
});
|
||||
@@ -81,7 +81,7 @@ class TimersTest extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
requestAnimationFrame(fn: () => void): AnimationFrameID {
|
||||
const id: AnimationFrameID = requestAnimationFrame(() => {
|
||||
const id = requestAnimationFrame(() => {
|
||||
this._animationFrameIDs.delete(id);
|
||||
fn();
|
||||
});
|
||||
@@ -253,7 +253,7 @@ class TimersTest extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_incrementInterval(): void {
|
||||
_incrementInterval() {
|
||||
if (this.state.count > 3) {
|
||||
throw new Error('interval incremented past end.');
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ class WebSocketTest extends React.Component<{...}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
_socketIsConnected = (): boolean => {
|
||||
_socketIsConnected = () => {
|
||||
return this.state.socketState === 1; //'OPEN'
|
||||
};
|
||||
|
||||
_socketIsDisconnected = (): boolean => {
|
||||
_socketIsDisconnected = () => {
|
||||
return this.state.socketState === 3; //'CLOSED'
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@ class WebSocketTest extends React.Component<{...}, State> {
|
||||
this._sendText(this.state.testMessage);
|
||||
};
|
||||
|
||||
_receivedTestExpectedResponse = (): boolean => {
|
||||
_receivedTestExpectedResponse = () => {
|
||||
return this.state.lastMessage === this.state.testExpectedResponse;
|
||||
};
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/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 terminal title
|
||||
echo -en "\033]0;Web Socket Test Server\a"
|
||||
clear
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
pushd "$THIS_DIR"
|
||||
./websocket_integration_test_server.js
|
||||
popd
|
||||
|
||||
echo "Process terminated. Press <enter> to close the window"
|
||||
read
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/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."
|
||||
+3
-11
@@ -8,13 +8,12 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {ProcessedColorValue} from '../StyleSheet/processColor';
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheet';
|
||||
|
||||
import RCTActionSheetManager from './NativeActionSheetManager';
|
||||
|
||||
const processColor = require('../StyleSheet/processColor').default;
|
||||
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.
|
||||
@@ -144,13 +143,6 @@ const ActionSheetIOS = {
|
||||
successCallback,
|
||||
);
|
||||
},
|
||||
|
||||
dismissActionSheet: () => {
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
|
||||
if (typeof RCTActionSheetManager.dismissActionSheet === 'function') {
|
||||
RCTActionSheetManager.dismissActionSheet();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ActionSheetIOS;
|
||||
-2
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
@@ -48,7 +47,6 @@ export interface Spec extends TurboModule {
|
||||
|}) => void,
|
||||
successCallback: (completed: boolean, activityType: ?string) => void,
|
||||
) => void;
|
||||
+dismissActionSheet?: () => void;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>('ActionSheetManager'): ?Spec);
|
||||
+2
-2
@@ -23,8 +23,8 @@ Pod::Spec.new do |s|
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.documentation_url = "https://reactnative.dev/docs/actionsheetios"
|
||||
s.license = package["license"]
|
||||
s.author = "Meta Platforms, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "12.4" }
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "11.0" }
|
||||
s.source = source
|
||||
s.source_files = "*.{m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
@@ -8,9 +8,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
|
||||
import Platform from '../Utilities/Platform';
|
||||
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
import RCTAlertManager from './RCTAlertManager';
|
||||
|
||||
export type AlertType =
|
||||
@@ -22,14 +21,12 @@ export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
|
||||
export type Buttons = Array<{
|
||||
text?: string,
|
||||
onPress?: ?Function,
|
||||
isPreferred?: boolean,
|
||||
style?: AlertButtonStyle,
|
||||
...
|
||||
}>;
|
||||
|
||||
type Options = {
|
||||
cancelable?: ?boolean,
|
||||
userInterfaceStyle?: 'unspecified' | 'light' | 'dark',
|
||||
onDismiss?: ?() => void,
|
||||
...
|
||||
};
|
||||
@@ -47,15 +44,7 @@ class Alert {
|
||||
options?: Options,
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
Alert.prompt(
|
||||
title,
|
||||
message,
|
||||
buttons,
|
||||
'default',
|
||||
undefined,
|
||||
undefined,
|
||||
options,
|
||||
);
|
||||
Alert.prompt(title, message, buttons, 'default');
|
||||
} else if (Platform.OS === 'android') {
|
||||
const NativeDialogManagerAndroid =
|
||||
require('../NativeModules/specs/NativeDialogManagerAndroid').default;
|
||||
@@ -93,8 +82,6 @@ class Alert {
|
||||
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) {
|
||||
@@ -108,7 +95,7 @@ class Alert {
|
||||
options && options.onDismiss && options.onDismiss();
|
||||
}
|
||||
};
|
||||
const onError = (errorMessage: string) => console.warn(errorMessage);
|
||||
const onError = errorMessage => console.warn(errorMessage);
|
||||
NativeDialogManagerAndroid.showAlert(config, onError, onAction);
|
||||
}
|
||||
}
|
||||
@@ -120,14 +107,12 @@ class Alert {
|
||||
type?: ?AlertType = 'plain-text',
|
||||
defaultValue?: string,
|
||||
keyboardType?: string,
|
||||
options?: Options,
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
let callbacks: Array<?any> = [];
|
||||
let callbacks = [];
|
||||
const buttons = [];
|
||||
let cancelButtonKey;
|
||||
let destructiveButtonKey;
|
||||
let preferredButtonKey;
|
||||
if (typeof callbackOrButtons === 'function') {
|
||||
callbacks = [callbackOrButtons];
|
||||
} else if (Array.isArray(callbackOrButtons)) {
|
||||
@@ -138,11 +123,8 @@ class Alert {
|
||||
} else if (btn.style === 'destructive') {
|
||||
destructiveButtonKey = String(index);
|
||||
}
|
||||
if (btn.isPreferred) {
|
||||
preferredButtonKey = String(index);
|
||||
}
|
||||
if (btn.text || index < (callbackOrButtons || []).length - 1) {
|
||||
const btnDef: {[number]: string} = {};
|
||||
const btnDef = {};
|
||||
btnDef[index] = btn.text || '';
|
||||
buttons.push(btnDef);
|
||||
}
|
||||
@@ -158,9 +140,7 @@ class Alert {
|
||||
defaultValue,
|
||||
cancelButtonKey,
|
||||
destructiveButtonKey,
|
||||
preferredButtonKey,
|
||||
keyboardType,
|
||||
userInterfaceStyle: options?.userInterfaceStyle || undefined,
|
||||
},
|
||||
(id, value) => {
|
||||
const cb = callbacks[id];
|
||||
-3
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
export type Args = {|
|
||||
@@ -20,9 +19,7 @@ export type Args = {|
|
||||
defaultValue?: string,
|
||||
cancelButtonKey?: string,
|
||||
destructiveButtonKey?: string,
|
||||
preferredButtonKey?: string,
|
||||
keyboardType?: string,
|
||||
userInterfaceStyle?: string,
|
||||
|};
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+1
-2
@@ -8,9 +8,8 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
import type {Args} from './NativeAlertManager';
|
||||
|
||||
import NativeAlertManager from './NativeAlertManager';
|
||||
import type {Args} from './NativeAlertManager';
|
||||
|
||||
module.exports = {
|
||||
alertWithArgs(
|
||||
@@ -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';
|
||||
|
||||
const AnimatedMock = require('./AnimatedMock');
|
||||
const AnimatedImplementation = require('./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,
|
||||
};
|
||||
+21
-19
@@ -10,13 +10,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {PlatformConfig} from './AnimatedPlatformConfig';
|
||||
const AnimatedValue = require('./nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('./nodes/AnimatedValueXY');
|
||||
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
|
||||
const ReactNative = require('../Renderer/shims/ReactNative');
|
||||
|
||||
import {findNodeHandle} from '../ReactNative/RendererProxy';
|
||||
import NativeAnimatedHelper from './NativeAnimatedHelper';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import AnimatedValueXY from './nodes/AnimatedValueXY';
|
||||
import invariant from 'invariant';
|
||||
const invariant = require('invariant');
|
||||
|
||||
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');
|
||||
|
||||
import type {PlatformConfig} from './AnimatedPlatformConfig';
|
||||
|
||||
export type Mapping =
|
||||
| {[key: string]: Mapping, ...}
|
||||
@@ -28,7 +31,7 @@ export type EventConfig = {
|
||||
platformConfig?: PlatformConfig,
|
||||
};
|
||||
|
||||
export function attachNativeEvent(
|
||||
function attachNativeEvent(
|
||||
viewRef: any,
|
||||
eventName: string,
|
||||
argMapping: $ReadOnlyArray<?Mapping>,
|
||||
@@ -38,7 +41,7 @@ export function attachNativeEvent(
|
||||
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
|
||||
const eventMappings = [];
|
||||
|
||||
const traverse = (value: mixed, path: Array<string>) => {
|
||||
const traverse = (value, path) => {
|
||||
if (value instanceof AnimatedValue) {
|
||||
value.__makeNative(platformConfig);
|
||||
|
||||
@@ -64,7 +67,7 @@ export function attachNativeEvent(
|
||||
// Assume that the event containing `nativeEvent` is always the first argument.
|
||||
traverse(argMapping[0].nativeEvent, []);
|
||||
|
||||
const viewTag = findNodeHandle(viewRef);
|
||||
const viewTag = ReactNative.findNodeHandle(viewRef);
|
||||
if (viewTag != null) {
|
||||
eventMappings.forEach(mapping => {
|
||||
NativeAnimatedHelper.API.addAnimatedEventToView(
|
||||
@@ -91,8 +94,8 @@ export function attachNativeEvent(
|
||||
};
|
||||
}
|
||||
|
||||
function validateMapping(argMapping: $ReadOnlyArray<?Mapping>, args: any) {
|
||||
const validate = (recMapping: ?Mapping, recEvt: any, key: string) => {
|
||||
function validateMapping(argMapping, args) {
|
||||
const validate = (recMapping, recEvt, key) => {
|
||||
if (recMapping instanceof AnimatedValue) {
|
||||
invariant(
|
||||
typeof recEvt === 'number',
|
||||
@@ -143,7 +146,7 @@ function validateMapping(argMapping: $ReadOnlyArray<?Mapping>, args: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export class AnimatedEvent {
|
||||
class AnimatedEvent {
|
||||
_argMapping: $ReadOnlyArray<?Mapping>;
|
||||
_listeners: Array<Function> = [];
|
||||
_attachedEvent: ?{detach: () => void, ...};
|
||||
@@ -162,7 +165,7 @@ export class AnimatedEvent {
|
||||
this.__addListener(config.listener);
|
||||
}
|
||||
this._attachedEvent = null;
|
||||
this.__isNative = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
this.__isNative = shouldUseNativeDriver(config);
|
||||
this.__platformConfig = config.platformConfig;
|
||||
}
|
||||
|
||||
@@ -174,7 +177,7 @@ export class AnimatedEvent {
|
||||
this._listeners = this._listeners.filter(listener => listener !== callback);
|
||||
}
|
||||
|
||||
__attach(viewRef: any, eventName: string): void {
|
||||
__attach(viewRef: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be attached.',
|
||||
@@ -188,7 +191,7 @@ export class AnimatedEvent {
|
||||
);
|
||||
}
|
||||
|
||||
__detach(viewTag: any, eventName: string): void {
|
||||
__detach(viewTag: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be detached.',
|
||||
@@ -220,10 +223,7 @@ export class AnimatedEvent {
|
||||
validatedMapping = true;
|
||||
}
|
||||
|
||||
const traverse = (
|
||||
recMapping: ?(Mapping | AnimatedValue),
|
||||
recEvt: any,
|
||||
) => {
|
||||
const traverse = (recMapping, recEvt) => {
|
||||
if (recMapping instanceof AnimatedValue) {
|
||||
if (typeof recEvt === 'number') {
|
||||
recMapping.setValue(recEvt);
|
||||
@@ -254,3 +254,5 @@ export class AnimatedEvent {
|
||||
this._listeners.forEach(listener => listener(...args));
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {AnimatedEvent, attachNativeEvent};
|
||||
+27
-39
@@ -10,33 +10,35 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {EventConfig, Mapping} from './AnimatedEvent';
|
||||
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 {TimingAnimationConfig} from './animations/TimingAnimation';
|
||||
import type {Mapping, EventConfig} from './AnimatedEvent';
|
||||
|
||||
import {AnimatedEvent, attachNativeEvent} from './AnimatedEvent';
|
||||
import DecayAnimation from './animations/DecayAnimation';
|
||||
import SpringAnimation from './animations/SpringAnimation';
|
||||
import TimingAnimation from './animations/TimingAnimation';
|
||||
import createAnimatedComponent from './createAnimatedComponent';
|
||||
import AnimatedAddition from './nodes/AnimatedAddition';
|
||||
import AnimatedColor from './nodes/AnimatedColor';
|
||||
import AnimatedDiffClamp from './nodes/AnimatedDiffClamp';
|
||||
import AnimatedDivision from './nodes/AnimatedDivision';
|
||||
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
|
||||
import AnimatedModulo from './nodes/AnimatedModulo';
|
||||
import AnimatedMultiplication from './nodes/AnimatedMultiplication';
|
||||
import AnimatedNode from './nodes/AnimatedNode';
|
||||
import AnimatedSubtraction from './nodes/AnimatedSubtraction';
|
||||
import AnimatedTracking from './nodes/AnimatedTracking';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import AnimatedValueXY from './nodes/AnimatedValueXY';
|
||||
|
||||
export type CompositeAnimation = {
|
||||
start: (callback?: ?EndCallback) => void,
|
||||
@@ -89,10 +91,10 @@ const diffClamp = function (
|
||||
|
||||
const _combineCallbacks = function (
|
||||
callback: ?EndCallback,
|
||||
config: $ReadOnly<{...AnimationConfig, ...}>,
|
||||
config: {...AnimationConfig, ...},
|
||||
) {
|
||||
if (callback && config.onComplete) {
|
||||
return (...args: Array<EndResult>) => {
|
||||
return (...args) => {
|
||||
config.onComplete && config.onComplete(...args);
|
||||
callback && callback(...args);
|
||||
};
|
||||
@@ -306,7 +308,7 @@ const sequence = function (
|
||||
let current = 0;
|
||||
return {
|
||||
start: function (callback?: ?EndCallback) {
|
||||
const onComplete = function (result: EndResult) {
|
||||
const onComplete = function (result) {
|
||||
if (!result.finished) {
|
||||
callback && callback(result);
|
||||
return;
|
||||
@@ -367,7 +369,7 @@ const parallel = function (
|
||||
): CompositeAnimation {
|
||||
let doneCount = 0;
|
||||
// Make sure we only call stop() at most once for each animation
|
||||
const hasEnded: {[number]: boolean} = {};
|
||||
const hasEnded = {};
|
||||
const stopTogether = !(config && config.stopTogether === false);
|
||||
|
||||
const result = {
|
||||
@@ -378,7 +380,7 @@ const parallel = function (
|
||||
}
|
||||
|
||||
animations.forEach((animation, idx) => {
|
||||
const cb = function (endResult: EndResult | {finished: boolean}) {
|
||||
const cb = function (endResult) {
|
||||
hasEnded[idx] = true;
|
||||
doneCount++;
|
||||
if (doneCount === animations.length) {
|
||||
@@ -415,7 +417,7 @@ const parallel = function (
|
||||
});
|
||||
},
|
||||
|
||||
_startNativeLoop: function (): empty {
|
||||
_startNativeLoop: function () {
|
||||
throw new Error(
|
||||
'Loops run using the native driver cannot contain Animated.parallel animations',
|
||||
);
|
||||
@@ -458,7 +460,6 @@ type LoopAnimationConfig = {
|
||||
|
||||
const loop = function (
|
||||
animation: CompositeAnimation,
|
||||
// $FlowFixMe[prop-missing]
|
||||
{iterations = -1, resetBeforeIteration = true}: LoopAnimationConfig = {},
|
||||
): CompositeAnimation {
|
||||
let isFinished = false;
|
||||
@@ -550,19 +551,6 @@ const event = function (
|
||||
}
|
||||
};
|
||||
|
||||
// All types of animated nodes that represent scalar numbers and can be interpolated (etc)
|
||||
type AnimatedNumeric =
|
||||
| AnimatedAddition
|
||||
| AnimatedDiffClamp
|
||||
| AnimatedDivision
|
||||
| AnimatedInterpolation<number>
|
||||
| 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
|
||||
@@ -573,7 +561,7 @@ export type {AnimatedNumeric as Numeric};
|
||||
*
|
||||
* See https://reactnative.dev/docs/animated
|
||||
*/
|
||||
export default {
|
||||
module.exports = {
|
||||
/**
|
||||
* Standard value class for driving animations. Typically initialized with
|
||||
* `new Animated.Value(0);`
|
||||
+12
-15
@@ -10,21 +10,21 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Numeric as AnimatedNumeric} from './AnimatedImplementation';
|
||||
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 {TimingAnimationConfig} from './animations/TimingAnimation';
|
||||
|
||||
import {AnimatedEvent, attachNativeEvent} from './AnimatedEvent';
|
||||
import AnimatedImplementation from './AnimatedImplementation';
|
||||
import createAnimatedComponent from './createAnimatedComponent';
|
||||
import AnimatedColor from './nodes/AnimatedColor';
|
||||
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
|
||||
import AnimatedNode from './nodes/AnimatedNode';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import AnimatedValueXY from './nodes/AnimatedValueXY';
|
||||
|
||||
/**
|
||||
* Animations are a source of flakiness in snapshot testing. This mock replaces
|
||||
@@ -43,7 +43,7 @@ function mockAnimationStart(
|
||||
const guardedCallback =
|
||||
callback == null
|
||||
? callback
|
||||
: (...args: Array<EndResult>) => {
|
||||
: (...args) => {
|
||||
if (inAnimationCallback) {
|
||||
console.warn(
|
||||
'Ignoring recursive animation callback when running mock animations',
|
||||
@@ -158,15 +158,12 @@ type LoopAnimationConfig = {
|
||||
|
||||
const loop = function (
|
||||
animation: CompositeAnimation,
|
||||
// $FlowFixMe[prop-missing]
|
||||
{iterations = -1}: LoopAnimationConfig = {},
|
||||
): CompositeAnimation {
|
||||
return emptyAnimation;
|
||||
};
|
||||
|
||||
export type {AnimatedNumeric as Numeric};
|
||||
|
||||
export default {
|
||||
module.exports = {
|
||||
Value: AnimatedValue,
|
||||
ValueXY: AnimatedValueXY,
|
||||
Color: AnimatedColor,
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import AnimatedImplementation from './AnimatedImplementation';
|
||||
const AnimatedImplementation = require('./AnimatedImplementation');
|
||||
|
||||
export default {
|
||||
module.exports = {
|
||||
...AnimatedImplementation,
|
||||
/* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive
|
||||
* types. Plain intrinsic components can't be typed like this */
|
||||
@@ -214,7 +214,7 @@ const Easing = {
|
||||
x2: number,
|
||||
y2: number,
|
||||
): (t: number) => number {
|
||||
const _bezier = require('./bezier').default;
|
||||
const _bezier = require('./bezier');
|
||||
return _bezier(x1, y1, x2, y2);
|
||||
},
|
||||
|
||||
@@ -247,4 +247,4 @@ const Easing = {
|
||||
},
|
||||
};
|
||||
|
||||
export default Easing;
|
||||
module.exports = Easing;
|
||||
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* 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 invariant from 'invariant';
|
||||
|
||||
// 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> = [];
|
||||
|
||||
/**
|
||||
* Simple wrappers around NativeAnimatedModule to provide flow and autocomplete support for
|
||||
* the native module methods
|
||||
*/
|
||||
const API = {
|
||||
getValue: function (
|
||||
tag: number,
|
||||
saveValueCallback: (value: number) => void,
|
||||
): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() => {
|
||||
NativeAnimatedModule.getValue(tag, saveValueCallback);
|
||||
});
|
||||
},
|
||||
setWaitingForIdentifier: function (id: string): void {
|
||||
waitingForQueuedOperations.add(id);
|
||||
queueOperations = true;
|
||||
},
|
||||
unsetWaitingForIdentifier: function (id: string): void {
|
||||
waitingForQueuedOperations.delete(id);
|
||||
|
||||
if (waitingForQueuedOperations.size === 0) {
|
||||
queueOperations = false;
|
||||
API.disableQueue();
|
||||
}
|
||||
},
|
||||
disableQueue: function (): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
NativeAnimatedModule.startOperationBatch();
|
||||
}
|
||||
for (let q = 0, l = queue.length; q < l; q++) {
|
||||
queue[q]();
|
||||
}
|
||||
queue.length = 0;
|
||||
if (Platform.OS === 'android') {
|
||||
NativeAnimatedModule.finishOperationBatch();
|
||||
}
|
||||
},
|
||||
queueOperation: (fn: () => void): void => {
|
||||
if (queueOperations) {
|
||||
queue.push(fn);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
},
|
||||
createAnimatedNode: function (tag: number, config: AnimatedNodeConfig): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.createAnimatedNode(tag, config),
|
||||
);
|
||||
},
|
||||
startListeningToAnimatedNodeValue: function (tag: number) {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag),
|
||||
);
|
||||
},
|
||||
stopListeningToAnimatedNodeValue: function (tag: number) {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag),
|
||||
);
|
||||
},
|
||||
connectAnimatedNodes: function (parentTag: number, childTag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag),
|
||||
);
|
||||
},
|
||||
disconnectAnimatedNodes: function (
|
||||
parentTag: number,
|
||||
childTag: number,
|
||||
): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag),
|
||||
);
|
||||
},
|
||||
startAnimatingNode: function (
|
||||
animationId: number,
|
||||
nodeTag: number,
|
||||
config: AnimatingNodeConfig,
|
||||
endCallback: EndCallback,
|
||||
): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.startAnimatingNode(
|
||||
animationId,
|
||||
nodeTag,
|
||||
config,
|
||||
endCallback,
|
||||
),
|
||||
);
|
||||
},
|
||||
stopAnimation: function (animationId: number) {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() => NativeAnimatedModule.stopAnimation(animationId));
|
||||
},
|
||||
setAnimatedNodeValue: function (nodeTag: number, value: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value),
|
||||
);
|
||||
},
|
||||
setAnimatedNodeOffset: function (nodeTag: number, offset: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset),
|
||||
);
|
||||
},
|
||||
flattenAnimatedNodeOffset: function (nodeTag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag),
|
||||
);
|
||||
},
|
||||
extractAnimatedNodeOffset: function (nodeTag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag),
|
||||
);
|
||||
},
|
||||
connectAnimatedNodeToView: function (nodeTag: number, viewTag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag),
|
||||
);
|
||||
},
|
||||
disconnectAnimatedNodeFromView: function (
|
||||
nodeTag: number,
|
||||
viewTag: number,
|
||||
): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag),
|
||||
);
|
||||
},
|
||||
restoreDefaultValues: function (nodeTag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
// Backwards compat with older native runtimes, can be removed later.
|
||||
if (NativeAnimatedModule.restoreDefaultValues != null) {
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.restoreDefaultValues(nodeTag),
|
||||
);
|
||||
}
|
||||
},
|
||||
dropAnimatedNode: function (tag: number): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() => NativeAnimatedModule.dropAnimatedNode(tag));
|
||||
},
|
||||
addAnimatedEventToView: function (
|
||||
viewTag: number,
|
||||
eventName: string,
|
||||
eventMapping: EventMapping,
|
||||
) {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
NativeAnimatedModule.addAnimatedEventToView(
|
||||
viewTag,
|
||||
eventName,
|
||||
eventMapping,
|
||||
),
|
||||
);
|
||||
},
|
||||
removeAnimatedEventFromView(
|
||||
viewTag: number,
|
||||
eventName: string,
|
||||
animatedNodeTag: number,
|
||||
) {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
API.queueOperation(() =>
|
||||
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 SUPPORTED_STYLES = {
|
||||
opacity: true,
|
||||
transform: true,
|
||||
borderRadius: true,
|
||||
borderBottomEndRadius: true,
|
||||
borderBottomLeftRadius: true,
|
||||
borderBottomRightRadius: true,
|
||||
borderBottomStartRadius: true,
|
||||
borderTopEndRadius: true,
|
||||
borderTopLeftRadius: true,
|
||||
borderTopRightRadius: true,
|
||||
borderTopStartRadius: true,
|
||||
elevation: 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 validateTransform(
|
||||
configs: Array<
|
||||
| {
|
||||
type: 'animated',
|
||||
property: string,
|
||||
nodeTag: ?number,
|
||||
...
|
||||
}
|
||||
| {
|
||||
type: 'static',
|
||||
property: string,
|
||||
value: number | string,
|
||||
...
|
||||
},
|
||||
>,
|
||||
): void {
|
||||
configs.forEach(config => {
|
||||
if (!SUPPORTED_TRANSFORMS.hasOwnProperty(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 (!SUPPORTED_STYLES.hasOwnProperty(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 (!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 == 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,
|
||||
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;
|
||||
},
|
||||
};
|
||||
-5
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
type EndResult = {finished: boolean, ...};
|
||||
@@ -30,7 +29,6 @@ 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;
|
||||
@@ -65,9 +63,6 @@ export interface Spec extends TurboModule {
|
||||
// Events
|
||||
+addListener: (eventName: string) => void;
|
||||
+removeListeners: (count: number) => void;
|
||||
|
||||
// All of the above in a batched mode
|
||||
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>('NativeAnimatedModule'): ?Spec);
|
||||
-5
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
type EndResult = {finished: boolean, ...};
|
||||
@@ -30,7 +29,6 @@ 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;
|
||||
@@ -65,9 +63,6 @@ export interface Spec extends TurboModule {
|
||||
// Events
|
||||
+addListener: (eventName: string) => void;
|
||||
+removeListeners: (count: number) => void;
|
||||
|
||||
// All of the above in a batched mode
|
||||
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>(
|
||||
+7
-2
@@ -24,7 +24,7 @@ function dampingFromOrigamiValue(oValue: number) {
|
||||
return (oValue - 8) * 3 + 25;
|
||||
}
|
||||
|
||||
export function fromOrigamiTensionAndFriction(
|
||||
function fromOrigamiTensionAndFriction(
|
||||
tension: number,
|
||||
friction: number,
|
||||
): SpringConfigType {
|
||||
@@ -34,7 +34,7 @@ export function fromOrigamiTensionAndFriction(
|
||||
};
|
||||
}
|
||||
|
||||
export function fromBouncinessAndSpeed(
|
||||
function fromBouncinessAndSpeed(
|
||||
bounciness: number,
|
||||
speed: number,
|
||||
): SpringConfigType {
|
||||
@@ -96,3 +96,8 @@ export function fromBouncinessAndSpeed(
|
||||
damping: dampingFromOrigamiValue(bouncyFriction),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fromOrigamiTensionAndFriction,
|
||||
fromBouncinessAndSpeed,
|
||||
};
|
||||
+20
-55
@@ -5,16 +5,13 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
|
||||
import AnimatedProps from '../nodes/AnimatedProps';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import * as React from 'react';
|
||||
|
||||
const {PlatformColor} = require('../../StyleSheet/PlatformColorValueTypes');
|
||||
let Animated = require('../Animated').default;
|
||||
const AnimatedProps = require('../nodes/AnimatedProps').default;
|
||||
const TestRenderer = require('react-test-renderer');
|
||||
|
||||
jest.mock('../../BatchedBridge/NativeModules', () => ({
|
||||
NativeAnimatedModule: {},
|
||||
PlatformConstants: {
|
||||
@@ -24,6 +21,8 @@ jest.mock('../../BatchedBridge/NativeModules', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
let Animated = require('../Animated');
|
||||
|
||||
describe('Animated tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -32,10 +31,6 @@ describe('Animated tests', () => {
|
||||
describe('Animated', () => {
|
||||
it('works end to end', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
const translateAnim = anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
@@ -46,10 +41,10 @@ describe('Animated tests', () => {
|
||||
opacity: anim,
|
||||
transform: [
|
||||
{
|
||||
translate: [translateAnim, translateAnim],
|
||||
},
|
||||
{
|
||||
translateX: translateAnim,
|
||||
translateX: anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
}),
|
||||
},
|
||||
{scale: anim},
|
||||
],
|
||||
@@ -66,7 +61,7 @@ describe('Animated tests', () => {
|
||||
style: {
|
||||
backgroundColor: 'red',
|
||||
opacity: 0,
|
||||
transform: [{translate: [100, 100]}, {translateX: 100}, {scale: 0}],
|
||||
transform: [{translateX: 100}, {scale: 0}],
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -88,7 +83,7 @@ describe('Animated tests', () => {
|
||||
style: {
|
||||
backgroundColor: 'red',
|
||||
opacity: 0.5,
|
||||
transform: [{translate: [150, 150]}, {translateX: 150}, {scale: 0.5}],
|
||||
transform: [{translateX: 150}, {scale: 0.5}],
|
||||
shadowOffset: {
|
||||
width: 0.5,
|
||||
height: 0.5,
|
||||
@@ -176,13 +171,11 @@ describe('Animated tests', () => {
|
||||
|
||||
expect(testRenderer.toJSON().props.style.opacity).toEqual(0);
|
||||
|
||||
TestRenderer.act(() => {
|
||||
Animated.timing(opacity, {
|
||||
toValue: 1,
|
||||
duration: 0,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
});
|
||||
Animated.timing(opacity, {
|
||||
toValue: 1,
|
||||
duration: 0,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
|
||||
expect(testRenderer.toJSON().props.style.opacity).toEqual(1);
|
||||
});
|
||||
@@ -699,7 +692,7 @@ describe('Animated tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.mock('../../Interaction/InteractionManager');
|
||||
Animated = require('../Animated').default;
|
||||
Animated = require('../Animated');
|
||||
InteractionManager = require('../../Interaction/InteractionManager');
|
||||
});
|
||||
|
||||
@@ -1040,10 +1033,10 @@ describe('Animated tests', () => {
|
||||
});
|
||||
|
||||
node.__attach();
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
expect(callback.mock.calls.length).toBe(0);
|
||||
|
||||
color.setValue({r: 11, g: 22, b: 33, a: 0.5});
|
||||
expect(callback).toHaveBeenCalledTimes(5);
|
||||
expect(callback.mock.calls.length).toBe(4);
|
||||
expect(node.__getValue()).toEqual({
|
||||
style: {
|
||||
backgroundColor: 'rgba(11, 22, 33, 0.5)',
|
||||
@@ -1053,7 +1046,7 @@ describe('Animated tests', () => {
|
||||
|
||||
node.__detach();
|
||||
color.setValue({r: 255, g: 0, b: 0, a: 1.0});
|
||||
expect(callback).toHaveBeenCalledTimes(5);
|
||||
expect(callback.mock.calls.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should track colors', () => {
|
||||
@@ -1089,33 +1082,5 @@ describe('Animated tests', () => {
|
||||
jest.runAllTimers();
|
||||
expect(color2.__getValue()).toEqual('rgba(44, 55, 66, 0)');
|
||||
});
|
||||
|
||||
it('should provide updates for native colors', () => {
|
||||
const color = new Animated.Color('red');
|
||||
|
||||
const listener = jest.fn();
|
||||
color.addListener(listener);
|
||||
|
||||
const callback = jest.fn(() => {
|
||||
console.log('callback', color.__getValue());
|
||||
});
|
||||
const node = new AnimatedProps({style: {color}}, callback);
|
||||
node.__attach();
|
||||
|
||||
color.setValue('blue');
|
||||
expect(callback).toHaveBeenCalledTimes(5);
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toHaveBeenCalledWith({value: 'rgba(0, 0, 255, 1)'});
|
||||
expect(color.__getValue()).toEqual('rgba(0, 0, 255, 1)');
|
||||
|
||||
callback.mockClear();
|
||||
listener.mockClear();
|
||||
|
||||
color.setValue(PlatformColor('bar'));
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toHaveBeenCalledWith({value: PlatformColor('bar')});
|
||||
expect(color.__getValue()).toEqual(PlatformColor('bar'));
|
||||
});
|
||||
});
|
||||
});
|
||||
+3
-3
@@ -5,13 +5,13 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import AnimatedImplementation from '../AnimatedImplementation';
|
||||
import AnimatedMock from '../AnimatedMock';
|
||||
const AnimatedMock = require('../AnimatedMock');
|
||||
const AnimatedImplementation = require('../AnimatedImplementation');
|
||||
|
||||
describe('Animated Mock', () => {
|
||||
it('matches implementation keys', () => {
|
||||
@@ -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(<Animated.View ref={ref} style={{opacity}} />);
|
||||
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
|
||||
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(<Animated.View onTouchMove={event} />);
|
||||
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(<Animated.View onTouchMove={event} />);
|
||||
['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(<Animated.View onTouchMove={event} />);
|
||||
}).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.View style={{opacity}} />);
|
||||
|
||||
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.View style={{opacity}} />);
|
||||
|
||||
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(
|
||||
<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.subtract nodes', () => {
|
||||
const first = new Animated.Value(2);
|
||||
const second = new Animated.Value(1);
|
||||
first.__makeNative();
|
||||
second.__makeNative();
|
||||
|
||||
TestRenderer.create(
|
||||
<Animated.View style={{opacity: Animated.subtract(first, second)}} />,
|
||||
);
|
||||
|
||||
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(
|
||||
<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();
|
||||
|
||||
TestRenderer.create(
|
||||
<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();
|
||||
|
||||
TestRenderer.create(
|
||||
<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();
|
||||
|
||||
TestRenderer.create(
|
||||
<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 translateX = new Animated.Value(0);
|
||||
translateX.__makeNative();
|
||||
|
||||
TestRenderer.create(
|
||||
<Animated.View style={{transform: [{translateX}, {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();
|
||||
|
||||
TestRenderer.create(
|
||||
<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 opacity = new Animated.Value(0);
|
||||
|
||||
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
|
||||
|
||||
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(<Animated.View ref={ref} style={{opacity}} />);
|
||||
|
||||
// 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(<Animated.View style={{left}} />);
|
||||
|
||||
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(
|
||||
<Animated.View
|
||||
removeClippedSubviews={true}
|
||||
style={{left: 10, opacity, top: 20}}
|
||||
/>,
|
||||
);
|
||||
|
||||
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(<Animated.View style={{opacity}} />);
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled();
|
||||
|
||||
root.update(<Animated.View style={{opacity}} />);
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledWith(
|
||||
expect.any(Number),
|
||||
);
|
||||
|
||||
root.unmount();
|
||||
// Make sure it doesn't get called on unmount.
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Easing from '../Easing';
|
||||
const Easing = require('../Easing');
|
||||
describe('Easing', () => {
|
||||
it('should work with linear', () => {
|
||||
const easing = Easing.linear;
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 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 AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
|
||||
const Easing = require('../Easing');
|
||||
|
||||
describe('Interpolation', () => {
|
||||
it('should work with defaults', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(0.5)).toBe(0.5);
|
||||
expect(interpolation(0.8)).toBe(0.8);
|
||||
expect(interpolation(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with output range', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(100);
|
||||
expect(interpolation(0.5)).toBe(150);
|
||||
expect(interpolation(0.8)).toBe(180);
|
||||
expect(interpolation(1)).toBe(200);
|
||||
});
|
||||
|
||||
it('should work with input range', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [100, 200],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
|
||||
expect(interpolation(100)).toBe(0);
|
||||
expect(interpolation(150)).toBe(0.5);
|
||||
expect(interpolation(180)).toBe(0.8);
|
||||
expect(interpolation(200)).toBe(1);
|
||||
});
|
||||
|
||||
it('should throw for non monotonic input ranges', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 2, 1],
|
||||
outputRange: [0, 1, 2],
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 3, 1],
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with empty input range', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 10, 10],
|
||||
outputRange: [1, 2, 3],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(1);
|
||||
expect(interpolation(5)).toBe(1.5);
|
||||
expect(interpolation(10)).toBe(2);
|
||||
expect(interpolation(10.1)).toBe(3);
|
||||
expect(interpolation(15)).toBe(3);
|
||||
});
|
||||
|
||||
it('should work with empty output range', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [1, 2, 3],
|
||||
outputRange: [0, 10, 10],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(-10);
|
||||
expect(interpolation(1.5)).toBe(5);
|
||||
expect(interpolation(2)).toBe(10);
|
||||
expect(interpolation(2.5)).toBe(10);
|
||||
expect(interpolation(3)).toBe(10);
|
||||
expect(interpolation(4)).toBe(10);
|
||||
});
|
||||
|
||||
it('should work with easing', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(0.5)).toBe(0.25);
|
||||
expect(interpolation(0.9)).toBe(0.81);
|
||||
expect(interpolation(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with extrapolate', () => {
|
||||
let interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'extend',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(4);
|
||||
expect(interpolation(2)).toBe(4);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'clamp',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(0);
|
||||
expect(interpolation(2)).toBe(1);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'identity',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(-2);
|
||||
expect(interpolation(2)).toBe(2);
|
||||
});
|
||||
|
||||
it('should work with keyframes with extrapolate', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 10, 100, 1000],
|
||||
outputRange: [0, 5, 50, 500],
|
||||
extrapolate: true,
|
||||
});
|
||||
|
||||
expect(interpolation(-5)).toBe(-2.5);
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(5)).toBe(2.5);
|
||||
expect(interpolation(10)).toBe(5);
|
||||
expect(interpolation(50)).toBe(25);
|
||||
expect(interpolation(100)).toBe(50);
|
||||
expect(interpolation(500)).toBe(250);
|
||||
expect(interpolation(1000)).toBe(500);
|
||||
expect(interpolation(2000)).toBe(1000);
|
||||
});
|
||||
|
||||
it('should work with keyframes without extrapolate', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0.2, 1, 0.2],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
expect(interpolation(5)).toBeCloseTo(0.2);
|
||||
});
|
||||
|
||||
it('should throw for an infinite input range', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, Infinity],
|
||||
outputRange: [0, 1],
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, 0, Infinity],
|
||||
outputRange: [1, 2, 3],
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with negative infinite', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, 0],
|
||||
outputRange: [-Infinity, 0],
|
||||
easing: Easing.quad,
|
||||
extrapolate: 'identity',
|
||||
});
|
||||
|
||||
expect(interpolation(-Infinity)).toBe(-Infinity);
|
||||
expect(interpolation(-100)).toBeCloseTo(-10000);
|
||||
expect(interpolation(-10)).toBeCloseTo(-100);
|
||||
expect(interpolation(0)).toBeCloseTo(0);
|
||||
expect(interpolation(1)).toBeCloseTo(1);
|
||||
expect(interpolation(100)).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
it('should work with positive infinite', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [5, Infinity],
|
||||
outputRange: [5, Infinity],
|
||||
easing: Easing.quad,
|
||||
extrapolate: 'identity',
|
||||
});
|
||||
|
||||
expect(interpolation(-100)).toBeCloseTo(-100);
|
||||
expect(interpolation(-10)).toBeCloseTo(-10);
|
||||
expect(interpolation(0)).toBeCloseTo(0);
|
||||
expect(interpolation(5)).toBeCloseTo(5);
|
||||
expect(interpolation(6)).toBeCloseTo(5 + 1);
|
||||
expect(interpolation(10)).toBeCloseTo(5 + 25);
|
||||
expect(interpolation(100)).toBeCloseTo(5 + 95 * 95);
|
||||
expect(interpolation(Infinity)).toBe(Infinity);
|
||||
});
|
||||
|
||||
it('should work with output ranges as string', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
|
||||
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.2)');
|
||||
expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.4)');
|
||||
});
|
||||
|
||||
it('should work with output ranges as short hex string', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#024', '#9BF'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 34, 68, 1)');
|
||||
expect(interpolation(0.5)).toBe('rgba(77, 111, 162, 1)');
|
||||
expect(interpolation(1)).toBe('rgba(153, 187, 255, 1)');
|
||||
});
|
||||
|
||||
it('should work with output ranges as long hex string', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#FF9500', '#87FC70'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(255, 149, 0, 1)');
|
||||
expect(interpolation(0.5)).toBe('rgba(195, 201, 56, 1)');
|
||||
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
|
||||
});
|
||||
|
||||
it('should work with output ranges with mixed hex and rgba strings', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.4)');
|
||||
expect(interpolation(0.5)).toBe('rgba(118, 186, 126, 0.7)');
|
||||
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
|
||||
});
|
||||
|
||||
it('should work with negative and decimal values in string ranges', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['-100.5deg', '100deg'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('-100.5deg');
|
||||
expect(interpolation(0.5)).toBe('-0.25deg');
|
||||
expect(interpolation(1)).toBe('100deg');
|
||||
});
|
||||
|
||||
it('should crash when chaining an interpolation that returns a string', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
expect(() => {
|
||||
interpolation('45rad');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should support a mix of color patterns', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
|
||||
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.5)');
|
||||
expect(interpolation(1.5)).toBe('rgba(153, 75, 125, 1)');
|
||||
expect(interpolation(2)).toBe('rgba(255, 0, 0, 1)');
|
||||
});
|
||||
|
||||
it('should crash when defining output range with different pattern', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['20deg', '30rad'],
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should round the alpha channel of a color to the nearest thousandth', () => {
|
||||
const interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'],
|
||||
});
|
||||
|
||||
expect(interpolation(1e-12)).toBe('rgba(0, 0, 0, 0)');
|
||||
expect(interpolation(2 / 3)).toBe('rgba(0, 0, 0, 0.667)');
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import TimingAnimation from '../animations/TimingAnimation';
|
||||
const TimingAnimation = require('../animations/TimingAnimation');
|
||||
|
||||
describe('Timing Animation', () => {
|
||||
it('should return array of 61 items', () => {
|
||||
+11
-16
@@ -4,9 +4,9 @@
|
||||
* 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
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -17,28 +17,23 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import bezier from '../bezier';
|
||||
const bezier = require('../bezier');
|
||||
|
||||
const identity = function (x: number) {
|
||||
const identity = function (x) {
|
||||
return x;
|
||||
};
|
||||
|
||||
function assertClose(a: number, b: number, precision: number = 3) {
|
||||
function assertClose(a, b, precision = 3) {
|
||||
expect(a).toBeCloseTo(b, precision);
|
||||
}
|
||||
|
||||
function makeAssertCloseWithPrecision(precision: number) {
|
||||
return function (a: number, b: number) {
|
||||
function makeAssertCloseWithPrecision(precision) {
|
||||
return function (a, b) {
|
||||
assertClose(a, b, precision);
|
||||
};
|
||||
}
|
||||
|
||||
function allEquals(
|
||||
be1: (x: number) => number,
|
||||
be2: (x: number) => number,
|
||||
samples: number,
|
||||
assertion: $FlowFixMe,
|
||||
) {
|
||||
function allEquals(be1, be2, samples, assertion) {
|
||||
if (!assertion) {
|
||||
assertion = assertClose;
|
||||
}
|
||||
@@ -48,8 +43,8 @@ function allEquals(
|
||||
}
|
||||
}
|
||||
|
||||
function repeat(n: number) {
|
||||
return function (f: () => void) {
|
||||
function repeat(n) {
|
||||
return function (f) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
f();
|
||||
}
|
||||
@@ -104,7 +99,7 @@ describe('bezier', function () {
|
||||
d = Math.random();
|
||||
const easing = bezier(a, b, c, d);
|
||||
const projected = bezier(b, a, d, c);
|
||||
const composed = function (x: number) {
|
||||
const composed = function (x) {
|
||||
return projected(easing(x));
|
||||
};
|
||||
allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2));
|
||||
@@ -140,7 +135,7 @@ describe('bezier', function () {
|
||||
c = 1 - a,
|
||||
d = 1 - b;
|
||||
const easing = bezier(a, b, c, d);
|
||||
const sym = function (x: number) {
|
||||
const sym = function (x) {
|
||||
return 1 - easing(1 - x);
|
||||
};
|
||||
allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2));
|
||||
@@ -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<TProps: {...}, TInstance>(
|
||||
Component: React.AbstractComponent<TProps, TInstance>,
|
||||
): React.AbstractComponent<TProps, TInstance> {
|
||||
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.',
|
||||
);
|
||||
});
|
||||
+4
-3
@@ -10,11 +10,10 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
import NativeAnimatedHelper from '../NativeAnimatedHelper';
|
||||
|
||||
export type EndResult = {finished: boolean, ...};
|
||||
export type EndCallback = (result: EndResult) => void;
|
||||
|
||||
@@ -31,7 +30,7 @@ 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.
|
||||
export default class Animation {
|
||||
class Animation {
|
||||
__active: boolean;
|
||||
__isInteraction: boolean;
|
||||
__nativeId: number;
|
||||
@@ -86,3 +85,5 @@ export default class Animation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Animation;
|
||||
+8
-5
@@ -10,13 +10,14 @@
|
||||
|
||||
'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';
|
||||
|
||||
import NativeAnimatedHelper from '../NativeAnimatedHelper';
|
||||
import Animation from './Animation';
|
||||
|
||||
export type DecayAnimationConfig = {
|
||||
...AnimationConfig,
|
||||
velocity:
|
||||
@@ -35,7 +36,7 @@ export type DecayAnimationConfigSingle = {
|
||||
deceleration?: number,
|
||||
};
|
||||
|
||||
export default class DecayAnimation extends Animation {
|
||||
class DecayAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_lastValue: number;
|
||||
_fromValue: number;
|
||||
@@ -50,7 +51,7 @@ export default class DecayAnimation extends Animation {
|
||||
super();
|
||||
this._deceleration = config.deceleration ?? 0.998;
|
||||
this._velocity = config.velocity;
|
||||
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this._platformConfig = config.platformConfig;
|
||||
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
|
||||
this.__iterations = config.iterations ?? 1;
|
||||
@@ -122,3 +123,5 @@ export default class DecayAnimation extends Animation {
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DecayAnimation;
|
||||
+17
-12
@@ -10,17 +10,20 @@
|
||||
|
||||
'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('invariant');
|
||||
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
import NativeAnimatedHelper from '../NativeAnimatedHelper';
|
||||
import AnimatedColor from '../nodes/AnimatedColor';
|
||||
import * as SpringConfig from '../SpringConfig';
|
||||
import Animation from './Animation';
|
||||
import invariant from 'invariant';
|
||||
|
||||
export type SpringAnimationConfig = {
|
||||
...AnimationConfig,
|
||||
@@ -41,7 +44,7 @@ export type SpringAnimationConfig = {
|
||||
...
|
||||
}
|
||||
| AnimatedColor
|
||||
| AnimatedInterpolation<number>,
|
||||
| AnimatedInterpolation,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
@@ -64,7 +67,7 @@ export type SpringAnimationConfig = {
|
||||
|
||||
export type SpringAnimationConfigSingle = {
|
||||
...AnimationConfig,
|
||||
toValue: number,
|
||||
toValue: number | AnimatedValue | AnimatedInterpolation,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
@@ -79,7 +82,7 @@ export type SpringAnimationConfigSingle = {
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
export default class SpringAnimation extends Animation {
|
||||
class SpringAnimation extends Animation {
|
||||
_overshootClamping: boolean;
|
||||
_restDisplacementThreshold: number;
|
||||
_restSpeedThreshold: number;
|
||||
@@ -87,7 +90,7 @@ export default class SpringAnimation extends Animation {
|
||||
_startPosition: number;
|
||||
_lastPosition: number;
|
||||
_fromValue: number;
|
||||
_toValue: number;
|
||||
_toValue: any;
|
||||
_stiffness: number;
|
||||
_damping: number;
|
||||
_mass: number;
|
||||
@@ -112,7 +115,7 @@ export default class SpringAnimation extends Animation {
|
||||
this._lastVelocity = config.velocity ?? 0;
|
||||
this._toValue = config.toValue;
|
||||
this._delay = config.delay ?? 0;
|
||||
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this._platformConfig = config.platformConfig;
|
||||
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
|
||||
this.__iterations = config.iterations ?? 1;
|
||||
@@ -368,3 +371,5 @@ export default class SpringAnimation extends Animation {
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpringAnimation;
|
||||
+26
-17
@@ -10,18 +10,19 @@
|
||||
|
||||
'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 {RgbaValue} from '../nodes/AnimatedColor';
|
||||
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
import NativeAnimatedHelper from '../NativeAnimatedHelper';
|
||||
import AnimatedColor from '../nodes/AnimatedColor';
|
||||
import Animation from './Animation';
|
||||
|
||||
export type TimingAnimationConfig = $ReadOnly<{
|
||||
export type TimingAnimationConfig = {
|
||||
...AnimationConfig,
|
||||
toValue:
|
||||
| number
|
||||
@@ -32,35 +33,41 @@ export type TimingAnimationConfig = $ReadOnly<{
|
||||
...
|
||||
}
|
||||
| AnimatedValueXY
|
||||
| RgbaValue
|
||||
| {
|
||||
r: number,
|
||||
g: number,
|
||||
b: number,
|
||||
a: number,
|
||||
...
|
||||
}
|
||||
| AnimatedColor
|
||||
| AnimatedInterpolation<number>,
|
||||
| AnimatedInterpolation,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TimingAnimationConfigSingle = $ReadOnly<{
|
||||
export type TimingAnimationConfigSingle = {
|
||||
...AnimationConfig,
|
||||
toValue: number,
|
||||
toValue: number | AnimatedValue | AnimatedInterpolation,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
}>;
|
||||
};
|
||||
|
||||
let _easeInOut;
|
||||
function easeInOut() {
|
||||
if (!_easeInOut) {
|
||||
const Easing = require('../Easing').default;
|
||||
const Easing = require('../Easing');
|
||||
_easeInOut = Easing.inOut(Easing.ease);
|
||||
}
|
||||
return _easeInOut;
|
||||
}
|
||||
|
||||
export default class TimingAnimation extends Animation {
|
||||
class TimingAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_fromValue: number;
|
||||
_toValue: number;
|
||||
_toValue: any;
|
||||
_duration: number;
|
||||
_delay: number;
|
||||
_easing: (value: number) => number;
|
||||
@@ -77,7 +84,7 @@ export default class TimingAnimation extends Animation {
|
||||
this._duration = config.duration ?? 500;
|
||||
this._delay = config.delay ?? 0;
|
||||
this.__iterations = config.iterations ?? 1;
|
||||
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this._platformConfig = config.platformConfig;
|
||||
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
|
||||
}
|
||||
@@ -170,3 +177,5 @@ export default class TimingAnimation extends Animation {
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimingAnimation;
|
||||
@@ -92,7 +92,7 @@ function newtonRaphsonIterate(
|
||||
return aGuessT;
|
||||
}
|
||||
|
||||
export default function bezier(
|
||||
module.exports = function bezier(
|
||||
mX1: number,
|
||||
mY1: number,
|
||||
mX2: number,
|
||||
@@ -105,7 +105,7 @@ export default function bezier(
|
||||
// Precompute samples table
|
||||
const sampleValues = float32ArraySupported
|
||||
? new Float32Array(kSplineTableSize)
|
||||
: new Array<number>(kSplineTableSize);
|
||||
: new Array(kSplineTableSize);
|
||||
if (mX1 !== mY1 || mX2 !== mY2) {
|
||||
for (let i = 0; i < kSplineTableSize; ++i) {
|
||||
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||
@@ -161,4 +161,4 @@ export default function bezier(
|
||||
}
|
||||
return calcBezier(getTForX(x), mY1, mY2);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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) => (
|
||||
<FlatList scrollEventThrottle={0.0001} {...props} ref={ref} />
|
||||
));
|
||||
|
||||
module.exports = (createAnimatedComponent(
|
||||
FlatListWithEventThrottle,
|
||||
): AnimatedComponentType<
|
||||
React.ElementConfig<typeof FlatList>,
|
||||
React.ElementRef<typeof FlatList>,
|
||||
>);
|
||||
@@ -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<typeof Image>,
|
||||
React.ElementRef<typeof Image>,
|
||||
>);
|
||||
@@ -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) => (
|
||||
<ScrollView scrollEventThrottle={0.0001} {...props} ref={ref} />
|
||||
));
|
||||
|
||||
module.exports = (createAnimatedComponent(
|
||||
ScrollViewWithEventThrottle,
|
||||
): AnimatedComponentType<
|
||||
React.ElementConfig<typeof ScrollView>,
|
||||
React.ElementRef<typeof ScrollView>,
|
||||
>);
|
||||
@@ -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) => (
|
||||
<SectionList scrollEventThrottle={0.0001} {...props} ref={ref} />
|
||||
));
|
||||
|
||||
module.exports = (createAnimatedComponent(
|
||||
SectionListWithEventThrottle,
|
||||
): AnimatedComponentType<
|
||||
React.ElementConfig<typeof SectionList>,
|
||||
React.ElementRef<typeof SectionList>,
|
||||
>);
|
||||
@@ -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<typeof Text>,
|
||||
React.ElementRef<typeof Text>,
|
||||
>);
|
||||
@@ -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<typeof View>,
|
||||
React.ElementRef<typeof View>,
|
||||
>);
|
||||
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* 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,
|
||||
> = React.AbstractComponent<
|
||||
$ObjMap<
|
||||
Props &
|
||||
$ReadOnly<{
|
||||
passthroughAnimatedPropExplicitValues?: React.ElementConfig<
|
||||
typeof View,
|
||||
>,
|
||||
}>,
|
||||
() => any,
|
||||
>,
|
||||
Instance,
|
||||
>;
|
||||
|
||||
function createAnimatedComponent<Props: {+[string]: mixed, ...}, Instance>(
|
||||
Component: React.AbstractComponent<Props, Instance>,
|
||||
): AnimatedComponentType<Props, Instance> {
|
||||
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<Object> {
|
||||
_component: any; // TODO T53738161: flow type this, and the whole file
|
||||
_invokeAnimatedPropsCallbackOnMount: boolean = false;
|
||||
_prevComponent: any;
|
||||
_propsAnimated: AnimatedProps;
|
||||
_eventDetachers: Array<Function> = [];
|
||||
|
||||
// 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 {style = {}, ...props} = this._propsAnimated.__getValue() || {};
|
||||
const {style: passthruStyle = {}, ...passthruProps} =
|
||||
this.props.passthroughAnimatedPropExplicitValues || {};
|
||||
const mergedStyle = {...style, ...passthruStyle};
|
||||
|
||||
// Force `collapsable` to be false so that native view is not flattened.
|
||||
// Flattened views cannot be accurately referenced by a native driver.
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
{...passthruProps}
|
||||
collapsable={false}
|
||||
style={mergedStyle}
|
||||
ref={this._setComponentRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<AnimatedComponent
|
||||
{...props}
|
||||
{...(ref == null ? null : {forwardedRef: ref})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// $FlowIgnore[incompatible-cast] - Will be compatible after refactors.
|
||||
module.exports = (createAnimatedComponentInjection.recordAndRetrieve() ??
|
||||
createAnimatedComponent: typeof createAnimatedComponent);
|
||||
@@ -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 = <TProps: {...}, TInstance>(
|
||||
Component: React.AbstractComponent<TProps, TInstance>,
|
||||
) => React.AbstractComponent<TProps, TInstance>;
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -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<TProps: {...}, TInstance>(
|
||||
Component: React.AbstractComponent<TProps, TInstance>,
|
||||
): React.AbstractComponent<TProps, TInstance> {
|
||||
return React.forwardRef((props, forwardedRef) => {
|
||||
const [reducedProps, callbackRef] = useAnimatedProps<TProps, TInstance>(
|
||||
props,
|
||||
);
|
||||
const ref = useMergeRefs<TInstance | null>(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 (
|
||||
<Component
|
||||
{...reducedProps}
|
||||
{...passthroughProps}
|
||||
style={mergedStyle}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
+9
-9
@@ -10,15 +10,15 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
import type AnimatedNode from './AnimatedNode';
|
||||
|
||||
import AnimatedInterpolation from './AnimatedInterpolation';
|
||||
import AnimatedValue from './AnimatedValue';
|
||||
import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
export default class AnimatedAddition extends AnimatedWithChildren {
|
||||
class AnimatedAddition extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
@@ -38,9 +38,7 @@ export default class AnimatedAddition extends AnimatedWithChildren {
|
||||
return this._a.__getValue() + this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate<OutputT: number | string>(
|
||||
config: InterpolationConfigType<OutputT>,
|
||||
): AnimatedInterpolation<OutputT> {
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
@@ -62,3 +60,5 @@ export default class AnimatedAddition extends AnimatedWithChildren {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedAddition;
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* 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 type {ColorValue} from '../../StyleSheet/StyleSheet';
|
||||
import type {NativeColorValue} from '../../StyleSheet/PlatformColorValueTypes';
|
||||
|
||||
type ColorListenerCallback = (value: string) => mixed;
|
||||
type RgbaValue = {
|
||||
+r: number,
|
||||
+g: number,
|
||||
+b: number,
|
||||
+a: number,
|
||||
...
|
||||
};
|
||||
type RgbaAnimatedValue = {
|
||||
+r: AnimatedValue,
|
||||
+g: AnimatedValue,
|
||||
+b: AnimatedValue,
|
||||
+a: AnimatedValue,
|
||||
...
|
||||
};
|
||||
|
||||
const defaultColor: RgbaValue = {r: 0, g: 0, b: 0, a: 1.0};
|
||||
let _uniqueId = 1;
|
||||
|
||||
/* eslint no-bitwise: 0 */
|
||||
function processColor(color?: ?ColorValue): ?(RgbaValue | NativeColorValue) {
|
||||
if (color === undefined || color === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let normalizedColor = normalizeColor(color);
|
||||
if (normalizedColor === undefined || normalizedColor === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof normalizedColor === 'object') {
|
||||
const processedColorObj = processColorObject(normalizedColor);
|
||||
if (processedColorObj != null) {
|
||||
return processedColorObj;
|
||||
}
|
||||
} else if (typeof normalizedColor === 'number') {
|
||||
const r = (normalizedColor & 0xff000000) >>> 24;
|
||||
const g = (normalizedColor & 0x00ff0000) >>> 16;
|
||||
const b = (normalizedColor & 0x0000ff00) >>> 8;
|
||||
const a = (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;
|
||||
_listeners: {
|
||||
[key: string]: {
|
||||
r: string,
|
||||
g: string,
|
||||
b: string,
|
||||
a: string,
|
||||
...
|
||||
},
|
||||
...
|
||||
};
|
||||
|
||||
constructor(valueIn?: ?(RgbaValue | RgbaAnimatedValue | ColorValue)) {
|
||||
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 {
|
||||
// Handle potential parsable string color or platform color object
|
||||
if (!isRgbaValue(value)) {
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified via conditionals
|
||||
value = processColor((value: ColorValue)) || {r: 0, g: 0, b: 0, a: 1.0};
|
||||
// TODO: support platform color
|
||||
}
|
||||
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified via conditionals
|
||||
const rgbaValue: RgbaValue = (value: RgbaValue);
|
||||
this.r = new AnimatedValue(rgbaValue.r);
|
||||
this.g = new AnimatedValue(rgbaValue.g);
|
||||
this.b = new AnimatedValue(rgbaValue.b);
|
||||
this.a = new AnimatedValue(rgbaValue.a);
|
||||
}
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value. This will stop any animations running on the value
|
||||
* and update all the bound properties.
|
||||
*/
|
||||
setValue(value: {r: number, g: number, b: number, a: number, ...}): void {
|
||||
this.r.setValue(value.r);
|
||||
this.g.setValue(value.g);
|
||||
this.b.setValue(value.b);
|
||||
this.a.setValue(value.a);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: {r: number, g: number, b: number, a: number, ...}): 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}) => {
|
||||
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?: (value: string) => void): 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?: (value: string) => void): void {
|
||||
this.r.resetAnimation();
|
||||
this.g.resetAnimation();
|
||||
this.b.resetAnimation();
|
||||
this.a.resetAnimation();
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
__getValue(): string {
|
||||
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();
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -10,14 +10,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
import type AnimatedNode from './AnimatedNode';
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
|
||||
import AnimatedInterpolation from './AnimatedInterpolation';
|
||||
import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
export default class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_min: number;
|
||||
_max: number;
|
||||
@@ -38,9 +38,7 @@ export default class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
super.__makeNative(platformConfig);
|
||||
}
|
||||
|
||||
interpolate<OutputT: number | string>(
|
||||
config: InterpolationConfigType<OutputT>,
|
||||
): AnimatedInterpolation<OutputT> {
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
@@ -70,3 +68,5 @@ export default class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDiffClamp;
|
||||
+10
-10
@@ -10,15 +10,15 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
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';
|
||||
|
||||
import AnimatedInterpolation from './AnimatedInterpolation';
|
||||
import AnimatedNode from './AnimatedNode';
|
||||
import AnimatedValue from './AnimatedValue';
|
||||
import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
export default class AnimatedDivision extends AnimatedWithChildren {
|
||||
class AnimatedDivision extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
_warnedAboutDivideByZero: boolean = false;
|
||||
@@ -54,9 +54,7 @@ export default class AnimatedDivision extends AnimatedWithChildren {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
interpolate<OutputT: number | string>(
|
||||
config: InterpolationConfigType<OutputT>,
|
||||
): AnimatedInterpolation<OutputT> {
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
@@ -78,3 +76,5 @@ export default class AnimatedDivision extends AnimatedWithChildren {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDivision;
|
||||
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* 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 = {
|
||||
inputRange: $ReadOnlyArray<number>,
|
||||
outputRange: $ReadOnlyArray<number> | $ReadOnlyArray<string>,
|
||||
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) => number | string {
|
||||
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||
return createInterpolationFromStringOutputRange(config);
|
||||
}
|
||||
|
||||
const outputRange: Array<number> = (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 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<string> = (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<string>) {
|
||||
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<number>) {
|
||||
let i;
|
||||
for (i = 1; i < inputRange.length - 1; ++i) {
|
||||
if (inputRange[i] >= input) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
function checkValidInputRange(arr: $ReadOnlyArray<number>) {
|
||||
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[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) */
|
||||
'inputRange must be monotonically non-decreasing ' + arr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkInfiniteRange(name: string, arr: $ReadOnlyArray<number>) {
|
||||
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 extends AnimatedWithChildren {
|
||||
// Export for testing.
|
||||
static __createInterpolation: (
|
||||
config: InterpolationConfigType,
|
||||
) => (input: number) => number | string = 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(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: Array<any>): Array<any> {
|
||||
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
|
||||
/* $FlowFixMe[incompatible-call] (>=0.38.0) - Flow error detected during
|
||||
* the deployment of v0.38.0. To see the error, remove this comment and
|
||||
* run flow */
|
||||
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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user