diff --git a/.buckconfig b/.buckconfig index 0740647479601e..3d98e74d71e06a 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,6 +1,6 @@ [android] - target = android-29 + target = android-30 [download] max_number_of_retries = 3 diff --git a/.circleci/Dockerfiles/Dockerfile.android b/.circleci/Dockerfiles/Dockerfile.android index 5b313b53bfa679..92bf645af1d260 100644 --- a/.circleci/Dockerfiles/Dockerfile.android +++ b/.circleci/Dockerfiles/Dockerfile.android @@ -14,7 +14,7 @@ # and build a Android application that can be used to run the # tests specified in the scripts/ directory. # -FROM reactnativecommunity/react-native-android:2.1 +FROM reactnativecommunity/react-native-android:3.2 LABEL Description="React Native Android Test Image" LABEL maintainer="Héctor Ramos " diff --git a/.circleci/config.yml b/.circleci/config.yml index ca3e88fc57bad4..dc1337a2e107e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ executors: reactnativeandroid: <<: *defaults docker: - - image: reactnativecommunity/react-native-android:2.1 + - image: reactnativecommunity/react-native-android:3.2 resource_class: "large" environment: - TERM: "dumb" @@ -617,7 +617,7 @@ jobs: environment: - ANDROID_HOME: "C:\\Android\\android-sdk" - ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\20.1.5948944" - - ANDROID_BUILD_VERSION: 28 + - ANDROID_BUILD_VERSION: 30 - ANDROID_TOOLS_VERSION: 29.0.3 - GRADLE_OPTS: -Dorg.gradle.daemon=false - NDK_VERSION: 20.1.5948944 diff --git a/.flowconfig b/.flowconfig index 3efecf280b7c1e..01dcce1eb86ed6 100644 --- a/.flowconfig +++ b/.flowconfig @@ -47,8 +47,6 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -experimental.abstract_locations=true - [lints] sketchy-null-number=warn sketchy-null-mixed=warn @@ -70,4 +68,4 @@ untyped-import untyped-type-import [version] -^0.145.0 +^0.148.0 diff --git a/.flowconfig.android b/.flowconfig.android index 27654ff3ec9e31..48daf51de417ff 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -47,8 +47,6 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -experimental.abstract_locations=true - [lints] sketchy-null-number=warn sketchy-null-mixed=warn @@ -70,4 +68,4 @@ untyped-import untyped-type-import [version] -^0.145.0 +^0.148.0 diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js index a6039d4b6d571d..51ce374556e90b 100644 --- a/IntegrationTests/AsyncStorageTest.js +++ b/IntegrationTests/AsyncStorageTest.js @@ -218,9 +218,10 @@ class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> { return ( - {/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error found when Flow v0.54 was deployed. - * To see the error delete this comment and run Flow. */ + {/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_ + * native_oss) This comment suppresses an error found when Flow v0.54 + * was deployed. To see the error delete this comment and run Flow. + */ this.constructor.displayName + ': '} {this.state.done ? 'Done' : 'Testing...'} {'\n\n' + this.state.messages} diff --git a/IntegrationTests/IntegrationTestHarnessTest.js b/IntegrationTests/IntegrationTestHarnessTest.js index fbc1ef8d472440..256bcd210e7538 100644 --- a/IntegrationTests/IntegrationTestHarnessTest.js +++ b/IntegrationTests/IntegrationTestHarnessTest.js @@ -56,9 +56,10 @@ class IntegrationTestHarnessTest extends React.Component { return ( - {/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error found when Flow v0.54 was deployed. - * To see the error delete this comment and run Flow. */ + {/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_ + * native_oss) This comment suppresses an error found when Flow v0.54 + * was deployed. To see the error delete this comment and run Flow. + */ this.constructor.displayName + ': '} {this.state.done ? 'Done' : 'Testing...'} diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index 1e52877b7b9908..af41da151c47de 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -40,9 +40,9 @@ const TESTS = [ ]; TESTS.forEach( - /* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment - * suppresses an error found when Flow v0.54 was deployed. To see the error - * delete this comment and run Flow. */ + /* $FlowFixMe[incompatible-call] (>=0.54.0 site=react_native_fb,react_native_ + * oss) This comment suppresses an error found when Flow v0.54 was deployed. + * To see the error delete this comment and run Flow. */ test => AppRegistry.registerComponent(test.displayName, () => test), ); @@ -60,9 +60,10 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> { if (this.state.test) { return ( - {/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for - * React. To see the error delete this comment and run Flow. */} + {/* $FlowFixMe[type-as-value] (>=0.53.0 site=react_native_fb,react_ + * native_oss) This comment suppresses an error when upgrading + * Flow's support for React. To see the error delete this comment + * and run Flow. */} ); @@ -79,9 +80,10 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> { {TESTS.map(test => [ this.setState({test})} - /* $FlowFixMe(>=0.115.0 site=react_native_fb) This comment - * suppresses an error found when Flow v0.115 was deployed. To - * see the error, delete this comment and run Flow. */ + /* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_native_fb) + * This comment suppresses an error found when Flow v0.115 was + * deployed. To see the error, delete this comment and run Flow. + */ style={styles.row}> {test.displayName} , diff --git a/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec b/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec index 7a318174f5d539..f080a2b2ffb25f 100644 --- a/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec +++ b/Libraries/ActionSheetIOS/React-RCTActionSheet.podspec @@ -11,7 +11,7 @@ version = package['version'] source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. - source[:commit] = `git rev-parse HEAD`.strip + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") else source[:tag] = "v#{version}" end diff --git a/Libraries/Alert/Alert.js b/Libraries/Alert/Alert.js index 625a284bed48f5..02a7a312e5b2ac 100644 --- a/Libraries/Alert/Alert.js +++ b/Libraries/Alert/Alert.js @@ -9,9 +9,7 @@ */ import Platform from '../Utilities/Platform'; -import NativeDialogManagerAndroid, { - type DialogOptions, -} from '../NativeModules/specs/NativeDialogManagerAndroid'; +import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid'; import RCTAlertManager from './RCTAlertManager'; export type AlertType = @@ -48,6 +46,8 @@ class Alert { if (Platform.OS === 'ios') { Alert.prompt(title, message, buttons, 'default'); } else if (Platform.OS === 'android') { + const NativeDialogManagerAndroid = require('../NativeModules/specs/NativeDialogManagerAndroid') + .default; if (!NativeDialogManagerAndroid) { return; } diff --git a/Libraries/Animated/AnimatedEvent.js b/Libraries/Animated/AnimatedEvent.js index 87a434752be9e2..b6de725948a961 100644 --- a/Libraries/Animated/AnimatedEvent.js +++ b/Libraries/Animated/AnimatedEvent.js @@ -211,9 +211,9 @@ class AnimatedEvent { } } else if (typeof recMapping === 'object') { for (const mappingKey in recMapping) { - /* $FlowFixMe(>=0.120.0) This comment suppresses an error found - * when Flow v0.120 was deployed. To see the error, delete this - * comment and run Flow. */ + /* $FlowFixMe[prop-missing] (>=0.120.0) This comment suppresses an + * error found when Flow v0.120 was deployed. To see the error, + * delete this comment and run Flow. */ traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey); } } diff --git a/Libraries/Animated/AnimatedWeb.js b/Libraries/Animated/AnimatedWeb.js index 3f3313fe6c69e2..513f322b709d08 100644 --- a/Libraries/Animated/AnimatedWeb.js +++ b/Libraries/Animated/AnimatedWeb.js @@ -14,10 +14,13 @@ const AnimatedImplementation = require('./AnimatedImplementation'); module.exports = { ...AnimatedImplementation, - // $FlowFixMe createAnimatedComponent expects to receive types. Plain intrinsic components can't be typed like this + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ div: (AnimatedImplementation.createAnimatedComponent('div'): $FlowFixMe), - // $FlowFixMe createAnimatedComponent expects to receive types. Plain intrinsic components can't be typed like this + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ span: (AnimatedImplementation.createAnimatedComponent('span'): $FlowFixMe), - // $FlowFixMe createAnimatedComponent expects to receive types. Plain intrinsic components can't be typed like this + /* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive + * types. Plain intrinsic components can't be typed like this */ img: (AnimatedImplementation.createAnimatedComponent('img'): $FlowFixMe), }; diff --git a/Libraries/Animated/NativeAnimatedHelper.js b/Libraries/Animated/NativeAnimatedHelper.js index d3a080f3de31e1..dd1fc6f260925e 100644 --- a/Libraries/Animated/NativeAnimatedHelper.js +++ b/Libraries/Animated/NativeAnimatedHelper.js @@ -393,10 +393,15 @@ module.exports = { assertNativeAnimatedModule, shouldUseNativeDriver, transformDataType, - // $FlowExpectedError - unsafe getter lint suppresion + // $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppresion + // $FlowExpectedError[missing-type-arg] - unsafe getter lint suppresion get nativeEventEmitter(): NativeEventEmitter { if (!nativeEventEmitter) { - nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); + nativeEventEmitter = new NativeEventEmitter( + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeAnimatedModule, + ); } return nativeEventEmitter; }, diff --git a/Libraries/Animated/components/AnimatedSectionList.js b/Libraries/Animated/components/AnimatedSectionList.js index 2dd80bc400b9c7..98ccd918460bc6 100644 --- a/Libraries/Animated/components/AnimatedSectionList.js +++ b/Libraries/Animated/components/AnimatedSectionList.js @@ -10,7 +10,7 @@ import * as React from 'react'; -const SectionList = require('../../Lists/SectionList'); +import SectionList from '../../Lists/SectionList'; const createAnimatedComponent = require('../createAnimatedComponent'); import type {AnimatedComponentType} from '../createAnimatedComponent'; diff --git a/Libraries/Animated/nodes/AnimatedInterpolation.js b/Libraries/Animated/nodes/AnimatedInterpolation.js index 63dfa670f1705e..ce06272b5c6d79 100644 --- a/Libraries/Animated/nodes/AnimatedInterpolation.js +++ b/Libraries/Animated/nodes/AnimatedInterpolation.js @@ -205,14 +205,12 @@ function createInterpolationFromStringOutputRange( // [200, 250], // [0, 0.5], // ] - /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to - * guard against this possibility. - */ + /* $FlowFixMe[incompatible-use] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []); outputRange.forEach(value => { - /* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard - * against this possibility. - */ + /* $FlowFixMe[incompatible-use] (>=0.18.0): `value.match()` can return + * `null`. Need to guard against this possibility. */ value.match(stringShapeRegex).forEach((number, i) => { outputRanges[i].push(+number); }); @@ -220,8 +218,10 @@ function createInterpolationFromStringOutputRange( const interpolations = outputRange[0] .match(stringShapeRegex) - /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need - * to guard against this possibility. */ + /* $FlowFixMe[incompatible-use] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ + /* $FlowFixMe[incompatible-call] (>=0.18.0): `outputRange[0].match()` can + * return `null`. Need to guard against this possibility. */ .map((value, i) => { return createInterpolation({ ...config, @@ -277,12 +277,11 @@ function checkValidInputRange(arr: $ReadOnlyArray) { for (let i = 1; i < arr.length; ++i) { invariant( arr[i] >= arr[i - 1], - /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, - * one or both of the operands may be something that doesn't cleanly - * convert to a string, like undefined, null, and object, etc. If you really - * mean this implicit string conversion, you can do something like - * String(myThing) - */ + /* $FlowFixMe[incompatible-type] (>=0.13.0) - In the addition expression + * below this comment, one or both of the operands may be something that + * doesn't cleanly convert to a string, like undefined, null, and object, + * etc. If you really mean this implicit string conversion, you can do + * something like String(myThing) */ 'inputRange must be monotonically non-decreasing ' + arr, ); } @@ -292,12 +291,11 @@ function checkInfiniteRange(name: string, arr: $ReadOnlyArray) { invariant(arr.length >= 2, name + ' must have at least 2 elements'); invariant( arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, - /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, - * one or both of the operands may be something that doesn't cleanly convert - * to a string, like undefined, null, and object, etc. If you really mean - * this implicit string conversion, you can do something like - * String(myThing) - */ + /* $FlowFixMe[incompatible-type] (>=0.13.0) - In the addition expression + * below this comment, one or both of the operands may be something that + * doesn't cleanly convert to a string, like undefined, null, and object, + * etc. If you really mean this implicit string conversion, you can do + * something like String(myThing) */ name + 'cannot be ]-infinity;+infinity[ ' + arr, ); } @@ -358,8 +356,9 @@ class AnimatedInterpolation extends AnimatedWithChildren { return { inputRange: this._config.inputRange, // Only the `outputRange` can contain strings so we don't need to transform `inputRange` here - /* $FlowFixMe(>=0.38.0) - Flow error detected during the deployment of - * v0.38.0. To see the error, remove this comment and run flow */ + /* $FlowFixMe[incompatible-call] (>=0.38.0) - Flow error detected during + * the deployment of v0.38.0. To see the error, remove this comment and + * run flow */ outputRange: this.__transformDataType(this._config.outputRange), extrapolateLeft: this._config.extrapolateLeft || this._config.extrapolate || 'extend', diff --git a/Libraries/Animated/nodes/AnimatedValue.js b/Libraries/Animated/nodes/AnimatedValue.js index b9d757e1026d9e..e1d1963e0cb38d 100644 --- a/Libraries/Animated/nodes/AnimatedValue.js +++ b/Libraries/Animated/nodes/AnimatedValue.js @@ -46,9 +46,9 @@ const NativeAnimatedAPI = NativeAnimatedHelper.API; function _flush(rootNode: AnimatedValue): void { const animatedStyles = new Set(); function findAnimatedStyles(node) { - /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.68 was deployed. To see the error delete this - * comment and run Flow. */ + /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment + * suppresses an error found when Flow v0.68 was deployed. To see the error + * delete this comment and run Flow. */ if (typeof node.update === 'function') { animatedStyles.add(node); } else { @@ -56,7 +56,7 @@ function _flush(rootNode: AnimatedValue): void { } } findAnimatedStyles(rootNode); - /* $FlowFixMe */ + // $FlowFixMe[prop-missing] animatedStyles.forEach(animatedStyle => animatedStyle.update()); } diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 4668efc4ecd3cc..303c97ed28e2e1 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -12,6 +12,7 @@ import {type EventSubscription} from '../vendor/emitter/EventEmitter'; import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; import logError from '../Utilities/logError'; import NativeAppState from './NativeAppState'; +import Platform from '../Utilities/Platform'; export type AppStateValues = 'inactive' | 'background' | 'active'; @@ -47,7 +48,9 @@ class AppState { this.isAvailable = true; const emitter: NativeEventEmitter = new NativeEventEmitter( - NativeAppState, + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeAppState, ); this._emitter = emitter; @@ -123,6 +126,38 @@ class AppState { } throw new Error('Trying to subscribe to unknown event: ' + type); } + + /** + * @deprecated Use `remove` on the EventSubscription from `addEventListener`. + */ + removeEventListener>( + type: K, + listener: (...$ElementType) => mixed, + ): void { + const emitter = this._emitter; + if (emitter == null) { + throw new Error('Cannot use AppState when `isAvailable` is false.'); + } + // NOTE: This will report a deprecation notice via `console.error`. + switch (type) { + case 'change': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + // $FlowIssue[incompatible-call] + emitter.removeListener('appStateDidChange', listener); + return; + case 'memoryWarning': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + emitter.removeListener('memoryWarning', listener); + return; + case 'blur': + case 'focus': + // $FlowIssue[invalid-tuple-arity] Flow cannot refine handler based on the event type + // $FlowIssue[incompatible-call] + emitter.addListener('appStateFocusChange', listener); + return; + } + throw new Error('Trying to unsubscribe from unknown event: ' + type); + } } module.exports = (new AppState(): AppState); diff --git a/Libraries/BUCK b/Libraries/BUCK index 586510279a0321..389a6e37799081 100644 --- a/Libraries/BUCK +++ b/Libraries/BUCK @@ -3,54 +3,22 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") load( "//tools/build_defs/oss:rn_codegen_defs.bzl", - "rn_codegen_components", - "rn_codegen_modules", -) -load( - "//tools/build_defs/oss:rn_defs.bzl", - "react_native_root_target", -) - -fb_native.genrule( - # The schema name must have the following format: "{name}-codegen-modules-schema" - # Why: Internally, we have build scripts that find all NativeModule schemas in the - # dependencies of an app, and build TurboModuleManager delegates. Those scripts assume - # that all schema targets have the aforementioned naming scheme. - name = "FBReactNativeSpec-codegen-modules-schema", - srcs = glob( - [ - "**/*.js", - ], - exclude = [ - "**/__tests__/**/*", - ], - ), - cmd = "$(exe {}) $OUT $SRCS".format(react_native_root_target("packages/react-native-codegen:write_to_json")), - out = "schema.json", - labels = [ - "codegen_rule", - "react_native_schema_target", - ], + "rn_codegen", ) -rn_codegen_modules( +rn_codegen( name = "FBReactNativeSpec", android_package_name = "com.facebook.fbreact.specs", + codegen_modules = True, library_labels = ["supermodule:xplat/default/public.react_native.infra"], - schema_target = ":FBReactNativeSpec-codegen-modules-schema", + native_module_spec_name = "FBReactNativeSpec", ) -rn_codegen_components( +# TODO: Merge this into FBReactNativeSpec +rn_codegen( name = "FBReactNativeComponentSpec", + codegen_components = True, library_labels = ["supermodule:xplat/default/public.react_native.infra"], - # Why does FBReactNativeComponentSpec depend on -codegen-modules-schema? - # The module codegen schema also contains components. We cannot change the name - # of the schema target, because internally, we have infra that depends on how - # it's named. - # - # TODO(T83341482): Clean up how OSS NativeModule codegen is declared - schema_target = ":FBReactNativeSpec-codegen-modules-schema", ) diff --git a/Libraries/BatchedBridge/MessageQueue.js b/Libraries/BatchedBridge/MessageQueue.js index fa2147b540d299..f1d59f07eb5114 100644 --- a/Libraries/BatchedBridge/MessageQueue.js +++ b/Libraries/BatchedBridge/MessageQueue.js @@ -145,9 +145,9 @@ class MessageQueue { this._lazyCallableModules[name] = () => module; } - registerLazyCallableModule(name: string, factory: void => {...}) { - let module: {...}; - let getValue: ?(void) => {...} = factory; + registerLazyCallableModule(name: string, factory: void => interface {}) { + let module: interface {}; + let getValue: ?(void) => interface {} = factory; this._lazyCallableModules[name] = () => { if (getValue) { module = getValue(); @@ -376,7 +376,7 @@ class MessageQueue { // can be configured by the VM or any Inspector __shouldPauseOnThrow(): boolean { return ( - // $FlowFixMe + // $FlowFixMe[cannot-resolve-name] typeof DebuggerInternal !== 'undefined' && DebuggerInternal.shouldPauseOnThrow === true // eslint-disable-line no-undef ); diff --git a/Libraries/Blob/React-RCTBlob.podspec b/Libraries/Blob/React-RCTBlob.podspec index 43725560121dba..c3f1fb27545d9a 100644 --- a/Libraries/Blob/React-RCTBlob.podspec +++ b/Libraries/Blob/React-RCTBlob.podspec @@ -11,7 +11,7 @@ version = package['version'] source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. - source[:commit] = `git rev-parse HEAD`.strip + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") else source[:tag] = "v#{version}" end diff --git a/Libraries/Blob/URL.js b/Libraries/Blob/URL.js index bced25e695a316..0b0e9be74d191d 100644 --- a/Libraries/Blob/URL.js +++ b/Libraries/Blob/URL.js @@ -65,27 +65,27 @@ export class URLSearchParams { } delete(name) { - throw new Error('not implemented'); + throw new Error('URLSearchParams.delete is not implemented'); } get(name) { - throw new Error('not implemented'); + throw new Error('URLSearchParams.get is not implemented'); } getAll(name) { - throw new Error('not implemented'); + throw new Error('URLSearchParams.getAll is not implemented'); } has(name) { - throw new Error('not implemented'); + throw new Error('URLSearchParams.has is not implemented'); } set(name, value) { - throw new Error('not implemented'); + throw new Error('URLSearchParams.set is not implemented'); } sort() { - throw new Error('not implemented'); + throw new Error('URLSearchParams.sort is not implemented'); } [Symbol.iterator]() { @@ -154,15 +154,15 @@ export class URL { } get hash() { - throw new Error('not implemented'); + throw new Error('URL.hash is not implemented'); } get host() { - throw new Error('not implemented'); + throw new Error('URL.host is not implemented'); } get hostname() { - throw new Error('not implemented'); + throw new Error('URL.hostname is not implemented'); } get href(): string { @@ -170,27 +170,27 @@ export class URL { } get origin() { - throw new Error('not implemented'); + throw new Error('URL.origin is not implemented'); } get password() { - throw new Error('not implemented'); + throw new Error('URL.password is not implemented'); } get pathname() { - throw new Error('not implemented'); + throw new Error('URL.pathname not implemented'); } get port() { - throw new Error('not implemented'); + throw new Error('URL.port is not implemented'); } get protocol() { - throw new Error('not implemented'); + throw new Error('URL.protocol is not implemented'); } get search() { - throw new Error('not implemented'); + throw new Error('URL.search is not implemented'); } get searchParams(): URLSearchParams { @@ -213,6 +213,6 @@ export class URL { } get username() { - throw new Error('not implemented'); + throw new Error('URL.username is not implemented'); } } diff --git a/Libraries/BugReporting/getReactData.js b/Libraries/BugReporting/getReactData.js index cc9bc55f0cc394..103f8ed86029ff 100644 --- a/Libraries/BugReporting/getReactData.js +++ b/Libraries/BugReporting/getReactData.js @@ -162,7 +162,7 @@ function copyWithSetImpl(obj, path, idx, value) { } const key = path[idx]; const updated = Array.isArray(obj) ? obj.slice() : {...obj}; - // $FlowFixMe number or string is fine here + // $FlowFixMe[incompatible-use] number or string is fine here updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value); return updated; } diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js deleted file mode 100644 index d619b897254e93..00000000000000 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict-local - */ - -import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter'; -import NativeAccessibilityInfo from './NativeAccessibilityInfo'; -import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter'; -import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; -import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative'; -import legacySendAccessibilityEvent from './legacySendAccessibilityEvent'; -import {type ElementRef} from 'react'; - -const REDUCE_MOTION_EVENT = 'reduceMotionDidChange'; -const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange'; - -type AccessibilityEventDefinitions = { - reduceMotionChanged: [boolean], - screenReaderChanged: [boolean], - // alias for screenReaderChanged - change: [boolean], -}; - -type AccessibilityEventTypes = 'focus'; - -const _subscriptions = new Map(); - -/** - * Sometimes it's useful to know whether or not the device has a screen reader - * that is currently active. The `AccessibilityInfo` API is designed for this - * purpose. You can use it to query the current state of the screen reader as - * well as to register to be notified when the state of the screen reader - * changes. - * - * See https://reactnative.dev/docs/accessibilityinfo.html - */ - -const AccessibilityInfo = { - /** - * iOS only - */ - isBoldTextEnabled: function(): Promise { - return Promise.resolve(false); - }, - - /** - * iOS only - */ - isGrayscaleEnabled: function(): Promise { - return Promise.resolve(false); - }, - - /** - * iOS only - */ - isInvertColorsEnabled: function(): Promise { - return Promise.resolve(false); - }, - - isReduceMotionEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityInfo) { - NativeAccessibilityInfo.isReduceMotionEnabled(resolve); - } else { - reject(false); - } - }); - }, - - /** - * iOS only - */ - isReduceTransparencyEnabled: function(): Promise { - return Promise.resolve(false); - }, - - isScreenReaderEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityInfo) { - NativeAccessibilityInfo.isTouchExplorationEnabled(resolve); - } else { - reject(false); - } - }); - }, - - /** - * Deprecated - * - * Same as `isScreenReaderEnabled` - */ - // $FlowFixMe[unsafe-getters-setters] - get fetch(): () => Promise { - console.warn( - 'AccessibilityInfo.fetch is deprecated, call AccessibilityInfo.isScreenReaderEnabled instead', - ); - return this.isScreenReaderEnabled; - }, - - addEventListener: function>( - eventName: K, - handler: (...$ElementType) => void, - ): EventSubscription { - let listener; - - if (eventName === 'change' || eventName === 'screenReaderChanged') { - listener = RCTDeviceEventEmitter.addListener( - TOUCH_EXPLORATION_EVENT, - handler, - ); - } else if (eventName === 'reduceMotionChanged') { - listener = RCTDeviceEventEmitter.addListener( - REDUCE_MOTION_EVENT, - handler, - ); - } - - // $FlowFixMe[escaped-generic] - _subscriptions.set(handler, listener); - - return { - remove: () => { - // $FlowIssue flow does not recognize handler properly - AccessibilityInfo.removeEventListener(eventName, handler); - }, - }; - }, - - removeEventListener: function>( - eventName: K, - handler: (...$ElementType) => void, - ): void { - // $FlowFixMe[escaped-generic] - const listener = _subscriptions.get(handler); - if (!listener) { - return; - } - listener.remove(); - // $FlowFixMe[escaped-generic] - _subscriptions.delete(handler); - }, - - /** - * Set accessibility focus to a react component. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus - */ - setAccessibilityFocus: function(reactTag: number): void { - legacySendAccessibilityEvent(reactTag, 'focus'); - }, - - /** - * Send a named accessibility event to a HostComponent. - */ - sendAccessibilityEvent_unstable: function( - handle: ElementRef>, - eventType: AccessibilityEventTypes, - ) { - // route through React renderer to distinguish between Fabric and non-Fabric handles - sendAccessibilityEvent(handle, eventType); - }, - - /** - * Post a string to be announced by the screen reader. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#announceforaccessibility - */ - announceForAccessibility: function(announcement: string): void { - if (NativeAccessibilityInfo) { - NativeAccessibilityInfo.announceForAccessibility(announcement); - } - }, -}; - -module.exports = AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js deleted file mode 100644 index bcdefd1eae3e98..00000000000000 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict-local - */ - -import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter'; -import NativeAccessibilityManager from './NativeAccessibilityManager'; -import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter'; -import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; -import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative'; -import legacySendAccessibilityEvent from './legacySendAccessibilityEvent'; -import {type ElementRef} from 'react'; - -const CHANGE_EVENT_NAME = { - announcementFinished: 'announcementFinished', - boldTextChanged: 'boldTextChanged', - grayscaleChanged: 'grayscaleChanged', - invertColorsChanged: 'invertColorsChanged', - reduceMotionChanged: 'reduceMotionChanged', - reduceTransparencyChanged: 'reduceTransparencyChanged', - screenReaderChanged: 'screenReaderChanged', -}; - -type AccessibilityEventDefinitions = { - boldTextChanged: [boolean], - grayscaleChanged: [boolean], - invertColorsChanged: [boolean], - reduceMotionChanged: [boolean], - reduceTransparencyChanged: [boolean], - screenReaderChanged: [boolean], - // alias for screenReaderChanged - change: [boolean], - announcementFinished: [ - { - announcement: string, - success: boolean, - }, - ], -}; - -type AccessibilityEventTypes = 'focus'; - -const _subscriptions = new Map(); - -/** - * Sometimes it's useful to know whether or not the device has a screen reader - * that is currently active. The `AccessibilityInfo` API is designed for this - * purpose. You can use it to query the current state of the screen reader as - * well as to register to be notified when the state of the screen reader - * changes. - * - * See https://reactnative.dev/docs/accessibilityinfo.html - */ -const AccessibilityInfo = { - /** - * Query whether bold text is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when bold text is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isBoldTextEnabled - */ - isBoldTextEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentBoldTextState(resolve, reject); - } else { - reject(reject); - } - }); - }, - - /** - * Query whether grayscale is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when grayscale is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isGrayscaleEnabled - */ - isGrayscaleEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentGrayscaleState(resolve, reject); - } else { - reject(reject); - } - }); - }, - - /** - * Query whether inverted colors are currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when invert color is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isInvertColorsEnabled - */ - isInvertColorsEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentInvertColorsState(resolve, reject); - } else { - reject(reject); - } - }); - }, - - /** - * Query whether reduced motion is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when a reduce motion is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isReduceMotionEnabled - */ - isReduceMotionEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentReduceMotionState(resolve, reject); - } else { - reject(reject); - } - }); - }, - - /** - * Query whether reduced transparency is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when a reduce transparency is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isReduceTransparencyEnabled - */ - isReduceTransparencyEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentReduceTransparencyState( - resolve, - reject, - ); - } else { - reject(reject); - } - }); - }, - - /** - * Query whether a screen reader is currently enabled. - * - * Returns a promise which resolves to a boolean. - * The result is `true` when a screen reader is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#isScreenReaderEnabled - */ - isScreenReaderEnabled: function(): Promise { - return new Promise((resolve, reject) => { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.getCurrentVoiceOverState(resolve, reject); - } else { - reject(reject); - } - }); - }, - - /** - * Deprecated - * - * Same as `isScreenReaderEnabled` - */ - // $FlowFixMe[unsafe-getters-setters] - get fetch(): $FlowFixMe { - console.warn( - 'AccessibilityInfo.fetch is deprecated, call AccessibilityInfo.isScreenReaderEnabled instead', - ); - return this.isScreenReaderEnabled; - }, - - /** - * Add an event handler. Supported events: - * - * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. - * The argument to the event handler is a boolean. The boolean is `true` when a bold text - * is enabled and `false` otherwise. - * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. - * The argument to the event handler is a boolean. The boolean is `true` when a gray scale - * is enabled and `false` otherwise. - * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle - * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert - * colors is enabled and `false` otherwise. - * - `reduceMotionChanged`: Fires when the state of the reduce motion toggle changes. - * The argument to the event handler is a boolean. The boolean is `true` when a reduce - * motion is enabled (or when "Transition Animation Scale" in "Developer options" is - * "Animation off") and `false` otherwise. - * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency - * toggle changes. The argument to the event handler is a boolean. The boolean is `true` - * when a reduce transparency is enabled and `false` otherwise. - * - `screenReaderChanged`: Fires when the state of the screen reader changes. The argument - * to the event handler is a boolean. The boolean is `true` when a screen - * reader is enabled and `false` otherwise. - * - `announcementFinished`: iOS-only event. Fires when the screen reader has - * finished making an announcement. The argument to the event handler is a - * dictionary with these keys: - * - `announcement`: The string announced by the screen reader. - * - `success`: A boolean indicating whether the announcement was - * successfully made. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#addeventlistener - */ - addEventListener: function>( - eventName: K, - handler: (...$ElementType) => void, - ): EventSubscription { - let subscription: EventSubscription; - - if (eventName === 'change') { - subscription = RCTDeviceEventEmitter.addListener( - CHANGE_EVENT_NAME.screenReaderChanged, - // $FlowFixMe[incompatible-call] - handler, - ); - } else if (CHANGE_EVENT_NAME[eventName]) { - subscription = RCTDeviceEventEmitter.addListener(eventName, handler); - } - - // $FlowFixMe[escaped-generic] - _subscriptions.set(handler, subscription); - - return { - remove: () => { - // $FlowIssue flow does not recognize handler properly - AccessibilityInfo.removeEventListener(eventName, handler); - }, - }; - }, - - /** - * Set accessibility focus to a react component. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus - */ - setAccessibilityFocus: function(reactTag: number): void { - legacySendAccessibilityEvent(reactTag, 'focus'); - }, - - /** - * Send a named accessibility event to a HostComponent. - */ - sendAccessibilityEvent_unstable: function( - handle: ElementRef>, - eventType: AccessibilityEventTypes, - ) { - // route through React renderer to distinguish between Fabric and non-Fabric handles - sendAccessibilityEvent(handle, eventType); - }, - - /** - * Post a string to be announced by the screen reader. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#announceforaccessibility - */ - announceForAccessibility: function(announcement: string): void { - if (NativeAccessibilityManager) { - NativeAccessibilityManager.announceForAccessibility(announcement); - } - }, - - /** - * Remove an event handler. - * - * See https://reactnative.dev/docs/accessibilityinfo.html#removeeventlistener - */ - removeEventListener: function>( - eventName: K, - handler: (...$ElementType) => void, - ): void { - // $FlowFixMe[escaped-generic] - const listener = _subscriptions.get(handler); - if (!listener) { - return; - } - listener.remove(); - // $FlowFixMe[escaped-generic] - _subscriptions.delete(handler); - }, -}; - -module.exports = AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js new file mode 100644 index 00000000000000..3933de41bc9d95 --- /dev/null +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js @@ -0,0 +1,350 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import RCTDeviceEventEmitter from '../../EventEmitter/RCTDeviceEventEmitter'; +import {sendAccessibilityEvent} from '../../Renderer/shims/ReactNative'; +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import Platform from '../../Utilities/Platform'; +import type EventEmitter from '../../vendor/emitter/EventEmitter'; +import type {EventSubscription} from '../../vendor/emitter/EventEmitter'; +import NativeAccessibilityInfoAndroid from './NativeAccessibilityInfo'; +import NativeAccessibilityManagerIOS from './NativeAccessibilityManager'; +import legacySendAccessibilityEvent from './legacySendAccessibilityEvent'; +import type {ElementRef} from 'react'; + +// Events that are only supported on iOS. +type AccessibilityEventDefinitionsIOS = { + announcementFinished: [{announcement: string, success: boolean}], + boldTextChanged: [boolean], + grayscaleChanged: [boolean], + invertColorsChanged: [boolean], + reduceTransparencyChanged: [boolean], +}; + +type AccessibilityEventDefinitions = { + ...AccessibilityEventDefinitionsIOS, + change: [boolean], // screenReaderChanged + reduceMotionChanged: [boolean], + screenReaderChanged: [boolean], +}; + +type AccessibilityEventTypes = 'click' | 'focus'; + +// Mapping of public event names to platform-specific event names. +const EventNames: Map<$Keys, string> = + Platform.OS === 'android' + ? new Map([ + ['change', 'touchExplorationDidChange'], + ['reduceMotionChanged', 'reduceMotionDidChange'], + ['screenReaderChanged', 'touchExplorationDidChange'], + ]) + : new Map([ + ['announcementFinished', 'announcementFinished'], + ['boldTextChanged', 'boldTextChanged'], + ['change', 'screenReaderChanged'], + ['grayscaleChanged', 'grayscaleChanged'], + ['invertColorsChanged', 'invertColorsChanged'], + ['reduceMotionChanged', 'reduceMotionChanged'], + ['reduceTransparencyChanged', 'reduceTransparencyChanged'], + ['screenReaderChanged', 'screenReaderChanged'], + ]); + +/** + * Sometimes it's useful to know whether or not the device has a screen reader + * that is currently active. The `AccessibilityInfo` API is designed for this + * purpose. You can use it to query the current state of the screen reader as + * well as to register to be notified when the state of the screen reader + * changes. + * + * See https://reactnative.dev/docs/accessibilityinfo.html + */ +const AccessibilityInfo = { + /** + * Query whether bold text is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when bold text is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isBoldTextEnabled + */ + isBoldTextEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentBoldTextState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether grayscale is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when grayscale is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isGrayscaleEnabled + */ + isGrayscaleEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentGrayscaleState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether inverted colors are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isInvertColorsEnabled + */ + isInvertColorsEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentInvertColorsState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether reduced motion is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce motion is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isReduceMotionEnabled + */ + isReduceMotionEnabled(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid != null) { + NativeAccessibilityInfoAndroid.isReduceMotionEnabled(resolve); + } else { + reject(null); + } + } else { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentReduceMotionState( + resolve, + reject, + ); + } else { + reject(null); + } + } + }); + }, + + /** + * Query whether reduced transparency is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce transparency is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isReduceTransparencyEnabled + */ + isReduceTransparencyEnabled(): Promise { + if (Platform.OS === 'android') { + return Promise.resolve(false); + } else { + return new Promise((resolve, reject) => { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentReduceTransparencyState( + resolve, + reject, + ); + } else { + reject(null); + } + }); + } + }, + + /** + * Query whether a screen reader is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a screen reader is enabled and `false` otherwise. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#isScreenReaderEnabled + */ + isScreenReaderEnabled(): Promise { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid != null) { + NativeAccessibilityInfoAndroid.isTouchExplorationEnabled(resolve); + } else { + reject(null); + } + } else { + if (NativeAccessibilityManagerIOS != null) { + NativeAccessibilityManagerIOS.getCurrentVoiceOverState( + resolve, + reject, + ); + } else { + reject(null); + } + } + }); + }, + + /** + * Add an event handler. Supported events: + * + * - `reduceMotionChanged`: Fires when the state of the reduce motion toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a reduce + * motion is enabled (or when "Transition Animation Scale" in "Developer options" is + * "Animation off") and `false` otherwise. + * - `screenReaderChanged`: Fires when the state of the screen reader changes. The argument + * to the event handler is a boolean. The boolean is `true` when a screen + * reader is enabled and `false` otherwise. + * + * These events are only supported on iOS: + * + * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a bold text + * is enabled and `false` otherwise. + * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when a gray scale + * is enabled and `false` otherwise. + * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle + * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert + * colors is enabled and `false` otherwise. + * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency + * toggle changes. The argument to the event handler is a boolean. The boolean is `true` + * when a reduce transparency is enabled and `false` otherwise. + * - `announcementFinished`: iOS-only event. Fires when the screen reader has + * finished making an announcement. The argument to the event handler is a + * dictionary with these keys: + * - `announcement`: The string announced by the screen reader. + * - `success`: A boolean indicating whether the announcement was + * successfully made. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#addeventlistener + */ + addEventListener>( + eventName: K, + handler: (...$ElementType) => void, + ): EventSubscription { + const deviceEventName = EventNames.get(eventName); + return deviceEventName == null + ? {remove(): void {}} + : RCTDeviceEventEmitter.addListener(deviceEventName, handler); + }, + + /** + * Set accessibility focus to a React component. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#setaccessibilityfocus + */ + setAccessibilityFocus(reactTag: number): void { + legacySendAccessibilityEvent(reactTag, 'focus'); + }, + + /** + * Send a named accessibility event to a HostComponent. + */ + sendAccessibilityEvent_unstable( + handle: ElementRef>, + eventType: AccessibilityEventTypes, + ) { + // iOS only supports 'focus' event types + if (Platform.OS === 'ios' && eventType === 'click') { + return; + } + // route through React renderer to distinguish between Fabric and non-Fabric handles + sendAccessibilityEvent(handle, eventType); + }, + + /** + * Post a string to be announced by the screen reader. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#announceforaccessibility + */ + announceForAccessibility(announcement: string): void { + if (Platform.OS === 'android') { + NativeAccessibilityInfoAndroid?.announceForAccessibility(announcement); + } else { + NativeAccessibilityManagerIOS?.announceForAccessibility(announcement); + } + }, + + /** + * @deprecated Use `remove` on the EventSubscription from `addEventListener`. + */ + removeEventListener>( + eventName: K, + handler: (...$ElementType) => void, + ): void { + // NOTE: This will report a deprecation notice via `console.error`. + const deviceEventName = EventNames.get(eventName); + if (deviceEventName != null) { + // $FlowIgnore[incompatible-cast] + (RCTDeviceEventEmitter: EventEmitter<$FlowFixMe>).removeListener( + 'deviceEventName', + // $FlowFixMe[invalid-tuple-arity] + handler, + ); + } + }, + + /** + * Get the recommended timeout for changes to the UI needed by this user. + * + * See https://reactnative.dev/docs/accessibilityinfo.html#getrecommendedtimeoutmillis + */ + getRecommendedTimeoutMillis(originalTimeout: number): Promise { + if (Platform.OS === 'android') { + return new Promise((resolve, reject) => { + if (NativeAccessibilityInfoAndroid?.getRecommendedTimeoutMillis) { + NativeAccessibilityInfoAndroid.getRecommendedTimeoutMillis( + originalTimeout, + resolve, + ); + } else { + resolve(originalTimeout); + } + }); + } else { + return Promise.resolve(originalTimeout); + } + }, +}; + +export default AccessibilityInfo; diff --git a/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js b/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js index 4810127fd7d48b..916667aa5c2c0a 100644 --- a/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js +++ b/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js @@ -20,6 +20,10 @@ export interface Spec extends TurboModule { ) => void; +setAccessibilityFocus: (reactTag: number) => void; +announceForAccessibility: (announcement: string) => void; + +getRecommendedTimeoutMillis?: ( + mSec: number, + onSuccess: (recommendedTimeoutMillis: number) => void, + ) => void; } export default (TurboModuleRegistry.get('AccessibilityInfo'): ?Spec); diff --git a/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js index edc950427d6b9f..3bb1c554950d50 100644 --- a/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js +++ b/Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent.android.js @@ -24,6 +24,12 @@ function legacySendAccessibilityEvent( UIManager.getConstants().AccessibilityEventTypes.typeViewFocused, ); } + if (eventType === 'click') { + UIManager.sendAccessibilityEvent( + reactTag, + UIManager.getConstants().AccessibilityEventTypes.typeViewClicked, + ); + } } module.exports = legacySendAccessibilityEvent; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index 05f125d86698bf..6202d610d95e22 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -111,10 +111,10 @@ const ActivityIndicator = ( onLayout={onLayout} style={StyleSheet.compose(styles.container, style)}> {Platform.OS === 'android' ? ( - // $FlowFixMe Flow doesn't know when this is the android component + // $FlowFixMe[prop-missing] Flow doesn't know when this is the android component ) : ( - /* $FlowFixMe(>=0.106.0 site=react_native_android_fb) This comment + /* $FlowFixMe[prop-missing] (>=0.106.0 site=react_native_android_fb) This comment * suppresses an error found when Flow v0.106 was deployed. To see the * error, delete this comment and run Flow. */ diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js index 9659a7135bbece..a235b375d02a06 100644 --- a/Libraries/Components/Button.js +++ b/Libraries/Components/Button.js @@ -18,9 +18,9 @@ const Text = require('../Text/Text'); const TouchableNativeFeedback = require('./Touchable/TouchableNativeFeedback'); const TouchableOpacity = require('./Touchable/TouchableOpacity'); const View = require('./View/View'); - const invariant = require('invariant'); +import type {AccessibilityState} from './View/ViewAccessibility'; import type {PressEvent} from '../Types/CoreEventTypes'; import type {ColorValue} from '../StyleSheet/StyleSheet'; @@ -134,6 +134,11 @@ type ButtonProps = $ReadOnly<{| Used to locate this view in end-to-end tests. */ testID?: ?string, + + /** + * Accessibility props. + */ + accessibilityState?: ?AccessibilityState, |}>; /** @@ -261,7 +266,6 @@ class Button extends React.Component { nextFocusLeft, nextFocusRight, nextFocusUp, - disabled, testID, } = this.props; const buttonStyles = [styles.button]; @@ -273,12 +277,22 @@ class Button extends React.Component { buttonStyles.push({backgroundColor: color}); } } - const accessibilityState = {}; + + const disabled = + this.props.disabled != null + ? this.props.disabled + : this.props.accessibilityState?.disabled; + + const accessibilityState = + disabled !== this.props.accessibilityState?.disabled + ? {...this.props.accessibilityState, disabled} + : this.props.accessibilityState; + if (disabled) { buttonStyles.push(styles.buttonDisabled); textStyles.push(styles.textDisabled); - accessibilityState.disabled = true; } + invariant( typeof title === 'string', 'The title prop of a Button must be a string', @@ -287,6 +301,7 @@ class Button extends React.Component { Platform.OS === 'android' ? title.toUpperCase() : title; const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity; + return ( =0.99.0 site=react_native_ios_fb) This comment suppresses an - * error found when Flow v0.99 was deployed. To see the error, delete this - * comment and run Flow. */ +/* $FlowFixMe[cannot-resolve-module] (>=0.99.0 site=react_native_ios_fb) This + * comment suppresses an error found when Flow v0.99 was deployed. To see the + * error, delete this comment and run Flow. */ const DrawerLayoutAndroid = require('../DrawerLayoutAndroid.android'); const View = require('../../View/View'); diff --git a/Libraries/Components/Keyboard/Keyboard.js b/Libraries/Components/Keyboard/Keyboard.js index db52a90e31a512..6227b975adf12c 100644 --- a/Libraries/Components/Keyboard/Keyboard.js +++ b/Libraries/Components/Keyboard/Keyboard.js @@ -11,6 +11,7 @@ import NativeEventEmitter from '../../EventEmitter/NativeEventEmitter'; import LayoutAnimation from '../../LayoutAnimation/LayoutAnimation'; import dismissKeyboard from '../../Utilities/dismissKeyboard'; +import Platform from '../../Utilities/Platform'; import NativeKeyboardObserver from './NativeKeyboardObserver'; import {type EventSubscription} from '../../vendor/emitter/EventEmitter'; @@ -103,7 +104,9 @@ type KeyboardEventDefinitions = { class Keyboard { _emitter: NativeEventEmitter = new NativeEventEmitter( - NativeKeyboardObserver, + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeKeyboardObserver, ); /** @@ -137,6 +140,17 @@ class Keyboard { return this._emitter.addListener(eventType, listener); } + /** + * @deprecated Use `remove` on the EventSubscription from `addEventListener`. + */ + removeEventListener>( + eventType: K, + listener: (...$ElementType) => mixed, + ): void { + // NOTE: This will report a deprecation notice via `console.error`. + this._emitter.removeListener(eventType, listener); + } + /** * Removes all listeners for a specific event type. * diff --git a/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/Libraries/Components/Keyboard/KeyboardAvoidingView.js index 65eb67d36150eb..194731e3b24f43 100644 --- a/Libraries/Components/Keyboard/KeyboardAvoidingView.js +++ b/Libraries/Components/Keyboard/KeyboardAvoidingView.js @@ -5,15 +5,15 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow + * @flow strict-local */ -const Keyboard = require('./Keyboard'); -const LayoutAnimation = require('../../LayoutAnimation/LayoutAnimation'); -const Platform = require('../../Utilities/Platform'); -const React = require('react'); -const StyleSheet = require('../../StyleSheet/StyleSheet'); -const View = require('../View/View'); +import Keyboard from './Keyboard'; +import LayoutAnimation from '../../LayoutAnimation/LayoutAnimation'; +import Platform from '../../Utilities/Platform'; +import * as React from 'react'; +import StyleSheet from '../../StyleSheet/StyleSheet'; +import View from '../View/View'; import type {ViewStyleProp} from '../../StyleSheet/StyleSheet'; import {type EventSubscription} from '../../vendor/emitter/EventEmitter'; @@ -41,13 +41,13 @@ type Props = $ReadOnly<{| * Controls whether this `KeyboardAvoidingView` instance should take effect. * This is useful when more than one is on the screen. Defaults to true. */ - enabled: ?boolean, + enabled?: ?boolean, /** * Distance between the top of the user screen and the React Native view. This * may be non-zero in some cases. Defaults to 0. */ - keyboardVerticalOffset: number, + keyboardVerticalOffset?: number, |}>; type State = {| @@ -59,15 +59,10 @@ type State = {| * adjusting its height, position, or bottom padding. */ class KeyboardAvoidingView extends React.Component { - static defaultProps: {|enabled: boolean, keyboardVerticalOffset: number|} = { - enabled: true, - keyboardVerticalOffset: 0, - }; - _frame: ?ViewLayout = null; _keyboardEvent: ?KeyboardEvent = null; _subscriptions: Array = []; - viewRef: {current: React.ElementRef | null, ...}; + viewRef: {current: React.ElementRef | null, ...}; _initialFrameHeight: number = 0; constructor(props: Props) { @@ -82,7 +77,8 @@ class KeyboardAvoidingView extends React.Component { return 0; } - const keyboardY = keyboardFrame.screenY - this.props.keyboardVerticalOffset; + const keyboardY = + keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0); // Calculate the displacement needed for the view such that it // no longer overlaps with the keyboard @@ -157,12 +153,13 @@ class KeyboardAvoidingView extends React.Component { behavior, children, contentContainerStyle, - enabled, - keyboardVerticalOffset, + enabled = true, + // eslint-disable-next-line no-unused-vars + keyboardVerticalOffset = 0, style, ...props } = this.props; - const bottomHeight = enabled ? this.state.bottom : 0; + const bottomHeight = enabled === true ? this.state.bottom : 0; switch (behavior) { case 'height': let heightStyle; diff --git a/Libraries/Components/Picker/AndroidDialogPickerNativeComponent.js b/Libraries/Components/Picker/AndroidDialogPickerNativeComponent.js index a831311f2b18c9..d1ac537d064fb6 100644 --- a/Libraries/Components/Picker/AndroidDialogPickerNativeComponent.js +++ b/Libraries/Components/Picker/AndroidDialogPickerNativeComponent.js @@ -11,15 +11,13 @@ import * as React from 'react'; import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; -import requireNativeComponent from '../../ReactNative/requireNativeComponent'; -import registerGeneratedViewConfig from '../../Utilities/registerGeneratedViewConfig'; -import AndroidDialogPickerViewConfig from './AndroidDialogPickerViewConfig'; import type { DirectEventHandler, Int32, WithDefault, } from '../../Types/CodegenTypes'; +import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry'; import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; import type {TextStyleProp} from '../../StyleSheet/StyleSheet'; import type {ColorValue} from '../../StyleSheet/StyleSheet'; @@ -64,17 +62,22 @@ export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['setNativeSelectedPosition'], }); -let AndroidDialogPickerNativeComponent; -if (global.RN$Bridgeless) { - registerGeneratedViewConfig( - 'AndroidDialogPicker', - AndroidDialogPickerViewConfig, - ); - AndroidDialogPickerNativeComponent = 'AndroidDialogPicker'; -} else { - AndroidDialogPickerNativeComponent = requireNativeComponent( - 'AndroidDialogPicker', - ); -} +const AndroidDialogPickerNativeComponent: HostComponent = NativeComponentRegistry.get( + 'AndroidDialogPicker', + () => ({ + uiViewClassName: 'AndroidDialogPicker', + bubblingEventTypes: {}, + directEventTypes: {}, + validAttributes: { + color: {process: require('../../StyleSheet/processColor')}, + backgroundColor: {process: require('../../StyleSheet/processColor')}, + enabled: true, + items: true, + prompt: true, + selected: true, + onSelect: true, + }, + }), +); export default ((AndroidDialogPickerNativeComponent: any): NativeType); diff --git a/Libraries/Components/Picker/AndroidDialogPickerViewConfig.js b/Libraries/Components/Picker/AndroidDialogPickerViewConfig.js deleted file mode 100644 index 440006163698c2..00000000000000 --- a/Libraries/Components/Picker/AndroidDialogPickerViewConfig.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -'use strict'; - -import type {PartialViewConfig} from 'react-native/Libraries/Renderer/shims/ReactNativeTypes'; - -const AndroidDialogPickerViewConfig = { - uiViewClassName: 'AndroidDialogPicker', - bubblingEventTypes: {}, - directEventTypes: {}, - validAttributes: { - color: {process: require('../../StyleSheet/processColor')}, - backgroundColor: {process: require('../../StyleSheet/processColor')}, - enabled: true, - items: true, - prompt: true, - selected: true, - onSelect: true, - }, -}; - -module.exports = (AndroidDialogPickerViewConfig: PartialViewConfig); diff --git a/Libraries/Components/Picker/AndroidDropdownPickerNativeComponent.js b/Libraries/Components/Picker/AndroidDropdownPickerNativeComponent.js index 6d2946b43fa706..21b5f41e13e550 100644 --- a/Libraries/Components/Picker/AndroidDropdownPickerNativeComponent.js +++ b/Libraries/Components/Picker/AndroidDropdownPickerNativeComponent.js @@ -11,13 +11,14 @@ import * as React from 'react'; import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; -import requireNativeComponent from '../../ReactNative/requireNativeComponent'; import type { DirectEventHandler, Int32, WithDefault, } from '../../Types/CodegenTypes'; + +import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry'; import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; import type {TextStyleProp} from '../../StyleSheet/StyleSheet'; import type {ColorValue} from '../../StyleSheet/StyleSheet'; @@ -49,11 +50,9 @@ type NativeProps = $ReadOnly<{| onSelect?: DirectEventHandler, |}>; -type NativeType = HostComponent; - interface NativeCommands { +setNativeSelectedPosition: ( - viewRef: React.ElementRef, + viewRef: React.ElementRef, index: number, ) => void; } @@ -62,6 +61,22 @@ export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['setNativeSelectedPosition'], }); -export default (requireNativeComponent( +const AndroidDropdownPickerNativeComponent: HostComponent = NativeComponentRegistry.get( 'AndroidDropdownPicker', -): NativeType); + () => ({ + uiViewClassName: 'AndroidDropdownPicker', + bubblingEventTypes: {}, + directEventTypes: {}, + validAttributes: { + color: {process: require('../../StyleSheet/processColor')}, + backgroundColor: {process: require('../../StyleSheet/processColor')}, + enabled: true, + items: true, + prompt: true, + selected: true, + onSelect: true, + }, + }), +); + +export default AndroidDropdownPickerNativeComponent; diff --git a/Libraries/Components/Picker/Picker.js b/Libraries/Components/Picker/Picker.js index 87c1f8133e4a5d..7a733f32637bcf 100644 --- a/Libraries/Components/Picker/Picker.js +++ b/Libraries/Components/Picker/Picker.js @@ -147,13 +147,17 @@ class Picker extends React.Component { render(): React.Node { if (Platform.OS === 'ios') { - /* $FlowFixMe(>=0.81.0 site=react_native_ios_fb) This suppression was - * added when renaming suppression sites. */ + /* $FlowFixMe[prop-missing] (>=0.81.0 site=react_native_ios_fb) This + * suppression was added when renaming suppression sites. */ + /* $FlowFixMe[incompatible-type] (>=0.81.0 site=react_native_ios_fb) This + * suppression was added when renaming suppression sites. */ return {this.props.children}; } else if (Platform.OS === 'android') { return ( - /* $FlowFixMe(>=0.81.0 site=react_native_android_fb) This suppression - * was added when renaming suppression sites. */ + /* $FlowFixMe[incompatible-type] (>=0.81.0 site=react_native_android_fb) This + * suppression was added when renaming suppression sites. */ + /* $FlowFixMe[prop-missing] (>=0.81.0 site=react_native_android_fb) This + * suppression was added when renaming suppression sites. */ {this.props.children} ); } else { diff --git a/Libraries/Components/Picker/RCTPickerNativeComponent.js b/Libraries/Components/Picker/RCTPickerNativeComponent.js index a3396e0e5c4f37..647a8abc841bda 100644 --- a/Libraries/Components/Picker/RCTPickerNativeComponent.js +++ b/Libraries/Components/Picker/RCTPickerNativeComponent.js @@ -8,15 +8,13 @@ * @format */ -const requireNativeComponent = require('../../ReactNative/requireNativeComponent'); -const ReactNativeViewConfigRegistry = require('../../Renderer/shims/ReactNativeViewConfigRegistry'); - +import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry'; +import ReactNativeViewViewConfig from '../../Components/View/ReactNativeViewViewConfig'; import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; import type {SyntheticEvent} from '../../Types/CoreEventTypes'; import type {TextStyleProp} from '../../StyleSheet/StyleSheet'; import type {ProcessedColorValue} from '../../StyleSheet/processColor'; import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; -import RCTPickerViewConfig from './RCTPickerViewConfig'; import * as React from 'react'; type PickerIOSChangeEvent = SyntheticEvent< @@ -56,15 +54,33 @@ export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['setNativeSelectedIndex'], }); -let RCTPickerNativeComponent; -if (global.RN$Bridgeless) { - ReactNativeViewConfigRegistry.register('RCTPicker', () => { - return RCTPickerViewConfig; - }); - RCTPickerNativeComponent = 'RCTPicker'; -} else { - RCTPickerNativeComponent = requireNativeComponent('RCTPicker'); -} +const RCTPickerNativeComponent: HostComponent = NativeComponentRegistry.get( + 'RCTPicker', + () => ({ + uiViewClassName: 'RCTPicker', + bubblingEventTypes: { + topChange: { + phasedRegistrationNames: { + bubbled: 'onChange', + captured: 'onChangeCapture', + }, + }, + }, + directEventTypes: {}, + validAttributes: { + ...ReactNativeViewViewConfig.validAttributes, + color: {process: require('../../StyleSheet/processColor')}, + fontFamily: true, + fontSize: true, + fontStyle: true, + fontWeight: true, + items: true, + onChange: true, + selectedIndex: true, + textAlign: true, + }, + }), +); // flowlint-next-line unclear-type:off export default ((RCTPickerNativeComponent: any): HostComponent); diff --git a/Libraries/Components/Picker/RCTPickerViewConfig.js b/Libraries/Components/Picker/RCTPickerViewConfig.js deleted file mode 100644 index fc44b0b9b4c0a5..00000000000000 --- a/Libraries/Components/Picker/RCTPickerViewConfig.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -import ReactNativeViewViewConfig from '../../Components/View/ReactNativeViewViewConfig'; -import {type ViewConfig} from '../../Renderer/shims/ReactNativeTypes'; - -const RCTPickerViewConfig = { - uiViewClassName: 'RCTPicker', - bubblingEventTypes: { - topChange: { - phasedRegistrationNames: { - bubbled: 'onChange', - captured: 'onChangeCapture', - }, - }, - }, - directEventTypes: {}, - validAttributes: { - ...ReactNativeViewViewConfig.validAttributes, - color: {process: require('../../StyleSheet/processColor')}, - fontFamily: true, - fontSize: true, - fontStyle: true, - fontWeight: true, - items: true, - onChange: true, - selectedIndex: true, - textAlign: true, - }, -}; - -module.exports = (RCTPickerViewConfig: ViewConfig); diff --git a/Libraries/Components/ProgressBarAndroid/__tests__/ProgressBarAndroid-test.js b/Libraries/Components/ProgressBarAndroid/__tests__/ProgressBarAndroid-test.js index 88e4f25cab5bdc..127a0d7149fc54 100644 --- a/Libraries/Components/ProgressBarAndroid/__tests__/ProgressBarAndroid-test.js +++ b/Libraries/Components/ProgressBarAndroid/__tests__/ProgressBarAndroid-test.js @@ -12,9 +12,9 @@ 'use strict'; const React = require('react'); -/* $FlowFixMe(>=0.99.0 site=react_native_ios_fb) This comment suppresses an - * error found when Flow v0.99 was deployed. To see the error, delete this - * comment and run Flow. */ +/* $FlowFixMe[cannot-resolve-module] (>=0.99.0 site=react_native_ios_fb) This + * comment suppresses an error found when Flow v0.99 was deployed. To see the + * error, delete this comment and run Flow. */ const ProgressBarAndroid = require('../ProgressBarAndroid.android'); const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); diff --git a/Libraries/Components/RefreshControl/PullToRefreshViewNativeComponent.js b/Libraries/Components/RefreshControl/PullToRefreshViewNativeComponent.js index 4947b1e31df3b3..fa4791f2ab620c 100644 --- a/Libraries/Components/RefreshControl/PullToRefreshViewNativeComponent.js +++ b/Libraries/Components/RefreshControl/PullToRefreshViewNativeComponent.js @@ -8,7 +8,11 @@ * @flow strict-local */ -import type {DirectEventHandler, WithDefault} from '../../Types/CodegenTypes'; +import type { + DirectEventHandler, + Float, + WithDefault, +} from '../../Types/CodegenTypes'; import type {ColorValue} from '../../StyleSheet/StyleSheet'; import type {ViewProps} from '../View/ViewPropTypes'; import * as React from 'react'; @@ -32,6 +36,10 @@ type NativeProps = $ReadOnly<{| * The title displayed under the refresh indicator. */ title?: WithDefault, + /** + * Progress view top offset + */ + progressViewOffset?: WithDefault, /** * Called when the view starts refreshing. diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js index 48bafb5ca62c83..0f0a16dbbccfc5 100644 --- a/Libraries/Components/RefreshControl/RefreshControl.js +++ b/Libraries/Components/RefreshControl/RefreshControl.js @@ -52,10 +52,6 @@ type AndroidProps = $ReadOnly<{| * Size of the refresh indicator. */ size?: ?('default' | 'large'), - /** - * Progress view top offset - */ - progressViewOffset?: ?number, |}>; export type RefreshControlProps = $ReadOnly<{| @@ -72,6 +68,11 @@ export type RefreshControlProps = $ReadOnly<{| * Whether the view should be indicating an active refresh. */ refreshing: boolean, + + /** + * Progress view top offset + */ + progressViewOffset?: ?number, |}>; /** @@ -162,7 +163,6 @@ class RefreshControl extends React.Component { colors, progressBackgroundColor, size, - progressViewOffset, ...props } = this.props; return ( diff --git a/Libraries/Components/SafeAreaView/SafeAreaView.js b/Libraries/Components/SafeAreaView/SafeAreaView.js index 168d06cb7ee7bc..2e6e0e74c7c74d 100644 --- a/Libraries/Components/SafeAreaView/SafeAreaView.js +++ b/Libraries/Components/SafeAreaView/SafeAreaView.js @@ -58,4 +58,4 @@ if (Platform.OS === 'android') { ); } -module.exports = exported; +export default exported; diff --git a/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-test.js b/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-test.js index 133fd1da8a9f85..4b7edf60fee47e 100644 --- a/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-test.js +++ b/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-test.js @@ -12,7 +12,7 @@ 'use strict'; const React = require('react'); -const SafeAreaView = require('../SafeAreaView'); +import SafeAreaView from '../SafeAreaView'; const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); const View = require('../../View/View'); const Text = require('../../../Text/Text'); diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index a5f17fe18890bb..8eca425604dfc8 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -26,7 +26,6 @@ import dismissKeyboard from '../../Utilities/dismissKeyboard'; import flattenStyle from '../../StyleSheet/flattenStyle'; import invariant from 'invariant'; import processDecelerationRate from './processDecelerationRate'; -import resolveAssetSource from '../../Image/resolveAssetSource'; import splitLayoutProps from '../../StyleSheet/splitLayoutProps'; import setAndForwardRef from '../../Utilities/setAndForwardRef'; @@ -428,32 +427,15 @@ type AndroidProps = $ReadOnly<{| fadingEdgeLength?: ?number, |}>; -type VRProps = $ReadOnly<{| - /** - * 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' -|}>; - type StickyHeaderComponentType = React.AbstractComponent< ScrollViewStickyHeaderProps, - $ReadOnly<{setNextHeaderY: number => void, ...}>, + $ReadOnly void}>, >; export type Props = $ReadOnly<{| ...ViewProps, ...IOSProps, ...AndroidProps, - ...VRProps, /** * These styles will be applied to the scroll view content container which @@ -589,6 +571,11 @@ export type Props = $ReadOnly<{| * The default value is true. */ showsVerticalScrollIndicator?: ?boolean, + /** + * When true, Sticky header is hidden when scrolling down, and dock at the top + * when scrolling up + */ + stickyHeaderHiddenOnScroll?: ?boolean, /** * An array of child indices determining which children get docked to the * top of the screen when scrolling. For example, passing @@ -652,7 +639,8 @@ export type Props = $ReadOnly<{| * * See [RefreshControl](docs/refreshcontrol.html). */ - // $FlowFixMe - how to handle generic type without existential operator? + /* $FlowFixMe[unclear-type] - how to handle generic type without existential + * operator? */ refreshControl?: ?React.Element, children?: React.Node, /** @@ -717,13 +705,17 @@ type ScrollViewComponentStatics = $ReadOnly<{| */ class ScrollView extends React.Component { static Context: typeof ScrollViewContext = ScrollViewContext; + constructor(props: Props) { super(props); + + this._scrollAnimatedValue = new AnimatedImplementation.Value( + this.props.contentOffset?.y ?? 0, + ); + this._scrollAnimatedValue.setOffset(this.props.contentInset?.top ?? 0); } - _scrollAnimatedValue: AnimatedImplementation.Value = new AnimatedImplementation.Value( - 0, - ); + _scrollAnimatedValue: AnimatedImplementation.Value; _scrollAnimatedValueAttachment: ?{detach: () => void, ...} = null; _stickyHeaderRefs: Map< string, @@ -789,13 +781,6 @@ class ScrollView extends React.Component { this.scrollResponderKeyboardDidHide, ); - this._scrollAnimatedValue = new AnimatedImplementation.Value( - this.props.contentOffset?.y ?? 0, - ); - this._scrollAnimatedValue.setOffset(this.props.contentInset?.top ?? 0); - this._stickyHeaderRefs = new Map(); - this._headerLayoutYs = new Map(); - this._updateAnimatedNodeAttachment(); } @@ -868,7 +853,7 @@ class ScrollView extends React.Component { * to the underlying scroll responder's methods. */ getScrollResponder: () => ScrollResponderType = () => { - // $FlowFixMe + // $FlowFixMe[unclear-type] return ((this: any): ScrollResponderType); }; @@ -1077,23 +1062,38 @@ class ScrollView extends React.Component { height: number, ) => void = (left: number, top: number, width: number, height: number) => { let keyboardScreenY = Dimensions.get('window').height; - if (this._keyboardWillOpenTo != null) { - keyboardScreenY = this._keyboardWillOpenTo.endCoordinates.screenY; - } - let scrollOffsetY = - top - keyboardScreenY + height + this._additionalScrollOffset; - - // By default, this can scroll with negative offset, pulling the content - // down so that the target component's bottom meets the keyboard's top. - // If requested otherwise, cap the offset at 0 minimum to avoid content - // shifting down. - if (this._preventNegativeScrollOffset === true) { - scrollOffsetY = Math.max(0, scrollOffsetY); - } - this.scrollTo({x: 0, y: scrollOffsetY, animated: true}); - this._additionalScrollOffset = 0; - this._preventNegativeScrollOffset = false; + const scrollTextInputIntoVisibleRect = () => { + if (this._keyboardWillOpenTo != null) { + keyboardScreenY = this._keyboardWillOpenTo.endCoordinates.screenY; + } + let scrollOffsetY = + top - keyboardScreenY + height + this._additionalScrollOffset; + + // By default, this can scroll with negative offset, pulling the content + // down so that the target component's bottom meets the keyboard's top. + // If requested otherwise, cap the offset at 0 minimum to avoid content + // shifting down. + if (this._preventNegativeScrollOffset === true) { + scrollOffsetY = Math.max(0, scrollOffsetY); + } + this.scrollTo({x: 0, y: scrollOffsetY, animated: true}); + + this._additionalScrollOffset = 0; + this._preventNegativeScrollOffset = false; + }; + + if (this._keyboardWillOpenTo == null) { + // `_keyboardWillOpenTo` is set inside `scrollResponderKeyboardWillShow` which + // is not guaranteed to be called before `_inputMeasureAndScrollToKeyboard` but native has already scheduled it. + // In case it was not called before `_inputMeasureAndScrollToKeyboard`, we postpone scrolling to + // text input. + setTimeout(() => { + scrollTextInputIntoVisibleRect(); + }, 0); + } else { + scrollTextInputIntoVisibleRect(); + } }; _getKeyForIndex(index, childArray) { @@ -1642,6 +1642,7 @@ class ScrollView extends React.Component { onLayout={event => this._onStickyHeaderLayout(index, event, key)} scrollAnimatedValue={this._scrollAnimatedValue} inverted={this.props.invertStickyHeaders} + hiddenOnScroll={this.props.stickyHeaderHiddenOnScroll} scrollViewHeight={this.state.layoutHeight}> {child} @@ -1718,7 +1719,6 @@ class ScrollView extends React.Component { onTouchStart: this._handleTouchStart, onTouchCancel: this._handleTouchCancel, onScroll: this._handleScroll, - scrollBarThumbImage: resolveAssetSource(this.props.scrollBarThumbImage), scrollEventThrottle: hasStickyHeaders ? 1 : this.props.scrollEventThrottle, @@ -1812,7 +1812,7 @@ function Wrapper(props, ref) { Wrapper.displayName = 'ScrollView'; const ForwardedScrollView = React.forwardRef(Wrapper); -// $FlowFixMe Add static context to ForwardedScrollView +// $FlowFixMe[prop-missing] Add static context to ForwardedScrollView ForwardedScrollView.Context = ScrollViewContext; ForwardedScrollView.displayName = 'ScrollView'; diff --git a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js index f7acb3d1cfbd81..a17d6350c65abc 100644 --- a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +++ b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -9,6 +9,10 @@ */ import AnimatedImplementation from '../../Animated/AnimatedImplementation'; +import AnimatedAddition from '../../Animated/nodes/AnimatedAddition'; +import AnimatedDiffClamp from '../../Animated/nodes/AnimatedDiffClamp'; +import AnimatedNode from '../../Animated/nodes/AnimatedNode'; + import * as React from 'react'; import StyleSheet from '../../StyleSheet/StyleSheet'; import View from '../View/View'; @@ -16,9 +20,11 @@ import Platform from '../../Utilities/Platform'; import type {LayoutEvent} from '../../Types/CoreEventTypes'; +import ScrollViewStickyHeaderInjection from './ScrollViewStickyHeaderInjection'; + const AnimatedView = AnimatedImplementation.createAnimatedComponent(View); -export type Props = { +export type Props = $ReadOnly<{ children?: React.Element, nextHeaderLayoutY: ?number, onLayout: (event: LayoutEvent) => void, @@ -29,8 +35,8 @@ export type Props = { // The height of the parent ScrollView. Currently only set when inverted. scrollViewHeight: ?number, nativeID?: ?string, - ... -}; + hiddenOnScroll?: ?boolean, +}>; type State = { measured: boolean, @@ -50,7 +56,7 @@ class ScrollViewStickyHeader extends React.Component { translateY: null, }; - _translateY: ?AnimatedImplementation.Interpolation = null; + _translateY: ?AnimatedNode = null; _shouldRecreateTranslateY: boolean = true; _haveReceivedInitialZeroTranslateY: boolean = true; _ref: any; // TODO T53738161: flow type this, and the whole file @@ -62,6 +68,7 @@ class ScrollViewStickyHeader extends React.Component { _debounceTimeout: number = Platform.OS === 'android' ? 15 : 64; setNextHeaderY(y: number) { + this._shouldRecreateTranslateY = true; this.setState({nextHeaderLayoutY: y}); } @@ -87,12 +94,15 @@ class ScrollViewStickyHeader extends React.Component { updateTranslateListener( translateY: AnimatedImplementation.Interpolation, isFabric: boolean, + offset: AnimatedDiffClamp | null, ) { if (this._translateY != null && this._animatedValueListenerId != null) { this._translateY.removeListener(this._animatedValueListenerId); } + offset + ? (this._translateY = new AnimatedAddition(translateY, offset)) + : (this._translateY = translateY); - this._translateY = translateY; this._shouldRecreateTranslateY = false; if (!isFabric) { @@ -178,7 +188,6 @@ class ScrollViewStickyHeader extends React.Component { // eslint-disable-next-line dot-notation (this._ref && this._ref['_internalInstanceHandle']?.stateNode?.canonical) ); - // Initially and in the case of updated props or layout, we // recreate this interpolated value. Otherwise, we do not recreate // when there are state changes. @@ -259,6 +268,22 @@ class ScrollViewStickyHeader extends React.Component { outputRange, }), isFabric, + this.props.hiddenOnScroll + ? new AnimatedDiffClamp( + this.props.scrollAnimatedValue + .interpolate({ + extrapolateLeft: 'clamp', + inputRange: [layoutY, layoutY + 1], + outputRange: ([0, 1]: Array), + }) + .interpolate({ + inputRange: [0, 1], + outputRange: ([0, -1]: Array), + }), + -this.state.layoutHeight, + 0, + ) + : null, ); } @@ -305,4 +330,8 @@ const styles = StyleSheet.create({ }, }); -module.exports = ScrollViewStickyHeader; +const SHToExport: React.AbstractComponent< + Props, + $ReadOnly<{setNextHeaderY: number => void, ...}>, +> = ScrollViewStickyHeaderInjection.unstable_SH ?? ScrollViewStickyHeader; +module.exports = SHToExport; diff --git a/Libraries/Components/ScrollView/ScrollViewStickyHeaderInjection.js b/Libraries/Components/ScrollView/ScrollViewStickyHeaderInjection.js new file mode 100644 index 00000000000000..ab8684ffb6818a --- /dev/null +++ b/Libraries/Components/ScrollView/ScrollViewStickyHeaderInjection.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +import typeof ScrollViewStickyHeader from './ScrollViewStickyHeader'; + +export default { + unstable_SH: (null: ?ScrollViewStickyHeader), +}; diff --git a/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap b/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap index de40fd6e028016..e4723b567bc504 100644 --- a/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap +++ b/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap @@ -34,7 +34,6 @@ exports[` should render as expected: should deep render when not m onTouchMove={[Function]} onTouchStart={[Function]} pagingEnabled={false} - scrollBarThumbImage={null} scrollViewRef={null} sendMomentumEvents={false} snapToEnd={true} diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js index 9bc4a4a4fb9355..a0b835238136ae 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js @@ -115,7 +115,7 @@ const SegmentedControlIOSWithRef = React.forwardRef( }, ); -/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an - * error found when Flow v0.89 was deployed. To see the error, delete this - * comment and run Flow. */ +/* $FlowFixMe[cannot-resolve-name] (>=0.89.0 site=react_native_ios_fb) This + * comment suppresses an error found when Flow v0.89 was deployed. To see the + * error, delete this comment and run Flow. */ module.exports = (SegmentedControlIOSWithRef: NativeSegmentedControlIOS); diff --git a/Libraries/Components/StatusBar/StatusBarIOS.js b/Libraries/Components/StatusBar/StatusBarIOS.js index fe012374b2198d..be8ee04add4faa 100644 --- a/Libraries/Components/StatusBar/StatusBarIOS.js +++ b/Libraries/Components/StatusBar/StatusBarIOS.js @@ -10,6 +10,7 @@ import NativeEventEmitter from '../../EventEmitter/NativeEventEmitter'; import NativeStatusBarManagerIOS from './NativeStatusBarManagerIOS'; +import Platform from '../../Utilities/Platform'; type StatusBarFrameChangeEvent = { frame: { @@ -30,4 +31,8 @@ type StatusBarIOSEventDefinitions = { */ class StatusBarIOS extends NativeEventEmitter {} -module.exports = (new StatusBarIOS(NativeStatusBarManagerIOS): StatusBarIOS); +module.exports = (new StatusBarIOS( + // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior + // If you want to use the native module on other platforms, please remove this condition and test its behavior + Platform.OS !== 'ios' ? null : NativeStatusBarManagerIOS, +): StatusBarIOS); diff --git a/Libraries/Components/Switch/Switch.js b/Libraries/Components/Switch/Switch.js index 4135959bb43710..873120b84ab669 100644 --- a/Libraries/Components/Switch/Switch.js +++ b/Libraries/Components/Switch/Switch.js @@ -12,6 +12,7 @@ import Platform from '../../Utilities/Platform'; import * as React from 'react'; import StyleSheet from '../../StyleSheet/StyleSheet'; +import SwitchInjection from './SwitchInjection'; import AndroidSwitchNativeComponent, { Commands as AndroidSwitchCommands, @@ -210,25 +211,21 @@ class Switch extends React.Component { // This is necessary in case native updates the switch and JS decides // that the update should be ignored and we should stick with the value // that we have in JS. - const nativeProps = {}; const value = this.props.value === true; - - if (this._lastNativeValue !== value) { - nativeProps.value = value; - } + const nativeValue = this._lastNativeValue !== value ? value : null; if ( - Object.keys(nativeProps).length > 0 && + nativeValue != null && this._nativeSwitchRef && this._nativeSwitchRef.setNativeProps ) { if (Platform.OS === 'android') { AndroidSwitchCommands.setNativeValue( this._nativeSwitchRef, - nativeProps.value, + nativeValue, ); } else { - SwitchCommands.setValue(this._nativeSwitchRef, nativeProps.value); + SwitchCommands.setValue(this._nativeSwitchRef, nativeValue); } } } @@ -258,4 +255,5 @@ class Switch extends React.Component { const returnsFalse = () => false; const returnsTrue = () => true; -module.exports = Switch; +module.exports = (SwitchInjection.unstable_Switch ?? + Switch: React.AbstractComponent>); diff --git a/Libraries/Components/Switch/SwitchInjection.js b/Libraries/Components/Switch/SwitchInjection.js new file mode 100644 index 00000000000000..9b3130bfdb3a5c --- /dev/null +++ b/Libraries/Components/Switch/SwitchInjection.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +import typeof Switch from './Switch'; + +export default { + unstable_Switch: (null: ?Switch), +}; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 39967a67111649..5bad7e6cf849cc 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -1083,6 +1083,13 @@ function InternalTextInput(props: Props): React.Node { ], ); + // Hide caret during test runs due to a flashing caret + // makes screenshot tests flakey + let caretHidden = props.caretHidden; + if (Platform.isTesting) { + caretHidden = true; + } + // TextInput handles onBlur and onFocus events // so omitting onBlur and onFocus pressability handlers here. const {onBlur, onFocus, ...eventHandlers} = usePressability(config) || {}; @@ -1105,6 +1112,7 @@ function InternalTextInput(props: Props): React.Node { {...eventHandlers} accessible={accessible} blurOnSubmit={blurOnSubmit} + caretHidden={caretHidden} dataDetectorTypes={props.dataDetectorTypes} focusable={focusable} mostRecentEventCount={mostRecentEventCount} @@ -1134,8 +1142,14 @@ function InternalTextInput(props: Props): React.Node { } textInput = ( - /* $FlowFixMe the types for AndroidTextInput don't match up exactly with - the props for TextInput. This will need to get fixed */ + /* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match up + * exactly with the props for TextInput. This will need to get fixed */ + /* $FlowFixMe[incompatible-type] the types for AndroidTextInput don't + * match up exactly with the props for TextInput. This will need to get + * fixed */ + /* $FlowFixMe[incompatible-type-arg] the types for AndroidTextInput don't + * match up exactly with the props for TextInput. This will need to get + * fixed */ { _createPressabilityConfig(): PressabilityConfig { return { cancelable: !this.props.rejectResponderTermination, - disabled: this.props.disabled, + disabled: + this.props.disabled != null + ? this.props.disabled + : this.props.accessibilityState?.disabled, hitSlop: this.props.hitSlop, delayLongPress: this.props.delayLongPress, delayPressIn: this.props.delayPressIn, @@ -283,13 +286,21 @@ class TouchableHighlight extends React.Component { ...eventHandlersWithoutBlurAndFocus } = this.state.pressability.getEventHandlers(); + const accessibilityState = + this.props.disabled != null + ? { + ...this.props.accessibilityState, + disabled: this.props.disabled, + } + : this.props.accessibilityState; + return ( { } } -module.exports = (React.forwardRef((props, hostRef) => ( +const Touchable = (React.forwardRef((props, hostRef) => ( )): React.AbstractComponent< $ReadOnly<$Diff|}>>, React.ElementRef, >); + +Touchable.displayName = 'TouchableHighlight'; + +module.exports = Touchable; diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.js b/Libraries/Components/Touchable/TouchableNativeFeedback.js index 739ea4c15a74f2..8c1ad218fb4809 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.js @@ -168,7 +168,10 @@ class TouchableNativeFeedback extends React.Component { _createPressabilityConfig(): PressabilityConfig { return { cancelable: !this.props.rejectResponderTermination, - disabled: this.props.disabled, + disabled: + this.props.disabled != null + ? this.props.disabled + : this.props.accessibilityState?.disabled, hitSlop: this.props.hitSlop, delayLongPress: this.props.delayLongPress, delayPressIn: this.props.delayPressIn, @@ -255,6 +258,14 @@ class TouchableNativeFeedback extends React.Component { ...eventHandlersWithoutBlurAndFocus } = this.state.pressability.getEventHandlers(); + const accessibilityState = + this.props.disabled != null + ? { + ...this.props.accessibilityState, + disabled: this.props.disabled, + } + : this.props.accessibilityState; + return React.cloneElement( element, { @@ -269,7 +280,7 @@ class TouchableNativeFeedback extends React.Component { accessibilityHint: this.props.accessibilityHint, accessibilityLabel: this.props.accessibilityLabel, accessibilityRole: this.props.accessibilityRole, - accessibilityState: this.props.accessibilityState, + accessibilityState: accessibilityState, accessibilityActions: this.props.accessibilityActions, onAccessibilityAction: this.props.onAccessibilityAction, accessibilityValue: this.props.accessibilityValue, @@ -313,4 +324,6 @@ const getBackgroundProp = : {nativeBackgroundAndroid: background} : (background, useForeground) => null; +TouchableNativeFeedback.displayName = 'TouchableNativeFeedback'; + module.exports = TouchableNativeFeedback; diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 7a17eef9a2fdbe..6ae8cd850f7824 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -265,6 +265,10 @@ class TouchableOpacity extends React.Component { } } -module.exports = (React.forwardRef((props, ref) => ( +const Touchable = (React.forwardRef((props, ref) => ( )): React.AbstractComponent>); + +Touchable.displayName = 'TouchableOpacity'; + +module.exports = Touchable; diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 0a9ec5518310f9..0b2c305b232dc4 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -76,7 +76,6 @@ const PASSTHROUGH_PROPS = [ 'accessibilityLabel', 'accessibilityLiveRegion', 'accessibilityRole', - 'accessibilityState', 'accessibilityValue', 'accessibilityViewIsModal', 'hitSlop', @@ -116,6 +115,13 @@ class TouchableWithoutFeedback extends React.Component { const elementProps: {[string]: mixed, ...} = { ...eventHandlersWithoutBlurAndFocus, accessible: this.props.accessible !== false, + accessibilityState: + this.props.disabled != null + ? { + ...this.props.accessibilityState, + disabled: this.props.disabled, + } + : this.props.accessibilityState, focusable: this.props.focusable !== false && this.props.onPress !== undefined, }; @@ -140,7 +146,10 @@ class TouchableWithoutFeedback extends React.Component { function createPressabilityConfig(props: Props): PressabilityConfig { return { cancelable: !props.rejectResponderTermination, - disabled: props.disabled, + disabled: + props.disabled !== null + ? props.disabled + : props.accessibilityState?.disabled, hitSlop: props.hitSlop, delayLongPress: props.delayLongPress, delayPressIn: props.delayPressIn, @@ -157,4 +166,6 @@ function createPressabilityConfig(props: Props): PressabilityConfig { }; } +TouchableWithoutFeedback.displayName = 'TouchableWithoutFeedback'; + module.exports = TouchableWithoutFeedback; diff --git a/Libraries/Components/Touchable/__tests__/TouchableHighlight-test.js b/Libraries/Components/Touchable/__tests__/TouchableHighlight-test.js index 453f7407a2d12e..f25f4150dedf31 100644 --- a/Libraries/Components/Touchable/__tests__/TouchableHighlight-test.js +++ b/Libraries/Components/Touchable/__tests__/TouchableHighlight-test.js @@ -10,14 +10,16 @@ 'use strict'; -const React = require('react'); -const ReactTestRenderer = require('react-test-renderer'); -const Text = require('../../../Text/Text'); -const TouchableHighlight = require('../TouchableHighlight'); +import * as React from 'react'; +import Text from '../../../Text/Text'; +import View from '../../View/View'; +import TouchableHighlight from '../TouchableHighlight'; + +const render = require('../../../../jest/renderer'); describe('TouchableHighlight', () => { it('renders correctly', () => { - const instance = ReactTestRenderer.create( + const instance = render.create( Touchable , @@ -25,4 +27,64 @@ describe('TouchableHighlight', () => { expect(instance.toJSON()).toMatchSnapshot(); }); + + it('has displayName', () => { + expect(TouchableHighlight.displayName).toEqual('TouchableHighlight'); + }); +}); + +describe('TouchableHighlight with disabled state', () => { + it('should be disabled when disabled is true', () => { + expect( + render.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should be disabled when disabled is true and accessibilityState is empty', () => { + expect( + render.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should keep accessibilityState when disabled is true', () => { + expect( + render.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should overwrite accessibilityState with value of disabled prop', () => { + expect( + render.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should disable button when accessibilityState is disabled', () => { + expect( + render.create( + + + , + ), + ).toMatchSnapshot(); + }); }); diff --git a/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js b/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js new file mode 100644 index 00000000000000..237b1a22745cb9 --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js @@ -0,0 +1,115 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @emails oncall+react_native + */ + +'use strict'; + +import * as React from 'react'; +import ReactTestRenderer from 'react-test-renderer'; +import Text from '../../../Text/Text'; +import TouchableNativeFeedback from '../TouchableNativeFeedback'; +import View from '../../View/View'; + +const render = require('../../../../jest/renderer'); + +describe('TouchableWithoutFeedback', () => { + it('renders correctly', () => { + const instance = render.create( + + Touchable + , + ); + + expect(instance.toJSON()).toMatchSnapshot(); + }); + + it('has displayName', () => { + expect(TouchableNativeFeedback.displayName).toEqual( + 'TouchableNativeFeedback', + ); + }); +}); + +describe('', () => { + it('should render as expected', () => { + const instance = ReactTestRenderer.create( + + + , + ); + + expect(instance.toJSON()).toMatchSnapshot(); + }); +}); + +describe('', () => { + it('should be disabled when disabled is true', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); + +describe('', () => { + it('should be disabled when disabled is true and accessibilityState is empty', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); + +describe('', () => { + it('should keep accessibilityState when disabled is true', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); + +describe('', () => { + it('should overwrite accessibilityState with value of disabled prop', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); + +describe('', () => { + it('should overwrite accessibilityState with value of disabled prop', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); diff --git a/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js b/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js new file mode 100644 index 00000000000000..77a0575b7e49f0 --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @emails oncall+react_native + */ + +'use strict'; + +const React = require('react'); +const Text = require('../../../Text/Text'); +const TouchableOpacity = require('../TouchableOpacity'); + +const render = require('../../../../jest/renderer'); + +describe('TouchableOpacity', () => { + it('renders correctly', () => { + const instance = render.create( + + Touchable + , + ); + + expect(instance.toJSON()).toMatchSnapshot(); + }); + + it('has displayName', () => { + expect(TouchableOpacity.displayName).toEqual('TouchableOpacity'); + }); +}); diff --git a/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js b/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js new file mode 100644 index 00000000000000..0648d372d3883d --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @emails oncall+react_native + */ + +'use strict'; + +import * as React from 'react'; +import ReactTestRenderer from 'react-test-renderer'; +import Text from '../../../Text/Text'; +import View from '../../View/View'; +import TouchableWithoutFeedback from '../TouchableWithoutFeedback'; + +describe('TouchableWithoutFeedback', () => { + it('renders correctly', () => { + const instance = ReactTestRenderer.create( + + Touchable + , + ); + + expect(instance.toJSON()).toMatchSnapshot(); + }); + + it('has displayName', () => { + expect(TouchableWithoutFeedback.displayName).toEqual( + 'TouchableWithoutFeedback', + ); + }); +}); + +describe('TouchableWithoutFeedback with disabled state', () => { + it('should be disabled when disabled is true', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should be disabled when disabled is true and accessibilityState is empty', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should keep accessibilityState when disabled is true', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should overwrite accessibilityState with value of disabled prop', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); + + it('should disable button when accessibilityState is disabled', () => { + expect( + ReactTestRenderer.create( + + + , + ), + ).toMatchSnapshot(); + }); +}); diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap index 8defbd09d1f4c5..70eaabe704a1f0 100644 --- a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap @@ -18,3 +18,109 @@ exports[`TouchableHighlight renders correctly 1`] = ` `; + +exports[`TouchableHighlight with disabled state should be disabled when disabled is true 1`] = ` + + + +`; + +exports[`TouchableHighlight with disabled state should be disabled when disabled is true and accessibilityState is empty 1`] = ` + + + +`; + +exports[`TouchableHighlight with disabled state should disable button when accessibilityState is disabled 1`] = ` + + + +`; + +exports[`TouchableHighlight with disabled state should keep accessibilityState when disabled is true 1`] = ` + + + +`; + +exports[`TouchableHighlight with disabled state should overwrite accessibilityState with value of disabled prop 1`] = ` + + + +`; diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap new file mode 100644 index 00000000000000..64b098803b11c3 --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap @@ -0,0 +1,127 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TouchableWithoutFeedback renders correctly 1`] = ` + + Touchable + +`; + +exports[` should render as expected 1`] = ` + +`; + +exports[` should be disabled when disabled is true 1`] = ` + +`; + +exports[` should be disabled when disabled is true and accessibilityState is empty 1`] = ` + +`; + +exports[` should keep accessibilityState when disabled is true 1`] = ` + +`; + +exports[` should overwrite accessibilityState with value of disabled prop 1`] = ` + +`; + +exports[` should overwrite accessibilityState with value of disabled prop 1`] = ` + +`; diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap new file mode 100644 index 00000000000000..c1a2ee35013e24 --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TouchableOpacity renders correctly 1`] = ` + + + Touchable + + +`; diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap new file mode 100644 index 00000000000000..545e77e54e3bd9 --- /dev/null +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap @@ -0,0 +1,113 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TouchableWithoutFeedback renders correctly 1`] = ` + + Touchable + +`; + +exports[`TouchableWithoutFeedback with disabled state should be disabled when disabled is true 1`] = ` + +`; + +exports[`TouchableWithoutFeedback with disabled state should be disabled when disabled is true and accessibilityState is empty 1`] = ` + +`; + +exports[`TouchableWithoutFeedback with disabled state should disable button when accessibilityState is disabled 1`] = ` + +`; + +exports[`TouchableWithoutFeedback with disabled state should keep accessibilityState when disabled is true 1`] = ` + +`; + +exports[`TouchableWithoutFeedback with disabled state should overwrite accessibilityState with value of disabled prop 1`] = ` + +`; diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 87545071913370..19bcf46922ad05 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -10,9 +10,9 @@ import type {ViewProps} from './ViewPropTypes'; -const React = require('react'); import ViewNativeComponent from './ViewNativeComponent'; -const TextAncestor = require('../../Text/TextAncestor'); +import TextAncestor from '../../Text/TextAncestor'; +import * as React from 'react'; export type Props = ViewProps; diff --git a/Libraries/Components/__tests__/Button-test.js b/Libraries/Components/__tests__/Button-test.js new file mode 100644 index 00000000000000..f81f9bc63d229e --- /dev/null +++ b/Libraries/Components/__tests__/Button-test.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from 'react'; +import ReactTestRenderer from 'react-test-renderer'; +import Button from '../Button'; + +describe('