diff --git a/.circleci/config.yml b/.circleci/config.yml index 1985e9c89de4c9..336b4dbf200818 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,12 +67,12 @@ aliases: - &restore-cache-buck keys: - - v3-buck-{{ arch }}-v2018.03.26.01 + - v3-buck-{{ arch }}-v2018.06.25.01 - &save-cache-buck paths: - ~/buck - ~/okbuck - key: v3-buck-{{ arch }}-v2018.03.26.01 + key: v3-buck-{{ arch }}-v2018.06.25.01 - &restore-cache-watchman keys: @@ -145,7 +145,7 @@ aliases: name: Install BUCK command: | if [[ ! -e ~/buck ]]; then - git clone https://github.com/facebook/buck.git ~/buck --branch v2018.03.26.01 --depth=1 + git clone https://github.com/facebook/buck.git ~/buck --branch v2018.06.25.01 --depth=1 fi cd ~/buck && ant buck --version @@ -176,7 +176,7 @@ aliases: - &configure-android-path name: Configure Environment Variables command: | - echo 'export PATH=${ANDROID_NDK}:~/react-native/gradle-2.9/bin:~/buck/bin:$PATH' >> $BASH_ENV + echo 'export PATH=${ANDROID_NDK}:~/buck/bin:$PATH' >> $BASH_ENV source $BASH_ENV - &install-android-packages @@ -203,13 +203,17 @@ aliases: # eslint sometimes runs into trouble generating the reports - &run-lint-checks name: Lint code - command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/react-native/reports/junit/eslint/results.xml - when: always + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/react-native/reports/junit/eslint/results.xml + fi - &run-flow-checks name: Check for errors in code using Flow - command: yarn flow check - when: always + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + yarn flow check + fi - &run-sanity-checks name: Sanity checks @@ -281,31 +285,64 @@ aliases: - &boot-simulator-iphone name: Boot iPhone Simulator - command: xcrun simctl boot "iPhone 5s" || true + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + xcrun simctl boot "iPhone 5s" || true + fi - &boot-simulator-appletv name: Boot Apple TV Simulator - command: xcrun simctl boot "Apple TV" || true + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + xcrun simctl boot "Apple TV" || true + fi - &run-objc-ios-tests name: iOS Test Suite - command: ./scripts/objc-test-ios.sh test + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/objc-test-ios.sh test + fi - &run-objc-tvos-tests name: tvOS Test Suite - command: ./scripts/objc-test-tvos.sh test + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/objc-test-tvos.sh test + fi + + - &run-podspec-tests + name: Test CocoaPods + command: | + if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + ./scripts/process-podspecs.sh + fi - &run-e2e-tests name: End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --ios --tvos --js --retries 3; + command: node ./scripts/run-ci-e2e-tests.js --android --ios --tvos --js --retries 3; - &run-objc-ios-e2e-tests name: iOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --ios --retries 3; + command: | + if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + node ./scripts/run-ci-e2e-tests.js --ios --retries 3; + fi - &run-objc-tvos-e2e-tests name: tvOS End-to-End Test Suite - command: node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; + command: | + if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; + fi + + - &run-android-e2e-tests + name: Android End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --android --retries 3; + + - &run-js-e2e-tests + name: JavaScript End-to-End Test Suite + command: node ./scripts/run-ci-e2e-tests.js --js --retries 3; defaults: &defaults working_directory: ~/react-native @@ -339,7 +376,6 @@ macos_defaults: &macos_defaults version: 2 jobs: - # Set up a Node environment for downstream jobs checkout_code: <<: *js_defaults @@ -386,30 +422,21 @@ jobs: - store_test_results: path: ~/react-native/reports/junit - # Runs unit tests on iOS devices - test_ios: + # Runs unit tests on iOS and Apple TV devices + test_objc: <<: *macos_defaults + parallelism: 3 steps: - attach_workspace: at: ~/react-native - run: *boot-simulator-iphone - - run: brew install watchman - - run: *run-objc-ios-tests - - - store_test_results: - path: ~/react-native/reports/junit - - # Runs unit tests on tvOS devices - test_tvos: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native - - run: *boot-simulator-appletv - run: brew install watchman + + - run: *run-objc-ios-tests - run: *run-objc-tvos-tests + - run: *run-podspec-tests - store_test_results: path: ~/react-native/reports/junit @@ -417,11 +444,13 @@ jobs: # Runs end to end tests test_end_to_end: <<: *macos_defaults + parallelism: 2 steps: - attach_workspace: at: ~/react-native - run: *boot-simulator-iphone + - run: *boot-simulator-appletv - run: name: Configure Environment Variables @@ -437,19 +466,12 @@ jobs: node -v - run: *run-objc-ios-e2e-tests + # Disabled for now + # - run: *run-objc-tvos-e2e-tests - store_test_results: path: ~/react-native/reports/junit - # Checks podspec - test_podspec: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/react-native - - - run: ./scripts/process-podspecs.sh - # Publishes new version onto npm publish_npm_package: <<: *android_defaults @@ -569,7 +591,11 @@ jobs: - run: name: Build Android RNTester command: | - ./gradlew RNTester:android:app:assembleRelease + ./gradlew RNTester:android:app:assembleRelease -Pjobs=$BUILD_THREADS + + # Run Android end-to-end tests + # Disabled + # - run: *run-android-e2e-tests # Collect Results - run: *collect-android-test-results @@ -626,7 +652,6 @@ workflows: tests: jobs: - # Checkout repo and run Yarn - checkout_code: filters: *filter-ignore-gh-pages @@ -650,11 +675,7 @@ workflows: - checkout_code # Test iOS & tvOS - - test_ios: - filters: *filter-ignore-gh-pages - requires: - - checkout_code - - test_tvos: + - test_objc: filters: *filter-ignore-gh-pages requires: - checkout_code @@ -674,8 +695,8 @@ workflows: only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ requires: - test_javascript - - test_ios - - test_tvos + - test_objc + - test_android - test_end_to_end - analyze @@ -691,22 +712,3 @@ workflows: filters: *filter-ignore-master-stable requires: - checkout_code - - - # These tests are flaky or are yet to be fixed. They are placed on their own - # workflow to avoid marking benign PRs as broken. - # To run them, uncomment the entire block and open a PR (do not merge). - # Once a test is fixed, move the test definition to the 'tests' workflow. - # disabled_tests: - # jobs: - # # Checkout repo and run Yarn (pre-req, should succeed) - # - checkout_code: - # filters: *filter-ignore-gh-pages - - # # The following were DISABLED because they have not run since - # # the migration from Travis, and they have broken since then, - # # CocoaPods - # - test_podspec: - # filters: *filter-ignore-gh-pages - # requires: - # - checkout_code diff --git a/.eslintrc b/.eslintrc index 395f7d8243492b..e05c065a0bb432 100644 --- a/.eslintrc +++ b/.eslintrc @@ -143,7 +143,7 @@ "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block "no-undefined": 0, // disallow use of undefined variable (off by default) "no-undef-init": 1, // disallow use of undefined when initializing variables - "no-unused-vars": [1, {"vars": "all", "args": "none"}], // disallow declaration of variables that are not used in the code + "no-unused-vars": [1, {"vars": "all", "args": "none", ignoreRestSiblings: true}], // disallow declaration of variables that are not used in the code "no-use-before-define": 0, // disallow use of variables before they are defined // Node.js diff --git a/.flowconfig b/.flowconfig index 32624ea479b9da..7ba7b7ccf41d93 100644 --- a/.flowconfig +++ b/.flowconfig @@ -73,5 +73,28 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]* suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError +[lints] +all=warn + +# There is an ESLint rule for this +unclear-type=off + +sketchy-null=off +sketchy-null-number=warn +sketchy-null-mixed=warn + +# This is noisy for now. We *do* still want to warn on importing types +# from untyped files, which is covered by untyped-type-import +untyped-import=off + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + [version] -^0.74.0 +^0.76.0 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 93f140001bd706..2ae837de23e7e3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,31 +1,15 @@ Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. - - -## Test Plan - - +If this PR fixes an issue, type "Fixes #issueNumber" to automatically close the issue when the PR is merged. -## Related PRs +Test Plan: +---------- +Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos! - - -## Release Notes - - +Release Notes: +-------------- +Help reviewers and the release process by writing your own release notes. See below for an example. [CATEGORY] [TYPE] [LOCATION] - Message diff --git a/ContainerShip/Dockerfile.android-base b/ContainerShip/Dockerfile.android-base index b630b396e9599d..78cb4dc22ca478 100644 --- a/ContainerShip/Dockerfile.android-base +++ b/ContainerShip/Dockerfile.android-base @@ -20,7 +20,7 @@ LABEL maintainer="Héctor Ramos " ARG SDK_VERSION=sdk-tools-linux-3859397.zip ARG ANDROID_BUILD_VERSION=26 ARG ANDROID_TOOLS_VERSION=26.0.3 -ARG BUCK_VERSION=v2018.03.26.01 +ARG BUCK_VERSION=v2018.06.25.01 ARG NDK_VERSION=10e ARG NODE_VERSION=8.10.0 ARG WATCHMAN_VERSION=4.9.0 diff --git a/DockerTests.md b/DockerTests.md index 790d1952786954..f1714db87a0a05 100644 --- a/DockerTests.md +++ b/DockerTests.md @@ -35,7 +35,7 @@ There are two Dockerfiles for use with the Android codebase. The `Dockerfile.android-base` contains all the necessary prerequisites required to run the React Android tests. It is separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite -a beastly image since it contains all the Android depedencies for running android and the emulators (~9GB). +a beastly image since it contains all the Android dependencies for running android and the emulators (~9GB). The good news is you should rarely have to build or pull down the base image! All iterative code updates happen as part of the `Dockerfile.android` image build. diff --git a/Libraries/ART/ART.xcodeproj/project.pbxproj b/Libraries/ART/ART.xcodeproj/project.pbxproj index b1cafee230d119..2aec17b1d79203 100644 --- a/Libraries/ART/ART.xcodeproj/project.pbxproj +++ b/Libraries/ART/ART.xcodeproj/project.pbxproj @@ -245,7 +245,7 @@ 0CF68AB91AF0540F00FF9E5C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0620; + LastUpgradeCheck = 0940; TargetAttributes = { 0CF68AC01AF0540F00FF9E5C = { CreatedOnToolsVersion = 6.2; @@ -336,19 +336,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -377,19 +389,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; diff --git a/Libraries/ART/ReactNativeART.js b/Libraries/ART/ReactNativeART.js index 760b5ff809de40..cf56028b102cab 100644 --- a/Libraries/ART/ReactNativeART.js +++ b/Libraries/ART/ReactNativeART.js @@ -11,6 +11,7 @@ const Color = require('art/core/color'); const Path = require('ARTSerializablePath'); +const Platform = require('Platform'); const Transform = require('art/core/transform'); const React = require('React'); @@ -150,11 +151,14 @@ class Surface extends React.Component { } render() { - const props = this.props; - const w = extractNumber(props.width, 0); - const h = extractNumber(props.height, 0); + const height = extractNumber(this.props.height, 0); + const width = extractNumber(this.props.width, 0); + + // WORKAROUND: Android bug in which canvas does not reflect size changes. + const key = Platform.OS === 'android' ? height + ',' + width : null; + return ( - + {this.props.children} ); diff --git a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj b/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj index a639137bb7bbb1..c71f89bdd8c41b 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj +++ b/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj @@ -61,7 +61,7 @@ 58B511D31A9E6C8500147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 58B511DA1A9E6C8500147676 = { @@ -106,19 +106,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -153,19 +165,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; diff --git a/Libraries/Animated/release/package.json b/Libraries/Animated/release/package.json index 74f457a4dcd33c..49f6a2081ffd14 100644 --- a/Libraries/Animated/release/package.json +++ b/Libraries/Animated/release/package.json @@ -10,7 +10,7 @@ "license": "MIT", "main": "Animated.js", "dependencies": { - "fbjs": "^0.2.1" + "fbjs": "0.8.17" }, "scripts": { "build": "gulp" diff --git a/Libraries/Animated/src/Animated.js b/Libraries/Animated/src/Animated.js index f25d2a0796a1ad..8678b21f14b889 100644 --- a/Libraries/Animated/src/Animated.js +++ b/Libraries/Animated/src/Animated.js @@ -16,14 +16,10 @@ const ScrollView = require('ScrollView'); const Text = require('Text'); const View = require('View'); -const Animated = { +module.exports = { + ...AnimatedImplementation, View: AnimatedImplementation.createAnimatedComponent(View), Text: AnimatedImplementation.createAnimatedComponent(Text), Image: AnimatedImplementation.createAnimatedComponent(Image), ScrollView: AnimatedImplementation.createAnimatedComponent(ScrollView), }; - -Object.assign((Animated: Object), AnimatedImplementation); - -module.exports = ((Animated: any): typeof AnimatedImplementation & - typeof Animated); diff --git a/Libraries/Animated/src/SpringConfig.js b/Libraries/Animated/src/SpringConfig.js index ca25a115cb0917..456af9c40ec1ae 100644 --- a/Libraries/Animated/src/SpringConfig.js +++ b/Libraries/Animated/src/SpringConfig.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict */ 'use strict'; diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index 1c2066bfdc5e56..6c5ef66b14a170 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -572,7 +572,7 @@ describe('Animated tests', () => { expect(listener.mock.calls.length).toBe(1); expect(listener).toBeCalledWith({foo: 42}); }); - it('should call forked event listeners', () => { + it('should call forked event listeners, with Animated.event() listener', () => { const value = new Animated.Value(0); const listener = jest.fn(); const handler = Animated.event([{foo: value}], {listener}); @@ -585,6 +585,24 @@ describe('Animated tests', () => { expect(listener2.mock.calls.length).toBe(1); expect(listener2).toBeCalledWith({foo: 42}); }); + it('should call forked event listeners, with js listener', () => { + const listener = jest.fn(); + const listener2 = jest.fn(); + const forkedHandler = Animated.forkEvent(listener, listener2); + forkedHandler({foo: 42}); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); + it('should call forked event listeners, with undefined listener', () => { + const listener = undefined; + const listener2 = jest.fn(); + const forkedHandler = Animated.forkEvent(listener, listener2); + forkedHandler({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); }); describe('Animated Interactions', () => { diff --git a/Libraries/Blob/BlobRegistry.js b/Libraries/Blob/BlobRegistry.js index d78a9d26656e07..445fe6bb855835 100644 --- a/Libraries/Blob/BlobRegistry.js +++ b/Libraries/Blob/BlobRegistry.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ diff --git a/Libraries/Blob/BlobTypes.js b/Libraries/Blob/BlobTypes.js index b03922f24f32c5..2e5be53e19615f 100644 --- a/Libraries/Blob/BlobTypes.js +++ b/Libraries/Blob/BlobTypes.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ diff --git a/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj b/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj index 316c157dec507e..8bb32fec7b57fa 100755 --- a/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj +++ b/Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj @@ -145,7 +145,7 @@ 358F4ECF1D1E81A9004DF814 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0730; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 358F4ED61D1E81A9004DF814 = { @@ -206,13 +206,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -251,13 +261,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; diff --git a/Libraries/Blob/__mocks__/BlobModule.js b/Libraries/Blob/__mocks__/BlobModule.js index 2542c3a9339adc..e904d2929dd74b 100644 --- a/Libraries/Blob/__mocks__/BlobModule.js +++ b/Libraries/Blob/__mocks__/BlobModule.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ const BlobModule = { diff --git a/Libraries/Blob/__mocks__/FileReaderModule.js b/Libraries/Blob/__mocks__/FileReaderModule.js index f5ccc39d36e200..0f35aa89ace07e 100644 --- a/Libraries/Blob/__mocks__/FileReaderModule.js +++ b/Libraries/Blob/__mocks__/FileReaderModule.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ const FileReaderModule = { diff --git a/Libraries/BugReporting/dumpReactTree.js b/Libraries/BugReporting/dumpReactTree.js index 7cbb2530f2055c..ead8f4bdd76c32 100644 --- a/Libraries/BugReporting/dumpReactTree.js +++ b/Libraries/BugReporting/dumpReactTree.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict */ 'use strict'; diff --git a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m index e9637d7c4c16b5..9721b392b80f78 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m @@ -8,6 +8,8 @@ #import "RCTAssetsLibraryRequestHandler.h" #import +#import +#import #import #import @@ -23,10 +25,20 @@ @implementation RCTAssetsLibraryRequestHandler RCT_EXPORT_MODULE() @synthesize bridge = _bridge; - +static Class _ALAssetsLibrary = nil; +static void ensureAssetsLibLoaded(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + void * handle = dlopen("/System/Library/Frameworks/AssetsLibrary.framework/AssetsLibrary", RTLD_LAZY); +#pragma unused(handle) + _ALAssetsLibrary = objc_getClass("ALAssetsLibrary"); + }); +} - (ALAssetsLibrary *)assetsLibrary { - return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]); + ensureAssetsLibLoaded(); + return _assetsLibrary ?: (_assetsLibrary = [_ALAssetsLibrary new]); } #pragma mark - RCTURLRequestHandler diff --git a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj b/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj index 1132021a60d4a7..618dbab44ae880 100644 --- a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj +++ b/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj @@ -100,7 +100,7 @@ 58B511551A9E6B3D00147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 58B5115C1A9E6B3D00147676 = { @@ -148,20 +148,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -197,20 +208,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m index b5d9a85dadb44b..e6e397392bd159 100644 --- a/Libraries/CameraRoll/RCTCameraRollManager.m +++ b/Libraries/CameraRoll/RCTCameraRollManager.m @@ -11,6 +11,8 @@ #import #import #import +#import +#import #import #import @@ -44,22 +46,52 @@ @implementation RCTConvert (ALAssetGroup) }), ALAssetsGroupSavedPhotos, integerValue) +static Class _ALAssetsFilter = nil; +static NSString *_ALAssetsGroupPropertyName = nil; +static NSString *_ALAssetPropertyAssetURL = nil; +static NSString *_ALAssetPropertyLocation = nil; +static NSString *_ALAssetPropertyDate = nil; +static NSString *_ALAssetPropertyType = nil; +static NSString *_ALAssetPropertyDuration = nil; +static NSString *_ALAssetTypeVideo = nil; +static NSString *lookupNSString(void * handle, const char * name) +{ + void ** sym = dlsym(handle, name); + return (__bridge NSString *)(sym ? *sym : nil); +} +static void ensureAssetsLibLoaded(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + void * handle = dlopen("/System/Library/Frameworks/AssetsLibrary.framework/AssetsLibrary", RTLD_LAZY); + RCTAssert(handle != NULL, @"Unable to load AssetsLibrary.framework."); + _ALAssetsFilter = objc_getClass("ALAssetsFilter"); + _ALAssetsGroupPropertyName = lookupNSString(handle, "ALAssetsGroupPropertyName"); + _ALAssetPropertyAssetURL = lookupNSString(handle, "ALAssetPropertyAssetURL"); + _ALAssetPropertyLocation = lookupNSString(handle, "ALAssetPropertyLocation"); + _ALAssetPropertyDate = lookupNSString(handle, "ALAssetPropertyDate"); + _ALAssetPropertyType = lookupNSString(handle, "ALAssetPropertyType"); + _ALAssetPropertyDuration = lookupNSString(handle, "ALAssetPropertyDuration"); + _ALAssetTypeVideo = lookupNSString(handle, "ALAssetTypeVideo"); + }); +} + + (ALAssetsFilter *)ALAssetsFilter:(id)json { static NSDictionary *options; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + ensureAssetsLibLoaded(); options = @{ - // New values - @"photos": [ALAssetsFilter allPhotos], - @"videos": [ALAssetsFilter allVideos], - @"all": [ALAssetsFilter allAssets], + @"photos": [_ALAssetsFilter allPhotos], + @"videos": [_ALAssetsFilter allVideos], + @"all": [_ALAssetsFilter allAssets], // Legacy values - @"Photos": [ALAssetsFilter allPhotos], - @"Videos": [ALAssetsFilter allVideos], - @"All": [ALAssetsFilter allAssets], + @"Photos": [_ALAssetsFilter allPhotos], + @"Videos": [_ALAssetsFilter allVideos], + @"All": [_ALAssetsFilter allAssets], }; }); @@ -68,7 +100,7 @@ + (ALAssetsFilter *)ALAssetsFilter:(id)json RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos'," "'videos' or 'all'.", json); } - return filter ?: [ALAssetsFilter allPhotos]; + return filter ?: [_ALAssetsFilter allPhotos]; } @end @@ -149,6 +181,7 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve, { checkPhotoLibraryConfig(); + ensureAssetsLibLoaded(); NSUInteger first = [RCTConvert NSInteger:params[@"first"]]; NSString *afterCursor = [RCTConvert NSString:params[@"after"]]; NSString *groupName = [RCTConvert NSString:params[@"groupName"]]; @@ -161,12 +194,12 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve, NSMutableArray *> *assets = [NSMutableArray new]; [_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { - if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { + if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:_ALAssetsGroupPropertyName]])) { [group setAssetsFilter:assetType]; [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) { if (result) { - NSString *uri = ((NSURL *)[result valueForProperty:ALAssetPropertyAssetURL]).absoluteString; + NSString *uri = ((NSURL *)[result valueForProperty:_ALAssetPropertyAssetURL]).absoluteString; if (afterCursor && !foundAfter) { if ([afterCursor isEqualToString:uri]) { foundAfter = YES; @@ -183,18 +216,18 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve, return; } CGSize dimensions = [result defaultRepresentation].dimensions; - CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation]; - NSDate *date = [result valueForProperty:ALAssetPropertyDate]; + CLLocation *loc = [result valueForProperty:_ALAssetPropertyLocation]; + NSDate *date = [result valueForProperty:_ALAssetPropertyDate]; NSString *filename = [result defaultRepresentation].filename; int64_t duration = 0; - if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) { - duration = [[result valueForProperty:ALAssetPropertyDuration] intValue]; + if ([[result valueForProperty:_ALAssetPropertyType] isEqualToString:_ALAssetTypeVideo]) { + duration = [[result valueForProperty:_ALAssetPropertyDuration] intValue]; } [assets addObject:@{ @"node": @{ - @"type": [result valueForProperty:ALAssetPropertyType], - @"group_name": [group valueForProperty:ALAssetsGroupPropertyName], + @"type": [result valueForProperty:_ALAssetPropertyType], + @"group_name": [group valueForProperty:_ALAssetsGroupPropertyName], @"image": @{ @"uri": uri, @"filename" : filename ?: [NSNull null], diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index 1ee31752f2a992..4da2a6490b9504 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -153,6 +153,11 @@ - (void)_presentPicker:(UIImagePickerController *)imagePicker - (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args { NSUInteger index = [_pickers indexOfObject:picker]; + if (index == NSNotFound) { + // This happens if the user selects multiple items in succession. + return; + } + RCTResponseSenderBlock successCallback = _pickerCallbacks[index]; RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index]; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index f2a648797eb001..abe6a75af9478e 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -70,12 +70,10 @@ type Props = $ReadOnly<{| * See http://facebook.github.io/react-native/docs/activityindicator.html */ const ActivityIndicator = ( - props: $ReadOnly<{| - ...Props, - forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>, - |}>, + props: Props, + forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>, ) => { - const {onLayout, style, forwardedRef, ...restProps} = props; + const {onLayout, style, ...restProps} = props; let sizeStyle; switch (props.size) { @@ -99,16 +97,19 @@ const ActivityIndicator = ( }; return ( - + ); }; // $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. -const ActivityIndicatorWithRef = React.forwardRef((props: Props, ref) => { - return ; -}); +const ActivityIndicatorWithRef = React.forwardRef(ActivityIndicator); ActivityIndicatorWithRef.defaultProps = { animating: true, @@ -116,7 +117,6 @@ ActivityIndicatorWithRef.defaultProps = { hidesWhenStopped: true, size: 'small', }; -ActivityIndicatorWithRef.displayName = 'ActivityIndicator'; const styles = StyleSheet.create({ container: { diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js index 22c376467ea9bc..674b9265678699 100644 --- a/Libraries/Components/Button.js +++ b/Libraries/Components/Button.js @@ -152,21 +152,21 @@ const styles = StyleSheet.create({ borderRadius: 2, }, }), - text: Platform.select({ - ios: { - // iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/ - color: '#007AFF', - textAlign: 'center', - padding: 8, - fontSize: 18, - }, - android: { - color: 'white', - textAlign: 'center', - padding: 8, - fontWeight: '500', - }, - }), + text: { + textAlign: 'center', + padding: 8, + ...Platform.select({ + ios: { + // iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/ + color: '#007AFF', + fontSize: 18, + }, + android: { + color: 'white', + fontWeight: '500', + }, + }), + }, buttonDisabled: Platform.select({ ios: {}, android: { diff --git a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js index 7ea083dd0f19f8..f343e2e5452044 100644 --- a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js +++ b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js @@ -4,41 +4,60 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow * @format */ 'use strict'; -const ColorPropType = require('ColorPropType'); -const PropTypes = require('prop-types'); const React = require('React'); -const ViewPropTypes = require('ViewPropTypes'); const requireNativeComponent = require('requireNativeComponent'); -const STYLE_ATTRIBUTES = [ - 'Horizontal', - 'Normal', - 'Small', - 'Large', - 'Inverse', - 'SmallInverse', - 'LargeInverse', -]; +import type {NativeComponent} from 'ReactNative'; +import type {ViewProps} from 'ViewPropTypes'; -const indeterminateType = function(props, propName, componentName, ...rest) { - const checker = function() { - const indeterminate = props[propName]; - const styleAttr = props.styleAttr; - if (!indeterminate && styleAttr !== 'Horizontal') { - return new Error( - 'indeterminate=false is only valid for styleAttr=Horizontal', - ); - } - }; +const AndroidProgressBar = requireNativeComponent('AndroidProgressBar'); - return PropTypes.bool(props, propName, componentName, ...rest) || checker(); -}; +type Props = $ReadOnly<{| + ...ViewProps, + + /** + * Style of the ProgressBar and whether it shows indeterminate progress (e.g. spinner). + * + * `indeterminate` can only be false if `styleAttr` is Horizontal, and requires a + * `progress` value. + */ + ... + | {| + styleAttr: 'Horizontal', + indeterminate: false, + progress: number, + |} + | {| + typeAttr: + | 'Horizontal' + | 'Normal' + | 'Small' + | 'Large' + | 'Inverse' + | 'SmallInverse' + | 'LargeInverse', + indeterminate: true, + |}, + /** + * Whether to show the ProgressBar (true, the default) or hide it (false). + */ + animating?: ?boolean, + /** + * Color of the progress bar. + */ + color?: ?string, + /** + * Used to locate this view in end-to-end tests. + */ + testID?: ?string, +|}>; /** * React component that wraps the Android-only `ProgressBar`. This component is @@ -63,59 +82,20 @@ const indeterminateType = function(props, propName, componentName, ...rest) { * }, * ``` */ -class ProgressBarAndroid extends React.Component { - static propTypes = { - ...ViewPropTypes, - - /** - * Style of the ProgressBar. One of: - * - * - Horizontal - * - Normal (default) - * - Small - * - Large - * - Inverse - * - SmallInverse - * - LargeInverse - */ - styleAttr: PropTypes.oneOf(STYLE_ATTRIBUTES), - /** - * Whether to show the ProgressBar (true, the default) or hide it (false). - */ - animating: PropTypes.bool, - /** - * If the progress bar will show indeterminate progress. Note that this - * can only be false if styleAttr is Horizontal. - */ - indeterminate: indeterminateType, - /** - * The progress value (between 0 and 1). - */ - progress: PropTypes.number, - /** - * Color of the progress bar. - */ - color: ColorPropType, - /** - * Used to locate this view in end-to-end tests. - */ - testID: PropTypes.string, - }; - - static defaultProps = { - styleAttr: 'Normal', - indeterminate: true, - animating: true, - }; +const ProgressBarAndroid = ( + props: Props, + forwardedRef: ?React.Ref<'AndroidProgressBar'>, +) => { + return ; +}; - render() { - const {forwardedRef, ...props} = this.props; - return ; - } -} +// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. +const ProgressBarAndroidToExport = React.forwardRef(ProgressBarAndroid); -const AndroidProgressBar = requireNativeComponent('AndroidProgressBar'); +ProgressBarAndroidToExport.defaultProps = { + styleAttr: 'Normal', + indeterminate: true, + animating: true, +}; -module.exports = React.forwardRef((props, ref) => ( - -)); +module.exports = (ProgressBarAndroidToExport: Class>); diff --git a/Libraries/Components/ScrollView/InternalScrollViewType.js b/Libraries/Components/ScrollView/InternalScrollViewType.js index a1d42bb46a6d29..471e25d9a5dab9 100644 --- a/Libraries/Components/ScrollView/InternalScrollViewType.js +++ b/Libraries/Components/ScrollView/InternalScrollViewType.js @@ -22,7 +22,8 @@ class InternalScrollViewType extends ReactNative.NativeComponent { ) {} flashScrollIndicators() {} - scrollToEnd(options?: {animated?: boolean}) {} + propTypes: empty; + scrollToEnd(options?: ?{animated?: boolean}) {} scrollWithoutAnimationTo(y: number = 0, x: number = 0) {} getScrollResponder(): any {} diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index a2be38422765ef..9f0688e87489f3 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -11,20 +11,13 @@ 'use strict'; const AnimatedImplementation = require('AnimatedImplementation'); -const ColorPropType = require('ColorPropType'); -const EdgeInsetsPropType = require('EdgeInsetsPropType'); const Platform = require('Platform'); -const PointPropType = require('PointPropType'); -const PropTypes = require('prop-types'); const React = require('React'); const ReactNative = require('ReactNative'); const ScrollResponder = require('ScrollResponder'); const ScrollViewStickyHeader = require('ScrollViewStickyHeader'); const StyleSheet = require('StyleSheet'); -const StyleSheetPropType = require('StyleSheetPropType'); const View = require('View'); -const ViewPropTypes = require('ViewPropTypes'); -const ViewStylePropTypes = require('ViewStylePropTypes'); const InternalScrollViewType = require('InternalScrollViewType'); const createReactClass = require('create-react-class'); @@ -76,49 +69,263 @@ type TouchableProps = $ReadOnly<{| |}>; type IOSProps = $ReadOnly<{| + /** + * Controls whether iOS should automatically adjust the content inset + * for scroll views that are placed behind a navigation bar or + * tab bar/ toolbar. The default value is true. + * @platform ios + */ automaticallyAdjustContentInsets?: ?boolean, + /** + * The amount by which the scroll view content is inset from the edges + * of the scroll view. Defaults to `{top: 0, left: 0, bottom: 0, right: 0}`. + * @platform ios + */ contentInset?: ?EdgeInsetsProp, + /** + * Used to manually set the starting scroll offset. + * The default value is `{x: 0, y: 0}`. + * @platform ios + */ contentOffset?: ?PointProp, + /** + * When true, the scroll view bounces when it reaches the end of the + * content if the content is larger then the scroll view along the axis of + * the scroll direction. When false, it disables all bouncing even if + * the `alwaysBounce*` props are true. The default value is true. + * @platform ios + */ bounces?: ?boolean, + /** + * When true, gestures can drive zoom past min/max and the zoom will animate + * to the min/max value at gesture end, otherwise the zoom will not exceed + * the limits. + * @platform ios + */ bouncesZoom?: ?boolean, + /** + * When true, the scroll view bounces horizontally when it reaches the end + * even if the content is smaller than the scroll view itself. The default + * value is true when `horizontal={true}` and false otherwise. + * @platform ios + */ alwaysBounceHorizontal?: ?boolean, + /** + * When true, the scroll view bounces vertically when it reaches the end + * even if the content is smaller than the scroll view itself. The default + * value is false when `horizontal={true}` and true otherwise. + * @platform ios + */ alwaysBounceVertical?: ?boolean, + /** + * When true, the scroll view automatically centers the content when the + * content is smaller than the scroll view bounds; when the content is + * larger than the scroll view, this property has no effect. The default + * value is false. + * @platform ios + */ centerContent?: ?boolean, + /** + * A floating-point number that determines how quickly the scroll view + * decelerates after the user lifts their finger. You may also use string + * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings + * for `UIScrollViewDecelerationRateNormal` and + * `UIScrollViewDecelerationRateFast` respectively. + * + * - `'normal'`: 0.998 (the default) + * - `'fast'`: 0.99 + * + * @platform ios + */ decelerationRate?: ?('fast' | 'normal' | number), + /** + * The style of the scroll indicators. + * + * - `'default'` (the default), same as `black`. + * - `'black'`, scroll indicator is black. This style is good against a light background. + * - `'white'`, scroll indicator is white. This style is good against a dark background. + * + * @platform ios + */ indicatorStyle?: ?('default' | 'black' | 'white'), + /** + * When true, the ScrollView will try to lock to only vertical or horizontal + * scrolling while dragging. The default value is false. + * @platform ios + */ directionalLockEnabled?: ?boolean, + /** + * When false, once tracking starts, won't try to drag if the touch moves. + * The default value is true. + * @platform ios + */ canCancelContentTouches?: ?boolean, + /** + * When set, the scroll view will adjust the scroll position so that the first child that is + * currently visible and at or beyond `minIndexForVisible` will not change position. This is + * useful for lists that are loading content in both directions, e.g. a chat thread, where new + * messages coming in might otherwise cause the scroll position to jump. A value of 0 is common, + * but other values such as 1 can be used to skip loading spinners or other content that should + * not maintain position. + * + * The optional `autoscrollToTopThreshold` can be used to make the content automatically scroll + * to the top after making the adjustment if the user was within the threshold of the top before + * the adjustment was made. This is also useful for chat-like applications where you want to see + * new messages scroll into place, but not if the user has scrolled up a ways and it would be + * disruptive to scroll a bunch. + * + * Caveat 1: Reordering elements in the scrollview with this enabled will probably cause + * jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now, + * don't re-order the content of any ScrollViews or Lists that use this feature. + * + * Caveat 2: This simply uses `contentOffset` and `frame.origin` in native code to compute + * visibility. Occlusion, transforms, and other complexity won't be taken into account as to + * whether content is "visible" or not. + * + * @platform ios + */ maintainVisibleContentPosition?: ?$ReadOnly<{| minIndexForVisible: number, autoscrollToTopThreshold?: ?number, |}>, + /** + * The maximum allowed zoom scale. The default value is 1.0. + * @platform ios + */ maximumZoomScale?: ?number, + /** + * The minimum allowed zoom scale. The default value is 1.0. + * @platform ios + */ minimumZoomScale?: ?number, + /** + * When true, ScrollView allows use of pinch gestures to zoom in and out. + * The default value is true. + * @platform ios + */ pinchGestureEnabled?: ?boolean, + /** + * This controls how often the scroll event will be fired while scrolling + * (as a time interval in ms). A lower number yields better accuracy for code + * that is tracking the scroll position, but can lead to scroll performance + * problems due to the volume of information being send over the bridge. + * You will not notice a difference between values set between 1-16 as the + * JS run loop is synced to the screen refresh rate. If you do not need precise + * scroll position tracking, set this value higher to limit the information + * being sent across the bridge. The default value is zero, which results in + * the scroll event being sent only once each time the view is scrolled. + * @platform ios + */ scrollEventThrottle?: ?number, + /** + * The amount by which the scroll view indicators are inset from the edges + * of the scroll view. This should normally be set to the same value as + * the `contentInset`. Defaults to `{0, 0, 0, 0}`. + * @platform ios + */ scrollIndicatorInsets?: ?EdgeInsetsProp, + /** + * When true, the scroll view scrolls to top when the status bar is tapped. + * The default value is true. + * @platform ios + */ scrollsToTop?: ?boolean, + /** + * When true, shows a horizontal scroll indicator. + * The default value is true. + */ showsHorizontalScrollIndicator?: ?boolean, + /** + * When `snapToInterval` is set, `snapToAlignment` will define the relationship + * of the snapping to the scroll view. + * + * - `'start'` (the default) will align the snap at the left (horizontal) or top (vertical) + * - `'center'` will align the snap in the center + * - `'end'` will align the snap at the right (horizontal) or bottom (vertical) + * + * @platform ios + */ snapToAlignment?: ?('start' | 'center' | 'end'), + /** + * The current scale of the scroll view content. The default value is 1.0. + * @platform ios + */ zoomScale?: ?number, + /** + * This property specifies how the safe area insets are used to modify the + * content area of the scroll view. The default value of this property is + * "never". Available on iOS 11 and later. + * @platform ios + */ contentInsetAdjustmentBehavior?: ?( | 'automatic' | 'scrollableAxes' | 'never' | 'always' ), + /** + * When true, ScrollView will emit updateChildFrames data in scroll events, + * otherwise will not compute or emit child frame data. This only exists + * to support legacy issues, `onLayout` should be used instead to retrieve + * frame data. + * The default value is false. + * @platform ios + */ DEPRECATED_sendUpdatedChildFrames?: ?boolean, |}>; type AndroidProps = $ReadOnly<{| + /** + * Enables nested scrolling for Android API level 21+. + * Nested scrolling is supported by default on iOS + * @platform android + */ nestedScrollEnabled?: ?boolean, + /** + * Sometimes a scrollview takes up more space than its content fills. When this is + * the case, this prop will fill the rest of the scrollview with a color to avoid setting + * a background and creating unnecessary overdraw. This is an advanced optimization + * that is not needed in the general case. + * @platform android + */ endFillColor?: ?ColorValue, + /** + * Tag used to log scroll performance on this scroll view. Will force + * momentum events to be turned on (see sendMomentumEvents). This doesn't do + * anything out of the box and you need to implement a custom native + * FpsListener for it to be useful. + * @platform android + */ scrollPerfTag?: ?string, + /** + * Used to override default value of overScroll mode. + * + * Possible values: + * + * - `'auto'` - Default value, allow a user to over-scroll + * this view only if the content is large enough to meaningfully scroll. + * - `'always'` - Always allow a user to over-scroll this view. + * - `'never'` - Never allow a user to over-scroll this view. + * + * @platform android + */ overScrollMode?: ?('auto' | 'always' | 'never'), |}>; type VRProps = $ReadOnly<{| - scrollBarThumbImage?: ?($ReadOnly<{||}> | number), + /** + * Optionally an image can be used for the scroll bar thumb. This will + * override the color. While the image is loading or the image fails to + * load the color will be used instead. Use an alpha of 0 in the color + * to avoid seeing it while the image is loading. + * + * - `uri` - a string representing the resource identifier for the image, which + * should be either a local file path or the name of a static image resource + * - `number` - Opaque type returned by something like + * `import IMAGE from './image.jpg'`. + * @platform vr + */ + scrollBarThumbImage?: ?($ReadOnly<{||}> | number), // Opaque type returned by import IMAGE from './image.jpg' |}>; export type Props = $ReadOnly<{| @@ -128,30 +335,153 @@ export type Props = $ReadOnly<{| ...AndroidProps, ...VRProps, + /** + * These styles will be applied to the scroll view content container which + * wraps all of the child views. Example: + * + * ``` + * return ( + * + * + * ); + * ... + * const styles = StyleSheet.create({ + * contentContainer: { + * paddingVertical: 20 + * } + * }); + * ``` + */ contentContainerStyle?: ?ViewStyleProp, + /** + * When true, the scroll view's children are arranged horizontally in a row + * instead of vertically in a column. The default value is false. + */ horizontal?: ?boolean, + /** + * If sticky headers should stick at the bottom instead of the top of the + * ScrollView. This is usually used with inverted ScrollViews. + */ invertStickyHeaders?: ?boolean, + /** + * Determines whether the keyboard gets dismissed in response to a drag. + * + * *Cross platform* + * + * - `'none'` (the default), drags do not dismiss the keyboard. + * - `'on-drag'`, the keyboard is dismissed when a drag begins. + * + * *iOS Only* + * + * - `'interactive'`, the keyboard is dismissed interactively with the drag and moves in + * synchrony with the touch; dragging upwards cancels the dismissal. + * On android this is not supported and it will have the same behavior as 'none'. + */ keyboardDismissMode?: ?( | 'none' // default | 'on-drag' // cross-platform | 'interactive' ), // ios only - // $FlowFixMe Issues found when typing ScrollView + /** + * Determines when the keyboard should stay visible after a tap. + * + * - `'never'` (the default), tapping outside of the focused text input when the keyboard + * is up dismisses the keyboard. When this happens, children won't receive the tap. + * - `'always'`, the keyboard will not dismiss automatically, and the scroll view will not + * catch taps, but children of the scroll view can catch taps. + * - `'handled'`, the keyboard will not dismiss automatically when the tap was handled by + * a children, (or captured by an ancestor). + * - `false`, deprecated, use 'never' instead + * - `true`, deprecated, use 'always' instead + */ + // $FlowFixMe(site=react_native_fb) Issues found when typing ScrollView keyboardShouldPersistTaps?: ?('always' | 'never' | 'handled' | false | true), + /** + * Called when the momentum scroll starts (scroll which occurs as the ScrollView glides to a stop). + */ onMomentumScrollBegin?: ?Function, + /** + * Called when the momentum scroll ends (scroll which occurs as the ScrollView glides to a stop). + */ onMomentumScrollEnd?: ?Function, + /** + * Fires at most once per frame during scrolling. The frequency of the + * events can be controlled using the `scrollEventThrottle` prop. + */ onScroll?: ?Function, + /** + * Called when the user begins to drag the scroll view. + */ onScrollBeginDrag?: ?Function, + /** + * Called when the user stops dragging the scroll view and it either stops + * or begins to glide. + */ onScrollEndDrag?: ?Function, + /** + * Called when scrollable content view of the ScrollView changes. + * + * Handler function is passed the content width and content height as parameters: + * `(contentWidth, contentHeight)` + * + * It's implemented using onLayout handler attached to the content container + * which this ScrollView renders. + */ onContentSizeChange?: ?Function, onKeyboardDidShow?: (event: PressEvent) => void, + /** + * When true, the scroll view stops on multiples of the scroll view's size + * when scrolling. This can be used for horizontal pagination. The default + * value is false. + * + * Note: Vertical pagination is not supported on Android. + */ pagingEnabled?: ?boolean, + /** + * When false, the view cannot be scrolled via touch interaction. + * The default value is true. + * + * Note that the view can always be scrolled by calling `scrollTo`. + */ scrollEnabled?: ?boolean, + /** + * When true, shows a vertical scroll indicator. + * The default value is true. + */ showsVerticalScrollIndicator?: ?boolean, + /** + * An array of child indices determining which children get docked to the + * top of the screen when scrolling. For example, passing + * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the + * top of the scroll view. This property is not supported in conjunction + * with `horizontal={true}`. + */ stickyHeaderIndices?: ?$ReadOnlyArray, + /** + * When set, causes the scroll view to stop at multiples of the value of + * `snapToInterval`. This can be used for paginating through children + * that have lengths smaller than the scroll view. Typically used in + * combination with `snapToAlignment` and `decelerationRate="fast"` on ios. + * Overrides less configurable `pagingEnabled` prop. + * + * Supported for horizontal scrollview on android. + */ snapToInterval?: ?number, + /** + * Experimental: When true, offscreen child views (whose `overflow` value is + * `hidden`) are removed from their native backing superview when offscreen. + * This can improve scrolling performance on long lists. The default value is + * true. + */ removeClippedSubviews?: ?boolean, + /** + * A RefreshControl component, used to provide pull-to-refresh + * functionality for the ScrollView. Only works for vertical ScrollViews + * (`horizontal` prop must be `false`). + * + * See [RefreshControl](docs/refreshcontrol.html). + */ refreshControl?: ?React.Element, style?: ?ViewStyleProp, children?: React.Node, @@ -194,432 +524,6 @@ export type Props = $ReadOnly<{| */ const ScrollView = createReactClass({ displayName: 'ScrollView', - propTypes: { - ...ViewPropTypes, - /** - * Controls whether iOS should automatically adjust the content inset - * for scroll views that are placed behind a navigation bar or - * tab bar/ toolbar. The default value is true. - * @platform ios - */ - automaticallyAdjustContentInsets: PropTypes.bool, - /** - * The amount by which the scroll view content is inset from the edges - * of the scroll view. Defaults to `{top: 0, left: 0, bottom: 0, right: 0}`. - * @platform ios - */ - contentInset: EdgeInsetsPropType, - /** - * Used to manually set the starting scroll offset. - * The default value is `{x: 0, y: 0}`. - * @platform ios - */ - contentOffset: PointPropType, - /** - * When true, the scroll view bounces when it reaches the end of the - * content if the content is larger then the scroll view along the axis of - * the scroll direction. When false, it disables all bouncing even if - * the `alwaysBounce*` props are true. The default value is true. - * @platform ios - */ - bounces: PropTypes.bool, - /** - * When true, gestures can drive zoom past min/max and the zoom will animate - * to the min/max value at gesture end, otherwise the zoom will not exceed - * the limits. - * @platform ios - */ - bouncesZoom: PropTypes.bool, - /** - * When true, the scroll view bounces horizontally when it reaches the end - * even if the content is smaller than the scroll view itself. The default - * value is true when `horizontal={true}` and false otherwise. - * @platform ios - */ - alwaysBounceHorizontal: PropTypes.bool, - /** - * When true, the scroll view bounces vertically when it reaches the end - * even if the content is smaller than the scroll view itself. The default - * value is false when `horizontal={true}` and true otherwise. - * @platform ios - */ - alwaysBounceVertical: PropTypes.bool, - /** - * When true, the scroll view automatically centers the content when the - * content is smaller than the scroll view bounds; when the content is - * larger than the scroll view, this property has no effect. The default - * value is false. - * @platform ios - */ - centerContent: PropTypes.bool, - /** - * These styles will be applied to the scroll view content container which - * wraps all of the child views. Example: - * - * ``` - * return ( - * - * - * ); - * ... - * const styles = StyleSheet.create({ - * contentContainer: { - * paddingVertical: 20 - * } - * }); - * ``` - */ - contentContainerStyle: StyleSheetPropType(ViewStylePropTypes), - /** - * A floating-point number that determines how quickly the scroll view - * decelerates after the user lifts their finger. You may also use string - * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings - * for `UIScrollViewDecelerationRateNormal` and - * `UIScrollViewDecelerationRateFast` respectively. - * - * - `'normal'`: 0.998 (the default) - * - `'fast'`: 0.99 - * - * @platform ios - */ - decelerationRate: PropTypes.oneOfType([ - PropTypes.oneOf(['fast', 'normal']), - PropTypes.number, - ]), - /** - * When true, the scroll view's children are arranged horizontally in a row - * instead of vertically in a column. The default value is false. - */ - horizontal: PropTypes.bool, - /** - * The style of the scroll indicators. - * - * - `'default'` (the default), same as `black`. - * - `'black'`, scroll indicator is black. This style is good against a light background. - * - `'white'`, scroll indicator is white. This style is good against a dark background. - * - * @platform ios - */ - indicatorStyle: PropTypes.oneOf([ - 'default', // default - 'black', - 'white', - ]), - /** - * If sticky headers should stick at the bottom instead of the top of the - * ScrollView. This is usually used with inverted ScrollViews. - */ - invertStickyHeaders: PropTypes.bool, - /** - * When true, the ScrollView will try to lock to only vertical or horizontal - * scrolling while dragging. The default value is false. - * @platform ios - */ - directionalLockEnabled: PropTypes.bool, - /** - * When false, once tracking starts, won't try to drag if the touch moves. - * The default value is true. - * @platform ios - */ - canCancelContentTouches: PropTypes.bool, - /** - * Determines whether the keyboard gets dismissed in response to a drag. - * - * *Cross platform* - * - * - `'none'` (the default), drags do not dismiss the keyboard. - * - `'on-drag'`, the keyboard is dismissed when a drag begins. - * - * *iOS Only* - * - * - `'interactive'`, the keyboard is dismissed interactively with the drag and moves in - * synchrony with the touch; dragging upwards cancels the dismissal. - * On android this is not supported and it will have the same behavior as 'none'. - */ - keyboardDismissMode: PropTypes.oneOf([ - 'none', // default - 'on-drag', // Cross-platform - 'interactive', // iOS-only - ]), - /** - * Determines when the keyboard should stay visible after a tap. - * - * - `'never'` (the default), tapping outside of the focused text input when the keyboard - * is up dismisses the keyboard. When this happens, children won't receive the tap. - * - `'always'`, the keyboard will not dismiss automatically, and the scroll view will not - * catch taps, but children of the scroll view can catch taps. - * - `'handled'`, the keyboard will not dismiss automatically when the tap was handled by - * a children, (or captured by an ancestor). - * - `false`, deprecated, use 'never' instead - * - `true`, deprecated, use 'always' instead - */ - keyboardShouldPersistTaps: PropTypes.oneOf([ - 'always', - 'never', - 'handled', - false, - true, - ]), - /** - * When set, the scroll view will adjust the scroll position so that the first child that is - * currently visible and at or beyond `minIndexForVisible` will not change position. This is - * useful for lists that are loading content in both directions, e.g. a chat thread, where new - * messages coming in might otherwise cause the scroll position to jump. A value of 0 is common, - * but other values such as 1 can be used to skip loading spinners or other content that should - * not maintain position. - * - * The optional `autoscrollToTopThreshold` can be used to make the content automatically scroll - * to the top after making the adjustment if the user was within the threshold of the top before - * the adjustment was made. This is also useful for chat-like applications where you want to see - * new messages scroll into place, but not if the user has scrolled up a ways and it would be - * disruptive to scroll a bunch. - * - * Caveat 1: Reordering elements in the scrollview with this enabled will probably cause - * jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now, - * don't re-order the content of any ScrollViews or Lists that use this feature. - * - * Caveat 2: This simply uses `contentOffset` and `frame.origin` in native code to compute - * visibility. Occlusion, transforms, and other complexity won't be taken into account as to - * whether content is "visible" or not. - * - * @platform ios - */ - maintainVisibleContentPosition: PropTypes.shape({ - minIndexForVisible: PropTypes.number.isRequired, - autoscrollToTopThreshold: PropTypes.number, - }), - /** - * The maximum allowed zoom scale. The default value is 1.0. - * @platform ios - */ - maximumZoomScale: PropTypes.number, - /** - * The minimum allowed zoom scale. The default value is 1.0. - * @platform ios - */ - minimumZoomScale: PropTypes.number, - /** - * Enables nested scrolling for Android API level 21+. - * Nested scrolling is supported by default on iOS - * @platform android - */ - nestedScrollEnabled: PropTypes.bool, - /** - * Called when the momentum scroll starts (scroll which occurs as the ScrollView glides to a stop). - */ - onMomentumScrollBegin: PropTypes.func, - /** - * Called when the momentum scroll ends (scroll which occurs as the ScrollView glides to a stop). - */ - onMomentumScrollEnd: PropTypes.func, - /** - * Fires at most once per frame during scrolling. The frequency of the - * events can be controlled using the `scrollEventThrottle` prop. - */ - onScroll: PropTypes.func, - /** - * Called when the user begins to drag the scroll view. - */ - onScrollBeginDrag: PropTypes.func, - /** - * Called when the user stops dragging the scroll view and it either stops - * or begins to glide. - */ - onScrollEndDrag: PropTypes.func, - /** - * Called when scrollable content view of the ScrollView changes. - * - * Handler function is passed the content width and content height as parameters: - * `(contentWidth, contentHeight)` - * - * It's implemented using onLayout handler attached to the content container - * which this ScrollView renders. - */ - onContentSizeChange: PropTypes.func, - /** - * When true, the scroll view stops on multiples of the scroll view's size - * when scrolling. This can be used for horizontal pagination. The default - * value is false. - * - * Note: Vertical pagination is not supported on Android. - */ - pagingEnabled: PropTypes.bool, - /** - * When true, ScrollView allows use of pinch gestures to zoom in and out. - * The default value is true. - * @platform ios - */ - pinchGestureEnabled: PropTypes.bool, - /** - * When false, the view cannot be scrolled via touch interaction. - * The default value is true. - * - * Note that the view can always be scrolled by calling `scrollTo`. - */ - scrollEnabled: PropTypes.bool, - /** - * This controls how often the scroll event will be fired while scrolling - * (as a time interval in ms). A lower number yields better accuracy for code - * that is tracking the scroll position, but can lead to scroll performance - * problems due to the volume of information being send over the bridge. - * You will not notice a difference between values set between 1-16 as the - * JS run loop is synced to the screen refresh rate. If you do not need precise - * scroll position tracking, set this value higher to limit the information - * being sent across the bridge. The default value is zero, which results in - * the scroll event being sent only once each time the view is scrolled. - * @platform ios - */ - scrollEventThrottle: PropTypes.number, - /** - * The amount by which the scroll view indicators are inset from the edges - * of the scroll view. This should normally be set to the same value as - * the `contentInset`. Defaults to `{0, 0, 0, 0}`. - * @platform ios - */ - scrollIndicatorInsets: EdgeInsetsPropType, - /** - * When true, the scroll view scrolls to top when the status bar is tapped. - * The default value is true. - * @platform ios - */ - scrollsToTop: PropTypes.bool, - /** - * When true, shows a horizontal scroll indicator. - * The default value is true. - */ - showsHorizontalScrollIndicator: PropTypes.bool, - /** - * When true, shows a vertical scroll indicator. - * The default value is true. - */ - showsVerticalScrollIndicator: PropTypes.bool, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), - /** - * When set, causes the scroll view to stop at multiples of the value of - * `snapToInterval`. This can be used for paginating through children - * that have lengths smaller than the scroll view. Typically used in - * combination with `snapToAlignment` and `decelerationRate="fast"` on ios. - * Overrides less configurable `pagingEnabled` prop. - * - * Supported for horizontal scrollview on android. - */ - snapToInterval: PropTypes.number, - /** - * When `snapToInterval` is set, `snapToAlignment` will define the relationship - * of the snapping to the scroll view. - * - * - `'start'` (the default) will align the snap at the left (horizontal) or top (vertical) - * - `'center'` will align the snap in the center - * - `'end'` will align the snap at the right (horizontal) or bottom (vertical) - * - * @platform ios - */ - snapToAlignment: PropTypes.oneOf([ - 'start', // default - 'center', - 'end', - ]), - /** - * Experimental: When true, offscreen child views (whose `overflow` value is - * `hidden`) are removed from their native backing superview when offscreen. - * This can improve scrolling performance on long lists. The default value is - * true. - */ - removeClippedSubviews: PropTypes.bool, - /** - * The current scale of the scroll view content. The default value is 1.0. - * @platform ios - */ - zoomScale: PropTypes.number, - /** - * This property specifies how the safe area insets are used to modify the - * content area of the scroll view. The default value of this property is - * "never". Available on iOS 11 and later. - * @platform ios - */ - contentInsetAdjustmentBehavior: PropTypes.oneOf([ - 'automatic', - 'scrollableAxes', - 'never', // default - 'always', - ]), - /** - * A RefreshControl component, used to provide pull-to-refresh - * functionality for the ScrollView. Only works for vertical ScrollViews - * (`horizontal` prop must be `false`). - * - * See [RefreshControl](docs/refreshcontrol.html). - */ - refreshControl: PropTypes.element, - - /** - * Sometimes a scrollview takes up more space than its content fills. When this is - * the case, this prop will fill the rest of the scrollview with a color to avoid setting - * a background and creating unnecessary overdraw. This is an advanced optimization - * that is not needed in the general case. - * @platform android - */ - endFillColor: ColorPropType, - - /** - * Tag used to log scroll performance on this scroll view. Will force - * momentum events to be turned on (see sendMomentumEvents). This doesn't do - * anything out of the box and you need to implement a custom native - * FpsListener for it to be useful. - * @platform android - */ - scrollPerfTag: PropTypes.string, - - /** - * Used to override default value of overScroll mode. - * - * Possible values: - * - * - `'auto'` - Default value, allow a user to over-scroll - * this view only if the content is large enough to meaningfully scroll. - * - `'always'` - Always allow a user to over-scroll this view. - * - `'never'` - Never allow a user to over-scroll this view. - * - * @platform android - */ - overScrollMode: PropTypes.oneOf(['auto', 'always', 'never']), - /** - * When true, ScrollView will emit updateChildFrames data in scroll events, - * otherwise will not compute or emit child frame data. This only exists - * to support legacy issues, `onLayout` should be used instead to retrieve - * frame data. - * The default value is false. - * @platform ios - */ - DEPRECATED_sendUpdatedChildFrames: PropTypes.bool, - /** - * Optionally an image can be used for the scroll bar thumb. This will - * override the color. While the image is loading or the image fails to - * load the color will be used instead. Use an alpha of 0 in the color - * to avoid seeing it while the image is loading. - * - * - `uri` - a string representing the resource identifier for the image, which - * should be either a local file path or the name of a static image resource - * - `number` - Opaque type returned by something like - * `import IMAGE from './image.jpg'`. - * @platform vr - */ - scrollBarThumbImage: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - }), - // Opaque type returned by import IMAGE from './image.jpg' - PropTypes.number, - ]), - }, - mixins: [ScrollResponder.Mixin], _scrollAnimatedValue: (new AnimatedImplementation.Value( @@ -637,11 +541,9 @@ const ScrollView = createReactClass({ UNSAFE_componentWillMount: function() { this._scrollAnimatedValue = new AnimatedImplementation.Value( - // $FlowFixMe this.props.contentOffset ? this.props.contentOffset.y : 0, ); this._scrollAnimatedValue.setOffset( - // $FlowFixMe this.props.contentInset ? this.props.contentInset.top : 0, ); this._stickyHeaderRefs = new Map(); @@ -784,7 +686,6 @@ const ScrollView = createReactClass({ if (!this.props.stickyHeaderIndices) { return; } - // $FlowFixMe Invalid prop usage const childArray = React.Children.toArray(this.props.children); if (key !== this._getKeyForIndex(index, childArray)) { // ignore stale layout update @@ -794,10 +695,8 @@ const ScrollView = createReactClass({ const layoutY = event.nativeEvent.layout.y; this._headerLayoutYs.set(key, layoutY); - // $FlowFixMe Invalid prop usage const indexOfIndex = this.props.stickyHeaderIndices.indexOf(index); const previousHeaderIndex = this.props.stickyHeaderIndices[ - // $FlowFixMe Invalid prop usage indexOfIndex - 1 ]; if (previousHeaderIndex != null) { @@ -875,7 +774,7 @@ const ScrollView = createReactClass({ ScrollViewClass = RCTScrollView; ScrollContentContainerViewClass = RCTScrollContentView; warning( - !this.props.snapToInterval || !this.props.pagingEnabled, + this.props.snapToInterval == null || !this.props.pagingEnabled, 'snapToInterval is currently ignored when pagingEnabled is true.', ); } @@ -918,16 +817,13 @@ const ScrollView = createReactClass({ const hasStickyHeaders = stickyHeaderIndices && stickyHeaderIndices.length > 0; const childArray = - // $FlowFixMe Invalid prop usage hasStickyHeaders && React.Children.toArray(this.props.children); const children = hasStickyHeaders ? // $FlowFixMe Invalid prop usage childArray.map((child, index) => { - // $FlowFixMe Invalid prop usage const indexOfIndex = child ? stickyHeaderIndices.indexOf(index) : -1; if (indexOfIndex > -1) { const key = child.key; - // $FlowFixMe Invalid prop usage const nextIndex = stickyHeaderIndices[indexOfIndex + 1]; return ( {Platform.isTV ? null : refreshControl} {contentContainer} @@ -1058,7 +952,6 @@ const ScrollView = createReactClass({ {contentContainer} , @@ -1066,7 +959,6 @@ const ScrollView = createReactClass({ } } return ( - // $FlowFixMe Invalid prop usage {contentContainer} diff --git a/Libraries/Components/Slider/Slider.js b/Libraries/Components/Slider/Slider.js index d9efe0170d8dfd..7f18a2d3b35ef0 100644 --- a/Libraries/Components/Slider/Slider.js +++ b/Libraries/Components/Slider/Slider.js @@ -250,7 +250,6 @@ SliderWithRef.defaultProps = { maximumValue: 1, step: 0, }; -SliderWithRef.displayName = 'Slider'; let styles; if (Platform.OS === 'ios') { diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 6a8fa3cb5d8243..3f1874c8aac4a4 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -533,7 +533,7 @@ const TouchableMixin = { * focused at a time, in which case there may have been a previously focused * element that was blurred just prior to this. */ - touchableHandleFocus: function (e: Event) { + touchableHandleFocus: function(e: Event) { this.props.onFocus && this.props.onFocus(e); }, @@ -543,7 +543,7 @@ const TouchableMixin = { * no longer has focus. Most platforms only support a single element being * focused at a time, in which case the focus may have moved to another. */ - touchableHandleBlur: function (e: Event) { + touchableHandleBlur: function(e: Event) { this.props.onBlur && this.props.onBlur(e); }, diff --git a/Libraries/Components/View/ReactNativeViewAttributes.js b/Libraries/Components/View/ReactNativeViewAttributes.js index 7bf8117710ef5d..bc4ca6b3d229fc 100644 --- a/Libraries/Components/View/ReactNativeViewAttributes.js +++ b/Libraries/Components/View/ReactNativeViewAttributes.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict */ 'use strict'; diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index ed420338c243c3..2cfa05a8c118b2 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -45,7 +45,6 @@ if (__DEV__) { ); }; - View.displayName = 'View'; // TODO(T30332650) remove bug workaround // $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. ViewToExport = React.forwardRef(View); } diff --git a/Libraries/Components/View/ViewAccessibility.js b/Libraries/Components/View/ViewAccessibility.js index b6e46f5b65a990..66988382a205c1 100644 --- a/Libraries/Components/View/ViewAccessibility.js +++ b/Libraries/Components/View/ViewAccessibility.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict */ 'use strict'; @@ -39,6 +39,17 @@ export type AccessibilityComponentType = | 'radiobutton_checked' | 'radiobutton_unchecked'; +export type AccessibilityRole = + | 'none' + | 'button' + | 'link' + | 'search' + | 'image' + | 'keyboardkey' + | 'text' + | 'adjustable' + | 'imagebutton'; + module.exports = { AccessibilityTraits: [ 'none', @@ -65,4 +76,15 @@ module.exports = { 'radiobutton_checked', 'radiobutton_unchecked', ], + AccessibilityRoles: [ + 'none', + 'button', + 'link', + 'search', + 'image', + 'keyboardkey', + 'text', + 'adjustable', + 'imagebutton', + ], }; diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js index 0f7dea210694ce..73a39cf255fcbf 100644 --- a/Libraries/Components/View/ViewPropTypes.js +++ b/Libraries/Components/View/ViewPropTypes.js @@ -20,11 +20,13 @@ const ViewStylePropTypes = require('ViewStylePropTypes'); const { AccessibilityComponentTypes, AccessibilityTraits, + AccessibilityRoles, } = require('ViewAccessibility'); import type { AccessibilityComponentType, AccessibilityTrait, + AccessibilityRole, } from 'ViewAccessibility'; import type {EdgeInsetsProp} from 'EdgeInsetsPropType'; import type {TVViewProps} from 'TVViewPropTypes'; @@ -87,7 +89,9 @@ export type ViewProps = $ReadOnly<{| accessibilityComponentType?: AccessibilityComponentType, accessibilityLiveRegion?: 'none' | 'polite' | 'assertive', importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants', + accessibilityIgnoresInvertColors?: boolean, accessibilityTraits?: AccessibilityTrait | Array, + accessibilityRole?: AccessibilityRole, accessibilityViewIsModal?: boolean, accessibilityElementsHidden?: boolean, children?: ?React.Node, @@ -138,6 +142,12 @@ module.exports = { */ accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentTypes), + /** + * Indicates to accessibility services to treat UI component like a + * native one. Merging accessibilityComponentType and accessibilityTraits. + */ + accessibilityRole: PropTypes.oneOf(AccessibilityRoles), + /** * Indicates to accessibility services whether the user should be notified * when this view changes. Works for Android API >= 19 only. diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index ca91bc3b877edc..56290df97bcf94 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -203,7 +203,10 @@ class WebView extends React.Component { * - fast: 0.99 (the default for iOS web view) * @platform ios */ - decelerationRate: ScrollView.propTypes.decelerationRate, + decelerationRate: PropTypes.oneOfType([ + PropTypes.oneOf(['fast', 'normal']), + PropTypes.number, + ]), /** * Boolean value that determines whether scrolling is enabled in the * `WebView`. The default value is `true`. diff --git a/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js b/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js index dd002fe259745c..e0ea2ce8cabd23 100644 --- a/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js @@ -11,7 +11,6 @@ 'use strict'; const Image = require('Image'); -const PropTypes = require('prop-types'); const React = require('React'); const Text = require('Text'); const TouchableHighlight = require('TouchableHighlight'); @@ -27,43 +26,37 @@ import type {ImageSource} from 'ImageSource'; */ class SwipeableQuickActionButton extends React.Component<{ accessibilityLabel?: string, - imageSource: ImageSource | number, + imageSource?: ?(ImageSource | number), imageStyle?: ?ViewPropTypes.style, + mainView?: ?React.Node, onPress?: Function, style?: ?ViewPropTypes.style, testID?: string, text?: ?(string | Object | Array), textStyle?: ?ViewPropTypes.style, }> { - static propTypes = { - accessibilityLabel: PropTypes.string, - imageSource: Image.propTypes.source.isRequired, - imageStyle: Image.propTypes.style, - onPress: PropTypes.func, - style: ViewPropTypes.style, - testID: PropTypes.string, - text: PropTypes.string, - textStyle: Text.propTypes.style, - }; - render(): React.Node { - if (!this.props.imageSource && !this.props.text) { + if (!this.props.imageSource && !this.props.text && !this.props.mainView) { return null; } - + const mainView = this.props.mainView ? ( + this.props.mainView + ) : ( + + + {this.props.text} + + ); return ( - - - {this.props.text} - + {mainView} ); } diff --git a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj index b1e18399c68389..c0754118087ab4 100644 --- a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj +++ b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj @@ -61,7 +61,7 @@ 58B511D31A9E6C8500147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 58B511DA1A9E6C8500147676 = { @@ -106,20 +106,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -154,20 +165,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; diff --git a/Libraries/Image/AssetRegistry.js b/Libraries/Image/AssetRegistry.js index ad1b495af42894..b050a9e239cd27 100644 --- a/Libraries/Image/AssetRegistry.js +++ b/Libraries/Image/AssetRegistry.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict * @format */ 'use strict'; diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 350bba68ea8934..d0e685a5e64229 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -11,17 +11,15 @@ 'use strict'; const ImageStylePropTypes = require('ImageStylePropTypes'); -const NativeMethodsMixin = require('NativeMethodsMixin'); const NativeModules = require('NativeModules'); const React = require('React'); +const ReactNative = require('ReactNative'); const PropTypes = require('prop-types'); -const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const StyleSheet = require('StyleSheet'); const StyleSheetPropType = require('StyleSheetPropType'); const TextAncestor = require('TextAncestor'); const ViewPropTypes = require('ViewPropTypes'); -const createReactClass = require('create-react-class'); const flattenStyle = require('flattenStyle'); const merge = require('merge'); const requireNativeComponent = require('requireNativeComponent'); @@ -32,11 +30,149 @@ const {ImageLoader} = NativeModules; const RKImage = requireNativeComponent('RCTImageView'); const RCTTextInlineImage = requireNativeComponent('RCTTextInlineImage'); +import type {ImageProps as ImagePropsType} from 'ImageProps'; + let _requestId = 1; function generateRequestId() { return _requestId++; } +const ImageProps = { + ...ViewPropTypes, + style: StyleSheetPropType(ImageStylePropTypes), + /** + * See https://facebook.github.io/react-native/docs/image.html#source + */ + source: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string, + headers: PropTypes.objectOf(PropTypes.string), + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, + // Multiple sources + PropTypes.arrayOf( + PropTypes.shape({ + uri: PropTypes.string, + width: PropTypes.number, + height: PropTypes.number, + headers: PropTypes.objectOf(PropTypes.string), + }), + ), + ]), + /** + * blurRadius: the blur radius of the blur filter added to the image + * + * See https://facebook.github.io/react-native/docs/image.html#blurradius + */ + blurRadius: PropTypes.number, + /** + * See https://facebook.github.io/react-native/docs/image.html#defaultsource + */ + defaultSource: PropTypes.number, + /** + * See https://facebook.github.io/react-native/docs/image.html#loadingindicatorsource + */ + loadingIndicatorSource: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string, + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, + ]), + progressiveRenderingEnabled: PropTypes.bool, + fadeDuration: PropTypes.number, + /** + * Invoked on load start + */ + onLoadStart: PropTypes.func, + /** + * Invoked on load error + */ + onError: PropTypes.func, + /** + * Invoked when load completes successfully + */ + onLoad: PropTypes.func, + /** + * Invoked when load either succeeds or fails + */ + onLoadEnd: PropTypes.func, + /** + * Used to locate this view in end-to-end tests. + */ + testID: PropTypes.string, + /** + * The mechanism that should be used to resize the image when the image's dimensions + * differ from the image view's dimensions. Defaults to `auto`. + * + * See https://facebook.github.io/react-native/docs/image.html#resizemethod + */ + resizeMethod: PropTypes.oneOf(['auto', 'resize', 'scale']), + /** + * Determines how to resize the image when the frame doesn't match the raw + * image dimensions. + * + * See https://facebook.github.io/react-native/docs/image.html#resizemode + */ + resizeMode: PropTypes.oneOf([ + 'cover', + 'contain', + 'stretch', + 'repeat', + 'center', + ]), +}; + +function getSize( + url: string, + success: (width: number, height: number) => void, + failure?: (error: any) => void, +) { + return ImageLoader.getSize(url) + .then(function(sizes) { + success(sizes.width, sizes.height); + }) + .catch( + failure || + function() { + console.warn('Failed to get size for image: ' + url); + }, + ); +} + +function prefetch(url: string, callback: ?Function) { + const requestId = generateRequestId(); + callback && callback(requestId); + return ImageLoader.prefetchImage(url, requestId); +} + +function abortPrefetch(requestId: number) { + ImageLoader.abortRequest(requestId); +} + +/** + * Perform cache interrogation. + * + * See https://facebook.github.io/react-native/docs/image.html#querycache + */ +async function queryCache( + urls: Array, +): Promise> { + return await ImageLoader.queryCache(urls); +} + +declare class ImageComponentType extends ReactNative.NativeComponent< + ImagePropsType, +> { + static getSize: typeof getSize; + static prefetch: typeof prefetch; + static abortPrefetch: typeof abortPrefetch; + static queryCache: typeof queryCache; + static resolveAssetSource: typeof resolveAssetSource; + static propTypes: typeof ImageProps; +} + /** * A React component for displaying different types of images, * including network images, static resources, temporary local images, and @@ -44,236 +180,123 @@ function generateRequestId() { * * See https://facebook.github.io/react-native/docs/image.html */ -const Image = createReactClass({ - displayName: 'Image', - propTypes: { - ...ViewPropTypes, - style: StyleSheetPropType(ImageStylePropTypes), - /** - * See https://facebook.github.io/react-native/docs/image.html#source - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - headers: PropTypes.objectOf(PropTypes.string), - }), - // Opaque type returned by require('./image.jpg') - PropTypes.number, - // Multiple sources - PropTypes.arrayOf( - PropTypes.shape({ - uri: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - headers: PropTypes.objectOf(PropTypes.string), - }), - ), - ]), - /** - * blurRadius: the blur radius of the blur filter added to the image - * - * See https://facebook.github.io/react-native/docs/image.html#blurradius - */ - blurRadius: PropTypes.number, - /** - * See https://facebook.github.io/react-native/docs/image.html#defaultsource - */ - defaultSource: PropTypes.number, - /** - * See https://facebook.github.io/react-native/docs/image.html#loadingindicatorsource - */ - loadingIndicatorSource: PropTypes.oneOfType([ - PropTypes.shape({ - uri: PropTypes.string, - }), - // Opaque type returned by require('./image.jpg') - PropTypes.number, - ]), - progressiveRenderingEnabled: PropTypes.bool, - fadeDuration: PropTypes.number, - /** - * Invoked on load start - */ - onLoadStart: PropTypes.func, - /** - * Invoked on load error - */ - onError: PropTypes.func, - /** - * Invoked when load completes successfully - */ - onLoad: PropTypes.func, - /** - * Invoked when load either succeeds or fails - */ - onLoadEnd: PropTypes.func, - /** - * Used to locate this view in end-to-end tests. - */ - testID: PropTypes.string, - /** - * The mechanism that should be used to resize the image when the image's dimensions - * differ from the image view's dimensions. Defaults to `auto`. - * - * See https://facebook.github.io/react-native/docs/image.html#resizemethod - */ - resizeMethod: PropTypes.oneOf(['auto', 'resize', 'scale']), - /** - * Determines how to resize the image when the frame doesn't match the raw - * image dimensions. - * - * See https://facebook.github.io/react-native/docs/image.html#resizemode - */ - resizeMode: PropTypes.oneOf([ - 'cover', - 'contain', - 'stretch', - 'repeat', - 'center', - ]), - }, +let Image = ( + props: ImagePropsType, + forwardedRef: ?React.Ref<'RCTTextInlineImage' | 'RKImage'>, +) => { + const source = resolveAssetSource(props.source); + const defaultSource = resolveAssetSource(props.defaultSource); + const loadingIndicatorSource = resolveAssetSource( + props.loadingIndicatorSource, + ); - statics: { - getSize( - url: string, - success: (width: number, height: number) => void, - failure?: (error: any) => void, - ) { - return ImageLoader.getSize(url) - .then(function(sizes) { - success(sizes.width, sizes.height); - }) - .catch( - failure || - function() { - console.warn('Failed to get size for image: ' + url); - }, - ); - }, - - /** - * Prefetches a remote image for later use by downloading it to the disk - * cache - * - * See https://facebook.github.io/react-native/docs/image.html#prefetch - */ - prefetch(url: string, callback: ?Function) { - const requestId = generateRequestId(); - callback && callback(requestId); - return ImageLoader.prefetchImage(url, requestId); - }, - - /** - * Abort prefetch request. - * - * See https://facebook.github.io/react-native/docs/image.html#abortprefetch - */ - abortPrefetch(requestId: number) { - ImageLoader.abortRequest(requestId); - }, - - /** - * Perform cache interrogation. - * - * See https://facebook.github.io/react-native/docs/image.html#querycache - */ - async queryCache( - urls: Array, - ): Promise> { - return await ImageLoader.queryCache(urls); - }, - - /** - * Resolves an asset reference into an object. - * - * See https://facebook.github.io/react-native/docs/image.html#resolveassetsource - */ - resolveAssetSource: resolveAssetSource, - }, + // As opposed to the ios version, here we render `null` when there is no source, source.uri + // or source array. - mixins: [NativeMethodsMixin], + if (source && source.uri === '') { + console.warn('source.uri should not be an empty string'); + } - /** - * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We - * make `this` look like an actual native component class. - */ - viewConfig: { - uiViewClassName: 'RCTView', - validAttributes: ReactNativeViewAttributes.RCTView, - }, + if (props.src) { + console.warn( + 'The component requires a `source` property rather than `src`.', + ); + } - render: function() { - const source = resolveAssetSource(this.props.source); - const defaultSource = resolveAssetSource(this.props.defaultSource); - const loadingIndicatorSource = resolveAssetSource( - this.props.loadingIndicatorSource, + if (props.children) { + throw new Error( + 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', ); + } - // As opposed to the ios version, here we render `null` when there is no source, source.uri - // or source array. - - if (source && source.uri === '') { - console.warn('source.uri should not be an empty string'); - } - - if (this.props.src) { - console.warn( - 'The component requires a `source` property rather than `src`.', - ); - } - - if (this.props.children) { - throw new Error( - 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', - ); - } - - if (this.props.defaultSource && this.props.loadingIndicatorSource) { - throw new Error( - 'The component cannot have defaultSource and loadingIndicatorSource at the same time. Please use either defaultSource or loadingIndicatorSource.', - ); - } - - if (!source || (!source.uri && !Array.isArray(source))) { - return null; - } - - let style; - let sources; - if (source.uri) { - const {width, height} = source; - style = flattenStyle([{width, height}, styles.base, this.props.style]); - sources = [{uri: source.uri}]; - } else { - style = flattenStyle([styles.base, this.props.style]); - sources = source; - } - - const {onLoadStart, onLoad, onLoadEnd, onError} = this.props; - const nativeProps = merge(this.props, { - style, - shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError), - src: sources, - headers: source.headers, - defaultSrc: defaultSource ? defaultSource.uri : null, - loadingIndicatorSrc: loadingIndicatorSource - ? loadingIndicatorSource.uri - : null, - }); - - return ( - - {hasTextAncestor => - hasTextAncestor ? ( - - ) : ( - - ) - } - + if (props.defaultSource && props.loadingIndicatorSource) { + throw new Error( + 'The component cannot have defaultSource and loadingIndicatorSource at the same time. Please use either defaultSource or loadingIndicatorSource.', ); - }, -}); + } + + if (!source || (!source.uri && !Array.isArray(source))) { + return null; + } + + let style; + let sources; + if (source.uri) { + const {width, height} = source; + style = flattenStyle([{width, height}, styles.base, props.style]); + sources = [{uri: source.uri}]; + } else { + style = flattenStyle([styles.base, props.style]); + sources = source; + } + + const {onLoadStart, onLoad, onLoadEnd, onError} = props; + const nativeProps = merge(props, { + style, + shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError), + src: sources, + headers: source.headers, + defaultSrc: defaultSource ? defaultSource.uri : null, + loadingIndicatorSrc: loadingIndicatorSource + ? loadingIndicatorSource.uri + : null, + ref: forwardedRef, + }); + + return ( + + {hasTextAncestor => + hasTextAncestor ? ( + + ) : ( + + ) + } + + ); +}; + +// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. +Image = React.forwardRef(Image); + +/** + * Prefetches a remote image for later use by downloading it to the disk + * cache + * + * See https://facebook.github.io/react-native/docs/image.html#prefetch + */ +Image.getSize = getSize; + +/** + * Prefetches a remote image for later use by downloading it to the disk + * cache + * + * See https://facebook.github.io/react-native/docs/image.html#prefetch + */ +Image.prefetch = prefetch; + +/** + * Abort prefetch request. + * + * See https://facebook.github.io/react-native/docs/image.html#abortprefetch + */ +Image.abortPrefetch = abortPrefetch; + +/** + * Perform cache interrogation. + * + * See https://facebook.github.io/react-native/docs/image.html#querycache + */ +Image.queryCache = queryCache; + +/** + * Resolves an asset reference into an object. + * + * See https://facebook.github.io/react-native/docs/image.html#resolveassetsource + */ +Image.resolveAssetSource = resolveAssetSource; + +Image.propTypes = ImageProps; const styles = StyleSheet.create({ base: { @@ -281,4 +304,4 @@ const styles = StyleSheet.create({ }, }); -module.exports = Image; +module.exports = (Image: Class); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index e3f153242df8c9..82e651c0362924 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -10,13 +10,11 @@ 'use strict'; const ImageProps = require('ImageProps'); -const NativeMethodsMixin = require('NativeMethodsMixin'); const NativeModules = require('NativeModules'); const React = require('React'); -const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const ReactNative = require('ReactNative'); const StyleSheet = require('StyleSheet'); -const createReactClass = require('create-react-class'); const flattenStyle = require('flattenStyle'); const requireNativeComponent = require('requireNativeComponent'); const resolveAssetSource = require('resolveAssetSource'); @@ -25,6 +23,36 @@ const ImageViewManager = NativeModules.ImageViewManager; const RCTImageView = requireNativeComponent('RCTImageView'); +import type {ImageProps as ImagePropsType} from 'ImageProps'; + +function getSize( + uri: string, + success: (width: number, height: number) => void, + failure?: (error: any) => void, +) { + ImageViewManager.getSize( + uri, + success, + failure || + function() { + console.warn('Failed to get size for image: ' + uri); + }, + ); +} + +function prefetch(url: string) { + return ImageViewManager.prefetchImage(url); +} + +declare class ImageComponentType extends ReactNative.NativeComponent< + ImagePropsType, +> { + static getSize: typeof getSize; + static prefetch: typeof prefetch; + static resolveAssetSource: typeof resolveAssetSource; + static propTypes: typeof ImageProps; +} + /** * A React component for displaying different types of images, * including network images, static resources, temporary local images, and @@ -32,108 +60,84 @@ const RCTImageView = requireNativeComponent('RCTImageView'); * * See https://facebook.github.io/react-native/docs/image.html */ -const Image = createReactClass({ - displayName: 'Image', - propTypes: ImageProps, - - statics: { - /** - * Retrieve the width and height (in pixels) of an image prior to displaying it. - * - * See https://facebook.github.io/react-native/docs/image.html#getsize - */ - getSize: function( - uri: string, - success: (width: number, height: number) => void, - failure?: (error: any) => void, - ) { - ImageViewManager.getSize( - uri, - success, - failure || - function() { - console.warn('Failed to get size for image: ' + uri); - }, - ); - }, - /** - * Prefetches a remote image for later use by downloading it to the disk - * cache. - * - * See https://facebook.github.io/react-native/docs/image.html#prefetch - */ - prefetch(url: string) { - return ImageViewManager.prefetchImage(url); - }, - /** - * Resolves an asset reference into an object. - * - * See https://facebook.github.io/react-native/docs/image.html#resolveassetsource - */ - resolveAssetSource: resolveAssetSource, - }, +let Image = ( + props: ImagePropsType, + forwardedRef: ?React.Ref<'RCTImageView'>, +) => { + const source = resolveAssetSource(props.source) || { + uri: undefined, + width: undefined, + height: undefined, + }; + + let sources; + let style; + if (Array.isArray(source)) { + style = flattenStyle([styles.base, props.style]) || {}; + sources = source; + } else { + const {width, height, uri} = source; + style = flattenStyle([{width, height}, styles.base, props.style]) || {}; + sources = [source]; + + if (uri === '') { + console.warn('source.uri should not be an empty string'); + } + } - mixins: [NativeMethodsMixin], + const resizeMode = props.resizeMode || style.resizeMode || 'cover'; + const tintColor = style.tintColor; - /** - * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We - * make `this` look like an actual native component class. - */ - viewConfig: { - uiViewClassName: 'UIView', - validAttributes: ReactNativeViewAttributes.UIView, - }, + if (props.src != null) { + console.warn( + 'The component requires a `source` property rather than `src`.', + ); + } - render: function() { - const source = resolveAssetSource(this.props.source) || { - uri: undefined, - width: undefined, - height: undefined, - }; - - let sources; - let style; - if (Array.isArray(source)) { - style = flattenStyle([styles.base, this.props.style]) || {}; - sources = source; - } else { - const {width, height, uri} = source; - style = - flattenStyle([{width, height}, styles.base, this.props.style]) || {}; - sources = [source]; - - if (uri === '') { - console.warn('source.uri should not be an empty string'); - } - } + if (props.children != null) { + throw new Error( + 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', + ); + } + + return ( + + ); +}; + +// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. +Image = React.forwardRef(Image); - const resizeMode = - this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108 - const tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108 +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it. + * + * See https://facebook.github.io/react-native/docs/image.html#getsize + */ +Image.getSize = getSize; - if (this.props.src) { - console.warn( - 'The component requires a `source` property rather than `src`.', - ); - } +/** + * Prefetches a remote image for later use by downloading it to the disk + * cache. + * + * See https://facebook.github.io/react-native/docs/image.html#prefetch + */ +Image.prefetch = prefetch; - if (this.props.children) { - throw new Error( - 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', - ); - } +/** + * Resolves an asset reference into an object. + * + * See https://facebook.github.io/react-native/docs/image.html#resolveassetsource + */ +Image.resolveAssetSource = resolveAssetSource; - return ( - - ); - }, -}); +Image.propTypes = ImageProps; const styles = StyleSheet.create({ base: { @@ -141,4 +145,4 @@ const styles = StyleSheet.create({ }, }); -module.exports = Image; +module.exports = (Image: Class); diff --git a/Libraries/Image/ImageBackground.js b/Libraries/Image/ImageBackground.js index 7b86d9c5f5298d..a128880a17807c 100644 --- a/Libraries/Image/ImageBackground.js +++ b/Libraries/Image/ImageBackground.js @@ -60,7 +60,10 @@ class ImageBackground extends React.Component<$FlowFixMeProps> { const {children, style, imageStyle, imageRef, ...props} = this.props; return ( - + | any), +type OnLoadEvent = SyntheticEvent< + $ReadOnly<{| + // Only on Android + uri?: string, + + source: $ReadOnly<{| + width: number, + height: number, + url: string, + |}>, + |}>, +>; + +type IOSImageProps = $ReadOnly<{| + defaultSource?: ?ImageSource, + onPartialLoad?: ?() => void, + onProgress?: ?( + event: SyntheticEvent<$ReadOnly<{|loaded: number, total: number|}>>, + ) => void, +|}>; + +type AndroidImageProps = $ReadOnly<{| + loadingIndicatorSource?: ?(number | $ReadOnly<{|uri: string|}>), + progressiveRenderingEnabled?: ?boolean, + fadeDuration?: ?number, +|}>; + +export type ImageProps = {| + ...ViewProps, + ...IOSImageProps, + ...AndroidImageProps, blurRadius?: number, capInsets?: ?EdgeInsetsProp, onError?: ?(event: SyntheticEvent<$ReadOnly<{||}>>) => void, - onLayout?: ?(event: LayoutEvent) => void, - onLoad?: ?() => void, + onLoad?: ?(event: OnLoadEvent) => void, onLoadEnd?: ?() => void, onLoadStart?: ?() => void, resizeMethod?: ?('auto' | 'resize' | 'scale'), - resizeMode?: ?('cover' | 'contain' | 'stretch' | 'repeat' | 'center'), source?: ?ImageSource, style?: ImageStyleProp, - testID?: ?string, - // ios - defaultSource?: ?ImageSource, - onPartialLoad?: ?() => void, - onProgress?: ?( - event: SyntheticEvent<$ReadOnly<{|loaded: number, total: number|}>>, - ) => void, -}; + // Can be set via props or style, for now + height?: DimensionValue, + width?: DimensionValue, + resizeMode?: ?('cover' | 'contain' | 'stretch' | 'repeat' | 'center'), + + src?: empty, + children?: empty, +|}; module.exports = { /** diff --git a/Libraries/Image/ImageSource.js b/Libraries/Image/ImageSource.js index 8338d5feb24a8e..5c3c84df901209 100644 --- a/Libraries/Image/ImageSource.js +++ b/Libraries/Image/ImageSource.js @@ -10,7 +10,11 @@ 'use strict'; // This is to sync with ImageSourcePropTypes.js. -type ImageURISource = $ReadOnly<{| +// We explicitly don't want this to be strict so that we can pass in objects +// that might have more keys. This also has to be inexact to support taking +// instances of classes like FBIcon. +// https://fburl.com/8lynhvtw +type ImageURISource = $ReadOnly<{ uri?: ?string, bundle?: ?string, method?: ?string, @@ -20,6 +24,10 @@ type ImageURISource = $ReadOnly<{| width?: ?number, height?: ?number, scale?: ?number, -|}>; +}>; +// We have to export any because of an issue in Flow with objects that come from Relay: +// https://fburl.com/8ljo5tmr +// https://fb.facebook.com/groups/flow/permalink/1824103160971624/ +// $FlowFixMe T26861415 export type ImageSource = ImageURISource | number | Array; diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 0af0af8f1ee6f5..bbf4d7104224ba 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -251,7 +251,7 @@ 58B511551A9E6B3D00147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0810; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 2D2A28391D9B042B00D4039D = { @@ -365,15 +365,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -417,15 +424,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/Libraries/Image/RelativeImageStub.js b/Libraries/Image/RelativeImageStub.js index ab845a274d277a..dcdeb9b916ac45 100644 --- a/Libraries/Image/RelativeImageStub.js +++ b/Libraries/Image/RelativeImageStub.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict */ 'use strict'; diff --git a/Libraries/Inspector/ElementBox.js b/Libraries/Inspector/ElementBox.js index e43233fb0dce51..dea05dad737894 100644 --- a/Libraries/Inspector/ElementBox.js +++ b/Libraries/Inspector/ElementBox.js @@ -10,38 +10,51 @@ 'use strict'; +const BorderBox = require('BorderBox'); const React = require('React'); -const View = require('View'); const StyleSheet = require('StyleSheet'); -const BorderBox = require('BorderBox'); -const resolveBoxStyle = require('resolveBoxStyle'); +const View = require('View'); const flattenStyle = require('flattenStyle'); +const resolveBoxStyle = require('resolveBoxStyle'); class ElementBox extends React.Component<$FlowFixMeProps> { render() { const style = flattenStyle(this.props.style) || {}; const margin = resolveBoxStyle('margin', style); const padding = resolveBoxStyle('padding', style); - let frameStyle = this.props.frame; - if (margin) { - frameStyle = { - top: frameStyle.top - margin.top, - left: frameStyle.left - margin.left, - height: frameStyle.height + margin.top + margin.bottom, - width: frameStyle.width + margin.left + margin.right, - }; - } - let contentStyle = { + + const frameStyle = {...this.props.frame}; + const contentStyle = { width: this.props.frame.width, height: this.props.frame.height, }; - if (padding) { - contentStyle = { - width: contentStyle.width - padding.left - padding.right, - height: contentStyle.height - padding.top - padding.bottom, - }; + + if (margin != null) { + frameStyle.top -= margin.top; + frameStyle.left -= margin.left; + frameStyle.height += margin.top + margin.bottom; + frameStyle.width += margin.left + margin.right; + + if (margin.top < 0) { + contentStyle.height += margin.top; + } + if (margin.bottom < 0) { + contentStyle.height += margin.bottom; + } + if (margin.left < 0) { + contentStyle.width += margin.left; + } + if (margin.right < 0) { + contentStyle.width += margin.right; + } } + + if (padding != null) { + contentStyle.width -= padding.left + padding.right; + contentStyle.height -= padding.top + padding.bottom; + } + return ( @@ -59,13 +72,13 @@ const styles = StyleSheet.create({ position: 'absolute', }, content: { - backgroundColor: 'rgba(200, 230, 255, 0.8)', + backgroundColor: 'rgba(200, 230, 255, 0.8)', // blue }, padding: { - borderColor: 'rgba(77, 255, 0, 0.3)', + borderColor: 'rgba(77, 255, 0, 0.3)', // green }, margin: { - borderColor: 'rgba(255, 132, 0, 0.3)', + borderColor: 'rgba(255, 132, 0, 0.3)', // orange }, }); diff --git a/Libraries/Inspector/resolveBoxStyle.js b/Libraries/Inspector/resolveBoxStyle.js index 0a0e58e120f6ea..767b9edbc5f061 100644 --- a/Libraries/Inspector/resolveBoxStyle.js +++ b/Libraries/Inspector/resolveBoxStyle.js @@ -10,48 +10,103 @@ 'use strict'; +const I18nManager = require('I18nManager'); + /** - * Resolve a style property into it's component parts, e.g. + * Resolve a style property into its component parts. + * + * For example: * - * resolveProperties('margin', {margin: 5, marginBottom: 10}) - * -> - * {top: 5, left: 5, right: 5, bottom: 10} + * > resolveProperties('margin', {margin: 5, marginBottom: 10}) + * {top: 5, left: 5, right: 5, bottom: 10} * - * If none are set, returns false. + * If no parts exist, this returns null. */ -function resolveBoxStyle(prefix: string, style: Object): ?Object { - const res = {}; - const subs = ['top', 'left', 'bottom', 'right']; - let set = false; - subs.forEach(sub => { - res[sub] = style[prefix] || 0; - }); - if (style[prefix]) { - set = true; - } - if (style[prefix + 'Vertical']) { - res.top = res.bottom = style[prefix + 'Vertical']; - set = true; +function resolveBoxStyle( + prefix: string, + style: Object, +): ?$ReadOnly<{| + bottom: number, + left: number, + right: number, + top: number, +|}> { + let hasParts = false; + const result = { + bottom: 0, + left: 0, + right: 0, + top: 0, + }; + + // TODO: Fix issues with multiple properties affecting the same side. + + const styleForAll = style[prefix]; + if (styleForAll != null) { + for (const key of Object.keys(result)) { + result[key] = styleForAll; + } + hasParts = true; } - if (style[prefix + 'Horizontal']) { - res.left = res.right = style[prefix + 'Horizontal']; - set = true; + + const styleForHorizontal = style[prefix + 'Horizontal']; + if (styleForHorizontal != null) { + result.left = styleForHorizontal; + result.right = styleForHorizontal; + hasParts = true; + } else { + const styleForLeft = style[prefix + 'Left']; + if (styleForLeft != null) { + result.left = styleForLeft; + hasParts = true; + } + + const styleForRight = style[prefix + 'Right']; + if (styleForRight != null) { + result.right = styleForRight; + hasParts = true; + } + + const styleForEnd = style[prefix + 'End']; + if (styleForEnd != null) { + if (I18nManager.isRTL && I18nManager.doLeftAndRightSwapInRTL) { + result.left = styleForEnd; + } else { + result.right = styleForEnd; + } + hasParts = true; + } + const styleForStart = style[prefix + 'Start']; + if (styleForStart != null) { + if (I18nManager.isRTL && I18nManager.doLeftAndRightSwapInRTL) { + result.right = styleForStart; + } else { + result.left = styleForStart; + } + hasParts = true; + } } - subs.forEach(sub => { - const val = style[prefix + capFirst(sub)]; - if (val) { - res[sub] = val; - set = true; + + const styleForVertical = style[prefix + 'Vertical']; + if (styleForVertical != null) { + result.bottom = styleForVertical; + result.top = styleForVertical; + hasParts = true; + } else { + const styleForBottom = style[prefix + 'Bottom']; + if (styleForBottom != null) { + result.bottom = styleForBottom; + hasParts = true; + } + + const styleForTop = style[prefix + 'Top']; + if (styleForTop != null) { + result.top = styleForTop; + hasParts = true; } - }); - if (!set) { - return; } - return res; -} -function capFirst(text) { - return text[0].toUpperCase() + text.slice(1); + return hasParts ? result : null; } module.exports = resolveBoxStyle; diff --git a/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj b/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj index b56dcc67b9844a..34003d2a601639 100644 --- a/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj +++ b/Libraries/LinkingIOS/RCTLinking.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 58B511D31A9E6C8500147676 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Facebook; TargetAttributes = { 2D2A28461D9B043800D4039D = { @@ -171,20 +171,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -218,20 +229,30 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_SHADOW = YES; diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index cfa4ae85808824..adebd267f7f64d 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -18,7 +18,7 @@ const StyleSheet = require('StyleSheet'); const invariant = require('fbjs/lib/invariant'); -import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; +import type {DangerouslyImpreciseStyleProp, ViewStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -88,11 +88,19 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * Optional custom style for multi-item rows generated when numColumns > 1. */ diff --git a/Libraries/Lists/ListView/ListView.js b/Libraries/Lists/ListView/ListView.js index 10ab9aef3101e9..bed5dbda2e2f93 100644 --- a/Libraries/Lists/ListView/ListView.js +++ b/Libraries/Lists/ListView/ListView.js @@ -13,7 +13,6 @@ const InternalListViewType = require('InternalListViewType'); const ListViewDataSource = require('ListViewDataSource'); const Platform = require('Platform'); const React = require('React'); -const PropTypes = require('prop-types'); const ReactNative = require('ReactNative'); const RCTScrollViewManager = require('NativeModules').ScrollViewManager; const ScrollView = require('ScrollView'); @@ -37,22 +36,124 @@ const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; type Props = $ReadOnly<{| ...ScrollViewProps, + /** + * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use + */ dataSource: ListViewDataSource, + /** + * (sectionID, rowID, adjacentRowHighlighted) => renderable + * + * If provided, a renderable component to be rendered as the separator + * below each row but not the last row if there is a section header below. + * Take a sectionID and rowID of the row above and whether its adjacent row + * is highlighted. + */ renderSeparator?: ?Function, + /** + * (rowData, sectionID, rowID, highlightRow) => renderable + * + * Takes a data entry from the data source and its ids and should return + * a renderable component to be rendered as the row. By default the data + * is exactly what was put into the data source, but it's also possible to + * provide custom extractors. ListView can be notified when a row is + * being highlighted by calling `highlightRow(sectionID, rowID)`. This + * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you + * to control the separators above and below the highlighted row. The highlighted + * state of a row can be reset by calling highlightRow(null). + */ renderRow: Function, + /** + * How many rows to render on initial component mount. Use this to make + * it so that the first screen worth of data appears at one time instead of + * over the course of multiple frames. + */ initialListSize?: ?number, + /** + * Called when all rows have been rendered and the list has been scrolled + * to within onEndReachedThreshold of the bottom. The native scroll + * event is provided. + */ onEndReached?: ?Function, + /** + * Threshold in pixels (virtual, not physical) for calling onEndReached. + */ onEndReachedThreshold?: ?number, + /** + * Number of rows to render per event loop. Note: if your 'rows' are actually + * cells, i.e. they don't span the full width of your view (as in the + * ListViewGridLayoutExample), you should set the pageSize to be a multiple + * of the number of cells per row, otherwise you're likely to see gaps at + * the edge of the ListView as new pages are loaded. + */ pageSize?: ?number, + /** + * () => renderable + * + * The header and footer are always rendered (if these props are provided) + * on every render pass. If they are expensive to re-render, wrap them + * in StaticContainer or other mechanism as appropriate. Footer is always + * at the bottom of the list, and header at the top, on every render pass. + * In a horizontal ListView, the header is rendered on the left and the + * footer on the right. + */ renderFooter?: ?Function, renderHeader?: ?Function, + /** + * (sectionData, sectionID) => renderable + * + * If provided, a header is rendered for this section. + */ renderSectionHeader?: ?Function, + /** + * (props) => renderable + * + * A function that returns the scrollable component in which the list rows + * are rendered. Defaults to returning a ScrollView with the given props. + */ renderScrollComponent?: ?Function, + /** + * How early to start rendering rows before they come on screen, in + * pixels. + */ scrollRenderAheadDistance?: ?number, + /** + * (visibleRows, changedRows) => void + * + * Called when the set of visible rows changes. `visibleRows` maps + * { sectionID: { rowID: true }} for all the visible rows, and + * `changedRows` maps { sectionID: { rowID: true | false }} for the rows + * that have changed their visibility, with true indicating visible, and + * false indicating the view has moved out of view. + */ onChangeVisibleRows?: ?Function, + /** + * A performance optimization for improving scroll perf of + * large lists, used in conjunction with overflow: 'hidden' on the row + * containers. This is enabled by default. + */ removeClippedSubviews?: ?boolean, + /** + * Makes the sections headers sticky. The sticky behavior means that it + * will scroll with the content at the top of the section until it reaches + * the top of the screen, at which point it will stick to the top until it + * is pushed off the screen by the next section header. This property is + * not supported in conjunction with `horizontal={true}`. Only enabled by + * default on iOS because of typical platform standards. + */ stickySectionHeadersEnabled?: ?boolean, + /** + * An array of child indices determining which children get docked to the + * top of the screen when scrolling. For example, passing + * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the + * top of the scroll view. This property is not supported in conjunction + * with `horizontal={true}`. + */ stickyHeaderIndices?: ?$ReadOnlyArray, + /** + * Flag indicating whether empty section headers should be rendered. In the future release + * empty section headers will be rendered by default, and the flag will be deprecated. + * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. + */ enableEmptySections?: ?boolean, |}>; @@ -128,136 +229,6 @@ const ListView = createReactClass({ DataSource: ListViewDataSource, }, - /** - * You must provide a renderRow function. If you omit any of the other render - * functions, ListView will simply skip rendering them. - * - * - renderRow(rowData, sectionID, rowID, highlightRow); - * - renderSectionHeader(sectionData, sectionID); - */ - propTypes: { - ...ScrollView.propTypes, - /** - * An instance of [ListView.DataSource](docs/listviewdatasource.html) to use - */ - dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, - /** - * (sectionID, rowID, adjacentRowHighlighted) => renderable - * - * If provided, a renderable component to be rendered as the separator - * below each row but not the last row if there is a section header below. - * Take a sectionID and rowID of the row above and whether its adjacent row - * is highlighted. - */ - renderSeparator: PropTypes.func, - /** - * (rowData, sectionID, rowID, highlightRow) => renderable - * - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. ListView can be notified when a row is - * being highlighted by calling `highlightRow(sectionID, rowID)`. This - * sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you - * to control the separators above and below the highlighted row. The highlighted - * state of a row can be reset by calling highlightRow(null). - */ - renderRow: PropTypes.func.isRequired, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data appears at one time instead of - * over the course of multiple frames. - */ - initialListSize: PropTypes.number.isRequired, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels (virtual, not physical) for calling onEndReached. - */ - onEndReachedThreshold: PropTypes.number.isRequired, - /** - * Number of rows to render per event loop. Note: if your 'rows' are actually - * cells, i.e. they don't span the full width of your view (as in the - * ListViewGridLayoutExample), you should set the pageSize to be a multiple - * of the number of cells per row, otherwise you're likely to see gaps at - * the edge of the ListView as new pages are loaded. - */ - pageSize: PropTypes.number.isRequired, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - * In a horizontal ListView, the header is rendered on the left and the - * footer on the right. - */ - renderFooter: PropTypes.func, - renderHeader: PropTypes.func, - /** - * (sectionData, sectionID) => renderable - * - * If provided, a header is rendered for this section. - */ - renderSectionHeader: PropTypes.func, - /** - * (props) => renderable - * - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent: PropTypes.func.isRequired, - /** - * How early to start rendering rows before they come on screen, in - * pixels. - */ - scrollRenderAheadDistance: PropTypes.number.isRequired, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows: PropTypes.func, - /** - * A performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. This is enabled by default. - */ - removeClippedSubviews: PropTypes.bool, - /** - * Makes the sections headers sticky. The sticky behavior means that it - * will scroll with the content at the top of the section until it reaches - * the top of the screen, at which point it will stick to the top until it - * is pushed off the screen by the next section header. This property is - * not supported in conjunction with `horizontal={true}`. Only enabled by - * default on iOS because of typical platform standards. - */ - stickySectionHeadersEnabled: PropTypes.bool, - /** - * An array of child indices determining which children get docked to the - * top of the screen when scrolling. For example, passing - * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the - * top of the scroll view. This property is not supported in conjunction - * with `horizontal={true}`. - */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number).isRequired, - /** - * Flag indicating whether empty section headers should be rendered. In the future release - * empty section headers will be rendered by default, and the flag will be deprecated. - * If empty sections are not desired to be rendered their indices should be excluded from sectionID object. - */ - enableEmptySections: PropTypes.bool, - }, - /** * Exports some data, e.g. for perf investigations or analytics. */ @@ -296,7 +267,7 @@ const ListView = createReactClass({ * * See `ScrollView#scrollTo`. */ - scrollTo: function(...args: Array) { + scrollTo: function(...args: any) { if (this._scrollComponent && this._scrollComponent.scrollTo) { this._scrollComponent.scrollTo(...args); } @@ -312,7 +283,7 @@ const ListView = createReactClass({ * * See `ScrollView#scrollToEnd`. */ - scrollToEnd: function(options?: ?{animated?: ?boolean}) { + scrollToEnd: function(options?: ?{animated?: boolean}) { if (this._scrollComponent) { if (this._scrollComponent.scrollToEnd) { this._scrollComponent.scrollToEnd(options); @@ -366,7 +337,7 @@ const ListView = createReactClass({ }, getInnerViewNode: function() { - return this._scrollComponent.getInnerViewNode(); + return this._scrollComponent && this._scrollComponent.getInnerViewNode(); }, UNSAFE_componentWillMount: function() { diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 56364823f06d49..ba9224c4665fc9 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -31,7 +31,7 @@ const warning = require('fbjs/lib/warning'); const {computeWindowedRenderLimits} = require('VirtualizeUtils'); -import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; +import type {DangerouslyImpreciseStyleProp, ViewStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -124,11 +124,19 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * A unique identifier for this list. If there are multiple VirtualizedLists at the same level of * nesting within another VirtualizedList, this key is necessary for virtualization to @@ -756,7 +764,12 @@ class VirtualizedList extends React.PureComponent { - + { // $FlowFixMe - Typing ReactNativeComponent revealed errors element @@ -892,7 +905,12 @@ class VirtualizedList extends React.PureComponent { - + { // $FlowFixMe - Typing ReactNativeComponent revealed errors element diff --git a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap index d6d008065b2445..7f523be29ac848 100644 --- a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap +++ b/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap @@ -60,7 +60,6 @@ exports[`FlatList renders all the bells and whistles 1`] = `
@@ -119,7 +118,6 @@ exports[`FlatList renders all the bells and whistles 1`] = `