mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9101eaf121 | ||
|
|
1f8b698013 | ||
|
|
f6a8452e76 | ||
|
|
e8a368c9a0 | ||
|
|
83aff2c32f | ||
|
|
e41e146baa | ||
|
|
ba3815feae | ||
|
|
4bb17944f1 | ||
|
|
ada73a3549 | ||
|
|
4efa3baf0c | ||
|
|
409551ccd2 | ||
|
|
e16c5cb4bf | ||
|
|
0b6cd69dd4 | ||
|
|
d22cc4f2bd | ||
|
|
dfa8fed64b | ||
|
|
be5088401f | ||
|
|
26e7d6a062 | ||
|
|
ebb629d056 | ||
|
|
2799d677ca | ||
|
|
76604e7c5c | ||
|
|
8aeb5dc7ad | ||
|
|
360b53d77f | ||
|
|
ebfd1bdf6e | ||
|
|
b0eddefcbb | ||
|
|
9bf0d7f509 | ||
|
|
90e21dfd65 | ||
|
|
c5ecc6ffff | ||
|
|
cef001713f | ||
|
|
572f710492 | ||
|
|
474861f4e7 | ||
|
|
048b537800 | ||
|
|
2a50905c0b | ||
|
|
14398752e3 | ||
|
|
6d00239e49 | ||
|
|
cae662bfea | ||
|
|
77305cadf0 | ||
|
|
925330bb83 | ||
|
|
d68013dd71 | ||
|
|
80f5b6c110 | ||
|
|
99ef72cad7 | ||
|
|
b4d1fcfb2f | ||
|
|
13cefd396c | ||
|
|
e7bfa1fc2e | ||
|
|
6a10d5dfd3 | ||
|
|
60da5071a6 | ||
|
|
e3218a0d96 | ||
|
|
2c89e51507 | ||
|
|
6f00f5f6b8 | ||
|
|
05f5cb534f | ||
|
|
79e38e7d8e | ||
|
|
b86f16aa4e | ||
|
|
4d9385d531 | ||
|
|
8c11d3ffce | ||
|
|
89eb287be1 | ||
|
|
3c3b4cf575 | ||
|
|
8f954b3659 | ||
|
|
2bf7f1046d | ||
|
|
5b4816741b | ||
|
|
39e5a83062 | ||
|
|
fe348ab53d | ||
|
|
0fd83e4786 | ||
|
|
e97baa6066 | ||
|
|
76557f00d7 | ||
|
|
4693170d63 | ||
|
|
51dcb0195b | ||
|
|
203f3e3399 | ||
|
|
e7c289b56e | ||
|
|
3cfbae6540 | ||
|
|
3fd98d95ef | ||
|
|
c194ed2dbb | ||
|
|
93bbab3878 | ||
|
|
1a305fd79b | ||
|
|
45a43241cd | ||
|
|
041c392ec9 |
@@ -0,0 +1,47 @@
|
||||
environment:
|
||||
ANDROID_HOME: "C:\\android-sdk-windows"
|
||||
ANDROID_NDK: "C:\\android-sdk-windows\\android-ndk-r19c"
|
||||
ANDROID_BUILD_VERSION: 28
|
||||
ANDROID_TOOLS_VERSION: 28.0.3
|
||||
|
||||
GRADLE_OPTS: -Dorg.gradle.daemon=false
|
||||
|
||||
SDK_TOOLS_URL: https://dl.google.com/android/repository/sdk-tools-windows-3859397.zip
|
||||
NDK_TOOLS_URL: https://dl.google.com/android/repository/android-ndk-r19c-windows-x86_64.zip
|
||||
|
||||
matrix:
|
||||
- nodejs_version: 8
|
||||
- nodejs_version: 10
|
||||
|
||||
install:
|
||||
# Install Android SDK Tools
|
||||
- mkdir "%ANDROID_HOME%"
|
||||
- appveyor DownloadFile "%SDK_TOOLS_URL%" -FileName "%TMP%/sdk-tools.zip"
|
||||
- 7z x "%TMP%/sdk-tools.zip" -o"%ANDROID_HOME%" > nul
|
||||
- set PATH=%PATH%;"%ANDROID_HOME%\tools\bin"
|
||||
|
||||
- yes 2> nul | sdkmanager --licenses > nul
|
||||
- yes 2> nul | sdkmanager "system-images;android-19;google_apis;armeabi-v7a"
|
||||
- yes 2> nul | sdkmanager "platforms;android-%ANDROID_BUILD_VERSION%"
|
||||
- yes 2> nul | sdkmanager "build-tools;%ANDROID_TOOLS_VERSION%"
|
||||
- yes 2> nul | sdkmanager "add-ons;addon-google_apis-google-23"
|
||||
- yes 2> nul | sdkmanager "extras;android;m2repository"
|
||||
|
||||
- appveyor DownloadFile "%NDK_TOOLS_URL%" -FileName "%TMP%/ndk.zip"
|
||||
- 7z x "%TMP%/ndk.zip" -o"%ANDROID_HOME%" > nul
|
||||
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npx envinfo@latest
|
||||
- appveyor-retry yarn install
|
||||
|
||||
build_script:
|
||||
- yarn run flow-check-android
|
||||
- yarn run flow-check-ios
|
||||
- yarn run test
|
||||
# - gradlew.bat RNTester:android:app:assembleRelease
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- "%LOCALAPPDATA%/Yarn"
|
||||
- "%USERPROFILE%/.gradle/caches"
|
||||
- "%USERPROFILE%/.gradle/wrapper"
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
|
||||
[android]
|
||||
target = android-30
|
||||
target = android-28
|
||||
|
||||
[download]
|
||||
max_number_of_retries = 3
|
||||
@@ -8,6 +8,7 @@
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
||||
google = https://maven.google.com/
|
||||
jcenter = https://jcenter.bintray.com/
|
||||
|
||||
[alias]
|
||||
rntester = //packages/rn-tester/android/app:app
|
||||
rntester = //RNTester/android/app:app
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# and build a Android application that can be used to run the
|
||||
# tests specified in the scripts/ directory.
|
||||
#
|
||||
FROM reactnativecommunity/react-native-android:4.0
|
||||
FROM reactnativecommunity/react-native-android:2019-9-4
|
||||
|
||||
LABEL Description="React Native Android Test Image"
|
||||
LABEL maintainer="Héctor Ramos <hector@fb.com>"
|
||||
@@ -25,14 +25,11 @@ ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
|
||||
|
||||
ADD .buckconfig /app/.buckconfig
|
||||
ADD .buckjavaargs /app/.buckjavaargs
|
||||
ADD BUCK /app/BUCK
|
||||
ADD Libraries /app/Libraries
|
||||
ADD tools /app/tools
|
||||
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
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -52,3 +49,4 @@ RUN yarn
|
||||
RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
|
||||
|
||||
RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ mount -o remount,exec /dev/shm
|
||||
AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)
|
||||
|
||||
# create virtual device
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-19 --abi default/armeabi-v7a
|
||||
|
||||
# emulator setup
|
||||
emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim &
|
||||
|
||||
@@ -67,6 +67,11 @@ while :; do
|
||||
shift
|
||||
;;
|
||||
|
||||
--tvos)
|
||||
RUN_IOS=1
|
||||
shift
|
||||
;;
|
||||
|
||||
*)
|
||||
break
|
||||
esac
|
||||
@@ -109,7 +114,7 @@ function e2e_suite() {
|
||||
|
||||
# create virtual device
|
||||
if ! android list avd | grep "$AVD_UUID" > /dev/null; then
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-19 --abi default/armeabi-v7a
|
||||
fi
|
||||
|
||||
# newline at end of adb devices call and first line is headers
|
||||
@@ -194,7 +199,7 @@ function e2e_suite() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Starting Metro"
|
||||
echo "Starting packager server"
|
||||
npm start >> /dev/null &
|
||||
SERVER_PID=$!
|
||||
sleep 15
|
||||
|
||||
+197
-419
@@ -1,12 +1,5 @@
|
||||
version: 2.1
|
||||
|
||||
# -------------------------
|
||||
# ORBS
|
||||
# -------------------------
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.4.0
|
||||
|
||||
# -------------------------
|
||||
# DEFAULTS
|
||||
# -------------------------
|
||||
@@ -14,29 +7,23 @@ defaults: &defaults
|
||||
working_directory: ~/react-native
|
||||
environment:
|
||||
- GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1
|
||||
# The public github tokens are publicly visible by design
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: &github_pullbot_token_a "a6edf8e8d40ce4e8b11a"
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: &github_pullbot_token_b "150e1341f4dd9c944d2a"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: &github_analysisbot_token_a "312d354b5c36f082cfe9"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: &github_analysisbot_token_b "07973d757026bdd9f196"
|
||||
|
||||
# -------------------------
|
||||
# EXECUTORS
|
||||
# -------------------------
|
||||
executors:
|
||||
node8:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
nodelts:
|
||||
<<: *defaults
|
||||
docker:
|
||||
# Note: Version set separately for Windows builds, see below.
|
||||
- image: circleci/node:14
|
||||
nodeprevlts:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
- image: circleci/node:lts
|
||||
reactnativeandroid:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: reactnativecommunity/react-native-android:4.0
|
||||
- image: reactnativecommunity/react-native-android:2019-9-4
|
||||
resource_class: "large"
|
||||
environment:
|
||||
- TERM: "dumb"
|
||||
@@ -44,15 +31,10 @@ executors:
|
||||
- _JAVA_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
|
||||
- GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"'
|
||||
- BUILD_THREADS: 2
|
||||
# Repeated here, as the environment key in this executor will overwrite the one in defaults
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: *github_analysisbot_token_a
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: *github_analysisbot_token_b
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: *github_pullbot_token_a
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: *github_pullbot_token_b
|
||||
reactnativeios:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: &_XCODE_VERSION "12.5.0"
|
||||
xcode: "10.3.0"
|
||||
|
||||
# -------------------------
|
||||
# COMMANDS
|
||||
@@ -71,15 +53,16 @@ commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Initial Setup
|
||||
command: mkdir -p ./reports/{buck,build,junit,outputs}
|
||||
command: mkdir -p ~/reports/{buck,build,junit,outputs}
|
||||
|
||||
run_yarn:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v4-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- v4-yarn-cache-{{ arch }}
|
||||
- run:
|
||||
name: "Yarn: Install Dependencies"
|
||||
name: Run Yarn
|
||||
command: |
|
||||
# Skip yarn install on metro bump commits as the package is not yet
|
||||
# available on npm
|
||||
@@ -96,6 +79,7 @@ commands:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}}
|
||||
- v3-buck-v2019.01.10.01-
|
||||
- run:
|
||||
name: Install BUCK
|
||||
command: |
|
||||
@@ -110,21 +94,13 @@ commands:
|
||||
- ~/okbuck
|
||||
key: v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}
|
||||
|
||||
install_github_bot_deps:
|
||||
steps:
|
||||
- run:
|
||||
name: "Yarn: Install dependencies (GitHub bots)"
|
||||
command: cd bots && yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
|
||||
brew_install:
|
||||
parameters:
|
||||
package:
|
||||
description: Homebrew package to install
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Brew: Install << parameters.package >>"
|
||||
command: HOMEBREW_NO_AUTO_UPDATE=1 brew install << parameters.package >> >/dev/null
|
||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew install << parameters.package >> >/dev/null
|
||||
|
||||
with_brew_cache_span:
|
||||
parameters:
|
||||
@@ -133,39 +109,39 @@ commands:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v4-brew
|
||||
- v2-brew
|
||||
- steps: << parameters.steps >>
|
||||
- save_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
- ~/Library/Caches/Homebrew
|
||||
key: v4-brew
|
||||
key: v2-brew
|
||||
|
||||
with_rntester_pods_cache_span:
|
||||
with_pods_cache_span:
|
||||
parameters:
|
||||
steps:
|
||||
type: steps
|
||||
steps:
|
||||
- run:
|
||||
name: Setup CocoaPods cache
|
||||
# Copy packages/rn-tester/Podfile.lock since it can be changed by pod install
|
||||
command: cp packages/rn-tester/Podfile.lock packages/rn-tester/Podfile.lock.bak
|
||||
# Copy RNTester/Podfile.lock since it can be changed by pod install
|
||||
command: cp RNTester/Podfile.lock RNTester/Podfile.lock.bak
|
||||
- restore_cache:
|
||||
keys:
|
||||
# The committed lockfile is generated using USE_FRAMEWORKS=0 and USE_HERMES=0 so it could load an outdated cache if a change
|
||||
# only affects the frameworks or hermes config. To help prevent this also cache based on the content of Podfile.
|
||||
- v3-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "packages/rn-tester/Podfile.lock.bak" }}-{{ checksum "packages/rn-tester/Podfile" }}
|
||||
- v1-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "RNTester/Podfile.lock.bak" }}
|
||||
- v1-pods-{{ .Environment.CIRCLE_JOB }}-
|
||||
- steps: << parameters.steps >>
|
||||
- save_cache:
|
||||
paths:
|
||||
- packages/rn-tester/Pods
|
||||
key: v3-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "packages/rn-tester/Podfile.lock.bak" }}-{{ checksum "packages/rn-tester/Podfile" }}
|
||||
- RNTester/Pods
|
||||
key: v1-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "RNTester/Podfile.lock.bak" }}
|
||||
|
||||
download_gradle_dependencies:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-gradle-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }}
|
||||
- v1-gradle-
|
||||
- run:
|
||||
name: Download Dependencies Using Gradle
|
||||
command: ./scripts/circleci/gradle_download_deps.sh
|
||||
@@ -182,33 +158,24 @@ commands:
|
||||
name: Download Dependencies Using Buck
|
||||
command: ./scripts/circleci/buck_fetch.sh
|
||||
|
||||
run_e2e:
|
||||
parameters:
|
||||
platform:
|
||||
description: Target platform
|
||||
type: enum
|
||||
enum: ["android", "ios", "js"]
|
||||
default: "js"
|
||||
retries:
|
||||
description: How many times the job should try to run these tests
|
||||
type: integer
|
||||
default: 3
|
||||
# -------------------------
|
||||
# COMMANDS: Disabled Tests
|
||||
# -------------------------
|
||||
run_podspec_tests:
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: << parameters.platform >> End-to-End Tests"
|
||||
command: node ./scripts/run-ci-e2e-tests.js --<< parameters.platform >> --retries << parameters.retries >>
|
||||
|
||||
report_bundle_size:
|
||||
parameters:
|
||||
platform:
|
||||
description: Target platform
|
||||
type: enum
|
||||
enum: ["android", "ios"]
|
||||
name: Test CocoaPods
|
||||
command: ./scripts/process-podspecs.sh
|
||||
run_e2e_tests:
|
||||
steps:
|
||||
- install_github_bot_deps
|
||||
- run:
|
||||
name: Report size of RNTester.app (analysis-bot)
|
||||
command: GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" scripts/circleci/report-bundle-size.sh << parameters.platform >>
|
||||
name: Full End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --android --ios --js --retries 3;
|
||||
run_android_e2e_tests:
|
||||
steps:
|
||||
- run:
|
||||
name: Android End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --android --retries 3;
|
||||
|
||||
# -------------------------
|
||||
# JOBS
|
||||
@@ -216,12 +183,12 @@ commands:
|
||||
jobs:
|
||||
setup:
|
||||
parameters:
|
||||
executor:
|
||||
type: executor
|
||||
default: nodelts
|
||||
checkout_type:
|
||||
type: string
|
||||
default: node
|
||||
executor:
|
||||
type: executor
|
||||
default: node8
|
||||
checkout_type:
|
||||
type: string
|
||||
default: node
|
||||
executor: << parameters.executor >>
|
||||
steps:
|
||||
- checkout
|
||||
@@ -237,20 +204,24 @@ jobs:
|
||||
# Issues will be posted to the PR itself via GitHub bots.
|
||||
# This workflow should only fail if the bots fail to run.
|
||||
analyze_pr:
|
||||
executor: reactnativeandroid
|
||||
executor: nodelts
|
||||
# The public github tokens are publicly visible by design
|
||||
environment:
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: "a6edf8e8d40ce4e8b11a"
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: "150e1341f4dd9c944d2a"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: "78a72af35445ca3f8180"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: "b1a98e0bbd56ff1ccba1"
|
||||
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
checkout_type: node
|
||||
- run_yarn
|
||||
|
||||
- install_github_bot_deps
|
||||
|
||||
# Note: The yarn gpg key needs to be refreshed to work around https://github.com/yarnpkg/yarn/issues/7866
|
||||
- run:
|
||||
name: Install additional GitHub bot dependencies
|
||||
name: Install dependencies
|
||||
command: |
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
apt update && apt install -y shellcheck jq
|
||||
sudo apt update && sudo apt install -y shellcheck jq
|
||||
cd bots && yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Run linters against modified files (analysis-bot)
|
||||
@@ -264,25 +235,21 @@ jobs:
|
||||
DANGER_GITHUB_API_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" yarn danger ci --use-github-checks
|
||||
when: always
|
||||
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Analyze Code
|
||||
# -------------------------
|
||||
analyze_code:
|
||||
executor: reactnativeandroid
|
||||
executor: node8
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
|
||||
- run:
|
||||
name: Lint code
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ./reports/junit/eslint/results.xml
|
||||
when: always
|
||||
|
||||
- run:
|
||||
name: Lint Java
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint-java --check
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/reports/junit/eslint/results.xml
|
||||
when: always
|
||||
|
||||
- run:
|
||||
@@ -308,73 +275,42 @@ jobs:
|
||||
when: always
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test JavaScript
|
||||
# -------------------------
|
||||
# Runs JavaScript tests
|
||||
test_js:
|
||||
parameters:
|
||||
executor:
|
||||
type: executor
|
||||
default: nodelts
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
default: node8
|
||||
executor: << parameters.executor >>
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run:
|
||||
name: Install rsync
|
||||
command: sudo apt-get install rsync
|
||||
|
||||
# -------------------------
|
||||
# Run JavaScript tests
|
||||
- run:
|
||||
name: "Run Tests: JavaScript Tests"
|
||||
name: JavaScript Test Suite
|
||||
command: node ./scripts/run-ci-javascript-tests.js --maxWorkers 2
|
||||
- run_e2e:
|
||||
platform: js
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
# -------------------------
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
|
||||
path: ~/reports/junit
|
||||
# -------------------------
|
||||
# JOBS: Test iOS
|
||||
# -------------------------
|
||||
# Runs unit tests on iOS devices
|
||||
test_ios:
|
||||
executor: reactnativeios
|
||||
parameters:
|
||||
use_frameworks:
|
||||
type: boolean
|
||||
default: false
|
||||
use_hermes:
|
||||
type: boolean
|
||||
default: false
|
||||
run_unit_tests:
|
||||
description: Specifies whether unit tests should run.
|
||||
type: boolean
|
||||
default: false
|
||||
run_detox_tests:
|
||||
description: Specifies whether Detox e2e tests should run.
|
||||
type: boolean
|
||||
default: false
|
||||
run_disabled_tests:
|
||||
description: Specifies whether disabled tests should run. Set this to true to debug failing tests.
|
||||
type: boolean
|
||||
default: false
|
||||
environment:
|
||||
- REPORTS_DIR: "./reports/junit"
|
||||
- REPORTS_DIR: "./reports"
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: ios
|
||||
@@ -382,33 +318,16 @@ jobs:
|
||||
- run_yarn
|
||||
|
||||
- run: |
|
||||
cd packages/rn-tester
|
||||
cd RNTester
|
||||
bundle check || bundle install
|
||||
- run:
|
||||
name: Boot iPhone Simulator
|
||||
command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true
|
||||
|
||||
- run:
|
||||
name: Configure Environment Variables
|
||||
name: Fetch CocoaPods Specs
|
||||
command: |
|
||||
echo 'export PATH=/usr/local/opt/node@12/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- brew_install:
|
||||
package: watchman
|
||||
- brew_install:
|
||||
package: node@12
|
||||
- run:
|
||||
name: "Brew: Tap wix/brew"
|
||||
command: HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
- brew_install:
|
||||
package: applesimutils
|
||||
|
||||
- run:
|
||||
name: Configure Watchman
|
||||
command: touch .watchmanconfig
|
||||
curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
|
||||
|
||||
- when:
|
||||
condition: << parameters.use_frameworks >>
|
||||
@@ -417,73 +336,107 @@ jobs:
|
||||
name: Set USE_FRAMEWORKS=1
|
||||
command: echo "export USE_FRAMEWORKS=1" >> $BASH_ENV
|
||||
|
||||
- when:
|
||||
condition: << parameters.use_hermes >>
|
||||
steps:
|
||||
- run:
|
||||
name: Set USE_HERMES=1
|
||||
command: echo "export USE_HERMES=1" >> $BASH_ENV
|
||||
|
||||
- run:
|
||||
name: Setup the CocoaPods environment
|
||||
command: pod setup
|
||||
|
||||
- with_rntester_pods_cache_span:
|
||||
- with_pods_cache_span:
|
||||
steps:
|
||||
- run:
|
||||
name: Generate RNTesterPods Workspace
|
||||
command: cd packages/rn-tester && USE_FABRIC=1 bundle exec pod install --verbose
|
||||
command: cd RNTester && pod install --verbose
|
||||
|
||||
# -------------------------
|
||||
# Runs iOS unit tests
|
||||
- when:
|
||||
condition: << parameters.run_unit_tests >>
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: iOS Unit and Integration Tests"
|
||||
command: yarn test-ios
|
||||
# Runs iOS Detox e2e tests
|
||||
- when:
|
||||
condition: << parameters.run_detox_tests >>
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: Detox iOS End-to-End Tests"
|
||||
command: yarn run build-ios-e2e && yarn run test-ios-e2e
|
||||
- brew_install:
|
||||
package: watchman
|
||||
- run: touch .watchmanconfig
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run:
|
||||
name: "Run Tests: CocoaPods"
|
||||
command: ./scripts/process-podspecs.sh
|
||||
- run:
|
||||
name: Free up port 8081 for iOS End-to-End Tests
|
||||
command: |
|
||||
# free up port 8081 for the packager before running tests
|
||||
set +eo pipefail
|
||||
lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill
|
||||
set -eo pipefail
|
||||
- run_e2e:
|
||||
platform: ios
|
||||
# -------------------------
|
||||
|
||||
# Collect Results
|
||||
- report_bundle_size:
|
||||
platform: ios
|
||||
- run: yarn test-ios
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# Runs iOS end-to-end tests
|
||||
test_ios_e2e:
|
||||
executor: reactnativeios
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: ios
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
|
||||
- run:
|
||||
name: Boot iPhone Simulator
|
||||
command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true
|
||||
|
||||
- run:
|
||||
name: Configure Environment Variables
|
||||
command: |
|
||||
echo 'export PATH=/usr/local/opt/node@8/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
# Brew
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- brew_install:
|
||||
package: node@8
|
||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
- brew_install:
|
||||
package: applesimutils
|
||||
- brew_install:
|
||||
package: watchman
|
||||
# Configure Watchman
|
||||
- run: touch .watchmanconfig
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-cocoapods-{{ checksum "template/ios/Podfile" }}
|
||||
- v1-cocoapods-
|
||||
|
||||
- run: pod setup
|
||||
|
||||
- run:
|
||||
name: Generate RNTesterPods Workspace
|
||||
command: pushd RNTester && pod install --verbose && popd
|
||||
|
||||
- run:
|
||||
name: Run Detox iOS End-to-End Tests
|
||||
command: yarn run build-ios-e2e && yarn run test-ios-e2e
|
||||
when: always
|
||||
|
||||
- run:
|
||||
name: Run iOS End-to-End Tests
|
||||
command: |
|
||||
# free up port 8081 for the packager before running tests
|
||||
set +eo pipefail
|
||||
lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill
|
||||
set -eo pipefail
|
||||
node ./scripts/run-ci-e2e-tests.js --ios --retries 3;
|
||||
when: always
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.cocoapods/repos
|
||||
key: v1-cocoapods-{{ checksum "template/ios/Podfile" }}
|
||||
|
||||
test_js_e2e:
|
||||
executor: node8
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run: sudo apt-get install rsync
|
||||
|
||||
- run:
|
||||
name: Run JavaScript End-to-End Tests
|
||||
command: node ./scripts/run-ci-e2e-tests.js --js --retries 3
|
||||
|
||||
- store_test_results:
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Android
|
||||
# -------------------------
|
||||
# Run Android tests
|
||||
test_android:
|
||||
executor: reactnativeandroid
|
||||
parameters:
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
@@ -504,6 +457,8 @@ jobs:
|
||||
command: source scripts/android-setup.sh && launchAVD
|
||||
background: true
|
||||
|
||||
# Keep configuring Android dependencies while AVD boots up
|
||||
|
||||
# Install Buck
|
||||
- install_buck_tooling
|
||||
|
||||
@@ -536,49 +491,37 @@ jobs:
|
||||
name: Wait for Android Virtual Device
|
||||
command: source scripts/android-setup.sh && waitForAVD
|
||||
|
||||
# Test Suite
|
||||
- run:
|
||||
name: Assemble RNTester App
|
||||
command: ./gradlew packages:rn-tester:android:app:assembleRelease
|
||||
name: Run Unit Tests
|
||||
command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ~/reports/buck/all-results-raw.xml
|
||||
|
||||
# -------------------------
|
||||
# Run Android tests
|
||||
- run:
|
||||
name: "Run Tests: Android Unit Tests"
|
||||
command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ./reports/buck/all-results-raw.xml
|
||||
- run:
|
||||
name: "Build Tests: Android Instrumentation Tests"
|
||||
# Here, just build the instrumentation tests. There is a known issue with installing the APK to android-21+ emulator.
|
||||
name: Run Instrumentation Tests
|
||||
command: |
|
||||
if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then
|
||||
echo "JavaScript bundle missing, cannot run instrumentation tests. Verify Build JavaScript Bundle step completed successfully."; exit 1;
|
||||
fi
|
||||
source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck build ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run_e2e:
|
||||
platform: android
|
||||
# -------------------------
|
||||
- run:
|
||||
name: Build Android RNTester App
|
||||
command: ./gradlew RNTester:android:app:assembleRelease
|
||||
|
||||
# Collect Results
|
||||
- report_bundle_size:
|
||||
platform: android
|
||||
- run:
|
||||
name: Collect Test Results
|
||||
command: |
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ./reports/build/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ./reports/outputs/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ./reports/buck/ \;
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/reports/build/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/reports/outputs/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/reports/buck/ \;
|
||||
if [ -f ~/react-native/reports/buck/all-results-raw.xml ]; then
|
||||
cd ~/okbuck
|
||||
./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/results.xml
|
||||
./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/all-results-junit.xml
|
||||
fi
|
||||
when: always
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Docker
|
||||
@@ -593,101 +536,16 @@ jobs:
|
||||
source ~/.bashrc
|
||||
nvm i node
|
||||
npm i -g yarn
|
||||
echo y | npx envinfo@latest
|
||||
npx envinfo@latest
|
||||
yarn run docker-setup-android
|
||||
yarn run docker-build-android
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Windows
|
||||
# -------------------------
|
||||
test_windows:
|
||||
executor:
|
||||
name: win/default
|
||||
parameters:
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
environment:
|
||||
- ANDROID_HOME: "C:\\Android\\android-sdk"
|
||||
- ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\20.1.5948944"
|
||||
- ANDROID_BUILD_VERSION: 30
|
||||
- ANDROID_TOOLS_VERSION: 30.0.2
|
||||
- GRADLE_OPTS: -Dorg.gradle.daemon=false
|
||||
- NDK_VERSION: 21.4.7075529
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install Node
|
||||
# Note: Version set separately for non-Windows builds, see above.
|
||||
command: |
|
||||
nvm install 14.17.0
|
||||
nvm use 14.17.0
|
||||
|
||||
# Setup Dependencies
|
||||
- run:
|
||||
name: Install Yarn
|
||||
command: choco install yarn
|
||||
|
||||
- run:
|
||||
name: Display Environment info
|
||||
command: npx envinfo@latest
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-win-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: "Yarn: Install Dependencies"
|
||||
command: yarn install --frozen-lockfile --non-interactive
|
||||
- save_cache:
|
||||
key: v1-win-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- C:\Users\circleci\AppData\Local\Yarn
|
||||
|
||||
# Try to install the SDK up to 3 times, since network flakiness can cause install failures
|
||||
# Using a timeout of 9 mins, as circle ci will timeout if there is no output for 10 mins
|
||||
- run:
|
||||
name: Install Android SDK Tools
|
||||
command: choco install android-sdk --timeout 540; if (!$?) { choco install android-sdk --timeout 540 --force --forcedependencies}; if (!$?) { choco install android-sdk --force --forcedependencies}
|
||||
|
||||
- run:
|
||||
name: Setup Android SDKs
|
||||
command: |
|
||||
sdkmanager --licenses
|
||||
sdkmanager "system-images;android-21;google_apis;armeabi-v7a"
|
||||
sdkmanager "platforms;android-%ANDROID_BUILD_VERSION%"
|
||||
sdkmanager "build-tools;%ANDROID_TOOLS_VERSION%"
|
||||
sdkmanager "add-ons;addon-google_apis-google-23"
|
||||
sdkmanager "extras;android;m2repository"
|
||||
sdkmanager "ndk;%NDK_VERSION%"
|
||||
|
||||
# -------------------------
|
||||
# Run Tests
|
||||
- run:
|
||||
name: "Flow: Check Android"
|
||||
command: yarn flow-check-android
|
||||
- run:
|
||||
name: "Flow: Check iOS"
|
||||
command: yarn flow-check-ios
|
||||
- run:
|
||||
name: "Run Tests: JavaScript Tests"
|
||||
command: yarn test
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run:
|
||||
name: Android Build
|
||||
command: ./gradlew.bat packages:rn-tester:android:app:assembleRelease
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Coverage
|
||||
# -------------------------
|
||||
# Collect JavaScript test coverage
|
||||
js_coverage:
|
||||
executor: nodelts
|
||||
executor: node8
|
||||
environment:
|
||||
- CI_BRANCH: $CIRCLE_BRANCH
|
||||
- CI_PULL_REQUEST: $CIRCLE_PULL_REQUEST
|
||||
@@ -699,31 +557,22 @@ jobs:
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run:
|
||||
name: Collect test coverage information
|
||||
name: Test coverage
|
||||
command: |
|
||||
scripts/circleci/exec_swallow_error.sh yarn test --coverage --maxWorkers=2
|
||||
if [[ -e ./coverage/lcov.info ]]; then
|
||||
cat ./coverage/lcov.info | scripts/circleci/exec_swallow_error.sh ./node_modules/.bin/coveralls
|
||||
fi
|
||||
yarn test --coverage --maxWorkers=2
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: ~/react-native/coverage/
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Releases
|
||||
# -------------------------
|
||||
# Publishes a new version onto npm
|
||||
# Publishes new version onto npm
|
||||
# Only works on stable branches when a properly tagged commit is pushed
|
||||
publish_npm_package:
|
||||
parameters:
|
||||
publish_npm_args:
|
||||
type: string
|
||||
default: --nonightly
|
||||
executor: reactnativeandroid
|
||||
steps:
|
||||
- run:
|
||||
name: Add github.com to SSH known hosts
|
||||
command: |
|
||||
mkdir -p ~/.ssh
|
||||
echo '|1|If6MU203eXTaaWL678YEfWkVMrw=|kqLeIAyTy8pzpj8x8Ae4Fr8Mtlc= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
- run_yarn
|
||||
@@ -735,25 +584,12 @@ jobs:
|
||||
git config --global user.email "react-native-bot@users.noreply.github.com"
|
||||
git config --global user.name "npm Deployment Script"
|
||||
echo "machine github.com login react-native-bot password $GITHUB_TOKEN" > ~/.netrc
|
||||
- run: node ./scripts/publish-npm.js << parameters.publish_npm_args >>
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Nightly
|
||||
# -------------------------
|
||||
nightly_job:
|
||||
machine: true
|
||||
steps:
|
||||
- run:
|
||||
name: Nightly
|
||||
command: |
|
||||
echo "Nightly build run"
|
||||
- run: node ./scripts/publish-npm.js
|
||||
|
||||
# -------------------------
|
||||
# WORK FLOWS
|
||||
# -------------------------
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
tests:
|
||||
jobs:
|
||||
- setup:
|
||||
@@ -776,67 +612,36 @@ workflows:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- test_js:
|
||||
run_disabled_tests: false
|
||||
requires:
|
||||
- setup_js
|
||||
- test_js_e2e:
|
||||
requires:
|
||||
- setup_js
|
||||
- test_js
|
||||
- test_android:
|
||||
run_disabled_tests: false
|
||||
requires:
|
||||
- setup_android
|
||||
- test_ios:
|
||||
name: test_ios_unit_jsc
|
||||
run_unit_tests: true
|
||||
requires:
|
||||
- setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_unit_frameworks_jsc
|
||||
# use_frameworks: true
|
||||
# run_unit_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
- test_ios:
|
||||
name: test_ios_unit_hermes
|
||||
use_hermes: true
|
||||
run_unit_tests: true
|
||||
name: test_ios_frameworks
|
||||
use_frameworks: true
|
||||
requires:
|
||||
- setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_unit_frameworks_hermes
|
||||
# use_hermes: true
|
||||
# use_frameworks: true
|
||||
# run_unit_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
# DISABLED: Detox tests need to be fixed
|
||||
# - test_ios:
|
||||
# name: test_ios_detox
|
||||
# run_detox_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_detox_frameworks
|
||||
# use_frameworks: true
|
||||
# run_detox_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
- test_ios_e2e:
|
||||
requires:
|
||||
- setup_ios
|
||||
- test_js
|
||||
- test_js:
|
||||
name: test_js_prev_lts
|
||||
executor: nodeprevlts
|
||||
name: test_js_lts
|
||||
executor: nodelts
|
||||
requires:
|
||||
- setup_js
|
||||
- test_docker:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- test_windows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
run_disabled_tests: false
|
||||
|
||||
releases:
|
||||
jobs:
|
||||
- setup:
|
||||
@@ -865,18 +670,11 @@ workflows:
|
||||
|
||||
analysis:
|
||||
jobs:
|
||||
- setup:
|
||||
name: setup_js
|
||||
|
||||
- setup:
|
||||
name: setup_android
|
||||
checkout_type: android
|
||||
executor: reactnativeandroid
|
||||
|
||||
- setup
|
||||
# Run lints on every commit other than those to the gh-pages branch
|
||||
- analyze_code:
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
@@ -884,7 +682,7 @@ workflows:
|
||||
# Run code checks on PRs from forks
|
||||
- analyze_pr:
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
filters:
|
||||
branches:
|
||||
only: /^pull\/.*$/
|
||||
@@ -892,24 +690,4 @@ workflows:
|
||||
# Gather coverage
|
||||
- js_coverage:
|
||||
requires:
|
||||
- setup_js
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- nightly_job
|
||||
|
||||
- setup:
|
||||
name: setup_android
|
||||
checkout_type: android
|
||||
executor: reactnativeandroid
|
||||
|
||||
- publish_npm_package:
|
||||
publish_npm_args: --nightly
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
|
||||
@@ -13,12 +13,5 @@ indent_size = 2
|
||||
[*.gradle]
|
||||
indent_size = 4
|
||||
|
||||
[*.kts]
|
||||
indent_size = 4
|
||||
|
||||
[BUCK]
|
||||
indent_size = 4
|
||||
|
||||
# Windows files
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
|
||||
+8
-7
@@ -1,10 +1,11 @@
|
||||
**/main.js
|
||||
# node_modules ignored by default
|
||||
|
||||
**/staticBundle.js
|
||||
bots/node_modules
|
||||
docs/generatedComponentApiDocs.js
|
||||
flow/
|
||||
Libraries/Renderer/*
|
||||
**/main.js
|
||||
Libraries/vendor/**/*
|
||||
node_modules/
|
||||
Libraries/Renderer/*
|
||||
packages/*/node_modules
|
||||
packages/react-native-codegen/lib
|
||||
pr-inactivity-bookmarklet.js
|
||||
question-bookmarklet.js
|
||||
flow/
|
||||
bots/node_modules
|
||||
@@ -5,28 +5,14 @@
|
||||
"./packages/eslint-config-react-native-community/index.js"
|
||||
],
|
||||
|
||||
"plugins": [
|
||||
"@react-native/eslint-plugin-codegen"
|
||||
],
|
||||
|
||||
"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/codegen/react-native-modules": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"flow-typed/**/*.js",
|
||||
],
|
||||
"rules": {
|
||||
quotes: 0
|
||||
rules: {
|
||||
'@react-native-community/no-haste-imports': 2,
|
||||
'@react-native-community/error-subclass-name': 2,
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -35,7 +21,7 @@
|
||||
"**/__mocks__/**/*.js",
|
||||
"**/__tests__/**/*.js",
|
||||
"jest/**/*.js",
|
||||
"packages/rn-tester/**/*.js",
|
||||
"RNTester/**/*.js",
|
||||
],
|
||||
"globals": {
|
||||
// Expose some Jest globals for test helpers
|
||||
@@ -53,8 +39,8 @@
|
||||
],
|
||||
"env": {
|
||||
"jasmine": true,
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
"jest": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
+19
-5
@@ -11,6 +11,10 @@
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
.*/node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
@@ -29,10 +33,10 @@ flow/
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
indexed_access=false
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
format.bracket_spacing=false
|
||||
exact_by_default=true
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
@@ -50,16 +54,26 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FlowFixMeEmpty
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
experimental.well_formed_exports=true
|
||||
experimental.types_first=true
|
||||
experimental.abstract_locations=true
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -71,4 +85,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.158.0
|
||||
^0.113.0
|
||||
|
||||
+19
-5
@@ -11,6 +11,10 @@
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
.*/node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
@@ -29,10 +33,10 @@ flow/
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
indexed_access=false
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
format.bracket_spacing=false
|
||||
exact_by_default=true
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
@@ -50,16 +54,26 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FlowFixMeEmpty
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
experimental.well_formed_exports=true
|
||||
experimental.types_first=true
|
||||
experimental.abstract_locations=true
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -71,4 +85,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.158.0
|
||||
^0.113.0
|
||||
|
||||
+6
-3
@@ -1,3 +1,6 @@
|
||||
# Windows files should use crlf line endings
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
*.bat text eol=crlf
|
||||
# Force LF line endings for Bash scripts. On Windows the rest of the source
|
||||
# files will typically have CR+LF endings (Git default on Windows), but Bash
|
||||
# scripts need to have LF endings to work (under Cygwin), thus override to force
|
||||
# that.
|
||||
gradlew text eol=lf
|
||||
*.sh text eol=lf
|
||||
|
||||
@@ -35,6 +35,3 @@ Libraries/Text/* @shergin
|
||||
|
||||
# These should not be modified through a GitHub PR
|
||||
LICENSE* @hramos @cpojer @yungsters
|
||||
|
||||
# The eslint-config-react-native-community package requires manual publishing after merging
|
||||
/packages/eslint-config-react-native-community/* @matt-oakes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
✋ To keep the backlog clean and actionable, issues will be
|
||||
🚫 closed if they do not follow one of the issue templates:
|
||||
👉 https://github.com/facebook/react-native/issues/new/choose
|
||||
👉 Please follow one of these issue templates:
|
||||
- https://github.com/facebook/react-native/issues/new/choose
|
||||
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||
|
||||
@@ -2,29 +2,39 @@
|
||||
name: "🐛 Bug Report"
|
||||
about: Report a reproducible bug or regression in React Native.
|
||||
title: ''
|
||||
labels: 'Needs: Triage :mag:'
|
||||
labels: 'Bug'
|
||||
|
||||
---
|
||||
|
||||
Please provide all the information requested. Issues that do not follow this format are likely to stall.
|
||||
<!--
|
||||
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: http://facebook.github.io/react-native/docs/upgrading.html
|
||||
-->
|
||||
|
||||
## 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
|
||||
|
||||
## React Native version:
|
||||
Run `react-native info` in your terminal and copy the results here.
|
||||
React Native version:
|
||||
<!--
|
||||
Run `react-native info` in your terminal and copy the results here.
|
||||
-->
|
||||
|
||||
## Steps To Reproduce
|
||||
Provide a detailed list of steps that reproduce the issue.
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
## Expected Results
|
||||
Describe what you expected to happen.
|
||||
<!--
|
||||
Issues without reproduction steps or code are likely to stall.
|
||||
-->
|
||||
|
||||
Describe what you expected to happen:
|
||||
|
||||
|
||||
Snack, code example, screenshot, or link to a repository:
|
||||
|
||||
<!--
|
||||
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. --
|
||||
-->
|
||||
|
||||
## Snack, code example, screenshot, or link to a repository:
|
||||
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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃 Documentation Issue
|
||||
url: https://github.com/facebook/react-native-website/issues
|
||||
about: Please report documentation issues in the React Native website repository.
|
||||
- name: ⤴️ Upgrade Issue
|
||||
url: https://github.com/react-native-community/upgrade-support
|
||||
about: Need help upgrading to a newer React Native version? Visit the Upgrade Support repository.
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://reactnative.dev/help
|
||||
about: Looking for help with your app? Please refer to the React Native community's support resources.
|
||||
- name: 🚀 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.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: "📃 Documentation Issue"
|
||||
about: Documentation issues are handled in https://github.com/facebook/react-native-website.
|
||||
title: 'Docs:'
|
||||
labels: 'Type: Docs'
|
||||
|
||||
---
|
||||
|
||||
🚨 Please do not open a documentation issue in the core React Native repository. 🚨
|
||||
|
||||
The React Native website is hosted on a separate repository. You may let the
|
||||
team know about any issues with the documentation by opening an issue there:
|
||||
- https://github.com/facebook/react-native-website
|
||||
- https://github.com/facebook/react-native-website/issues
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: "🤔 Questions and Help"
|
||||
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/react-native.
|
||||
title: 'Question: '
|
||||
labels: 'Type: Question'
|
||||
|
||||
---
|
||||
|
||||
🚨 The issue tracker is not for questions. 🚨
|
||||
|
||||
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek. Please take a look at the following resources.
|
||||
|
||||
## Coding Questions
|
||||
|
||||
### https://stackoverflow.com/questions/tagged/react-native
|
||||
|
||||
If you have a coding question related to React Native, it might be better suited for Stack Overflow. It's a great place to browse through frequent questions about using React Native, as well as ask for help with specific questions.
|
||||
|
||||
|
||||
## Talk to other React Native developers
|
||||
|
||||
### https://www.reactiflux.com/
|
||||
|
||||
Reactiflux is an active community of React and React Native developers. If you are looking for immediate assistance or have a general question about React Native, the #react-native channel is a good place to start.
|
||||
|
||||
If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums:
|
||||
- Discord Community: https://discord.gg/0ZcbPKXt5bZjGY5n
|
||||
- Spectrum Chat: https://spectrum.chat/react-native
|
||||
- Facebook Group: https://www.facebook.com/groups/react.native.community
|
||||
|
||||
If you'd like to discuss topics related to the future of React Native, or would like to propose a new feature or change before sending a pull request, please check out the discussions and proposals repo:
|
||||
- https://github.com/react-native-community/discussions-and-proposals
|
||||
|
||||
> For a full list of community resources, check out React Native's Community page at https://facebook.github.io/react-native/help.
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
|
||||
https://github.com/facebook/react-native/wiki/Changelog
|
||||
-->
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry. See https://github.com/facebook/react-native/wiki/Changelog for an example. -->
|
||||
|
||||
[CATEGORY] [TYPE] - Message
|
||||
|
||||
|
||||
+2
-2
@@ -27,9 +27,9 @@ If you'd like to discuss topics related to the future of React Native, please ch
|
||||
|
||||
If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums:
|
||||
|
||||
- [Reactiflux Discord Server](https://www.reactiflux.com)
|
||||
- [Reactiflux Discord Server](https://www.reactiflux)
|
||||
- [Spectrum Chat](https://spectrum.chat/react-native)
|
||||
- [React Native Community Facebook Group](https://www.facebook.com/groups/react.native.community)
|
||||
|
||||
|
||||
> For a full list of community resources, check out [React Native's Community page](https://reactnative.dev/help).
|
||||
> For a full list of community resources, check out [React Native's Community page](https://facebook.github.io/react-native/help).
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# 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
|
||||
"Type: Upgrade Issue":
|
||||
comment: >
|
||||
Do you need help upgrading to a newer React Native version? Visit the [Upgrade Support repository](https://github.com/react-native-community/upgrade-support) or use the [upgrade helper](https://react-native-community.github.io/upgrade-helper/) to see the changes that need to be made to upgrade your app.
|
||||
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,21 +0,0 @@
|
||||
name: Issue Needs Attention
|
||||
# This workflow is triggered on issue comments.
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
applyNeedsAttentionLabel:
|
||||
name: Apply Needs Attention Label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Apply Needs Attention Label
|
||||
uses: hramos/needs-attention@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
response-required-label: "Needs: Author Feedback"
|
||||
needs-attention-label: "Needs: Attention"
|
||||
id: needs-attention
|
||||
- name: Result
|
||||
run: echo '${{ steps.needs-attention.outputs.result }}'
|
||||
@@ -1,16 +0,0 @@
|
||||
name: On Issue Labeled
|
||||
# This workflow is triggered when a label is added to an issue.
|
||||
on:
|
||||
issues:
|
||||
types: labeled
|
||||
|
||||
jobs:
|
||||
respondToIssueBasedOnLabel:
|
||||
name: Respond to Issue Based on Label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Respond to Issue Based on Label
|
||||
uses: hramos/respond-to-issue-based-on-label@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+13
-31
@@ -23,31 +23,19 @@ project.xcworkspace
|
||||
|
||||
# Gradle
|
||||
/build/
|
||||
/packages/react-native-codegen/android/build/
|
||||
/packages/react-native-gradle-plugin/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
|
||||
/RNTester/android/app/build/
|
||||
/RNTester/android/app/gradle/
|
||||
/RNTester/android/app/gradlew
|
||||
/RNTester/android/app/gradlew.bat
|
||||
/ReactAndroid/build/
|
||||
/ReactAndroid/gradle/
|
||||
/ReactAndroid/gradlew
|
||||
/ReactAndroid/gradlew.bat
|
||||
|
||||
# Buck
|
||||
.buckd
|
||||
buck-out
|
||||
/.lsp.buckd
|
||||
/.lsp-buck-out
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/x86/
|
||||
/ReactAndroid/src/main/gen
|
||||
|
||||
# Android Studio
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# Watchman
|
||||
.watchmanconfig
|
||||
|
||||
@@ -80,34 +68,28 @@ package-lock.json
|
||||
|
||||
# ReactCommon subdir shouldn't have Xcode project
|
||||
/ReactCommon/**/*.xcodeproj
|
||||
/packages/rn-tester/build
|
||||
/packages/rn-tester/android/app/build/*
|
||||
RNTester/build
|
||||
|
||||
# Libs that shouldn't have Xcode project
|
||||
/Libraries/FBLazyVector/**/*.xcodeproj
|
||||
/Libraries/FBReactNativeSpec/**/*.xcodeproj
|
||||
/Libraries/RCTRequired/**/*.xcodeproj
|
||||
/React/CoreModules/**/*.xcodeproj
|
||||
/React/FBReactNativeSpec/**/*.xcodeproj
|
||||
/packages/react-native-codegen/**/*.xcodeproj
|
||||
|
||||
# CocoaPods
|
||||
/template/ios/Pods/
|
||||
/template/ios/Podfile.lock
|
||||
/packages/rn-tester/Gemfile.lock
|
||||
/RNTester/Gemfile.lock
|
||||
|
||||
# Ignore RNTester specific Pods, but keep the __offline_mirrors__ here.
|
||||
/packages/rn-tester/Pods/*
|
||||
!/packages/rn-tester/Pods/__offline_mirrors__
|
||||
RNTester/Pods/*
|
||||
!RNTester/Pods/__offline_mirrors
|
||||
|
||||
# react-native-codegen
|
||||
/React/FBReactNativeSpec/FBReactNativeSpec
|
||||
/packages/react-native-codegen/lib
|
||||
/ReactCommon/react/renderer/components/rncore/
|
||||
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
|
||||
/ReactCommon/fabric/components/rncore/
|
||||
/schema-rncore.json
|
||||
|
||||
# Visual studio
|
||||
.vscode
|
||||
.vs
|
||||
|
||||
# Android memory profiler files
|
||||
*.hprof
|
||||
|
||||
+1
-2
@@ -3,6 +3,5 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": false,
|
||||
"jsxBracketSameLine": true,
|
||||
"arrowParens": "avoid"
|
||||
"jsxBracketSameLine": true
|
||||
}
|
||||
|
||||
+22
-26
@@ -8,9 +8,9 @@ The [Open Source Guides](https://opensource.guide/) website has a collection of
|
||||
* [Building Welcoming Communities](https://opensource.guide/building-community/)
|
||||
|
||||
|
||||
### [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md)
|
||||
### [Code of Conduct](https://github.com/facebook/react/blob/master/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).
|
||||
As a reminder, all contributors are expected to adhere to the [Code of Conduct](https://github.com/facebook/react/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
@@ -18,7 +18,7 @@ If you are eager to start contributing code right away, we have a list of [good
|
||||
|
||||
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).
|
||||
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/Issues#triage).
|
||||
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.
|
||||
|
||||
@@ -30,8 +30,8 @@ We use GitHub issues and pull requests to keep track of bug reports and contribu
|
||||
|
||||
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)
|
||||
* [Issues](https://github.com/facebook/react-native/wiki/Issues)
|
||||
* [Pull Requests](https://github.com/facebook/react-native/wiki/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).
|
||||
|
||||
@@ -42,7 +42,7 @@ The main repository, <https://github.com/facebook/react-native>, contains the Re
|
||||
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** are coordinated through the <https://github.com/react-native-community/releases> repository. This includes important documents such as the Changelog.
|
||||
* **Releases** are coordinated through the <https://github.com/react-native-community/react-native-releases> repository. This includes important documents such as the Changelog.
|
||||
* **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/).
|
||||
|
||||
@@ -50,7 +50,7 @@ Browsing through these repositories should provide some insight into how the Rea
|
||||
|
||||
## 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).
|
||||
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/Issues).
|
||||
|
||||
### Security Bugs
|
||||
|
||||
@@ -58,46 +58,42 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
|
||||
## 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.
|
||||
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://facebook.github.io/react-native> 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).
|
||||
The React Native blog is generated [from the Markdown sources for the blog](https://github.com/facebook/react-native-website/tree/master/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.
|
||||
We recommend referring to the [CONTRIBUTING](https://github.com/facebook/react-native-website/blob/master/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.
|
||||
Code-level contributions to React Native generally come in the form of [pull requests](https://help.github.com/en/articles/about-pull-requests). The process of proposing a change to React Native can be summarized as follows:
|
||||
|
||||
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. Fork the React Native repository and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes, either locally or on CI once you opened a pull request.
|
||||
5. Make sure your code lints (for example via `yarn lint --fix`).
|
||||
6. Push the changes to your fork.
|
||||
7. Create a pull request to the React Native repository.
|
||||
8. 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)**
|
||||
9. 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.
|
||||
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/Pull-Requests#getting-ready-to-submit-your-first-pull-request), or read the [How to Contribute Code](https://github.com/facebook/react-native/wiki/How-to-Contribute) 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).
|
||||
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/master) 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
|
||||
|
||||
|
||||
+4
-5
@@ -10,15 +10,14 @@ There are three types of stakeholders:
|
||||
|
||||
## Partners
|
||||
|
||||
Partners are companies that are significantly invested in React Native and have been for years. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Partners think of React Native as a product: they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications.
|
||||
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. Facebook's 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:
|
||||
Our current set of partners include Callstack, Expo, Infinite Red, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include:
|
||||
|
||||
* **[Callstack](https://callstack.com/):** Manages releases, maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and organizes [React Native EU](https://react-native.eu/)
|
||||
* **[Expo](https://expo.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
|
||||
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://infinite.red/ChainReactConf)
|
||||
* **[Microsoft](https://www.microsoft.com/en-gb/):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) for the Universal Windows Platform (UWP)
|
||||
* **[Software Mansion](https://swmansion.com/):** Maintain core infrastructure including JSC, Animated, and other popular third-party plugins.
|
||||
|
||||
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:
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
import invariant from 'invariant';
|
||||
import NativeAccessibilityManager from 'react-native/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager';
|
||||
import {DeviceEventEmitter, NativeModules, View} from 'react-native';
|
||||
import * as React from 'react';
|
||||
'use strict';
|
||||
|
||||
const {TestModule} = NativeModules;
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
const RCTDeviceEventEmitter = require('react-native/Libraries/EventEmitter/RCTDeviceEventEmitter');
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import NativeAccessibilityManager from 'react-native/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager';
|
||||
import invariant from 'invariant';
|
||||
|
||||
class AccessibilityManagerTest extends React.Component<{...}> {
|
||||
componentDidMount() {
|
||||
@@ -36,7 +39,7 @@ class AccessibilityManagerTest extends React.Component<{...}> {
|
||||
accessibilityExtraExtraLarge: 11.0,
|
||||
accessibilityExtraExtraExtraLarge: 12.0,
|
||||
});
|
||||
DeviceEventEmitter.addListener('didUpdateDimensions', update => {
|
||||
RCTDeviceEventEmitter.addListener('didUpdateDimensions', update => {
|
||||
TestModule.markTestPassed(update.window.fontScale === 4.0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ 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;
|
||||
|
||||
@@ -44,32 +43,15 @@ function expectTrue(condition: boolean, message: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) +
|
||||
JSON.stringify(rhs) +
|
||||
'\ngot\n' +
|
||||
stringify(lhs),
|
||||
JSON.stringify(lhs),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +61,7 @@ function expectAsyncNoError(place, err) {
|
||||
}
|
||||
expectTrue(
|
||||
err === null,
|
||||
'Unexpected error in ' + place + ': ' + stringify(err),
|
||||
'Unexpected error in ' + place + ': ' + JSON.stringify(err),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +71,7 @@ function testSetAndGet() {
|
||||
AsyncStorage.getItem(KEY_1, (err2, result) => {
|
||||
expectAsyncNoError('testSetAndGet/getItem', err2);
|
||||
expectEqual(result, VAL_1, 'testSetAndGet setItem');
|
||||
updateMessage('get(key_1) correctly returned ' + String(result));
|
||||
updateMessage('get(key_1) correctly returned ' + result);
|
||||
runTestCase('should get null for missing key', testMissingGet);
|
||||
});
|
||||
});
|
||||
@@ -99,7 +81,7 @@ 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));
|
||||
updateMessage('missing get(key_2) correctly returned ' + result);
|
||||
runTestCase('check set twice results in a single key', testSetTwice);
|
||||
});
|
||||
}
|
||||
@@ -123,9 +105,8 @@ function testRemoveItem() {
|
||||
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() + ')',
|
||||
result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0,
|
||||
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')',
|
||||
);
|
||||
updateMessage('testRemoveItem - add two items');
|
||||
AsyncStorage.removeItem(KEY_1, err2 => {
|
||||
@@ -142,8 +123,8 @@ function testRemoveItem() {
|
||||
AsyncStorage.getAllKeys((err4, result3) => {
|
||||
expectAsyncNoError('testRemoveItem/getAllKeys', err4);
|
||||
expectTrue(
|
||||
nullthrows(result3).indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + nullthrows(result3).join(),
|
||||
result3.indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + result3,
|
||||
);
|
||||
updateMessage('proper length returned.');
|
||||
runTestCase('should merge values', testMerge);
|
||||
@@ -156,17 +137,13 @@ function testRemoveItem() {
|
||||
}
|
||||
|
||||
function testMerge() {
|
||||
AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => {
|
||||
AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), err1 => {
|
||||
expectAsyncNoError('testMerge/setItem', err1);
|
||||
AsyncStorage.mergeItem(KEY_MERGE, stringify(VAL_MERGE_2), err2 => {
|
||||
AsyncStorage.mergeItem(KEY_MERGE, JSON.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',
|
||||
);
|
||||
expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
|
||||
updateMessage('objects deeply merged\nDone!');
|
||||
runTestCase('multi set and get', testOptimizedMultiGet);
|
||||
});
|
||||
@@ -175,10 +152,7 @@ function testMerge() {
|
||||
}
|
||||
|
||||
function testOptimizedMultiGet() {
|
||||
let batch = [
|
||||
[KEY_1, VAL_1],
|
||||
[KEY_2, VAL_2],
|
||||
];
|
||||
let batch = [[KEY_1, VAL_1], [KEY_2, VAL_2]];
|
||||
let keys = batch.map(([key, value]) => key);
|
||||
AsyncStorage.multiSet(batch, err1 => {
|
||||
// yes, twice on purpose
|
||||
@@ -188,7 +162,8 @@ function testOptimizedMultiGet() {
|
||||
expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2);
|
||||
expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`);
|
||||
updateMessage(
|
||||
'multiGet([key_1, key_2]) correctly returned ' + stringify(result),
|
||||
'multiGet([key_1, key_2]) correctly returned ' +
|
||||
JSON.stringify(result),
|
||||
);
|
||||
done();
|
||||
});
|
||||
@@ -218,10 +193,9 @@ class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> {
|
||||
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.
|
||||
*/
|
||||
{/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
{'\n\n' + this.state.messages}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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
|
||||
# js1 build buckfiles
|
||||
|
||||
rn_library(
|
||||
name = "IntegrationTests",
|
||||
srcs = js_glob(
|
||||
[
|
||||
"**/*",
|
||||
],
|
||||
excludes = [
|
||||
"**/__*__/**",
|
||||
"**/*.command",
|
||||
"**/*.md",
|
||||
"websocket_integration_test_server.js",
|
||||
],
|
||||
),
|
||||
labels = ["supermodule:xplat/default/public.react_native.tests"],
|
||||
skip_processors = True,
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
"//xplat/js:node_modules__invariant",
|
||||
"//xplat/js:node_modules__nullthrows",
|
||||
"//xplat/js/RKJSModules/vendor/react:react",
|
||||
"//xplat/js/react-native-github:react-native",
|
||||
"//xplat/js/react-native-github/packages/assets:assets",
|
||||
],
|
||||
)
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ExtendedError} from 'react-native/Libraries/Core/ExtendedError';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack');
|
||||
@@ -33,7 +31,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl(expression) to return a value',
|
||||
);
|
||||
}
|
||||
let syntaxError: ?ExtendedError;
|
||||
let syntaxError;
|
||||
try {
|
||||
global.globalEvalWithSourceUrl('{');
|
||||
} catch (e) {
|
||||
@@ -44,12 +42,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl to throw on a syntax error',
|
||||
);
|
||||
}
|
||||
// Hermes throws an Error instead of a SyntaxError
|
||||
// https://github.com/facebook/hermes/issues/400
|
||||
if (
|
||||
syntaxError.jsEngine !== 'hermes' &&
|
||||
!(syntaxError instanceof SyntaxError)
|
||||
) {
|
||||
if (!(syntaxError instanceof SyntaxError)) {
|
||||
throw new Error(
|
||||
'Expected globalEvalWithSourceUrl to throw SyntaxError on a syntax error',
|
||||
);
|
||||
@@ -66,7 +59,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl to throw an Error object',
|
||||
);
|
||||
}
|
||||
const parsedStack = parseErrorStack(error?.stack);
|
||||
const parsedStack = parseErrorStack(error);
|
||||
if (parsedStack[0].file !== url) {
|
||||
throw new Error(
|
||||
`Expected first eval stack frame to be in ${url} but found ${String(
|
||||
|
||||
@@ -64,7 +64,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://raw.githubusercontent.com/facebook/react-native/master/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'only-if-cached',
|
||||
}}
|
||||
@@ -75,7 +75,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://raw.githubusercontent.com/facebook/react-native/master/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'default',
|
||||
}}
|
||||
@@ -86,7 +86,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://raw.githubusercontent.com/facebook/react-native/master/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'reload',
|
||||
}}
|
||||
@@ -97,7 +97,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://raw.githubusercontent.com/facebook/react-native/master/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'force-cache',
|
||||
}}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
const {Text, View, StyleSheet} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
@@ -56,10 +57,9 @@ class IntegrationTestHarnessTest extends React.Component<Props, State> {
|
||||
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.
|
||||
*/
|
||||
{/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
</Text>
|
||||
|
||||
@@ -40,9 +40,9 @@ const TESTS = [
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
/* $FlowFixMe[incompatible-call] (>=0.54.0 site=react_native_fb,react_native_
|
||||
* oss) This comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
||||
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
||||
* delete this comment and run Flow. */
|
||||
test => AppRegistry.registerComponent(test.displayName, () => test),
|
||||
);
|
||||
|
||||
@@ -60,10 +60,9 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
if (this.state.test) {
|
||||
return (
|
||||
<ScrollView>
|
||||
{/* $FlowFixMe[type-as-value] (>=0.53.0 site=react_native_fb,react_
|
||||
* native_oss) This comment suppresses an error when upgrading
|
||||
* Flow's support for React. To see the error delete this comment
|
||||
* and run Flow. */}
|
||||
{/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */}
|
||||
<this.state.test />
|
||||
</ScrollView>
|
||||
);
|
||||
@@ -80,10 +79,6 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
{TESTS.map(test => [
|
||||
<TouchableOpacity
|
||||
onPress={() => this.setState({test})}
|
||||
/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_native_fb)
|
||||
* This comment suppresses an error found when Flow v0.115 was
|
||||
* deployed. To see the error, delete this comment and run Flow.
|
||||
*/
|
||||
style={styles.row}>
|
||||
<Text style={styles.testName}>{test.displayName}</Text>
|
||||
</TouchableOpacity>,
|
||||
|
||||
@@ -197,5 +197,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
LayoutEventsTest.displayName = 'LayoutEventsTest';
|
||||
module.exports = LayoutEventsTest;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
|
||||
|
||||
const warning = require('fbjs/lib/warning');
|
||||
const invariant = require('invariant');
|
||||
|
||||
const LoggingTestModule = {
|
||||
@@ -23,7 +24,7 @@ const LoggingTestModule = {
|
||||
}, timeout_ms);
|
||||
},
|
||||
warning: function(str) {
|
||||
console.warn(str);
|
||||
warning(false, str);
|
||||
},
|
||||
invariant: function(str) {
|
||||
invariant(false, str);
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
@@ -15,7 +17,7 @@ const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
||||
import type EmitterSubscription from 'react-native/Libraries/vendor/emitter/EmitterSubscription';
|
||||
|
||||
const reactViewWidth = 101;
|
||||
const reactViewHeight = 102;
|
||||
@@ -31,7 +33,7 @@ type State = {|
|
||||
|
||||
class ReactContentSizeUpdateTest extends React.Component<Props, State> {
|
||||
_timeoutID: ?TimeoutID = null;
|
||||
_subscription: ?EventSubscription = null;
|
||||
_subscription: ?EmitterSubscription = null;
|
||||
|
||||
state: State = {
|
||||
height: reactViewHeight,
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
|
||||
const {StyleSheet, View} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
||||
import type EmitterSubscription from 'react-native/Libraries/vendor/emitter/EmitterSubscription';
|
||||
|
||||
const reactViewWidth = 111;
|
||||
const reactViewHeight = 222;
|
||||
@@ -29,7 +31,7 @@ type Props = $ReadOnly<{|
|
||||
|}>;
|
||||
|
||||
class SizeFlexibilityUpdateTest extends React.Component<Props> {
|
||||
_subscription: ?EventSubscription = null;
|
||||
_subscription: ?EmitterSubscription = null;
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this._subscription = RCTNativeAppEventEmitter.addListener(
|
||||
|
||||
@@ -97,46 +97,36 @@ class TimersTest extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout0, 1000);
|
||||
}
|
||||
|
||||
testSetTimeout0() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout1, 0);
|
||||
}
|
||||
|
||||
testSetTimeout1() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout50, 1);
|
||||
}
|
||||
|
||||
testSetTimeout50() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testRequestAnimationFrame, 50);
|
||||
}
|
||||
|
||||
testRequestAnimationFrame() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.requestAnimationFrame(this.testSetInterval0);
|
||||
}
|
||||
|
||||
testSetInterval0() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._nextTest = this.testSetInterval20;
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._interval = this.setInterval(this._incrementInterval, 0);
|
||||
}
|
||||
|
||||
testSetInterval20() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._nextTest = this.testSetImmediate;
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._interval = this.setInterval(this._incrementInterval, 20);
|
||||
}
|
||||
|
||||
testSetImmediate() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setImmediate(this.testClearTimeout0);
|
||||
}
|
||||
|
||||
@@ -149,7 +139,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
testClearTimeout30() {
|
||||
const timeout = this.setTimeout(() => this._fail('testClearTimeout30'), 30);
|
||||
this.clearTimeout(timeout);
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testClearMulti, 50);
|
||||
}
|
||||
|
||||
@@ -167,7 +156,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
fails.forEach(timeout => this.clearTimeout(timeout));
|
||||
this.setTimeout(() => this.clearTimeout(delayClear), 20);
|
||||
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testOrdering, 50);
|
||||
}
|
||||
|
||||
@@ -203,7 +191,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
),
|
||||
25,
|
||||
);
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.done, 50);
|
||||
}
|
||||
|
||||
@@ -262,7 +249,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
this.clearInterval(this._interval);
|
||||
this._interval = null;
|
||||
}
|
||||
// $FlowFixMe[method-unbinding]
|
||||
this.setState({count: 0}, this._nextTest);
|
||||
return;
|
||||
}
|
||||
@@ -281,5 +267,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
TimersTest.displayName = 'TimersTest';
|
||||
module.exports = TimersTest;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// A little helper to make sure we have the right memory allocation ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when deallocated.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat *array;
|
||||
} ARTCGFloatArray;
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol ARTContainer <NSObject>
|
||||
|
||||
// This is used as a hook for child to mark it's parent as dirty.
|
||||
// This bubbles up to the root which gets marked as dirty.
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTGroup : ARTNode <ARTContainer>
|
||||
|
||||
@property (nonatomic, assign) CGRect clipping;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTGroup.h>
|
||||
|
||||
@implementation ARTGroup
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
|
||||
if (!CGRectIsEmpty(self.clipping)) {
|
||||
CGContextClipToRect(context, self.clipping);
|
||||
}
|
||||
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
/**
|
||||
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
|
||||
* into the existing view management. They should also be shadow views and painted on a background
|
||||
* thread.
|
||||
*/
|
||||
|
||||
@interface ARTNode : UIView
|
||||
|
||||
@property (nonatomic, assign) CGFloat opacity;
|
||||
|
||||
- (void)invalidate;
|
||||
- (void)renderTo:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity
|
||||
* specified, then composite that onto the context. renderLayerTo always draws at opacity=1.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)renderLayerTo:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
#import <React/ARTContainer.h>
|
||||
|
||||
@implementation ARTNode
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)setOpacity:(CGFloat)opacity
|
||||
{
|
||||
[self invalidate];
|
||||
_opacity = opacity;
|
||||
}
|
||||
|
||||
- (void)setTransform:(CGAffineTransform)transform
|
||||
{
|
||||
[self invalidate];
|
||||
super.transform = transform;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
id<ARTContainer> container = (id<ARTContainer>)self.superview;
|
||||
[container invalidate];
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0) {
|
||||
// Nothing to paint
|
||||
return;
|
||||
}
|
||||
if (self.opacity >= 1) {
|
||||
// Just paint at full opacity
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, 1);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
return;
|
||||
}
|
||||
// This needs to be painted on a layer before being composited.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
[self renderLayerTo:context];
|
||||
CGContextEndTransparencyLayer(context);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTRenderable : ARTNode
|
||||
|
||||
@property (nonatomic, strong) ARTBrush *fill;
|
||||
@property (nonatomic, assign) CGColorRef stroke;
|
||||
@property (nonatomic, assign) CGFloat strokeWidth;
|
||||
@property (nonatomic, assign) CGLineCap strokeCap;
|
||||
@property (nonatomic, assign) CGLineJoin strokeJoin;
|
||||
@property (nonatomic, assign) ARTCGFloatArray strokeDash;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRenderable.h>
|
||||
|
||||
@implementation ARTRenderable
|
||||
|
||||
- (void)setFill:(ARTBrush *)fill
|
||||
{
|
||||
[self invalidate];
|
||||
_fill = fill;
|
||||
}
|
||||
|
||||
- (void)setStroke:(CGColorRef)stroke
|
||||
{
|
||||
if (stroke == _stroke) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGColorRelease(_stroke);
|
||||
_stroke = CGColorRetain(stroke);
|
||||
}
|
||||
|
||||
- (void)setStrokeWidth:(CGFloat)strokeWidth
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeWidth = strokeWidth;
|
||||
}
|
||||
|
||||
- (void)setStrokeCap:(CGLineCap)strokeCap
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeCap = strokeCap;
|
||||
}
|
||||
|
||||
- (void)setStrokeJoin:(CGLineJoin)strokeJoin
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeJoin = strokeJoin;
|
||||
}
|
||||
|
||||
- (void)setStrokeDash:(ARTCGFloatArray)strokeDash
|
||||
{
|
||||
if (strokeDash.array == _strokeDash.array) {
|
||||
return;
|
||||
}
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
[self invalidate];
|
||||
_strokeDash = strokeDash;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_stroke);
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0 || self.opacity >= 1 || (self.fill && self.stroke)) {
|
||||
// If we have both fill and stroke, we will need to paint this using normal compositing
|
||||
[super renderTo: context];
|
||||
return;
|
||||
}
|
||||
// This is a terminal with only one painting. Therefore we don't need to paint this
|
||||
// off-screen. We can just composite it straight onto the buffer.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol RCTInitializing <NSObject>
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
- (void)initialize;
|
||||
@interface ARTShape : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CGPathRef d;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTShape.h>
|
||||
|
||||
@implementation ARTShape
|
||||
|
||||
- (void)setD:(CGPathRef)d
|
||||
{
|
||||
if (d == _d) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGPathRelease(_d);
|
||||
_d = CGPathRetain(d);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGPathRelease(_d);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
if ((!self.fill && !self.stroke) || !self.d) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGPathDrawingMode mode = kCGPathStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGPathFill;
|
||||
} else {
|
||||
CGContextSaveGState(context);
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextClip(context);
|
||||
[self.fill paint:context];
|
||||
CGContextRestoreGState(context);
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGPathFill) {
|
||||
mode = kCGPathFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextDrawPath(context, mode);
|
||||
}
|
||||
|
||||
@end
|
||||
+3
-1
@@ -7,6 +7,8 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTInputAccessoryContentView : UIView
|
||||
#import "ARTContainer.h"
|
||||
|
||||
@interface ARTSurfaceView : UIView <ARTContainer>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTSurfaceView.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
@implementation ARTSurfaceView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.opaque = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTRenderable.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface ARTText : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CTTextAlignment alignment;
|
||||
@property (nonatomic, assign) ARTTextFrame textFrame;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTText.h>
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
@implementation ARTText
|
||||
|
||||
- (void)setAlignment:(CTTextAlignment)alignment
|
||||
{
|
||||
[self invalidate];
|
||||
_alignment = alignment;
|
||||
}
|
||||
|
||||
static void ARTFreeTextFrame(ARTTextFrame frame)
|
||||
{
|
||||
if (frame.count) {
|
||||
// We must release each line before freeing up this struct
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CFRelease(frame.lines[i]);
|
||||
}
|
||||
free(frame.lines);
|
||||
free(frame.widths);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextFrame:(ARTTextFrame)frame
|
||||
{
|
||||
if (frame.lines != _textFrame.lines) {
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
[self invalidate];
|
||||
_textFrame = frame;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
|
||||
if ((!self.fill && !self.stroke) || !frame.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// to-do: draw along a path
|
||||
|
||||
CGTextDrawingMode mode = kCGTextStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGTextFill;
|
||||
} else {
|
||||
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CGContextSaveGState(context);
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
CGContextSetTextDrawingMode(context, kCGTextClip);
|
||||
[self renderLineTo:context atIndex:i];
|
||||
// Inverse the coordinate space back to the original before filling
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
[self.fill paint:context];
|
||||
// Restore the state so that the next line can be clipped separately
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGTextFill) {
|
||||
mode = kCGTextFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextSetTextDrawingMode(context, mode);
|
||||
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
[self renderLineTo:context atIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderLineTo:(CGContextRef)context atIndex:(int)index
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
CGFloat shift;
|
||||
switch (self.alignment) {
|
||||
case kCTTextAlignmentRight:
|
||||
shift = frame.widths[index];
|
||||
break;
|
||||
case kCTTextAlignmentCenter:
|
||||
shift = (frame.widths[index] / 2);
|
||||
break;
|
||||
default:
|
||||
shift = 0;
|
||||
break;
|
||||
}
|
||||
// We should consider snapping this shift to device pixels to improve rendering quality
|
||||
// when a line has subpixel width.
|
||||
CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
|
||||
CTLineRef line = frame.lines[index];
|
||||
CTLineDraw(line, context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
// A little helper to make sure we have a set of lines including width ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when deallocated.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat baseLine; // Distance from the origin to the base line of the first line
|
||||
CGFloat lineHeight; // Distance between lines
|
||||
CTLineRef *lines;
|
||||
CGFloat *widths; // Width of each line
|
||||
} ARTTextFrame;
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ARTBrush : NSObject
|
||||
|
||||
/* @abstract */
|
||||
- (instancetype)initWithArray:(NSArray *)data NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* For certain brushes we can fast path a combined fill and stroke.
|
||||
* For those brushes we override applyFillColor which sets the fill
|
||||
* color to be used by those batch paints. Those return YES.
|
||||
* We can't batch gradient painting in CoreGraphics, so those will
|
||||
* return NO and paint gets called instead.
|
||||
* @abstract
|
||||
*/
|
||||
- (BOOL)applyFillColor:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* paint fills the context with a brush. The context is assumed to
|
||||
* be clipped.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)paint:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTBrush.h>
|
||||
|
||||
#import <React/RCTDefines.h>
|
||||
|
||||
@implementation ARTBrush
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)data
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
+5
-1
@@ -5,4 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "StateUpdate.h"
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTLinearGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTLinearGradient.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTLinearGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _startPoint;
|
||||
CGPoint _endPoint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 5) {
|
||||
RCTLogError(@"-[%@ %@] expects 5 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_startPoint = [RCTConvert CGPoint:array offset:1];
|
||||
_endPoint = [RCTConvert CGPoint:array offset:3];
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:5]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGGradientDrawingOptions extendOptions =
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawLinearGradient(context, _gradient, _startPoint, _endPoint, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
+4
-2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ElementFragment.h"
|
||||
#import "ARTBrush.h"
|
||||
|
||||
// Intentionally empty.
|
||||
@interface ARTPattern : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTPattern.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTPattern
|
||||
{
|
||||
CGImageRef _image;
|
||||
CGRect _rect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<id /* imagesource + numbers */> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 6) {
|
||||
RCTLogError(@"-[%@ %@] expects 6 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_image = CGImageRetain([RCTConvert CGImage:array[1]]);
|
||||
_rect = [RCTConvert CGRect:array offset:2];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGImageRelease(_image);
|
||||
}
|
||||
|
||||
// Note: This could use applyFillColor with a pattern. This could be more efficient but
|
||||
// to do that, we need to calculate our own user space CTM.
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGContextDrawTiledImage(context, _rect, _image);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
+5
-1
@@ -5,4 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ShadowTreeRevision.h"
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTRadialGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRadialGradient.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRadialGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _focusPoint;
|
||||
CGPoint _centerPoint;
|
||||
CGFloat _radius;
|
||||
CGFloat _radiusRatio;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 7) {
|
||||
RCTLogError(@"-[%@ %@] expects 7 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_radius = [RCTConvert CGFloat:array[3]];
|
||||
_radiusRatio = [RCTConvert CGFloat:array[4]] / _radius;
|
||||
_focusPoint.x = [RCTConvert CGFloat:array[1]];
|
||||
_focusPoint.y = [RCTConvert CGFloat:array[2]] / _radiusRatio;
|
||||
_centerPoint.x = [RCTConvert CGFloat:array[5]];
|
||||
_centerPoint.y = [RCTConvert CGFloat:array[6]] / _radiusRatio;
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:7]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(1, _radiusRatio);
|
||||
CGContextConcatCTM(context, transform);
|
||||
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawRadialGradient(context, _gradient, _focusPoint, 0, _centerPoint, _radius, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
+4
-2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "Element.h"
|
||||
#import "ARTBrush.h"
|
||||
|
||||
// Intentionally empty.
|
||||
@interface ARTSolidColor : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTSolidColor.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTSolidColor
|
||||
{
|
||||
CGColorRef _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_color);
|
||||
}
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
CGContextSetFillColorWithColor(context, _color);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json CF_RETURNS_NOT_RETAINED;
|
||||
+ (CTTextAlignment)CTTextAlignment:(id)json;
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json;
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;
|
||||
+ (ARTBrush *)ARTBrush:(id)json;
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset CF_RETURNS_NOT_RETAINED;
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset CF_RETURNS_NOT_RETAINED;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import <React/ARTLinearGradient.h>
|
||||
#import <React/ARTPattern.h>
|
||||
#import <React/ARTRadialGradient.h>
|
||||
#import <React/ARTSolidColor.h>
|
||||
|
||||
@implementation RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
|
||||
NSUInteger count = [arr count];
|
||||
|
||||
#define NEXT_VALUE [self double:arr[i++]]
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathMoveToPoint(path, NULL, 0, 0);
|
||||
|
||||
@try {
|
||||
NSUInteger i = 0;
|
||||
while (i < count) {
|
||||
NSUInteger type = [arr[i++] unsignedIntegerValue];
|
||||
switch (type) {
|
||||
case 0:
|
||||
CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 1:
|
||||
CGPathCloseSubpath(path);
|
||||
break;
|
||||
case 2:
|
||||
CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 3:
|
||||
CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 4:
|
||||
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
|
||||
break;
|
||||
default:
|
||||
RCTLogError(@"Invalid CGPath type %llu at element %llu of %@", (unsigned long long)type, (unsigned long long)i, arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Invalid CGPath format: %@", arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (CGPathRef)CFAutorelease(path);
|
||||
}
|
||||
|
||||
RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
@"auto": @(kCTTextAlignmentNatural),
|
||||
@"left": @(kCTTextAlignmentLeft),
|
||||
@"center": @(kCTTextAlignmentCenter),
|
||||
@"right": @(kCTTextAlignmentRight),
|
||||
@"justify": @(kCTTextAlignmentJustified),
|
||||
}), kCTTextAlignmentNatural, integerValue)
|
||||
|
||||
// This takes a tuple of text lines and a font to generate a CTLine for each text line.
|
||||
// This prepares everything for rendering a frame of text in ARTText.
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json
|
||||
{
|
||||
NSDictionary *dict = [self NSDictionary:json];
|
||||
ARTTextFrame frame;
|
||||
frame.count = 0;
|
||||
|
||||
NSArray *lines = [self NSArray:dict[@"lines"]];
|
||||
NSUInteger lineCount = [lines count];
|
||||
if (lineCount == 0) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
CTFontRef font = (__bridge CTFontRef)[self UIFont:dict[@"font"]];
|
||||
if (!font) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Create a dictionary for this font
|
||||
CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
|
||||
(NSString *)kCTFontAttributeName:(__bridge id)font,
|
||||
(NSString *)kCTForegroundColorFromContextAttributeName: @YES
|
||||
};
|
||||
|
||||
// Set up text frame with font metrics
|
||||
CGFloat size = CTFontGetSize(font);
|
||||
frame.count = lineCount;
|
||||
frame.baseLine = size; // estimate base line
|
||||
frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate
|
||||
frame.lines = malloc(sizeof(CTLineRef) * lineCount);
|
||||
frame.widths = malloc(sizeof(CGFloat) * lineCount);
|
||||
|
||||
[lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) {
|
||||
|
||||
CFStringRef string = (__bridge CFStringRef)text;
|
||||
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attrString);
|
||||
CFRelease(attrString);
|
||||
|
||||
frame.lines[i] = line;
|
||||
frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
|
||||
}];
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
NSUInteger count = arr.count;
|
||||
|
||||
ARTCGFloatArray array;
|
||||
array.count = count;
|
||||
array.array = NULL;
|
||||
|
||||
if (count) {
|
||||
// Ideally, these arrays should already use the same memory layout.
|
||||
// In that case we shouldn't need this new malloc.
|
||||
array.array = malloc(sizeof(CGFloat) * count);
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
array.array[i] = [arr[i] doubleValue];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
+ (ARTBrush *)ARTBrush:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
NSUInteger type = [self NSUInteger:arr.firstObject];
|
||||
switch (type) {
|
||||
case 0: // solid color
|
||||
// These are probably expensive allocations since it's often the same value.
|
||||
// We should memoize colors but look ups may be just as expensive.
|
||||
return [[ARTSolidColor alloc] initWithArray:arr];
|
||||
case 1: // linear gradient
|
||||
return [[ARTLinearGradient alloc] initWithArray:arr];
|
||||
case 2: // radial gradient
|
||||
return [[ARTRadialGradient alloc] initWithArray:arr];
|
||||
case 3: // pattern
|
||||
return [[ARTPattern alloc] initWithArray:arr];
|
||||
default:
|
||||
RCTLogError(@"Unknown brush type: %llu", (unsigned long long)type);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 2) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(2 + offset), arr);
|
||||
return CGPointZero;
|
||||
}
|
||||
return (CGPoint){
|
||||
[self CGFloat:arr[offset]],
|
||||
[self CGFloat:arr[offset + 1]],
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
return CGRectZero;
|
||||
}
|
||||
return (CGRect){
|
||||
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
|
||||
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
return NULL;
|
||||
}
|
||||
return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
|
||||
}
|
||||
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
|
||||
ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr];
|
||||
size_t stops = colorsAndOffsets.count / 5;
|
||||
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
||||
CGGradientRef gradient = CGGradientCreateWithColorComponents(
|
||||
rgb,
|
||||
colorsAndOffsets.array,
|
||||
colorsAndOffsets.array + stops * 4,
|
||||
stops
|
||||
);
|
||||
CGColorSpaceRelease(rgb);
|
||||
free(colorsAndOffsets.array);
|
||||
return (CGGradientRef)CFAutorelease(gradient);
|
||||
}
|
||||
|
||||
@end
|
||||
+10
-11
@@ -11,24 +11,23 @@ version = package['version']
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
||||
folly_version = '2021.06.28.00-v2'
|
||||
boost_compiler_flags = '-Wno-documentation'
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-callinvoker"
|
||||
s.name = "React-ART"
|
||||
s.version = version
|
||||
s.summary = "-" # TODO
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.summary = "A library for drawing vector graphics."
|
||||
s.homepage = "http://facebook.github.io/react-native/"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "11.0" }
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "**/*.{cpp,h}"
|
||||
s.header_dir = "ReactCommon"
|
||||
s.source_files = "**/*.{m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "ART"
|
||||
|
||||
s.dependency "React-Core/ARTHeaders", version
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
|
||||
@interface ARTGroupManager : ARTNodeManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTGroupManager.h>
|
||||
|
||||
#import <React/ARTGroup.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTGroupManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTGroup new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(clipping, CGRect)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@class ARTNode;
|
||||
|
||||
@interface ARTNodeManager : RCTViewManager
|
||||
|
||||
- (ARTNode *)node;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTNodeManager.h>
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
@implementation ARTNodeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTNode new];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [self node];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTRenderableManager : ARTNodeManager
|
||||
|
||||
- (ARTRenderable *)node;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRenderableManager.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRenderableManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTRenderable new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeCap, CGLineCap)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeJoin, CGLineJoin)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fill, ARTBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeDash, ARTCGFloatArray)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTShapeManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTShapeManager.h>
|
||||
|
||||
#import <React/ARTShape.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTShapeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTShape new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface ARTSurfaceViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
+12
-4
@@ -5,9 +5,17 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTDevLoadingViewProtocol.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <React/ARTSurfaceViewManager.h>
|
||||
|
||||
#import <React/ARTSurfaceView.h>
|
||||
|
||||
@implementation ARTSurfaceViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [ARTSurfaceView new];
|
||||
}
|
||||
|
||||
@interface RCTDevLoadingView : NSObject <RCTDevLoadingViewProtocol, RCTBridgeModule>
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTTextManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTTextManager.h>
|
||||
|
||||
#import <React/ARTText.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTTextManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTText new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame)
|
||||
|
||||
@end
|
||||
@@ -8,17 +8,17 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import RCTActionSheetManager from './NativeActionSheetManager';
|
||||
|
||||
const invariant = require('invariant');
|
||||
const processColor = require('../StyleSheet/processColor');
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheet';
|
||||
import type {ProcessedColorValue} from '../StyleSheet/processColor';
|
||||
|
||||
/**
|
||||
* Display action sheets and share sheets on iOS.
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
*/
|
||||
const ActionSheetIOS = {
|
||||
/**
|
||||
@@ -31,12 +31,11 @@ const ActionSheetIOS = {
|
||||
* - `destructiveButtonIndex` (int or array of ints) - index or indices of destructive buttons in `options`
|
||||
* - `title` (string) - a title to show above the action sheet
|
||||
* - `message` (string) - a message to show below the title
|
||||
* - `disabledButtonIndices` (array of numbers) - a list of button indices which should be disabled
|
||||
*
|
||||
* The 'callback' function takes one parameter, the zero-based index
|
||||
* of the selected item.
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html#showactionsheetwithoptions
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showactionsheetwithoptions
|
||||
*/
|
||||
showActionSheetWithOptions(
|
||||
options: {|
|
||||
@@ -46,9 +45,7 @@ const ActionSheetIOS = {
|
||||
+destructiveButtonIndex?: ?number | ?Array<number>,
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ColorValue | ProcessedColorValue,
|
||||
+userInterfaceStyle?: string,
|
||||
+disabledButtonIndices?: Array<number>,
|
||||
+tintColor?: number | string,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
) {
|
||||
@@ -57,7 +54,7 @@ const ActionSheetIOS = {
|
||||
'Options must be a valid object',
|
||||
);
|
||||
invariant(typeof callback === 'function', 'Must provide a valid callback');
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager does't exist");
|
||||
|
||||
const {tintColor, destructiveButtonIndex, ...remainingOptions} = options;
|
||||
let destructiveButtonIndices = null;
|
||||
@@ -68,15 +65,10 @@ const ActionSheetIOS = {
|
||||
destructiveButtonIndices = [destructiveButtonIndex];
|
||||
}
|
||||
|
||||
const processedTintColor = processColor(tintColor);
|
||||
invariant(
|
||||
processedTintColor == null || typeof processedTintColor === 'number',
|
||||
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor',
|
||||
);
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
{
|
||||
...remainingOptions,
|
||||
tintColor: processedTintColor,
|
||||
tintColor: processColor(tintColor),
|
||||
destructiveButtonIndices,
|
||||
},
|
||||
callback,
|
||||
@@ -104,7 +96,7 @@ const ActionSheetIOS = {
|
||||
* - a boolean value signifying success or failure
|
||||
* - a string that, in the case of success, indicates the method of sharing
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html#showshareactionsheetwithoptions
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showshareactionsheetwithoptions
|
||||
*/
|
||||
showShareActionSheetWithOptions(
|
||||
options: Object,
|
||||
@@ -123,7 +115,7 @@ const ActionSheetIOS = {
|
||||
typeof successCallback === 'function',
|
||||
'Must provide a valid successCallback',
|
||||
);
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager does't exist");
|
||||
RCTActionSheetManager.showShareActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
failureCallback,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
@@ -22,8 +24,6 @@ export interface Spec extends TurboModule {
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ?number,
|
||||
+userInterfaceStyle?: ?string,
|
||||
+disabledButtonIndices?: Array<number>,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
) => void;
|
||||
@@ -35,7 +35,6 @@ export interface Spec extends TurboModule {
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ?number,
|
||||
+excludedActivityTypes?: ?Array<string>,
|
||||
+userInterfaceStyle?: ?string,
|
||||
|},
|
||||
failureCallback: (error: {|
|
||||
+domain: string,
|
||||
|
||||
@@ -11,7 +11,7 @@ version = package['version']
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
@@ -20,14 +20,14 @@ Pod::Spec.new do |s|
|
||||
s.name = "React-RCTActionSheet"
|
||||
s.version = version
|
||||
s.summary = "An API for displaying iOS action sheets and share sheets."
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.documentation_url = "https://reactnative.dev/docs/actionsheetios"
|
||||
s.homepage = "http://facebook.github.io/react-native/"
|
||||
s.documentation_url = "https://facebook.github.io/react-native/docs/actionsheetios"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "11.0" }
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "*.{m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "RCTActionSheet"
|
||||
|
||||
s.dependency "React-Core/RCTActionSheetHeaders", version
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Platform from '../Utilities/Platform';
|
||||
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
import NativeDialogManagerAndroid, {
|
||||
type DialogOptions,
|
||||
} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
import RCTAlertManager from './RCTAlertManager';
|
||||
|
||||
export type AlertType =
|
||||
@@ -34,7 +38,7 @@ type Options = {
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* See https://reactnative.dev/docs/alert.html
|
||||
* See http://facebook.github.io/react-native/docs/alert.html
|
||||
*/
|
||||
class Alert {
|
||||
static alert(
|
||||
@@ -46,8 +50,6 @@ class Alert {
|
||||
if (Platform.OS === 'ios') {
|
||||
Alert.prompt(title, message, buttons, 'default');
|
||||
} else if (Platform.OS === 'android') {
|
||||
const NativeDialogManagerAndroid = require('../NativeModules/specs/NativeDialogManagerAndroid')
|
||||
.default;
|
||||
if (!NativeDialogManagerAndroid) {
|
||||
return;
|
||||
}
|
||||
@@ -109,6 +111,28 @@ class Alert {
|
||||
keyboardType?: string,
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (typeof type === 'function') {
|
||||
console.warn(
|
||||
'You passed a callback function as the "type" argument to Alert.prompt(). React Native is ' +
|
||||
'assuming you want to use the deprecated Alert.prompt(title, defaultValue, buttons, callback) ' +
|
||||
'signature. The current signature is Alert.prompt(title, message, callbackOrButtons, type, defaultValue, ' +
|
||||
'keyboardType) and the old syntax will be removed in a future version.',
|
||||
);
|
||||
|
||||
const callback = type;
|
||||
RCTAlertManager.alertWithArgs(
|
||||
{
|
||||
title: title || '',
|
||||
type: 'plain-text',
|
||||
defaultValue: message || '',
|
||||
},
|
||||
(id, value) => {
|
||||
callback(value);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let callbacks = [];
|
||||
const buttons = [];
|
||||
let cancelButtonKey;
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
export type Args = {|
|
||||
title?: string,
|
||||
message?: string,
|
||||
buttons?: Array<Object>, // TODO(T67565166): have a better type
|
||||
buttons?: Array<Object>, // TODO: have a better type
|
||||
type?: string,
|
||||
defaultValue?: string,
|
||||
cancelButtonKey?: string,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import NativeDialogManagerAndroid from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
|
||||
function emptyCallback() {}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import NativeAlertManager from './NativeAlertManager';
|
||||
import type {Args} from './NativeAlertManager';
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
type EndResult = {finished: boolean, ...};
|
||||
type EndCallback = (result: EndResult) => void;
|
||||
type SaveValueCallback = (value: number) => void;
|
||||
|
||||
export type EventMapping = {|
|
||||
nativeEventPath: Array<string>,
|
||||
animatedValueTag: ?number,
|
||||
|};
|
||||
|
||||
// The config has different keys depending on the type of the Node
|
||||
// TODO(T54896888): Make these types strict
|
||||
export type AnimatedNodeConfig = Object;
|
||||
export type AnimatingNodeConfig = Object;
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+startOperationBatch: () => void;
|
||||
+finishOperationBatch: () => void;
|
||||
+createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void;
|
||||
+getValue: (tag: number, saveValueCallback: SaveValueCallback) => void;
|
||||
+startListeningToAnimatedNodeValue: (tag: number) => void;
|
||||
+stopListeningToAnimatedNodeValue: (tag: number) => void;
|
||||
+connectAnimatedNodes: (parentTag: number, childTag: number) => void;
|
||||
+disconnectAnimatedNodes: (parentTag: number, childTag: number) => void;
|
||||
+startAnimatingNode: (
|
||||
animationId: number,
|
||||
nodeTag: number,
|
||||
config: AnimatingNodeConfig,
|
||||
endCallback: EndCallback,
|
||||
) => void;
|
||||
+stopAnimation: (animationId: number) => void;
|
||||
+setAnimatedNodeValue: (nodeTag: number, value: number) => void;
|
||||
+setAnimatedNodeOffset: (nodeTag: number, offset: number) => void;
|
||||
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+extractAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
|
||||
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
|
||||
+restoreDefaultValues: (nodeTag: number) => void;
|
||||
+dropAnimatedNode: (tag: number) => void;
|
||||
+addAnimatedEventToView: (
|
||||
viewTag: number,
|
||||
eventName: string,
|
||||
eventMapping: EventMapping,
|
||||
) => void;
|
||||
+removeAnimatedEventFromView: (
|
||||
viewTag: number,
|
||||
eventName: string,
|
||||
animatedNodeTag: number,
|
||||
) => void;
|
||||
|
||||
// Events
|
||||
+addListener: (eventName: string) => void;
|
||||
+removeListeners: (count: number) => void;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>(
|
||||
'NativeAnimatedTurboModule',
|
||||
): ?Spec);
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @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.',
|
||||
);
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @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), {
|
||||
collapsable: false,
|
||||
}): AnimatedComponentType<
|
||||
React.ElementConfig<typeof Image>,
|
||||
React.ElementRef<typeof Image>,
|
||||
>);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @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), {
|
||||
collapsable: false,
|
||||
}): AnimatedComponentType<
|
||||
React.ElementConfig<typeof Text>,
|
||||
React.ElementRef<typeof Text>,
|
||||
>);
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='Content-type' content='text/html; charset=utf-8'>
|
||||
<title>Animated</title>
|
||||
<link href="./style.css" rel="stylesheet" type="text/css">
|
||||
<script src="https://fb.me/react-with-addons-0.13.3.js"></script>
|
||||
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
|
||||
<script src="../release/dist/animated.js"></script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<script>
|
||||
var TOTAL_EXAMPLES = 0;
|
||||
function example() {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var last = scripts[scripts.length - 2];
|
||||
last.className = 'Example' + (++TOTAL_EXAMPLES);
|
||||
}
|
||||
</script>
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
|
||||
var HorizontalPan = function(anim, config) {
|
||||
config = config || {};
|
||||
return {
|
||||
onMouseDown: function(event) {
|
||||
anim.stopAnimation(startValue => {
|
||||
config.onStart && config.onStart();
|
||||
var startPosition = event.clientX;
|
||||
var lastTime = Date.now();
|
||||
var lastPosition = event.clientX;
|
||||
var velocity = 0;
|
||||
|
||||
function updateVelocity(event) {
|
||||
var now = Date.now();
|
||||
if (event.clientX === lastPosition || now === lastTime) {
|
||||
return;
|
||||
}
|
||||
velocity = (event.clientX - lastPosition) / (now - lastTime);
|
||||
lastTime = now;
|
||||
lastPosition = event.clientX;
|
||||
}
|
||||
|
||||
var moveListener, upListener;
|
||||
window.addEventListener('mousemove', moveListener = (event) => {
|
||||
var value = startValue + (event.clientX - startPosition);
|
||||
anim.setValue(value);
|
||||
updateVelocity(event);
|
||||
});
|
||||
window.addEventListener('mouseup', upListener = (event) => {
|
||||
updateVelocity(event);
|
||||
window.removeEventListener('mousemove', moveListener);
|
||||
window.removeEventListener('mouseup', upListener);
|
||||
config.onEnd && config.onEnd({velocity});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var EXAMPLE_COUNT = 0;
|
||||
function examplify(Component) {
|
||||
if (EXAMPLE_COUNT) {
|
||||
var previousScript = document.getElementsByClassName('Example' + EXAMPLE_COUNT)[0].innerText;
|
||||
} else {
|
||||
var previousScript = `
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0), // ignore
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div // ignore
|
||||
style={{left: this.state.anim}} // ignore
|
||||
className="circle"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
var script = document.getElementsByClassName('Example' + ++EXAMPLE_COUNT)[0];
|
||||
|
||||
var previousLines = {};
|
||||
previousScript.split(/\n/g).forEach(line => { previousLines[line.trim()] = 1; });
|
||||
var code = document.createElement('div');
|
||||
script.parentNode.insertBefore(code, script);
|
||||
|
||||
var Example = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div className="code">
|
||||
<div className="example">
|
||||
<button
|
||||
className="reset"
|
||||
onClick={() => this.forceUpdate()}>
|
||||
Reset
|
||||
</button>
|
||||
<Component key={Math.random()} />
|
||||
</div>
|
||||
<hr />
|
||||
<pre>{'React.createClass({'}</pre>
|
||||
{script.innerText
|
||||
.trim()
|
||||
.split(/\n/g)
|
||||
.slice(1, -1)
|
||||
.map(line =>
|
||||
<pre
|
||||
className={!previousLines[line.trim()] && 'highlight'}>
|
||||
{line}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
<pre>{'});'}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
React.render(<Example />, code);
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Animated</h1>
|
||||
<p>Animations have for a long time been a weak point of the React ecosystem. The <code>Animated</code> library aims at solving this problem. It embraces the declarative aspect of React and obtains performance by using raw DOM manipulation behind the scenes instead of the usual diff.</p>
|
||||
|
||||
<h2>Animated.Value</h2>
|
||||
|
||||
<p>The basic building block of this library is <code>Animated.Value</code>. This is a variable that's going to drive the animation. You use it like a normal value in <code>style</code> attribute. Only animated components such as <code>Animated.div</code> will understand it.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(100),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>setValue</h2>
|
||||
|
||||
<p>As you can see, the value is being used inside of <code>render()</code> as you would expect. However, you don't call <code>setState()</code> in order to update the value. Instead, you can call <code>setValue()</code> directly on the value itself. We are using a form of data binding.</p>
|
||||
|
||||
<p>The <code>Animated.div</code> component when rendered tracks which animated values it received. This way, whenever that value changes, we don't need to re-render the entire component, we can directly update the specific style attribute that changed.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anim.setValue(400);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.timing</h2>
|
||||
|
||||
<p>Now that we understand how the system works, let's play with some animations! The hello world of animations is to move the element somewhere else. To do that, we're going to animate the value from the current value 0 to the value 400.</p>
|
||||
|
||||
<p>On every frame (via <code>requestAnimationFrame</code>), the <code>timing</code> animation is going to figure out the new value based on the current time, update the animated value which in turn is going to update the corresponding DOM node.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
Animated.timing(this.state.anim, {toValue: 400}).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Interrupt Animations</h2>
|
||||
|
||||
<p>As a developer, the mental model is that most animations are fire and forget. When the user presses the button, you want it to shrink to 80% and when she releases, you want it to go back to 100%.</p>
|
||||
|
||||
<p>There are multiple challenges to implement this correctly. You need to stop the current animation, grab the current value and restart an animation from there. As this is pretty tedious to do manually, <code>Animated</code> will do that automatically for you.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{transform: [{scale: this.state.anim}]}}
|
||||
className="circle"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}>
|
||||
Press
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleMouseDown: function() {
|
||||
Animated.timing(this.state.anim, { toValue: 0.8 }).start();
|
||||
},
|
||||
handleMouseUp: function() {
|
||||
Animated.timing(this.state.anim, { toValue: 1 }).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.spring</h2>
|
||||
|
||||
<p>Unfortunately, the <code>timing</code> animation doesn't feel good. The main reason is that no matter how far you are in the animation, it will trigger a new one with always the same duration.</p>
|
||||
|
||||
<p>The commonly used solution for this problem is to use the equation of a real-world spring. Imagine that you attach a spring to the target value, stretch it to the current value and let it go. The spring movement is going to be the same as the update.</p>
|
||||
|
||||
<p>It turns out that this model is useful in a very wide range of animations. I highly recommend you to always start with a <code>spring</code> animation instead of a <code>timing</code> animation. It will make your interface feels much better.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{transform: [{scale: this.state.anim}]}}
|
||||
className="circle"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}>
|
||||
Press
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleMouseDown: function() {
|
||||
Animated.spring(this.state.anim, { toValue: 0.8 }).start();
|
||||
},
|
||||
handleMouseUp: function() {
|
||||
Animated.spring(this.state.anim, { toValue: 1 }).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>interpolate</h2>
|
||||
|
||||
<p>It is very common to animate multiple attributes during the same animation. The usual way to implement it is to start a separate animation for each of the attribute. The downside is that you now have to manage a different state per attribute which is not ideal.</p>
|
||||
|
||||
<p>With <code>Animated</code>, you can use a single state variable and render it in multiple attributes. When the value is updated, all the places will reflect the change.</p>
|
||||
|
||||
<p>In the following example, we're going to model the animation with a variable where 1 means fully visible and 0 means fully hidden. We can pass it directly to the scale attribute as the ranges match. But for the rotation, we need to convert [0 ; 1] range to [260deg ; 0deg]. This is where <code>interpolate()</code> comes handy.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['260deg', '0deg']
|
||||
})},
|
||||
{scale: this.state.anim},
|
||||
]
|
||||
}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
Animated.spring(this.state.anim, {toValue: 0}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>stopAnimation</h2>
|
||||
|
||||
<p>The reason why we can get away with not calling <code>render()</code> and instead modify the DOM directly on updates is because the animated values are <strong>opaque</strong>. In render, <strong>you cannot know the current value</strong>, which prevents you from being able to modify the structure of the DOM.</p>
|
||||
|
||||
<p><code>Animated</code> can offload the animation to a different thread (CoreAnimation, CSS transitions, main thread...) and we don't have a good way to know the real value. If you try to query the value then modify it, you are going to be out of sync and the result will look terrible.</p>
|
||||
|
||||
<p>There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in <code>stopAnimation</code>. It will not suffer from being out of sync since the animation is no longer running.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0)
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => this.handleClick(-1)}><</button>
|
||||
<Animated.div
|
||||
style={{
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [0, 4],
|
||||
outputRange: ['0deg', '360deg']
|
||||
})},
|
||||
],
|
||||
position: 'relative'
|
||||
}}
|
||||
className="circle"
|
||||
/>
|
||||
<button onClick={() => this.handleClick(+1)}>></button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function(delta) {
|
||||
this.state.anim.stopAnimation(value => {
|
||||
Animated.spring(this.state.anim, {
|
||||
toValue: Math.round(value) + delta
|
||||
}).start();
|
||||
});
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<h1>Gesture-based Animations</h1>
|
||||
|
||||
<p>Most animations libraries only deal with time-based animations. But, as we move to mobile, a lot of animations are also gesture driven. Even more problematic, they often switch between both modes: once the gesture is over, you start a time-based animation using the same interpolations.</p>
|
||||
|
||||
<p><code>Animated</code> has been designed with this use case in mind. The key aspect is that there are three distinct and separate concepts: inputs, value, output. The same value can be updated either from a time-based animation or a gesture-based one. Because we use this intermediate representation for the animation, we can keep the same rendering as output.</p>
|
||||
|
||||
<h2>HorizontalPan</h2>
|
||||
|
||||
<p>The code needed to drag elements around is very messy with the DOM APIs. On mousedown, you need to register a mousemove listener on window otherwise you may drop touches if you move too fast. <code>removeEventListener</code> takes the same arguments as <code>addEventListener</code> instead of an id like <code>clearTimeout</code>. It's also really easy to forget to remove a listener and have a leak. And finally, you need to store the current position and value at the beginning and update only compared to it.</p>
|
||||
|
||||
<p>We introduce a little helper called <code>HorizontalPan</code> which handles all this annoying code for us. It takes an <code>Animated.Value</code> as first argument and returns the event handlers required for it to work. We just have to bind this value to the <code>left</code> attribute and we're good to go.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
{...HorizontalPan(this.state.anim)}>
|
||||
Drag
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.decay</h2>
|
||||
|
||||
<p>One of the big breakthrough of the iPhone is the fact that when you release the finger while scrolling, it will not abruptly stop but instead keep going for some time.</p>
|
||||
|
||||
<p>In order to implement this effect, we are using a second real-world simulation: an object moving on an icy surface. All it needs is two values: the current velocity and a deceleration coefficient. It is implemented by <code>Animated.decay</code>.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
{...HorizontalPan(this.state.anim, {
|
||||
onEnd: this.handleEnd
|
||||
})}>
|
||||
Throw
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleEnd: function({velocity}) {
|
||||
Animated.decay(this.state.anim, {velocity}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animation Chaining</h2>
|
||||
|
||||
<p>The target for an animation is usually a number but sometimes it is convenient to use another value as a target. This way, the first value will track the second. Using a spring animation, we can get a nice trailing effect.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
var anims = [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0));
|
||||
Animated.spring(anims[0], {toValue: anims[1]}).start();
|
||||
Animated.spring(anims[1], {toValue: anims[2]}).start();
|
||||
Animated.spring(anims[2], {toValue: anims[3]}).start();
|
||||
Animated.spring(anims[3], {toValue: anims[4]}).start();
|
||||
return {
|
||||
anims: anims,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{left: anim}}
|
||||
className="circle"
|
||||
{...(i === 4 && HorizontalPan(anim, {
|
||||
onEnd: this.handleEnd
|
||||
}))}>
|
||||
{i === 4 && 'Drag'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleEnd: function({velocity}) {
|
||||
Animated.decay(this.state.anims[4], {velocity}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>addListener</h2>
|
||||
|
||||
<p>As I said earlier, if you track a spring</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
var anims = [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(i * 100));
|
||||
anims[0].addListener(this.handleChange);
|
||||
return {
|
||||
selected: null,
|
||||
anims: anims,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{
|
||||
left: anim,
|
||||
opacity: i === 0 || i === this.state.selected ? 1 : 0.5
|
||||
}}
|
||||
className="circle"
|
||||
{...(i === 0 && HorizontalPan(anim, { onEnd: this.handleEnd }))}>
|
||||
{i === 0 && 'Drag'}
|
||||
{i === this.state.selected && 'Selected!'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleChange: function({value}) {
|
||||
var selected = null;
|
||||
this.state.anims.forEach((_, i) => {
|
||||
if (i !== 0 && i * 100 - 50 < value && value <= i * 100 + 50) {
|
||||
selected = i;
|
||||
}
|
||||
});
|
||||
if (selected !== this.state.selected) {
|
||||
this.select(selected)
|
||||
}
|
||||
},
|
||||
select(selected) {
|
||||
this.setState({selected}, () => {
|
||||
this.state.anims.forEach((anim, i) => {
|
||||
if (i === 0) { return; }
|
||||
if (selected === i) {
|
||||
Animated.spring(anim, {toValue: this.state.anims[0]}).start();
|
||||
} else {
|
||||
Animated.spring(anim, {toValue: i * 100}).start();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
handleEnd() {
|
||||
this.select(null);
|
||||
Animated.spring(this.state.anims[0], {toValue: 0}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<h2>Animated.sequence</h2>
|
||||
|
||||
<p>It is very common to animate </p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.sequence(
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.stagger(
|
||||
100,
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.sequence([
|
||||
Animated.parallel(
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
),
|
||||
Animated.stagger(
|
||||
100,
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 0.2 }))
|
||||
),
|
||||
]).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
handleClick: function() {
|
||||
var rec = () => {
|
||||
Animated.sequence([
|
||||
Animated.timing(this.state.anim, {toValue: -1, duration: 150}),
|
||||
Animated.timing(this.state.anim, {toValue: 1, duration: 150}),
|
||||
]).start(rec);
|
||||
};
|
||||
rec();
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{
|
||||
left: this.state.anim.interpolate({
|
||||
inputRange: [-1, -0.5, 0.5, 1],
|
||||
outputRange: [0, 5, 0, 5]
|
||||
}),
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [-1, 1],
|
||||
outputRange: ['-10deg', '10deg']
|
||||
})}
|
||||
]
|
||||
}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0)
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div
|
||||
style={{overflow: 'scroll', height: 60}}
|
||||
onScroll={Animated.event([
|
||||
{target: {scrollLeft: this.state.anim}}
|
||||
])}>
|
||||
<div style={{width: 1000, height: 1}} />
|
||||
{[0, 1, 2, 3, 4].map(i =>
|
||||
<Animated.div
|
||||
style={{
|
||||
left: this.state.anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, (i + 1)]
|
||||
}),
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
className="circle">
|
||||
{i === 4 && 'H-Scroll'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
html, h1, h2 {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.circle {
|
||||
margin: 2px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
box-shadow: 0 1px 2px #999;
|
||||
text-shadow: 0 1px 2px #999;
|
||||
background-image: url(pic1.jpg);
|
||||
background-size: cover;
|
||||
line-height: 80px;
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.circle:nth-child(2) {
|
||||
background-image: url(pic2.jpg);
|
||||
}
|
||||
|
||||
.circle:nth-child(3) {
|
||||
background-image: url(pic3.jpg);
|
||||
}
|
||||
|
||||
div.code {
|
||||
box-shadow: 0 1px 2px #999;
|
||||
width: 600px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
div.code .reset {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.code pre {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #D9D9D9;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.example > span {
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.example {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.code pre {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: rgb(228, 254, 253);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user