diff --git a/.ado/windows-vs-pr.yml b/.ado/windows-vs-pr.yml
index b425c0dffbd..2ac41dde3bd 100644
--- a/.ado/windows-vs-pr.yml
+++ b/.ado/windows-vs-pr.yml
@@ -11,7 +11,7 @@ variables:
- template: variables/msbuild.yml
- template: variables/vs2019.yml
- name: reactNativeVersion
- value: 0.61.5
+ value: 0.62.2
jobs:
- job: Setup
diff --git a/change/@office-iss-react-native-win32-2020-04-14-05-18-46-62.json b/change/@office-iss-react-native-win32-2020-04-14-05-18-46-62.json
new file mode 100644
index 00000000000..fbb8b835e90
--- /dev/null
+++ b/change/@office-iss-react-native-win32-2020-04-14-05-18-46-62.json
@@ -0,0 +1,8 @@
+{
+ "type": "prerelease",
+ "comment": "Upgrade to React Native 0.62",
+ "packageName": "@office-iss/react-native-win32",
+ "email": "ngerlem@microsoft.com",
+ "dependentChangeType": "patch",
+ "date": "2020-04-14T12:18:41.888Z"
+}
\ No newline at end of file
diff --git a/change/react-native-windows-2020-04-14-05-18-46-62.json b/change/react-native-windows-2020-04-14-05-18-46-62.json
new file mode 100644
index 00000000000..950d15458cb
--- /dev/null
+++ b/change/react-native-windows-2020-04-14-05-18-46-62.json
@@ -0,0 +1,8 @@
+{
+ "type": "prerelease",
+ "comment": "Upgrade to React Native 0.62",
+ "packageName": "react-native-windows",
+ "email": "ngerlem@microsoft.com",
+ "dependentChangeType": "patch",
+ "date": "2020-04-14T12:18:46.280Z"
+}
\ No newline at end of file
diff --git a/docs/api/react-native-windows.apptheme.md b/docs/api/react-native-windows.apptheme.md
index 60807d1aed2..43ffc937963 100644
--- a/docs/api/react-native-windows.apptheme.md
+++ b/docs/api/react-native-windows.apptheme.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-AppTheme: AppThemeModule
+AppTheme: AppThemeModule | MissingNativeAppThemeShim
```
diff --git a/packages/E2ETest/app/TreeDumpControl.ts b/packages/E2ETest/app/TreeDumpControl.ts
index 9de23d1c93f..8a910e9d0e8 100644
--- a/packages/E2ETest/app/TreeDumpControl.ts
+++ b/packages/E2ETest/app/TreeDumpControl.ts
@@ -4,5 +4,11 @@
* Licensed under the MIT License.
*/
-import { requireNativeComponent } from 'react-native';
-export const TreeDumpControl = requireNativeComponent('TreeDumpControl');
+import { requireNativeComponent, ViewStyle } from 'react-native';
+export const TreeDumpControl = requireNativeComponent<{
+ style: ViewStyle;
+ dumpID: string;
+ uiaID: string;
+ testID: string;
+ additionalProperties?: string[];
+}>('TreeDumpControl');
diff --git a/packages/E2ETest/package.json b/packages/E2ETest/package.json
index 1c3c5ba1cfa..515a3b67af1 100644
--- a/packages/E2ETest/package.json
+++ b/packages/E2ETest/package.json
@@ -23,7 +23,7 @@
},
"dependencies": {
"react": "16.9.0",
- "react-native": "0.61.5",
+ "react-native": "0.62.2",
"react-native-windows": "0.0.0-master.34",
"rnpm-plugin-windows": "^0.6.1"
},
@@ -33,7 +33,7 @@
"@types/jasmine": "2.8.7",
"@types/node": "^10.14.8",
"@types/react": "16.9.0",
- "@types/react-native": "~0.61.5",
+ "@types/react-native": "^0.62.2",
"@wdio/appium-service": "5.12.1",
"@wdio/cli": "5.12.1",
"@wdio/dot-reporter": "5.12.1",
diff --git a/packages/microsoft-reactnative-sampleapps/package.json b/packages/microsoft-reactnative-sampleapps/package.json
index 83446add086..f69c651db53 100644
--- a/packages/microsoft-reactnative-sampleapps/package.json
+++ b/packages/microsoft-reactnative-sampleapps/package.json
@@ -15,7 +15,7 @@
},
"dependencies": {
"react": "16.9.0",
- "react-native": "0.61.5",
+ "react-native": "0.62.2",
"react-native-windows": "0.0.0-master.34",
"rnpm-plugin-windows": "^0.6.1"
},
@@ -23,7 +23,7 @@
"@babel/core": "^7.8.4",
"@babel/runtime": "^7.8.4",
"@types/react": "16.9.0",
- "@types/react-native": "~0.61.5",
+ "@types/react-native": "^0.62.2",
"just-scripts": "^0.36.1",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.9.0"
diff --git a/packages/override-tools/src/Cli.ts b/packages/override-tools/src/Cli.ts
index 982c4aac7ce..1462c1ec85f 100644
--- a/packages/override-tools/src/Cli.ts
+++ b/packages/override-tools/src/Cli.ts
@@ -188,8 +188,6 @@ async function addOverride(overridePath: string) {
* Remove an override from the manifest
*/
async function removeOverride(overridePath: string) {
- await checkFileExists('override', overridePath);
-
const manifestPath = await FileSearch.findManifest(overridePath);
const manifestDir = path.dirname(manifestPath);
@@ -330,7 +328,7 @@ function printValidationErrors(errors: Array) {
if (filesMissing.length > 0) {
const errorMessage =
- "Found override files that aren't listed in the manifest. Overrides can be added to the manifest by using 'yarn override add ':";
+ "Found override files that aren't listed in the manifest. Overrides can be added to the manifest by using 'yarn override add ' (where override is package relative):";
console.error(chalk.red(errorMessage));
filesMissing.forEach(err => console.error(` - ${err.file}`));
console.error();
@@ -338,7 +336,7 @@ function printValidationErrors(errors: Array) {
if (overridesMissing.length > 0) {
const errorMessage =
- "Found overrides in the manifest that don't exist on disk. Remove existing overrides using 'yarn override remove ':";
+ "Found overrides in the manifest that don't exist on disk. Remove existing overrides using 'yarn override remove ' (where override is package relative):";
console.error(chalk.red(errorMessage));
overridesMissing.forEach(err => console.error(` - ${err.file}`));
console.error();
@@ -346,7 +344,7 @@ function printValidationErrors(errors: Array) {
if (baseFilesNotFound.length > 0) {
const errorMessage =
- "Found overrides whose original files do not exist. Remove existing overrides using 'yarn override remove ':";
+ "Found overrides whose original files do not exist. Remove existing overrides using 'yarn override remove ' (where override is package relative):";
console.error(chalk.red(errorMessage));
baseFilesNotFound.forEach(err => console.error(` - ${err.file}`));
console.error();
@@ -354,7 +352,7 @@ function printValidationErrors(errors: Array) {
if (outOfDateFiles.length > 0) {
const errorMessage =
- "Found overrides whose original files have changed. Upgrade overrides using 'yarn override auto-upgrade ' and 'yarn override manual-upgrade ':";
+ "Found overrides whose original files have changed. Upgrade overrides using 'yarn override auto-upgrade ' and 'yarn override manual-upgrade ' (where manifest is package relative):";
console.error(chalk.red(errorMessage));
outOfDateFiles.forEach(err => console.error(` - ${err.file}`));
console.error();
diff --git a/packages/override-tools/src/Manifest.ts b/packages/override-tools/src/Manifest.ts
index c48a7848ab0..78e872cf3d3 100644
--- a/packages/override-tools/src/Manifest.ts
+++ b/packages/override-tools/src/Manifest.ts
@@ -81,7 +81,7 @@ export default class Manifest {
const baseFile = override.baseFile;
const baseContent = await this.reactRepo.getFileContents(baseFile);
if (baseContent === null) {
- errors.push({type: 'baseFileNotFound', file: override.baseFile});
+ errors.push({type: 'baseFileNotFound', file: override.file});
return;
}
diff --git a/packages/override-tools/src/test/Manifest.test.ts b/packages/override-tools/src/test/Manifest.test.ts
index 80ae99c30c8..2900c3cef09 100644
--- a/packages/override-tools/src/test/Manifest.test.ts
+++ b/packages/override-tools/src/test/Manifest.test.ts
@@ -156,7 +156,7 @@ test('BaseFileNotFound', async () => {
const expectedError: ValidationError = {
type: 'baseFileNotFound',
- file: 'foo\\bar.js',
+ file: 'aaa\\aaa.windows.js',
};
const testManifest = new Manifest(ourManifestData, ovrRepo, reactRepo);
diff --git a/packages/playground/package.json b/packages/playground/package.json
index 76cd07e2faa..30b34441ba6 100644
--- a/packages/playground/package.json
+++ b/packages/playground/package.json
@@ -10,14 +10,14 @@
},
"dependencies": {
"react": "16.9.0",
- "react-native": "0.61.5",
+ "react-native": "0.62.2",
"react-native-windows": "0.0.0-master.34"
},
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/runtime": "^7.8.4",
"@types/react": "16.9.0",
- "@types/react-native": "~0.61.5",
+ "@types/react-native": "^0.62.2",
"just-scripts": "^0.36.1",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.9.0",
diff --git a/packages/react-native-win32/.flowconfig b/packages/react-native-win32/.flowconfig
index ff810c24cac..e9e76df4a37 100644
--- a/packages/react-native-win32/.flowconfig
+++ b/packages/react-native-win32/.flowconfig
@@ -10,23 +10,30 @@
; initRNLibraries build step
/Libraries/Alert/Alert.js
/Libraries/Animated/src/nodes/AnimatedInterpolation.js
-/Libraries/Color/normalizeColor.js
-/Libraries/Color/normalizeColorObject.js
/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js
/Libraries/Components/Picker/Picker.js
/Libraries/Components/SafeAreaView/SafeAreaView.js
-/Libraries/Components/StatusBar/StatusBar.js
+/Libraries/Components/StatusBar/NativeStatusBarManagerAndroid.js
/Libraries/Components/TextInput/TextInput.js
/Libraries/Components/TextInput/TextInputState.js
+/Libraries/Components/Touchable/TouchableBounce.js
+/Libraries/Components/Touchable/TouchableHighlight.js
/Libraries/Components/Touchable/TouchableNativeFeedback.js
+/Libraries/Components/Touchable/TouchableOpacity.js
+/Libraries/Components/Touchable/TouchableWithoutFeedback.js
/Libraries/Components/View/ReactNativeViewAttributes.js
/Libraries/Components/View/ReactNativeViewViewConfig.js
/Libraries/Core/setUpDeveloperTools.js
+/Libraries/Core/setUpReactDevTools.js
/Libraries/Image/Image.js
+/Libraries/Image/ImageViewNativeComponent.js
/Libraries/Inspector/Inspector.js
/Libraries/Inspector/InspectorOverlay.js
/Libraries/Network/RCTNetworking.js
+/Libraries/Pressability/PressabilityDebug.js
/Libraries/ReactNative/getNativeComponentAttributes.js
+/Libraries/StyleSheet/normalizeColor.js
+/Libraries/StyleSheet/normalizeColorObject.js
/Libraries/StyleSheet/processColor.js
/Libraries/StyleSheet/processColorArray.js
/Libraries/StyleSheet/StyleSheet.js
@@ -39,10 +46,6 @@
/RNTester/js/components/ListExampleShared.js
/RNTester/js/components/RNTesterExampleFilter.js
-; These examples currently uses mac dynamic colors
-/RNTester/js/ActivityIndicatorExample.js
-/RNTester/js/DarkModeExample.js
-
; Schema files are used for turbo-module code generation, but are not bundled.
; We don't yet have the react-native-codegen package they rely on, so supress
; checks for now.
@@ -72,11 +75,6 @@
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
-; Ignore duplicate module providers
-; For RN Apps installed via npm, "Libraries" folder is inside
-; "node_modules/react-native" but in the source repo it is in the root
-.*/Libraries/react-native/React.js
-
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
.*/node_modules/warning/.*
@@ -102,7 +100,7 @@
../../node_modules/
[libs]
-Libraries/react-native/react-native-interface.js
+interface.js
flow/
[options]
@@ -115,34 +113,10 @@ module.file_ext=.js
module.file_ext=.json
module.file_ext=.win32.js
-module.system=haste
-module.system.haste.use_name_reducers=true
-# keep the following in sync with server/haste/hasteImpl.js
-# get basename
-module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
-# strip .js or .js.flow suffix
-module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
-# strip .ios suffix
-module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
-module.system.haste.name_reducers='^\(.*\)\.macos$' -> '\1'
-module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
-module.system.haste.name_reducers='^\(.*\)\.win32$' -> '\1'
-module.system.haste.name_reducers='^\(.*\)\.windesktop$' -> '\1'
-module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
-module.system.haste.paths.blacklist=/src/.*
-module.system.haste.paths.blacklist=.*/__tests__/.*
-module.system.haste.paths.blacklist=.*/__mocks__/.*
-module.system.haste.paths.whitelist=/Libraries/.*
-module.system.haste.paths.whitelist=/RNTester/.*
-module.system.haste.paths.whitelist=/IntegrationTests/.*
-module.system.haste.paths.blacklist=/Libraries/react-native/react-native-implementation.js
-module.system.haste.paths.blacklist=/Libraries/Animated/src/polyfills/.*
-module.system.haste.paths.blacklist=/Libraries/Image/resolveAssetSource.js
-
munge_underscores=true
-module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
-module.name_mapper='react-native$' -> '/Libraries/react-native/react-native-implementation.js'
+module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/Libraries/Image/RelativeImageStub'
+module.name_mapper='react-native$' -> '/index.js'
module.name_mapper='react-native/\(.*\)' -> '/\1'
suppress_type=$FlowIssue
@@ -153,8 +127,14 @@ suppress_type=$FlowFixMeEmpty
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
+experimental.well_formed_exports=true
+experimental.types_first=true
+experimental.abstract_locations=true
+
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
@@ -178,4 +158,4 @@ untyped-import
untyped-type-import
[version]
-^0.105.0
\ No newline at end of file
+^0.113.0
\ No newline at end of file
diff --git a/packages/react-native-win32/.gitignore b/packages/react-native-win32/.gitignore
index a16c2e663b4..08468799856 100644
--- a/packages/react-native-win32/.gitignore
+++ b/packages/react-native-win32/.gitignore
@@ -1,7 +1,7 @@
/dist
/flow
-/flow-typed
/index.*
+/interface.*
/IntegrationTests
/jest
/lib
@@ -9,4 +9,4 @@
/RNTester
/RNTester.*
/temp
-/WorkingHeaders
\ No newline at end of file
+/typings-index.*
diff --git a/packages/react-native-win32/api-extractor.json b/packages/react-native-win32/api-extractor.json
index 98a562b21a7..2c856a25261 100644
--- a/packages/react-native-win32/api-extractor.json
+++ b/packages/react-native-win32/api-extractor.json
@@ -1,6 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
- "mainEntryPointFilePath": "./Libraries/react-native/typings-main.d.ts",
+ "mainEntryPointFilePath": "./typings-index.d.ts",
"docModel": {
"enabled": true
},
diff --git a/packages/react-native-win32/etc/react-native-win32.api.md b/packages/react-native-win32/etc/react-native-win32.api.md
index 5a1a3a6eb99..656bdd6c40c 100644
--- a/packages/react-native-win32/etc/react-native-win32.api.md
+++ b/packages/react-native-win32/etc/react-native-win32.api.md
@@ -51,7 +51,7 @@ export type BasePropsWin32 = {
accessibilityActions?: ReadonlyArray;
};
-// Warning: (ae-forgotten-export) The symbol "IButtonWin32State" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "IButtonWin32State" needs to be exported by the entry point typings-index.d.ts
//
// @public
export class ButtonWin32 extends React_2.Component {
@@ -89,7 +89,7 @@ export interface IButtonWin32Props extends RN.ButtonProps {
style?: RN.StyleProp;
}
-// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-index.d.ts
//
// @public (undocumented)
export interface IButtonWin32Style extends Omit_4 {
@@ -106,7 +106,7 @@ export interface IDimensions {
width: number;
}
-// Warning: (ae-forgotten-export) The symbol "PartiallyRequired" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "PartiallyRequired" needs to be exported by the entry point typings-index.d.ts
//
// @public (undocumented)
export type IHandledKeyboardEvent = PartiallyRequired;
@@ -152,7 +152,7 @@ export interface IPersonaCoinProps extends ViewProps {
presence: PersonaCoinPresence;
// (undocumented)
size: PersonaCoinSize;
- // Warning: (ae-forgotten-export) The symbol "IImageSourceProps" needs to be exported by the entry point typings-main.d.ts
+ // Warning: (ae-forgotten-export) The symbol "IImageSourceProps" needs to be exported by the entry point typings-index.d.ts
//
// (undocumented)
source?: string | IImageSourceProps;
@@ -220,7 +220,7 @@ export type IStateConditions = {
[P in IState]: boolean;
};
-// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-index.d.ts
//
// @public (undocumented)
export interface ITextWin32Props extends Omit_3, BasePropsWin32 {
@@ -302,7 +302,7 @@ export interface ITouchInfo {
touchActive: boolean;
}
-// Warning: (ae-forgotten-export) The symbol "ISignalTransitions" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "ISignalTransitions" needs to be exported by the entry point typings-index.d.ts
//
// @public
export type ITransitions = {
@@ -315,7 +315,7 @@ export interface IViewWin32 {
focus: () => void;
}
-// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "Omit" needs to be exported by the entry point typings-index.d.ts
//
// @public
export interface IViewWin32Props extends Omit_2, BasePropsWin32 {
@@ -425,7 +425,7 @@ export class TextWin32 extends React_2.Component {
// @public (undocumented)
export type TextWin32OmitTypes = RN.TextPropsAndroid & RN.TextPropsIOS & RN.AccessibilityPropsAndroid & Omit_3 & OmittedAccessibilityPropsWin32;
-// Warning: (ae-forgotten-export) The symbol "IInternalTouchableWin32State" needs to be exported by the entry point typings-main.d.ts
+// Warning: (ae-forgotten-export) The symbol "IInternalTouchableWin32State" needs to be exported by the entry point typings-index.d.ts
//
// @public
export class TouchableWin32 extends React_2.Component {
diff --git a/packages/react-native-win32/just-task.js b/packages/react-native-win32/just-task.js
index e56e4b66486..ba25aa659cd 100644
--- a/packages/react-native-win32/just-task.js
+++ b/packages/react-native-win32/just-task.js
@@ -33,7 +33,7 @@ task('apiExtractorUpdate', apiExtractorUpdateTask());
task('apiDocumenter', () => {
require('child_process').execSync(
'npx @microsoft/api-documenter markdown -i temp -o docs/api',
- { stdio: 'inherit' },
+ {stdio: 'inherit'},
);
});
@@ -41,7 +41,7 @@ task('eslint', () => {
return eslintTask();
});
task('eslint:fix', () => {
- return eslintTask({ fix: true });
+ return eslintTask({fix: true});
});
task('copyFlowFiles', () => {
return copyTask(['src/**/*.js'], '.');
@@ -54,7 +54,7 @@ task('initRNLibraries', () => {
});
task('flow-check', () => {
- require('child_process').execSync('npx flow check', { stdio: 'inherit' });
+ require('child_process').execSync('npx flow check', {stdio: 'inherit'});
});
task('ts', () => {
@@ -70,8 +70,8 @@ task('ts', () => {
});
task('clean', () => {
return cleanTask(
- ['dist', 'flow', 'flow-typed', 'jest', 'Libraries', 'RNTester', 'lib'].map(
- p => path.join(process.cwd(), p),
+ ['dist', 'flow', 'jest', 'Libraries', 'RNTester'].map(p =>
+ path.join(process.cwd(), p),
),
);
});
diff --git a/packages/react-native-win32/package.json b/packages/react-native-win32/package.json
index 5e8ad7cfb8d..2149237bb86 100644
--- a/packages/react-native-win32/package.json
+++ b/packages/react-native-win32/package.json
@@ -3,8 +3,8 @@
"version": "0.0.0-master.3",
"description": "Implementation of react native on top of Office's Win32 platform.",
"license": "MIT",
- "main": "./Libraries/react-native/react-native-implementation.win32.js",
- "typings": "./Libraries/react-native/typings-main.d.ts",
+ "main": "./index.win32.js",
+ "typings": "./typings-index.d.ts",
"scripts": {
"build": "just-scripts build",
"bundle": "just-scripts prepareBundle && react-native bundle --platform win32 --entry-file RNTester.js --bundle-output dist/win32/dev/RNTester.bundle --assets-dest dist/win32/dev --sourcemap-output ./dist/win32/dev/sourcemap.js",
@@ -49,20 +49,20 @@
"@types/node": "^12.11.2",
"@types/prop-types": "15.5.1",
"@types/react": "16.9.0",
- "@types/react-native": "~0.61.5",
- "flow-bin": "^0.105.0",
+ "@types/react-native": "^0.62.2",
+ "flow-bin": "^0.113.0",
"jscodeshift": "^0.6.2",
"just-scripts": "^0.36.1",
"react": "16.9.0",
"react-native-windows-override-tools": "^0.0.1",
- "react-native": "0.61.5",
+ "react-native": "0.62.2",
"rimraf": "^3.0.0",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": "16.9.0",
"react-dom": "16.8.6",
- "react-native": "0.61.5"
+ "react-native": "0.62.2"
},
"beachball": {
"defaultNpmTag": "master",
diff --git a/packages/react-native-win32/src/Libraries/Alert/Alert.win32.js b/packages/react-native-win32/src/Libraries/Alert/Alert.win32.js
index 935e8956f96..6ab36927761 100644
--- a/packages/react-native-win32/src/Libraries/Alert/Alert.win32.js
+++ b/packages/react-native-win32/src/Libraries/Alert/Alert.win32.js
@@ -25,11 +25,13 @@ export type Buttons = Array<{
text?: string,
onPress?: ?Function,
style?: AlertButtonStyle,
+ ...
}>;
type Options = {
cancelable?: ?boolean,
onDismiss?: ?() => void,
+ ...
};
/**
diff --git a/packages/react-native-win32/src/Libraries/Animated/src/nodes/AnimatedInterpolation.win32.js b/packages/react-native-win32/src/Libraries/Animated/src/nodes/AnimatedInterpolation.win32.js
index daf3c0961af..b6c9c3eac4f 100644
--- a/packages/react-native-win32/src/Libraries/Animated/src/nodes/AnimatedInterpolation.win32.js
+++ b/packages/react-native-win32/src/Libraries/Animated/src/nodes/AnimatedInterpolation.win32.js
@@ -7,7 +7,9 @@
* @flow
* @format
*/
+
/* eslint no-bitwise: 0 */
+
'use strict';
const AnimatedNode = require('./AnimatedNode');
@@ -15,7 +17,7 @@ const AnimatedWithChildren = require('./AnimatedWithChildren');
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
const invariant = require('invariant');
-const normalizeColor = require('../../../Color/normalizeColor');
+const normalizeColor = require('../../../StyleSheet/normalizeColor');
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
@@ -30,6 +32,7 @@ export type InterpolationConfigType = {
extrapolate?: ExtrapolateType,
extrapolateLeft?: ExtrapolateType,
extrapolateRight?: ExtrapolateType,
+ ...
};
const linear = t => t;
diff --git a/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js b/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js
index c91a8d97049..aed6e3ff734 100644
--- a/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.win32.js
@@ -10,8 +10,6 @@
'use strict';
-import NativeAccessibilityInfo from './NativeAccessibilityInfo';
-
const RCTDeviceEventEmitter = require('../../EventEmitter/RCTDeviceEventEmitter');
// [Windows
@@ -20,6 +18,8 @@ const RCTDeviceEventEmitter = require('../../EventEmitter/RCTDeviceEventEmitter'
const SCREEN_READER_CHANGED_EVENT = 'screenReaderChanged';
// Windows]
+import NativeAccessibilityInfo from './NativeAccessibilityInfo';
+
const REDUCE_MOTION_EVENT = 'reduceMotionDidChange';
const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange';
@@ -27,6 +27,7 @@ type ChangeEventName = $Keys<{
change: string,
reduceMotionChanged: string,
screenReaderChanged: string,
+ ...
}>;
const _subscriptions = new Map();
@@ -97,7 +98,10 @@ const AccessibilityInfo = {
*
* Same as `isScreenReaderEnabled`
*/
- get fetch() {
+ get fetch(): () => Promise {
+ console.warn(
+ 'AccessibilityInfo.fetch is deprecated, call Accessibility.isScreenReaderEnabled instead',
+ );
return this.isScreenReaderEnabled;
},
diff --git a/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.win32.js b/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.win32.js
index 3b1fcbdae29..4ce7ce954ba 100644
--- a/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.win32.js
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @flow
+ * @flow strict-local
* @format
*/
diff --git a/packages/react-native-win32/src/Libraries/Components/MaskedView/MaskedViewIOS.win32.js b/packages/react-native-win32/src/Libraries/Components/MaskedView/MaskedViewIOS.win32.js
index f6d4cd7a409..c7a621c5054 100644
--- a/packages/react-native-win32/src/Libraries/Components/MaskedView/MaskedViewIOS.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/MaskedView/MaskedViewIOS.win32.js
@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
* @format
+ * @flow strict-local
*/
'use strict';
diff --git a/packages/react-native-win32/src/Libraries/Components/SafeAreaView/SafeAreaView.win32.js b/packages/react-native-win32/src/Libraries/Components/SafeAreaView/SafeAreaView.win32.js
index b748c6db52b..905864aa732 100644
--- a/packages/react-native-win32/src/Libraries/Components/SafeAreaView/SafeAreaView.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/SafeAreaView/SafeAreaView.win32.js
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @flow
+ * @flow strict-local
* @format
*/
@@ -12,7 +12,7 @@ const Platform = require('../../Utilities/Platform');
const React = require('react');
const View = require('../View/View');
-import type {NativeComponent} from '../../Renderer/shims/ReactNative';
+import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import type {ViewProps} from '../View/ViewPropTypes';
type Props = $ReadOnly<{|
@@ -20,7 +20,10 @@ type Props = $ReadOnly<{|
emulateUnlessSupported?: boolean,
|}>;
-let exported: Class> | Class>;
+let exported: React.AbstractComponent<
+ Props,
+ React.ElementRef>,
+>;
/**
* Renders nested content and automatically applies paddings reflect the portion
@@ -34,37 +37,27 @@ let exported: Class> | Class>;
// [Win32 - Added win32 to if
if (Platform.OS === 'android' || Platform.OS === 'win32') {
// Win32]
- const SafeAreaView = (
- props: Props,
- forwardedRef?: ?React.Ref,
- ) => {
- const {emulateUnlessSupported, ...localProps} = props;
- return ;
- };
-
- const SafeAreaViewRef = React.forwardRef(SafeAreaView);
- SafeAreaViewRef.displayName = 'SafeAreaView';
- exported = ((SafeAreaViewRef: any): Class>);
+ exported = React.forwardRef>>(
+ function SafeAreaView(props, forwardedRef) {
+ const {emulateUnlessSupported, ...localProps} = props;
+ return ;
+ },
+ );
} else {
const RCTSafeAreaViewNativeComponent = require('./RCTSafeAreaViewNativeComponent')
.default;
- const SafeAreaView = (
- props: Props,
- forwardedRef?: ?React.Ref,
- ) => {
- return (
-
- );
- };
-
- const SafeAreaViewRef = React.forwardRef(SafeAreaView);
- SafeAreaViewRef.displayName = 'SafeAreaView';
- exported = ((SafeAreaViewRef: any): Class>);
+ exported = React.forwardRef>>(
+ function SafeAreaView(props, forwardedRef) {
+ return (
+
+ );
+ },
+ );
}
module.exports = exported;
diff --git a/packages/react-native-win32/src/Libraries/Components/ScrollView/ScrollView.win32.js b/packages/react-native-win32/src/Libraries/Components/ScrollView/ScrollView.win32.js
new file mode 100644
index 00000000000..5f71d0ee231
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/ScrollView/ScrollView.win32.js
@@ -0,0 +1,1236 @@
+/**
+ * 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
+ */
+
+'use strict';
+
+const AnimatedImplementation = require('../../Animated/src/AnimatedImplementation');
+const Platform = require('../../Utilities/Platform');
+const React = require('react');
+const ReactNative = require('../../Renderer/shims/ReactNative');
+require('../../Renderer/shims/ReactNative'); // Force side effects to prevent T55744311
+const ScrollResponder = require('../ScrollResponder');
+const ScrollViewStickyHeader = require('./ScrollViewStickyHeader');
+const StyleSheet = require('../../StyleSheet/StyleSheet');
+const View = require('../View/View');
+
+const dismissKeyboard = require('../../Utilities/dismissKeyboard');
+const flattenStyle = require('../../StyleSheet/flattenStyle');
+const invariant = require('invariant');
+const processDecelerationRate = require('./processDecelerationRate');
+const requireNativeComponent = require('../../ReactNative/requireNativeComponent');
+const resolveAssetSource = require('../../Image/resolveAssetSource');
+const splitLayoutProps = require('../../StyleSheet/splitLayoutProps');
+
+import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType';
+import type {PointProp} from '../../StyleSheet/PointPropType';
+import type {ViewStyleProp} from '../../StyleSheet/StyleSheet';
+import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
+import type {
+ PressEvent,
+ ScrollEvent,
+ LayoutEvent,
+} from '../../Types/CoreEventTypes';
+import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
+import type {State as ScrollResponderState} from '../ScrollResponder';
+import type {ViewProps} from '../View/ViewPropTypes';
+import type {Props as ScrollViewStickyHeaderProps} from './ScrollViewStickyHeader';
+
+import ScrollViewNativeComponent from './ScrollViewNativeComponent';
+import ScrollContentViewNativeComponent from './ScrollContentViewNativeComponent';
+import AndroidHorizontalScrollViewNativeComponent from './AndroidHorizontalScrollViewNativeComponent';
+import AndroidHorizontalScrollContentViewNativeComponent from './AndroidHorizontalScrollContentViewNativeComponent';
+
+let AndroidScrollView;
+let AndroidHorizontalScrollContentView;
+let AndroidHorizontalScrollView;
+let RCTScrollView;
+let RCTScrollContentView;
+
+if (Platform.OS === 'android') {
+ AndroidScrollView = ScrollViewNativeComponent;
+ AndroidHorizontalScrollView = AndroidHorizontalScrollViewNativeComponent;
+ AndroidHorizontalScrollContentView = AndroidHorizontalScrollContentViewNativeComponent;
+} else { // [Windows] Remove iOS check
+ RCTScrollView = ScrollViewNativeComponent;
+ RCTScrollContentView = ScrollContentViewNativeComponent;
+}
+
+export type ScrollResponderType = {
+ // We'd like to do ...ScrollView here, however Flow doesn't seem
+ // to see the imperative methods of ScrollView that way. Workaround the
+ // issue by specifying them manually.
+ getScrollableNode: $PropertyType,
+ getInnerViewNode: $PropertyType,
+ getInnerViewRef: $PropertyType,
+ getNativeScrollRef: $PropertyType,
+ setNativeProps: $PropertyType,
+ scrollTo: $PropertyType,
+ flashScrollIndicators: $PropertyType,
+ ...typeof ScrollResponder.Mixin,
+ ...
+};
+
+type IOSProps = $ReadOnly<{|
+ /**
+ * Controls whether iOS should automatically adjust the content inset
+ * for scroll views that are placed behind a navigation bar or
+ * tab bar/ toolbar. The default value is true.
+ * @platform ios
+ */
+ automaticallyAdjustContentInsets?: ?boolean,
+ /**
+ * The amount by which the scroll view content is inset from the edges
+ * of the scroll view. Defaults to `{top: 0, left: 0, bottom: 0, right: 0}`.
+ * @platform ios
+ */
+ contentInset?: ?EdgeInsetsProp,
+ /**
+ * Used to manually set the starting scroll offset.
+ * The default value is `{x: 0, y: 0}`.
+ * @platform ios
+ */
+ contentOffset?: ?PointProp,
+ /**
+ * When true, the scroll view bounces when it reaches the end of the
+ * content if the content is larger then the scroll view along the axis of
+ * the scroll direction. When false, it disables all bouncing even if
+ * the `alwaysBounce*` props are true. The default value is true.
+ * @platform ios
+ */
+ bounces?: ?boolean,
+ /**
+ * By default, ScrollView has an active pan responder that hijacks panresponders
+ * deeper in the render tree in order to prevent accidental touches while scrolling.
+ * However, in certain occasions (such as when using snapToInterval) in a vertical scrollview
+ * You may want to disable this behavior in order to prevent the ScrollView from blocking touches
+ */
+ disableScrollViewPanResponder?: ?boolean,
+ /**
+ * When true, gestures can drive zoom past min/max and the zoom will animate
+ * to the min/max value at gesture end, otherwise the zoom will not exceed
+ * the limits.
+ * @platform ios
+ */
+ bouncesZoom?: ?boolean,
+ /**
+ * When true, the scroll view bounces horizontally when it reaches the end
+ * even if the content is smaller than the scroll view itself. The default
+ * value is true when `horizontal={true}` and false otherwise.
+ * @platform ios
+ */
+ alwaysBounceHorizontal?: ?boolean,
+ /**
+ * When true, the scroll view bounces vertically when it reaches the end
+ * even if the content is smaller than the scroll view itself. The default
+ * value is false when `horizontal={true}` and true otherwise.
+ * @platform ios
+ */
+ alwaysBounceVertical?: ?boolean,
+ /**
+ * When true, the scroll view automatically centers the content when the
+ * content is smaller than the scroll view bounds; when the content is
+ * larger than the scroll view, this property has no effect. The default
+ * value is false.
+ * @platform ios
+ */
+ centerContent?: ?boolean,
+ /**
+ * The style of the scroll indicators.
+ *
+ * - `'default'` (the default), same as `black`.
+ * - `'black'`, scroll indicator is black. This style is good against a light background.
+ * - `'white'`, scroll indicator is white. This style is good against a dark background.
+ *
+ * @platform ios
+ */
+ indicatorStyle?: ?('default' | 'black' | 'white'),
+ /**
+ * When true, the ScrollView will try to lock to only vertical or horizontal
+ * scrolling while dragging. The default value is false.
+ * @platform ios
+ */
+ directionalLockEnabled?: ?boolean,
+ /**
+ * When false, once tracking starts, won't try to drag if the touch moves.
+ * The default value is true.
+ * @platform ios
+ */
+ canCancelContentTouches?: ?boolean,
+ /**
+ * When set, the scroll view will adjust the scroll position so that the first child that is
+ * currently visible and at or beyond `minIndexForVisible` will not change position. This is
+ * useful for lists that are loading content in both directions, e.g. a chat thread, where new
+ * messages coming in might otherwise cause the scroll position to jump. A value of 0 is common,
+ * but other values such as 1 can be used to skip loading spinners or other content that should
+ * not maintain position.
+ *
+ * The optional `autoscrollToTopThreshold` can be used to make the content automatically scroll
+ * to the top after making the adjustment if the user was within the threshold of the top before
+ * the adjustment was made. This is also useful for chat-like applications where you want to see
+ * new messages scroll into place, but not if the user has scrolled up a ways and it would be
+ * disruptive to scroll a bunch.
+ *
+ * Caveat 1: Reordering elements in the scrollview with this enabled will probably cause
+ * jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now,
+ * don't re-order the content of any ScrollViews or Lists that use this feature.
+ *
+ * Caveat 2: This simply uses `contentOffset` and `frame.origin` in native code to compute
+ * visibility. Occlusion, transforms, and other complexity won't be taken into account as to
+ * whether content is "visible" or not.
+ *
+ * @platform ios
+ */
+ maintainVisibleContentPosition?: ?$ReadOnly<{|
+ minIndexForVisible: number,
+ autoscrollToTopThreshold?: ?number,
+ |}>,
+ /**
+ * The maximum allowed zoom scale. The default value is 1.0.
+ * @platform ios
+ */
+ maximumZoomScale?: ?number,
+ /**
+ * The minimum allowed zoom scale. The default value is 1.0.
+ * @platform ios
+ */
+ minimumZoomScale?: ?number,
+ /**
+ * When true, ScrollView allows use of pinch gestures to zoom in and out.
+ * The default value is true.
+ * @platform ios
+ */
+ pinchGestureEnabled?: ?boolean,
+ /**
+ * This controls how often the scroll event will be fired while scrolling
+ * (as a time interval in ms). A lower number yields better accuracy for code
+ * that is tracking the scroll position, but can lead to scroll performance
+ * problems due to the volume of information being send over the bridge.
+ *
+ * Values between 0 and 17ms indicate 60fps updates are needed and throttling
+ * will be disabled.
+ *
+ * If you do not need precise scroll position tracking, set this value higher
+ * to limit the information being sent across the bridge.
+ *
+ * The default value is zero, which results in the scroll event being sent only
+ * once each time the view is scrolled.
+ *
+ * @platform ios
+ */
+ scrollEventThrottle?: ?number,
+ /**
+ * The amount by which the scroll view indicators are inset from the edges
+ * of the scroll view. This should normally be set to the same value as
+ * the `contentInset`. Defaults to `{0, 0, 0, 0}`.
+ * @platform ios
+ */
+ scrollIndicatorInsets?: ?EdgeInsetsProp,
+ /**
+ * When true, the scroll view can be programmatically scrolled beyond its
+ * content size. The default value is false.
+ * @platform ios
+ */
+ scrollToOverflowEnabled?: ?boolean,
+ /**
+ * When true, the scroll view scrolls to top when the status bar is tapped.
+ * The default value is true.
+ * @platform ios
+ */
+ scrollsToTop?: ?boolean,
+ /**
+ * Fires when the scroll view scrolls to top after the status bar has been tapped
+ * @platform ios
+ */
+ onScrollToTop?: (event: ScrollEvent) => void,
+ /**
+ * When true, shows a horizontal scroll indicator.
+ * The default value is true.
+ */
+ showsHorizontalScrollIndicator?: ?boolean,
+ /**
+ * When `snapToInterval` is set, `snapToAlignment` will define the relationship
+ * of the snapping to the scroll view.
+ *
+ * - `'start'` (the default) will align the snap at the left (horizontal) or top (vertical)
+ * - `'center'` will align the snap in the center
+ * - `'end'` will align the snap at the right (horizontal) or bottom (vertical)
+ *
+ * @platform ios
+ */
+ snapToAlignment?: ?('start' | 'center' | 'end'),
+ /**
+ * The current scale of the scroll view content. The default value is 1.0.
+ * @platform ios
+ */
+ zoomScale?: ?number,
+ /**
+ * This property specifies how the safe area insets are used to modify the
+ * content area of the scroll view. The default value of this property is
+ * "never". Available on iOS 11 and later.
+ * @platform ios
+ */
+ contentInsetAdjustmentBehavior?: ?(
+ | 'automatic'
+ | 'scrollableAxes'
+ | 'never'
+ | 'always'
+ ),
+ /**
+ * When true, ScrollView will emit updateChildFrames data in scroll events,
+ * otherwise will not compute or emit child frame data. This only exists
+ * to support legacy issues, `onLayout` should be used instead to retrieve
+ * frame data.
+ * The default value is false.
+ * @platform ios
+ */
+ DEPRECATED_sendUpdatedChildFrames?: ?boolean,
+|}>;
+
+type AndroidProps = $ReadOnly<{|
+ /**
+ * Enables nested scrolling for Android API level 21+.
+ * Nested scrolling is supported by default on iOS
+ * @platform android
+ */
+ nestedScrollEnabled?: ?boolean,
+ /**
+ * Sometimes a scrollview takes up more space than its content fills. When this is
+ * the case, this prop will fill the rest of the scrollview with a color to avoid setting
+ * a background and creating unnecessary overdraw. This is an advanced optimization
+ * that is not needed in the general case.
+ * @platform android
+ */
+ endFillColor?: ?ColorValue,
+ /**
+ * Tag used to log scroll performance on this scroll view. Will force
+ * momentum events to be turned on (see sendMomentumEvents). This doesn't do
+ * anything out of the box and you need to implement a custom native
+ * FpsListener for it to be useful.
+ * @platform android
+ */
+ scrollPerfTag?: ?string,
+ /**
+ * Used to override default value of overScroll mode.
+ *
+ * Possible values:
+ *
+ * - `'auto'` - Default value, allow a user to over-scroll
+ * this view only if the content is large enough to meaningfully scroll.
+ * - `'always'` - Always allow a user to over-scroll this view.
+ * - `'never'` - Never allow a user to over-scroll this view.
+ *
+ * @platform android
+ */
+ overScrollMode?: ?('auto' | 'always' | 'never'),
+ /**
+ * Causes the scrollbars not to turn transparent when they are not in use.
+ * The default value is false.
+ *
+ * @platform android
+ */
+ persistentScrollbar?: ?boolean,
+ /**
+ * Fades out the edges of the the scroll content.
+ *
+ * If the value is greater than 0, the fading edges will be set accordingly
+ * to the current scroll direction and position,
+ * indicating if there is more content to show.
+ *
+ * The default value is 0.
+ *
+ * @platform android
+ */
+ 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, ...}>,
+>;
+
+export type Props = $ReadOnly<{|
+ ...ViewProps,
+ ...IOSProps,
+ ...AndroidProps,
+ ...VRProps,
+
+ /**
+ * These styles will be applied to the scroll view content container which
+ * wraps all of the child views. Example:
+ *
+ * ```
+ * return (
+ *
+ *
+ * );
+ * ...
+ * const styles = StyleSheet.create({
+ * contentContainer: {
+ * paddingVertical: 20
+ * }
+ * });
+ * ```
+ */
+ contentContainerStyle?: ?ViewStyleProp,
+ /**
+ * When true, the scroll view stops on the next index (in relation to scroll
+ * position at release) regardless of how fast the gesture is. This can be
+ * used for horizontal pagination when the page is less than the width of
+ * the ScrollView. The default value is false.
+ */
+ disableIntervalMomentum?: ?boolean,
+ /**
+ * A floating-point number that determines how quickly the scroll view
+ * decelerates after the user lifts their finger. You may also use string
+ * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
+ * for `UIScrollViewDecelerationRateNormal` and
+ * `UIScrollViewDecelerationRateFast` respectively.
+ *
+ * - `'normal'`: 0.998 on iOS, 0.985 on Android (the default)
+ * - `'fast'`: 0.99 on iOS, 0.9 on Android
+ */
+ decelerationRate?: ?('fast' | 'normal' | number),
+ /**
+ * When true, the scroll view's children are arranged horizontally in a row
+ * instead of vertically in a column. The default value is false.
+ */
+ horizontal?: ?boolean,
+ /**
+ * If sticky headers should stick at the bottom instead of the top of the
+ * ScrollView. This is usually used with inverted ScrollViews.
+ */
+ invertStickyHeaders?: ?boolean,
+ /**
+ * Determines whether the keyboard gets dismissed in response to a drag.
+ *
+ * *Cross platform*
+ *
+ * - `'none'` (the default), drags do not dismiss the keyboard.
+ * - `'on-drag'`, the keyboard is dismissed when a drag begins.
+ *
+ * *iOS Only*
+ *
+ * - `'interactive'`, the keyboard is dismissed interactively with the drag and moves in
+ * synchrony with the touch; dragging upwards cancels the dismissal.
+ * On android this is not supported and it will have the same behavior as 'none'.
+ */
+ keyboardDismissMode?: ?// default
+ (| 'none' // cross-platform
+ | 'on-drag'
+ | 'interactive'
+ ), // ios only
+ /**
+ * Determines when the keyboard should stay visible after a tap.
+ *
+ * - `'never'` (the default), tapping outside of the focused text input when the keyboard
+ * is up dismisses the keyboard. When this happens, children won't receive the tap.
+ * - `'always'`, the keyboard will not dismiss automatically, and the scroll view will not
+ * catch taps, but children of the scroll view can catch taps.
+ * - `'handled'`, the keyboard will not dismiss automatically when the tap was handled by
+ * a children, (or captured by an ancestor).
+ * - `false`, deprecated, use 'never' instead
+ * - `true`, deprecated, use 'always' instead
+ */
+ keyboardShouldPersistTaps?: ?('always' | 'never' | 'handled' | true | false),
+ /**
+ * Called when the momentum scroll starts (scroll which occurs as the ScrollView glides to a stop).
+ */
+ onMomentumScrollBegin?: ?(event: ScrollEvent) => void,
+ /**
+ * Called when the momentum scroll ends (scroll which occurs as the ScrollView glides to a stop).
+ */
+ onMomentumScrollEnd?: ?(event: ScrollEvent) => void,
+
+ /**
+ * Fires at most once per frame during scrolling. The frequency of the
+ * events can be controlled using the `scrollEventThrottle` prop.
+ */
+ onScroll?: ?(event: ScrollEvent) => void,
+ /**
+ * Called when the user begins to drag the scroll view.
+ */
+ onScrollBeginDrag?: ?(event: ScrollEvent) => void,
+ /**
+ * Called when the user stops dragging the scroll view and it either stops
+ * or begins to glide.
+ */
+ onScrollEndDrag?: ?(event: ScrollEvent) => void,
+ /**
+ * Called when scrollable content view of the ScrollView changes.
+ *
+ * Handler function is passed the content width and content height as parameters:
+ * `(contentWidth, contentHeight)`
+ *
+ * It's implemented using onLayout handler attached to the content container
+ * which this ScrollView renders.
+ */
+ onContentSizeChange?: (contentWidth: number, contentHeight: number) => void,
+ onKeyboardDidShow?: (event: PressEvent) => void,
+ /**
+ * When true, the scroll view stops on multiples of the scroll view's size
+ * when scrolling. This can be used for horizontal pagination. The default
+ * value is false.
+ *
+ * Note: Vertical pagination is not supported on Android.
+ */
+ pagingEnabled?: ?boolean,
+
+ /**
+ * When false, the view cannot be scrolled via touch interaction.
+ * The default value is true.
+ *
+ * Note that the view can always be scrolled by calling `scrollTo`.
+ */
+ scrollEnabled?: ?boolean,
+ /**
+ * When true, shows a vertical scroll indicator.
+ * The default value is true.
+ */
+ showsVerticalScrollIndicator?: ?boolean,
+ /**
+ * An array of child indices determining which children get docked to the
+ * top of the screen when scrolling. For example, passing
+ * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
+ * top of the scroll view. This property is not supported in conjunction
+ * with `horizontal={true}`.
+ */
+ stickyHeaderIndices?: ?$ReadOnlyArray,
+ /**
+ * A React Component that will be used to render sticky headers.
+ * To be used together with `stickyHeaderIndices` or with `SectionList`, defaults to `ScrollViewStickyHeader`.
+ * You may need to set this if your sticky header uses custom transforms (eg. translation),
+ * for example when you want your list to have an animated hidable header.
+ */
+ StickyHeaderComponent?: StickyHeaderComponentType,
+ /**
+ * When set, causes the scroll view to stop at multiples of the value of
+ * `snapToInterval`. This can be used for paginating through children
+ * that have lengths smaller than the scroll view. Typically used in
+ * combination with `snapToAlignment` and `decelerationRate="fast"`.
+ *
+ * Overrides less configurable `pagingEnabled` prop.
+ */
+ snapToInterval?: ?number,
+ /**
+ * When set, causes the scroll view to stop at the defined offsets.
+ * This can be used for paginating through variously sized children
+ * that have lengths smaller than the scroll view. Typically used in
+ * combination with `decelerationRate="fast"`.
+ *
+ * Overrides less configurable `pagingEnabled` and `snapToInterval` props.
+ */
+ snapToOffsets?: ?$ReadOnlyArray,
+ /**
+ * Use in conjunction with `snapToOffsets`. By default, the beginning
+ * of the list counts as a snap offset. Set `snapToStart` to false to disable
+ * this behavior and allow the list to scroll freely between its start and
+ * the first `snapToOffsets` offset.
+ * The default value is true.
+ */
+ snapToStart?: ?boolean,
+ /**
+ * Use in conjunction with `snapToOffsets`. By default, the end
+ * of the list counts as a snap offset. Set `snapToEnd` to false to disable
+ * this behavior and allow the list to scroll freely between its end and
+ * the last `snapToOffsets` offset.
+ * The default value is true.
+ */
+ snapToEnd?: ?boolean,
+ /**
+ * Experimental: When true, offscreen child views (whose `overflow` value is
+ * `hidden`) are removed from their native backing superview when offscreen.
+ * This can improve scrolling performance on long lists. The default value is
+ * true.
+ */
+ removeClippedSubviews?: ?boolean,
+ /**
+ * A RefreshControl component, used to provide pull-to-refresh
+ * functionality for the ScrollView. Only works for vertical ScrollViews
+ * (`horizontal` prop must be `false`).
+ *
+ * See [RefreshControl](docs/refreshcontrol.html).
+ */
+ // $FlowFixMe - how to handle generic type without existential operator?
+ refreshControl?: ?React.Element,
+ children?: React.Node,
+|}>;
+
+type State = {|
+ layoutHeight: ?number,
+ ...ScrollResponderState,
+|};
+
+function createScrollResponder(
+ node: React.ElementRef,
+): typeof ScrollResponder.Mixin {
+ const scrollResponder = {...ScrollResponder.Mixin};
+
+ for (const key in scrollResponder) {
+ if (typeof scrollResponder[key] === 'function') {
+ scrollResponder[key] = scrollResponder[key].bind(node);
+ }
+ }
+
+ return scrollResponder;
+}
+
+type ContextType = {|horizontal: boolean|} | null;
+const Context = React.createContext(null);
+const standardHorizontalContext: ContextType = Object.freeze({
+ horizontal: true,
+});
+const standardVerticalContext: ContextType = Object.freeze({horizontal: false});
+
+/**
+ * Component that wraps platform ScrollView while providing
+ * integration with touch locking "responder" system.
+ *
+ * Keep in mind that ScrollViews must have a bounded height in order to work,
+ * since they contain unbounded-height children into a bounded container (via
+ * a scroll interaction). In order to bound the height of a ScrollView, either
+ * set the height of the view directly (discouraged) or make sure all parent
+ * views have bounded height. Forgetting to transfer `{flex: 1}` down the
+ * view stack can lead to errors here, which the element inspector makes
+ * easy to debug.
+ *
+ * Doesn't yet support other contained responders from blocking this scroll
+ * view from becoming the responder.
+ *
+ *
+ * `` vs [``](/react-native/docs/flatlist.html) - which one to use?
+ *
+ * `ScrollView` simply renders all its react child components at once. That
+ * makes it very easy to understand and use.
+ *
+ * On the other hand, this has a performance downside. Imagine you have a very
+ * long list of items you want to display, maybe several screens worth of
+ * content. Creating JS components and native views for everything all at once,
+ * much of which may not even be shown, will contribute to slow rendering and
+ * increased memory usage.
+ *
+ * This is where `FlatList` comes into play. `FlatList` renders items lazily,
+ * just when they are about to appear, and removes items that scroll way off
+ * screen to save memory and processing time.
+ *
+ * `FlatList` is also handy if you want to render separators between your items,
+ * multiple columns, infinite scroll loading, or any number of other features it
+ * supports out of the box.
+ */
+class ScrollView extends React.Component {
+ static Context: React$Context = Context;
+ /**
+ * Part 1: Removing ScrollResponder.Mixin:
+ *
+ * 1. Mixin methods should be flow typed. That's why we create a
+ * copy of ScrollResponder.Mixin and attach it to this._scrollResponder.
+ * Otherwise, we'd have to manually declare each method on the component
+ * class and assign it a flow type.
+ * 2. Mixin methods can call component methods, and access the component's
+ * props and state. So, we need to bind all mixin methods to the
+ * component instance.
+ * 3. Continued...
+ */
+ _scrollResponder: typeof ScrollResponder.Mixin = createScrollResponder(this);
+
+ constructor(props: Props) {
+ super(props);
+
+ /**
+ * Part 2: Removing ScrollResponder.Mixin
+ *
+ * 3. Mixin methods access other mixin methods via dynamic dispatch using
+ * this. Since mixin methods are bound to the component instance, we need
+ * to copy all mixin methods to the component instance. This is also
+ * necessary because getScrollResponder() is a public method that returns
+ * an object that can be used to execute all scrollResponder methods.
+ * Since the object returned from that method is the ScrollView instance,
+ * we need to bind all mixin methods to the ScrollView instance.
+ */
+ for (const key in ScrollResponder.Mixin) {
+ if (
+ typeof ScrollResponder.Mixin[key] === 'function' &&
+ key.startsWith('scrollResponder')
+ ) {
+ // $FlowFixMe - dynamically adding properties to a class
+ (this: any)[key] = ScrollResponder.Mixin[key].bind(this);
+ }
+ }
+
+ /**
+ * Part 3: Removing ScrollResponder.Mixin
+ *
+ * 4. Mixins can initialize properties and use properties on the component
+ * instance.
+ */
+ Object.keys(ScrollResponder.Mixin)
+ .filter(key => typeof ScrollResponder.Mixin[key] !== 'function')
+ .forEach(key => {
+ // $FlowFixMe - dynamically adding properties to a class
+ (this: any)[key] = ScrollResponder.Mixin[key];
+ });
+ }
+
+ _scrollAnimatedValue: AnimatedImplementation.Value = new AnimatedImplementation.Value(
+ 0,
+ );
+ _scrollAnimatedValueAttachment: ?{detach: () => void, ...} = null;
+ _stickyHeaderRefs: Map<
+ string,
+ React.ElementRef,
+ > = new Map();
+ _headerLayoutYs: Map = new Map();
+
+ state: State = {
+ layoutHeight: null,
+ ...ScrollResponder.Mixin.scrollResponderMixinGetInitialState(),
+ };
+
+ UNSAFE_componentWillMount() {
+ this._scrollResponder.UNSAFE_componentWillMount();
+ this._scrollAnimatedValue = new AnimatedImplementation.Value(
+ this.props.contentOffset ? this.props.contentOffset.y : 0,
+ );
+ this._scrollAnimatedValue.setOffset(
+ this.props.contentInset ? this.props.contentInset.top || 0 : 0,
+ );
+ this._stickyHeaderRefs = new Map();
+ this._headerLayoutYs = new Map();
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps: Props) {
+ const currentContentInsetTop = this.props.contentInset
+ ? this.props.contentInset.top
+ : 0;
+ const nextContentInsetTop = nextProps.contentInset
+ ? nextProps.contentInset.top
+ : 0;
+ if (currentContentInsetTop !== nextContentInsetTop) {
+ this._scrollAnimatedValue.setOffset(nextContentInsetTop || 0);
+ }
+ }
+
+ componentDidMount() {
+ this._updateAnimatedNodeAttachment();
+ }
+
+ componentDidUpdate() {
+ this._updateAnimatedNodeAttachment();
+ }
+
+ componentWillUnmount() {
+ this._scrollResponder.componentWillUnmount();
+ if (this._scrollAnimatedValueAttachment) {
+ this._scrollAnimatedValueAttachment.detach();
+ }
+ }
+
+ setNativeProps(props: {[key: string]: mixed, ...}) {
+ this._scrollViewRef && this._scrollViewRef.setNativeProps(props);
+ }
+
+ /**
+ * Returns a reference to the underlying scroll responder, which supports
+ * operations like `scrollTo`. All ScrollView-like components should
+ * implement this method so that they can be composed while providing access
+ * to the underlying scroll responder's methods.
+ */
+ getScrollResponder(): ScrollResponderType {
+ // $FlowFixMe - overriding type to include ScrollResponder.Mixin
+ return ((this: any): ScrollResponderType);
+ }
+
+ getScrollableNode(): ?number {
+ return ReactNative.findNodeHandle(this._scrollViewRef);
+ }
+
+ getInnerViewNode(): ?number {
+ return ReactNative.findNodeHandle(this._innerViewRef);
+ }
+
+ getInnerViewRef(): ?React.ElementRef> {
+ return this._innerViewRef;
+ }
+
+ getNativeScrollRef(): ?React.ElementRef> {
+ return this._scrollViewRef;
+ }
+
+ /**
+ * Scrolls to a given x, y offset, either immediately or with a smooth animation.
+ *
+ * Example:
+ *
+ * `scrollTo({x: 0, y: 0, animated: true})`
+ *
+ * Note: The weird function signature is due to the fact that, for historical reasons,
+ * the function also accepts separate arguments as an alternative to the options object.
+ * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
+ */
+ scrollTo(
+ options?:
+ | {
+ x?: number,
+ y?: number,
+ animated?: boolean,
+ ...
+ }
+ | number,
+ deprecatedX?: number,
+ deprecatedAnimated?: boolean,
+ ) {
+ let x, y, animated;
+ if (typeof options === 'number') {
+ console.warn(
+ '`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, ' +
+ 'animated: true})` instead.',
+ );
+ y = options;
+ x = deprecatedX;
+ animated = deprecatedAnimated;
+ } else if (options) {
+ y = options.y;
+ x = options.x;
+ animated = options.animated;
+ }
+ this._scrollResponder.scrollResponderScrollTo({
+ x: x || 0,
+ y: y || 0,
+ animated: animated !== false,
+ });
+ }
+
+ /**
+ * If this is a vertical ScrollView scrolls to the bottom.
+ * If this is a horizontal ScrollView scrolls to the right.
+ *
+ * Use `scrollToEnd({animated: true})` for smooth animated scrolling,
+ * `scrollToEnd({animated: false})` for immediate scrolling.
+ * If no options are passed, `animated` defaults to true.
+ */
+ scrollToEnd(options?: ?{animated?: boolean, ...}) {
+ // Default to true
+ const animated = (options && options.animated) !== false;
+ this._scrollResponder.scrollResponderScrollToEnd({
+ animated: animated,
+ });
+ }
+
+ /**
+ * Displays the scroll indicators momentarily.
+ *
+ * @platform ios
+ */
+ flashScrollIndicators() {
+ this._scrollResponder.scrollResponderFlashScrollIndicators();
+ }
+
+ _getKeyForIndex(index, childArray) {
+ const child = childArray[index];
+ return child && child.key;
+ }
+
+ _updateAnimatedNodeAttachment() {
+ if (this._scrollAnimatedValueAttachment) {
+ this._scrollAnimatedValueAttachment.detach();
+ }
+ if (
+ this.props.stickyHeaderIndices &&
+ this.props.stickyHeaderIndices.length > 0
+ ) {
+ this._scrollAnimatedValueAttachment = AnimatedImplementation.attachNativeEvent(
+ this._scrollViewRef,
+ 'onScroll',
+ [{nativeEvent: {contentOffset: {y: this._scrollAnimatedValue}}}],
+ );
+ }
+ }
+
+ _setStickyHeaderRef(
+ key: string,
+ ref: ?React.ElementRef,
+ ) {
+ if (ref) {
+ this._stickyHeaderRefs.set(key, ref);
+ } else {
+ this._stickyHeaderRefs.delete(key);
+ }
+ }
+
+ _onStickyHeaderLayout(index, event, key) {
+ const {stickyHeaderIndices} = this.props;
+ if (!stickyHeaderIndices) {
+ return;
+ }
+ const childArray = React.Children.toArray(this.props.children);
+ if (key !== this._getKeyForIndex(index, childArray)) {
+ // ignore stale layout update
+ return;
+ }
+
+ const layoutY = event.nativeEvent.layout.y;
+ this._headerLayoutYs.set(key, layoutY);
+
+ const indexOfIndex = stickyHeaderIndices.indexOf(index);
+ const previousHeaderIndex = stickyHeaderIndices[indexOfIndex - 1];
+ if (previousHeaderIndex != null) {
+ const previousHeader = this._stickyHeaderRefs.get(
+ this._getKeyForIndex(previousHeaderIndex, childArray),
+ );
+ previousHeader &&
+ previousHeader.setNextHeaderY &&
+ previousHeader.setNextHeaderY(layoutY);
+ }
+ }
+
+ _handleScroll = (e: ScrollEvent) => {
+ if (__DEV__) {
+ if (
+ this.props.onScroll &&
+ this.props.scrollEventThrottle == null &&
+ Platform.OS === 'ios'
+ ) {
+ console.log(
+ 'You specified `onScroll` on a but not ' +
+ '`scrollEventThrottle`. You will only receive one event. ' +
+ 'Using `16` you get all the events but be aware that it may ' +
+ "cause frame drops, use a bigger number if you don't need as " +
+ 'much precision.',
+ );
+ }
+ }
+ if (Platform.OS === 'android') {
+ if (
+ this.props.keyboardDismissMode === 'on-drag' &&
+ this.state.isTouching
+ ) {
+ dismissKeyboard();
+ }
+ }
+ this._scrollResponder.scrollResponderHandleScroll(e);
+ };
+
+ _handleLayout = (e: LayoutEvent) => {
+ if (this.props.invertStickyHeaders === true) {
+ this.setState({layoutHeight: e.nativeEvent.layout.height});
+ }
+ if (this.props.onLayout) {
+ this.props.onLayout(e);
+ }
+ };
+
+ _handleContentOnLayout = (e: LayoutEvent) => {
+ const {width, height} = e.nativeEvent.layout;
+ this.props.onContentSizeChange &&
+ this.props.onContentSizeChange(width, height);
+ };
+
+ _scrollViewRef: ?React.ElementRef> = null;
+ _setScrollViewRef = (ref: ?React.ElementRef>) => {
+ this._scrollViewRef = ref;
+ };
+
+ _innerViewRef: ?React.ElementRef> = null;
+ _setInnerViewRef = (ref: ?React.ElementRef>) => {
+ this._innerViewRef = ref;
+ };
+
+ render(): React.Node | React.Element {
+ let ScrollViewClass;
+ let ScrollContentContainerViewClass;
+ if (Platform.OS === 'android') {
+ if (this.props.horizontal === true) {
+ ScrollViewClass = AndroidHorizontalScrollView;
+ ScrollContentContainerViewClass = AndroidHorizontalScrollContentView;
+ } else {
+ ScrollViewClass = AndroidScrollView;
+ ScrollContentContainerViewClass = View;
+ }
+ } else {
+ ScrollViewClass = RCTScrollView;
+ ScrollContentContainerViewClass = RCTScrollContentView;
+ }
+
+ invariant(
+ ScrollViewClass !== undefined,
+ 'ScrollViewClass must not be undefined',
+ );
+
+ invariant(
+ ScrollContentContainerViewClass !== undefined,
+ 'ScrollContentContainerViewClass must not be undefined',
+ );
+
+ const contentContainerStyle = [
+ this.props.horizontal === true && styles.contentContainerHorizontal,
+ this.props.contentContainerStyle,
+ ];
+ if (__DEV__ && this.props.style !== undefined) {
+ const style = flattenStyle(this.props.style);
+ const childLayoutProps = ['alignItems', 'justifyContent'].filter(
+ prop => style && style[prop] !== undefined,
+ );
+ invariant(
+ childLayoutProps.length === 0,
+ 'ScrollView child layout (' +
+ JSON.stringify(childLayoutProps) +
+ ') must be applied through the contentContainerStyle prop.',
+ );
+ }
+
+ let contentSizeChangeProps = {};
+ if (this.props.onContentSizeChange) {
+ contentSizeChangeProps = {
+ onLayout: this._handleContentOnLayout,
+ };
+ }
+
+ const {stickyHeaderIndices} = this.props;
+ let children = this.props.children;
+
+ if (stickyHeaderIndices != null && stickyHeaderIndices.length > 0) {
+ const childArray = React.Children.toArray(this.props.children);
+
+ children = childArray.map((child, index) => {
+ const indexOfIndex = child ? stickyHeaderIndices.indexOf(index) : -1;
+ if (indexOfIndex > -1) {
+ const key = child.key;
+ const nextIndex = stickyHeaderIndices[indexOfIndex + 1];
+ const StickyHeaderComponent =
+ this.props.StickyHeaderComponent || ScrollViewStickyHeader;
+ return (
+ this._setStickyHeaderRef(key, ref)}
+ nextHeaderLayoutY={this._headerLayoutYs.get(
+ this._getKeyForIndex(nextIndex, childArray),
+ )}
+ onLayout={event => this._onStickyHeaderLayout(index, event, key)}
+ scrollAnimatedValue={this._scrollAnimatedValue}
+ inverted={this.props.invertStickyHeaders}
+ scrollViewHeight={this.state.layoutHeight}>
+ {child}
+
+ );
+ } else {
+ return child;
+ }
+ });
+ }
+ children = (
+
+ {children}
+
+ );
+
+ const hasStickyHeaders =
+ Array.isArray(stickyHeaderIndices) && stickyHeaderIndices.length > 0;
+
+ const contentContainer = (
+ /* $FlowFixMe(>=0.112.0 site=react_native_fb) This comment suppresses an
+ * error found when Flow v0.112 was deployed. To see the error, delete
+ * this comment and run Flow. */
+
+ {children}
+
+ );
+
+ const alwaysBounceHorizontal =
+ this.props.alwaysBounceHorizontal !== undefined
+ ? this.props.alwaysBounceHorizontal
+ : this.props.horizontal;
+
+ const alwaysBounceVertical =
+ this.props.alwaysBounceVertical !== undefined
+ ? this.props.alwaysBounceVertical
+ : !this.props.horizontal;
+
+ const DEPRECATED_sendUpdatedChildFrames = !!this.props
+ .DEPRECATED_sendUpdatedChildFrames;
+
+ const baseStyle =
+ this.props.horizontal === true
+ ? styles.baseHorizontal
+ : styles.baseVertical;
+ const props = {
+ ...this.props,
+ alwaysBounceHorizontal,
+ alwaysBounceVertical,
+ style: [baseStyle, this.props.style],
+ // Override the onContentSizeChange from props, since this event can
+ // bubble up from TextInputs
+ onContentSizeChange: null,
+ onLayout: this._handleLayout,
+ onMomentumScrollBegin: this._scrollResponder
+ .scrollResponderHandleMomentumScrollBegin,
+ onMomentumScrollEnd: this._scrollResponder
+ .scrollResponderHandleMomentumScrollEnd,
+ onResponderGrant: this._scrollResponder
+ .scrollResponderHandleResponderGrant,
+ onResponderReject: this._scrollResponder
+ .scrollResponderHandleResponderReject,
+ onResponderRelease: this._scrollResponder
+ .scrollResponderHandleResponderRelease,
+ onResponderTerminationRequest: this._scrollResponder
+ .scrollResponderHandleTerminationRequest,
+ onScrollBeginDrag: this._scrollResponder
+ .scrollResponderHandleScrollBeginDrag,
+ onScrollEndDrag: this._scrollResponder.scrollResponderHandleScrollEndDrag,
+ onScrollShouldSetResponder: this._scrollResponder
+ .scrollResponderHandleScrollShouldSetResponder,
+ onStartShouldSetResponder: this._scrollResponder
+ .scrollResponderHandleStartShouldSetResponder,
+ onStartShouldSetResponderCapture: this._scrollResponder
+ .scrollResponderHandleStartShouldSetResponderCapture,
+ onTouchEnd: this._scrollResponder.scrollResponderHandleTouchEnd,
+ onTouchMove: this._scrollResponder.scrollResponderHandleTouchMove,
+ onTouchStart: this._scrollResponder.scrollResponderHandleTouchStart,
+ onTouchCancel: this._scrollResponder.scrollResponderHandleTouchCancel,
+ onScroll: this._handleScroll,
+ scrollBarThumbImage: resolveAssetSource(this.props.scrollBarThumbImage),
+ scrollEventThrottle: hasStickyHeaders
+ ? 1
+ : this.props.scrollEventThrottle,
+ sendMomentumEvents:
+ this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd
+ ? true
+ : false,
+ DEPRECATED_sendUpdatedChildFrames,
+ // default to true
+ snapToStart: this.props.snapToStart !== false,
+ // default to true
+ snapToEnd: this.props.snapToEnd !== false,
+ // pagingEnabled is overridden by snapToInterval / snapToOffsets
+ pagingEnabled: Platform.select({
+ // on iOS, pagingEnabled must be set to false to have snapToInterval / snapToOffsets work
+ ios:
+ this.props.pagingEnabled === true &&
+ this.props.snapToInterval == null &&
+ this.props.snapToOffsets == null,
+ // on Android, pagingEnabled must be set to true to have snapToInterval / snapToOffsets work
+ android:
+ this.props.pagingEnabled === true ||
+ this.props.snapToInterval != null ||
+ this.props.snapToOffsets != null,
+ // on Windows, pagingEnabled must be set to false to have snapToInterval / snapToOffsets work
+ windows:
+ this.props.pagingEnabled === true &&
+ this.props.snapToInterval == null &&
+ this.props.snapToOffsets == null,
+ }),
+ };
+
+ const {decelerationRate} = this.props;
+ if (decelerationRate != null) {
+ props.decelerationRate = processDecelerationRate(decelerationRate);
+ }
+
+ const refreshControl = this.props.refreshControl;
+
+ if (refreshControl) {
+ if (Platform.OS === 'ios') {
+ // On iOS the RefreshControl is a child of the ScrollView.
+ // tvOS lacks native support for RefreshControl, so don't include it in that case
+ return (
+
+ {Platform.isTV ? null : refreshControl}
+ {contentContainer}
+
+ );
+ } else if (Platform.OS === 'android') {
+ // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
+ // Since the ScrollView is wrapped add the style props to the
+ // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
+ // Note: we should split props.style on the inner and outer props
+ // however, the ScrollView still needs the baseStyle to be scrollable
+ const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
+ return React.cloneElement(
+ refreshControl,
+ {style: [baseStyle, outer]},
+
+ {contentContainer}
+ ,
+ );
+ } else if (Platform.OS === 'windows') {
+ const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
+ return React.cloneElement(
+ refreshControl,
+ {style: [baseStyle, outer]},
+
+ {contentContainer}
+ ,
+ );
+ }
+ }
+ return (
+
+ {contentContainer}
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ baseVertical: {
+ flexGrow: 1,
+ flexShrink: 1,
+ flexDirection: 'column',
+ overflow: 'scroll',
+ },
+ baseHorizontal: {
+ flexGrow: 1,
+ flexShrink: 1,
+ flexDirection: 'row',
+ overflow: 'scroll',
+ },
+ contentContainerHorizontal: {
+ flexDirection: 'row',
+ },
+});
+
+module.exports = ScrollView;
diff --git a/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerAndroid.win32.js b/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerAndroid.win32.js
new file mode 100644
index 00000000000..62400bd0df7
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerAndroid.win32.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ */
+
+'use strict';
+
+import type {TurboModule} from '../../TurboModule/RCTExport';
+import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry';
+import type {NativeOrDynamicColorType} from '../../StyleSheet/NativeOrDynamicColorType'; // [Win32]
+
+export interface Spec extends TurboModule {
+ +getConstants: () => {|
+ +HEIGHT: number,
+ +DEFAULT_BACKGROUND_COLOR: number,
+ |};
+ // [Win32 Allow NativeOrDynamicColorType to make Flow happy (this will never be called)
+ +setColor: (
+ color: number | NativeOrDynamicColorType,
+ animated: boolean,
+ ) => void;
+ // Win32]
+ +setTranslucent: (translucent: boolean) => void;
+
+ /**
+ * - statusBarStyles can be:
+ * - 'default'
+ * - 'dark-content'
+ */
+ +setStyle: (statusBarStyle?: ?string) => void;
+ +setHidden: (hidden: boolean) => void;
+}
+
+// [Win32 Change from getEnforcing to get and provide a stub (See #4363)
+let NativeStausBarManager = TurboModuleRegistry.get('StatusBarManager');
+if (!NativeStausBarManager) {
+ NativeStausBarManager = {
+ getConstants: () => ({HEIGHT: 0, DEFAULT_BACKGROUND_COLOR: 0}),
+ setColor: (
+ color: number | NativeOrDynamicColorType,
+ animated: boolean,
+ ) => {},
+ setTranslucent: (translucent: boolean) => {},
+ setStyle: (statusBarStyle?: ?string) => {},
+ setHidden: (hidden: boolean) => {},
+ };
+}
+
+export default (NativeStausBarManager: Spec);
+// Win32]
diff --git a/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.win32.js b/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.win32.js
new file mode 100644
index 00000000000..d298cae8d71
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.win32.js
@@ -0,0 +1,45 @@
+/**
+ * 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 {TurboModule} from '../../TurboModule/RCTExport';
+import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry';
+
+export interface Spec extends TurboModule {
+ +getConstants: () => {|
+ +HEIGHT: number,
+ +DEFAULT_BACKGROUND_COLOR?: number,
+ |};
+
+ // TODO(T47754272) Can we remove this method?
+ +getHeight: (callback: (result: {|height: number|}) => void) => void;
+ +setNetworkActivityIndicatorVisible: (visible: boolean) => void;
+ +addListener: (eventType: string) => void;
+ +removeListeners: (count: number) => void;
+
+ /**
+ * - statusBarStyles can be:
+ * - 'default'
+ * - 'dark-content'
+ * - 'light-content'
+ */
+ +setStyle: (statusBarStyle?: ?string, animated: boolean) => void;
+ /**
+ * - withAnimation can be: 'none' | 'fade' | 'slide'
+ */
+ +setHidden: (hidden: boolean, withAnimation: string) => void;
+}
+
+// [Win32 Do not enforce native module is present
+export default (TurboModuleRegistry.get(
+ 'StatusBarManager',
+): ?Spec);
+// Win32]
diff --git a/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBar.win32.ts b/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBar.win32.ts
deleted file mode 100644
index b0f877f19c2..00000000000
--- a/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBar.win32.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Copyright (c) Microsoft Corporation.
- * Licensed under the MIT license.
- *
- * @format
- */
-'use strict';
-
-// @ts-ignore This file is present after the JS build
-import * as UnimplementedView from '../UnimplementedViews/UnimplementedView';
-import {StatusBarProps} from 'react-native';
-
-class StatusBar extends UnimplementedView {}
-
-export = StatusBar;
diff --git a/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBarIOS.win32.ts b/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBarIOS.win32.ts
deleted file mode 100644
index 4ec9e204c68..00000000000
--- a/packages/react-native-win32/src/Libraries/Components/StatusBar/StatusBarIOS.win32.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * This copies what StatusBar.android.js does
- */
-'use strict';
-
-export = null;
diff --git a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.Types.win32.ts b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.Types.win32.ts
index 7e68a5e3f87..cedc19fed51 100644
--- a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.Types.win32.ts
+++ b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.Types.win32.ts
@@ -66,10 +66,3 @@ export type IEditingEvent = NativeSyntheticEvent>;
// TODO: Why do I need this one
export type IPasswordRules = string;
-
-// TODO: Can I get away without this?
-export interface IRCTInput {
- blur(): void;
- focus(): void;
- clear(): void;
-}
diff --git a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.win32.tsx b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.win32.tsx
index 2b923c54b48..39594d5c9cd 100644
--- a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.win32.tsx
+++ b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInput.win32.tsx
@@ -29,19 +29,23 @@ import {
NativeModules,
requireNativeComponent,
TextInputProps,
+ NativeMethods,
} from 'react-native';
import {
IBlurEvent,
IChangeEvent,
IFocusEvent,
- IRCTInput,
} from './TextInput.Types.win32';
const TextAncestor = require('../../Text/TextAncestor');
const TextInputState = require('./TextInputState');
+type RCTTextInputProps = TextInputProps & {
+ text: string;
+};
+
// RCTTextInput is the native component that win32 understands
-const RCTTextInput = requireNativeComponent('RCTTextInput');
+const RCTTextInput = requireNativeComponent('RCTTextInput');
// Adding typings on ViewManagers is problematic as available functionality is not known until
// registration at runtime and would require native and js to always be in sync.
@@ -55,13 +59,13 @@ class TextInput extends React.Component {
private _rafID: number;
- private _inputRef: React.RefObject;
+ private _inputRef: React.RefObject & Readonly>;
private _lastNativeText: string;
private _eventCount = 0;
constructor(props) {
super(props);
- this._inputRef = React.createRef();
+ this._inputRef = React.createRef();
}
/**
diff --git a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js
index 17accd4314c..97c6ead360d 100644
--- a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js
@@ -4,15 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- *
- * This class is responsible for coordinating the "focused"
- * state for TextInputs. All calls relating to the keyboard
- * should be funneled through here
- *
* @format
* @flow strict-local
*/
+// This class is responsible for coordinating the "focused" state for
+// TextInputs. All calls relating to the keyboard should be funneled
+// through here.
+
'use strict';
const Platform = require('../../Utilities/Platform');
@@ -29,17 +28,29 @@ function currentlyFocusedField(): ?number {
return currentlyFocusedID;
}
+function focusField(textFieldID: ?number): void {
+ if (currentlyFocusedID !== textFieldID && textFieldID != null) {
+ currentlyFocusedID = textFieldID;
+ }
+}
+
+function blurField(textFieldID: ?number) {
+ if (currentlyFocusedID === textFieldID && textFieldID != null) {
+ currentlyFocusedID = null;
+ }
+}
+
/**
* @param {number} TextInputID id of the text field to focus
* Focuses the specified text field
* noop if the text field was already focused
*/
function focusTextInput(textFieldID: ?number) {
- if (currentlyFocusedID !== textFieldID && textFieldID !== null) {
- currentlyFocusedID = textFieldID;
+ if (currentlyFocusedID !== textFieldID && textFieldID != null) {
+ focusField(textFieldID);
// [Windows
if (Platform.OS === 'ios' || Platform.OS === 'win32') {
- // Windows]
+ // Windows]
UIManager.focus(textFieldID);
} else if (Platform.OS === 'android') {
UIManager.dispatchViewManagerCommand(
@@ -58,11 +69,11 @@ function focusTextInput(textFieldID: ?number) {
* noop if it wasn't focused
*/
function blurTextInput(textFieldID: ?number) {
- if (currentlyFocusedID === textFieldID && textFieldID !== null) {
- currentlyFocusedID = null;
+ if (currentlyFocusedID === textFieldID && textFieldID != null) {
+ blurField(textFieldID);
// [Windows
if (Platform.OS === 'ios' || Platform.OS === 'win32') {
- // Windows]
+ // Windows]
UIManager.blur(textFieldID);
} else if (Platform.OS === 'android') {
UIManager.dispatchViewManagerCommand(
@@ -113,6 +124,8 @@ function isTextInput(textFieldID: number): boolean {
module.exports = {
currentlyFocusedField,
+ focusField,
+ blurField,
setFocusedTextInput, // TODO(android ISS)
clearFocusedTextInput, // TODO(android ISS)
focusTextInput,
diff --git a/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableBounce.win32.js b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableBounce.win32.js
new file mode 100644
index 00000000000..cc3543ec223
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableBounce.win32.js
@@ -0,0 +1,218 @@
+/**
+ * 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';
+
+// [Win32 Remove .js
+import Pressability from '../../Pressability/Pressability';
+import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
+import type {ViewStyleProp} from '../../StyleSheet/StyleSheet';
+import TVTouchable from './TVTouchable';
+import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback';
+// Win32]
+import {Animated, Platform} from 'react-native';
+import * as React from 'react';
+
+type Props = $ReadOnly<{|
+ ...React.ElementConfig,
+
+ onPressAnimationComplete?: ?() => void,
+ onPressWithCompletion?: ?(callback: () => void) => void,
+ releaseBounciness?: ?number,
+ releaseVelocity?: ?number,
+ style?: ?ViewStyleProp,
+
+ hostRef: React.Ref,
+|}>;
+
+type State = $ReadOnly<{|
+ pressability: Pressability,
+ scale: Animated.Value,
+|}>;
+
+class TouchableBounce extends React.Component {
+ _tvTouchable: ?TVTouchable;
+
+ state: State = {
+ pressability: new Pressability({
+ getHitSlop: () => this.props.hitSlop,
+ getLongPressDelayMS: () => {
+ if (this.props.delayLongPress != null) {
+ const maybeNumber = this.props.delayLongPress;
+ if (typeof maybeNumber === 'number') {
+ return maybeNumber;
+ }
+ }
+ return 500;
+ },
+ getPressDelayMS: () => this.props.delayPressIn,
+ getPressOutDelayMS: () => this.props.delayPressOut,
+ getPressRectOffset: () => this.props.pressRetentionOffset,
+ getTouchSoundDisabled: () => this.props.touchSoundDisabled,
+ onBlur: event => {
+ if (Platform.isTV) {
+ this._bounceTo(1, 0.4, 0);
+ }
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (Platform.isTV) {
+ this._bounceTo(0.93, 0.1, 0);
+ }
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onLongPress: event => {
+ if (this.props.onLongPress != null) {
+ this.props.onLongPress(event);
+ }
+ },
+ onPress: event => {
+ const {onPressAnimationComplete, onPressWithCompletion} = this.props;
+ const releaseBounciness = this.props.releaseBounciness ?? 10;
+ const releaseVelocity = this.props.releaseVelocity ?? 10;
+
+ if (onPressWithCompletion != null) {
+ onPressWithCompletion(() => {
+ this.state.scale.setValue(0.93);
+ this._bounceTo(
+ 1,
+ releaseVelocity,
+ releaseBounciness,
+ onPressAnimationComplete,
+ );
+ });
+ return;
+ }
+
+ this._bounceTo(
+ 1,
+ releaseVelocity,
+ releaseBounciness,
+ onPressAnimationComplete,
+ );
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ onPressIn: event => {
+ this._bounceTo(0.93, 0.1, 0);
+ if (this.props.onPressIn != null) {
+ this.props.onPressIn(event);
+ }
+ },
+ onPressOut: event => {
+ this._bounceTo(1, 0.4, 0);
+ if (this.props.onPressOut != null) {
+ this.props.onPressOut(event);
+ }
+ },
+ onResponderTerminationRequest: () =>
+ !this.props.rejectResponderTermination,
+ onStartShouldSetResponder: () => !this.props.disabled,
+ }),
+ scale: new Animated.Value(1),
+ };
+
+ _bounceTo(
+ toValue: number,
+ velocity: number,
+ bounciness: number,
+ callback?: ?() => void,
+ ) {
+ Animated.spring(this.state.scale, {
+ toValue,
+ velocity,
+ bounciness,
+ useNativeDriver: true,
+ }).start(callback);
+ }
+
+ render(): React.Node {
+ // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
+ // adopting `Pressability`, so preserve that behavior.
+ const {
+ onBlur,
+ onFocus,
+ ...eventHandlersWithoutBlurAndFocus
+ } = this.state.pressability.getEventHandlers();
+
+ return (
+
+ {this.props.children}
+ {__DEV__ ? (
+
+ ) : null}
+
+ );
+ }
+
+ componentDidMount(): void {
+ if (Platform.isTV) {
+ this._tvTouchable = new TVTouchable(this, {
+ getDisabled: () => this.props.disabled === true,
+ onBlur: event => {
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ });
+ }
+ }
+
+ componentWillUnmount(): void {
+ if (Platform.isTV) {
+ if (this._tvTouchable != null) {
+ this._tvTouchable.destroy();
+ }
+ }
+ this.state.pressability.reset();
+ }
+}
+
+module.exports = (React.forwardRef((props, hostRef) => (
+
+)): React.ComponentType<$ReadOnly<$Diff>>);
diff --git a/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableHighlight.win32.js b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableHighlight.win32.js
new file mode 100644
index 00000000000..475bbc09c95
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableHighlight.win32.js
@@ -0,0 +1,384 @@
+/**
+ * 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';
+
+// [Win32 Remove .js
+import Pressability from '../../Pressability/Pressability';
+import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
+import StyleSheet, {type ViewStyleProp} from '../../StyleSheet/StyleSheet';
+import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
+import TVTouchable from './TVTouchable.js';
+import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback';
+// Win32]
+import Platform from '../../Utilities/Platform';
+import View from '../../Components/View/View';
+import * as React from 'react';
+
+type AndroidProps = $ReadOnly<{|
+ nextFocusDown?: ?number,
+ nextFocusForward?: ?number,
+ nextFocusLeft?: ?number,
+ nextFocusRight?: ?number,
+ nextFocusUp?: ?number,
+|}>;
+
+type IOSProps = $ReadOnly<{|
+ hasTVPreferredFocus?: ?boolean,
+|}>;
+
+type Props = $ReadOnly<{|
+ ...React.ElementConfig,
+ ...AndroidProps,
+ ...IOSProps,
+
+ activeOpacity?: ?number,
+ underlayColor?: ?ColorValue,
+ style?: ?ViewStyleProp,
+ onShowUnderlay?: ?() => void,
+ onHideUnderlay?: ?() => void,
+ testOnly_pressed?: ?boolean,
+
+ hostRef: React.Ref,
+|}>;
+
+type ExtraStyles = $ReadOnly<{|
+ child: ViewStyleProp,
+ underlay: ViewStyleProp,
+|}>;
+
+type State = $ReadOnly<{|
+ pressability: Pressability,
+ extraStyles: ?ExtraStyles,
+|}>;
+
+/**
+ * A wrapper for making views respond properly to touches.
+ * On press down, the opacity of the wrapped view is decreased, which allows
+ * the underlay color to show through, darkening or tinting the view.
+ *
+ * The underlay comes from wrapping the child in a new View, which can affect
+ * layout, and sometimes cause unwanted visual artifacts if not used correctly,
+ * for example if the backgroundColor of the wrapped view isn't explicitly set
+ * to an opaque color.
+ *
+ * TouchableHighlight must have one child (not zero or more than one).
+ * If you wish to have several child components, wrap them in a View.
+ *
+ * Example:
+ *
+ * ```
+ * renderButton: function() {
+ * return (
+ *
+ *
+ *
+ * );
+ * },
+ * ```
+ *
+ *
+ * ### Example
+ *
+ * ```ReactNativeWebPlayer
+ * import React, { Component } from 'react'
+ * import {
+ * AppRegistry,
+ * StyleSheet,
+ * TouchableHighlight,
+ * Text,
+ * View,
+ * } from 'react-native'
+ *
+ * class App extends Component {
+ * constructor(props) {
+ * super(props)
+ * this.state = { count: 0 }
+ * }
+ *
+ * onPress = () => {
+ * this.setState({
+ * count: this.state.count+1
+ * })
+ * }
+ *
+ * render() {
+ * return (
+ *
+ *
+ * Touch Here
+ *
+ *
+ *
+ * { this.state.count !== 0 ? this.state.count: null}
+ *
+ *
+ *
+ * )
+ * }
+ * }
+ *
+ * const styles = StyleSheet.create({
+ * container: {
+ * flex: 1,
+ * justifyContent: 'center',
+ * paddingHorizontal: 10
+ * },
+ * button: {
+ * alignItems: 'center',
+ * backgroundColor: '#DDDDDD',
+ * padding: 10
+ * },
+ * countContainer: {
+ * alignItems: 'center',
+ * padding: 10
+ * },
+ * countText: {
+ * color: '#FF00FF'
+ * }
+ * })
+ *
+ * AppRegistry.registerComponent('App', () => App)
+ * ```
+ *
+ */
+class TouchableHighlight extends React.Component {
+ _hideTimeout: ?TimeoutID;
+ _isMounted: boolean = false;
+ _tvTouchable: ?TVTouchable;
+
+ state: State = {
+ pressability: new Pressability({
+ getHitSlop: () => this.props.hitSlop,
+ getLongPressDelayMS: () => {
+ if (this.props.delayLongPress != null) {
+ const maybeNumber = this.props.delayLongPress;
+ if (typeof maybeNumber === 'number') {
+ return maybeNumber;
+ }
+ }
+ return 500;
+ },
+ getPressDelayMS: () => this.props.delayPressIn,
+ getPressOutDelayMS: () => this.props.delayPressOut,
+ getPressRectOffset: () => this.props.pressRetentionOffset,
+ getTouchSoundDisabled: () => this.props.touchSoundDisabled,
+ onBlur: event => {
+ if (Platform.isTV) {
+ this._hideUnderlay();
+ }
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (Platform.isTV) {
+ this._showUnderlay();
+ }
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onLongPress: event => {
+ if (this.props.onLongPress != null) {
+ this.props.onLongPress(event);
+ }
+ },
+ onPress: event => {
+ if (this._hideTimeout != null) {
+ clearTimeout(this._hideTimeout);
+ }
+ if (!Platform.isTV) {
+ this._showUnderlay();
+ this._hideTimeout = setTimeout(() => {
+ this._hideUnderlay();
+ }, this.props.delayPressOut ?? 0);
+ }
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ onPressIn: event => {
+ if (this._hideTimeout != null) {
+ clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ }
+ this._showUnderlay();
+ if (this.props.onPressIn != null) {
+ this.props.onPressIn(event);
+ }
+ },
+ onPressOut: event => {
+ if (this._hideTimeout == null) {
+ this._hideUnderlay();
+ }
+ if (this.props.onPressOut != null) {
+ this.props.onPressOut(event);
+ }
+ },
+ onResponderTerminationRequest: () =>
+ !this.props.rejectResponderTermination,
+ onStartShouldSetResponder: () => !this.props.disabled,
+ }),
+ extraStyles:
+ this.props.testOnly_pressed === true ? this._createExtraStyles() : null,
+ };
+
+ _createExtraStyles(): ExtraStyles {
+ return {
+ child: {opacity: this.props.activeOpacity ?? 0.85},
+ underlay: {
+ backgroundColor:
+ this.props.underlayColor === undefined
+ ? 'black'
+ : this.props.underlayColor,
+ },
+ };
+ }
+
+ _showUnderlay(): void {
+ if (!this._isMounted || !this._hasPressHandler()) {
+ return;
+ }
+ this.setState({extraStyles: this._createExtraStyles()});
+ if (this.props.onShowUnderlay != null) {
+ this.props.onShowUnderlay();
+ }
+ }
+
+ _hideUnderlay(): void {
+ if (this._hideTimeout != null) {
+ clearTimeout(this._hideTimeout);
+ this._hideTimeout = null;
+ }
+ if (this.props.testOnly_pressed === true) {
+ return;
+ }
+ if (this._hasPressHandler()) {
+ this.setState({extraStyles: null});
+ if (this.props.onHideUnderlay != null) {
+ this.props.onHideUnderlay();
+ }
+ }
+ }
+
+ _hasPressHandler(): boolean {
+ return (
+ this.props.onPress != null ||
+ this.props.onPressIn != null ||
+ this.props.onPressOut != null ||
+ this.props.onLongPress != null
+ );
+ }
+
+ render(): React.Node {
+ const child = React.Children.only(this.props.children);
+
+ // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
+ // adopting `Pressability`, so preserve that behavior.
+ const {
+ onBlur,
+ onFocus,
+ ...eventHandlersWithoutBlurAndFocus
+ } = this.state.pressability.getEventHandlers();
+
+ return (
+
+ {React.cloneElement(child, {
+ style: StyleSheet.compose(
+ child.props.style,
+ this.state.extraStyles?.child,
+ ),
+ })}
+ {__DEV__ ? (
+
+ ) : null}
+
+ );
+ }
+
+ componentDidMount(): void {
+ this._isMounted = true;
+ if (Platform.isTV) {
+ this._tvTouchable = new TVTouchable(this, {
+ getDisabled: () => this.props.disabled === true,
+ onBlur: event => {
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ });
+ }
+ }
+
+ componentWillUnmount(): void {
+ this._isMounted = false;
+ if (this._hideTimeout != null) {
+ clearTimeout(this._hideTimeout);
+ }
+ if (Platform.isTV) {
+ if (this._tvTouchable != null) {
+ this._tvTouchable.destroy();
+ }
+ }
+ this.state.pressability.reset();
+ }
+}
+
+module.exports = (React.forwardRef((props, hostRef) => (
+
+)): React.ComponentType<$ReadOnly<$Diff>>);
diff --git a/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableOpacity.win32.js b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableOpacity.win32.js
new file mode 100644
index 00000000000..c6acd99289a
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableOpacity.win32.js
@@ -0,0 +1,314 @@
+/**
+ * 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';
+
+// [Win32 Remove ".js"
+import Pressability from '../../Pressability/Pressability';
+import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
+import TVTouchable from './TVTouchable';
+import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback';
+// Win32]
+import Animated from 'react-native/Libraries/Animated/src/Animated';
+import Easing from 'react-native/Libraries/Animated/src/Easing';
+import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
+import flattenStyle from 'react-native/Libraries/StyleSheet/flattenStyle';
+import Platform from '../../Utilities/Platform';
+import * as React from 'react';
+
+type TVProps = $ReadOnly<{|
+ hasTVPreferredFocus?: ?boolean,
+ nextFocusDown?: ?number,
+ nextFocusForward?: ?number,
+ nextFocusLeft?: ?number,
+ nextFocusRight?: ?number,
+ nextFocusUp?: ?number,
+|}>;
+
+type Props = $ReadOnly<{|
+ ...React.ElementConfig,
+ ...TVProps,
+
+ activeOpacity?: ?number,
+ style?: ?ViewStyleProp,
+
+ hostRef: React.Ref,
+|}>;
+
+type State = $ReadOnly<{|
+ anim: Animated.Value,
+ pressability: Pressability,
+|}>;
+
+/**
+ * A wrapper for making views respond properly to touches.
+ * On press down, the opacity of the wrapped view is decreased, dimming it.
+ *
+ * Opacity is controlled by wrapping the children in an Animated.View, which is
+ * added to the view hierarchy. Be aware that this can affect layout.
+ *
+ * Example:
+ *
+ * ```
+ * renderButton: function() {
+ * return (
+ *
+ *
+ *
+ * );
+ * },
+ * ```
+ * ### Example
+ *
+ * ```ReactNativeWebPlayer
+ * import React, { Component } from 'react'
+ * import {
+ * AppRegistry,
+ * StyleSheet,
+ * TouchableOpacity,
+ * Text,
+ * View,
+ * } from 'react-native'
+ *
+ * class App extends Component {
+ * state = { count: 0 }
+ *
+ * onPress = () => {
+ * this.setState(state => ({
+ * count: state.count + 1
+ * }));
+ * };
+ *
+ * render() {
+ * return (
+ *
+ *
+ * Touch Here
+ *
+ *
+ *
+ * { this.state.count !== 0 ? this.state.count: null}
+ *
+ *
+ *
+ * )
+ * }
+ * }
+ *
+ * const styles = StyleSheet.create({
+ * container: {
+ * flex: 1,
+ * justifyContent: 'center',
+ * paddingHorizontal: 10
+ * },
+ * button: {
+ * alignItems: 'center',
+ * backgroundColor: '#DDDDDD',
+ * padding: 10
+ * },
+ * countContainer: {
+ * alignItems: 'center',
+ * padding: 10
+ * },
+ * countText: {
+ * color: '#FF00FF'
+ * }
+ * })
+ *
+ * AppRegistry.registerComponent('App', () => App)
+ * ```
+ *
+ */
+class TouchableOpacity extends React.Component {
+ _tvTouchable: ?TVTouchable;
+
+ state: State = {
+ anim: new Animated.Value(this._getChildStyleOpacityWithDefault()),
+ pressability: new Pressability({
+ getHitSlop: () => this.props.hitSlop,
+ getLongPressDelayMS: () => {
+ if (this.props.delayLongPress != null) {
+ const maybeNumber = this.props.delayLongPress;
+ if (typeof maybeNumber === 'number') {
+ return maybeNumber;
+ }
+ }
+ return 500;
+ },
+ getPressDelayMS: () => this.props.delayPressIn,
+ getPressOutDelayMS: () => this.props.delayPressOut,
+ getPressRectOffset: () => this.props.pressRetentionOffset,
+ onBlur: event => {
+ if (Platform.isTV) {
+ this._opacityInactive(250);
+ }
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (Platform.isTV) {
+ this._opacityActive(150);
+ }
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onLongPress: event => {
+ if (this.props.onLongPress != null) {
+ this.props.onLongPress(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ onPressIn: event => {
+ this._opacityActive(
+ event.dispatchConfig.registrationName === 'onResponderGrant'
+ ? 0
+ : 150,
+ );
+ if (this.props.onPressIn != null) {
+ this.props.onPressIn(event);
+ }
+ },
+ onPressOut: event => {
+ this._opacityInactive(250);
+ if (this.props.onPressOut != null) {
+ this.props.onPressOut(event);
+ }
+ },
+ onResponderTerminationRequest: () =>
+ !this.props.rejectResponderTermination,
+ onStartShouldSetResponder: () => !this.props.disabled,
+ }),
+ };
+
+ /**
+ * Animate the touchable to a new opacity.
+ */
+ _setOpacityTo(toValue: number, duration: number): void {
+ Animated.timing(this.state.anim, {
+ toValue,
+ duration,
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
+ }).start();
+ }
+
+ _opacityActive(duration: number): void {
+ this._setOpacityTo(this.props.activeOpacity ?? 0.2, duration);
+ }
+
+ _opacityInactive(duration: number): void {
+ this._setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
+ }
+
+ _getChildStyleOpacityWithDefault(): number {
+ const opacity = flattenStyle(this.props.style)?.opacity;
+ return typeof opacity === 'number' ? opacity : 1;
+ }
+
+ render(): React.Node {
+ // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
+ // adopting `Pressability`, so preserve that behavior.
+ const {
+ onBlur,
+ onFocus,
+ ...eventHandlersWithoutBlurAndFocus
+ } = this.state.pressability.getEventHandlers();
+
+ return (
+
+ {this.props.children}
+ {__DEV__ ? (
+
+ ) : null}
+
+ );
+ }
+
+ componentDidMount(): void {
+ if (Platform.isTV) {
+ this._tvTouchable = new TVTouchable(this, {
+ getDisabled: () => this.props.disabled === true,
+ onBlur: event => {
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ });
+ }
+ }
+
+ componentDidUpdate(prevProps: Props, prevState: State) {
+ if (this.props.disabled !== prevProps.disabled) {
+ this._opacityInactive(250);
+ }
+ }
+
+ componentWillUnmount(): void {
+ if (Platform.isTV) {
+ if (this._tvTouchable != null) {
+ this._tvTouchable.destroy();
+ }
+ }
+ this.state.pressability.reset();
+ }
+}
+
+module.exports = (React.forwardRef((props, hostRef) => (
+
+)): React.ComponentType<$ReadOnly<$Diff>>);
diff --git a/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableWithoutFeedback.win32.js b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableWithoutFeedback.win32.js
new file mode 100644
index 00000000000..8ad6be00003
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Components/Touchable/TouchableWithoutFeedback.win32.js
@@ -0,0 +1,218 @@
+/**
+ * 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';
+
+// [Win32 Remove ".js"
+import Pressability from '../../Pressability/Pressability';
+import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
+import TVTouchable from './TVTouchable';
+// Win32]
+import type {
+ AccessibilityActionEvent,
+ AccessibilityActionInfo,
+ AccessibilityRole,
+ AccessibilityState,
+ AccessibilityValue,
+} from '../../Components/View/ViewAccessibility';
+import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType';
+import type {
+ BlurEvent,
+ FocusEvent,
+ LayoutEvent,
+ PressEvent,
+} from '../../Types/CoreEventTypes';
+import Platform from '../../Utilities/Platform';
+import View from '../../Components/View/View';
+import * as React from 'react';
+
+type Props = $ReadOnly<{|
+ accessibilityActions?: ?$ReadOnlyArray,
+ accessibilityElementsHidden?: ?boolean,
+ accessibilityHint?: ?Stringish,
+ accessibilityIgnoresInvertColors?: ?boolean,
+ accessibilityLabel?: ?Stringish,
+ accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'),
+ accessibilityRole?: ?AccessibilityRole,
+ accessibilityState?: ?AccessibilityState,
+ accessibilityValue?: ?AccessibilityValue,
+ accessibilityViewIsModal?: ?boolean,
+ accessible?: ?boolean,
+ children?: ?React.Node,
+ delayLongPress?: ?number,
+ delayPressIn?: ?number,
+ delayPressOut?: ?number,
+ disabled?: ?boolean,
+ focusable?: ?boolean,
+ hitSlop?: ?EdgeInsetsProp,
+ importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
+ nativeID?: ?string,
+ onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
+ onBlur?: ?(event: BlurEvent) => mixed,
+ onFocus?: ?(event: FocusEvent) => mixed,
+ onLayout?: ?(event: LayoutEvent) => mixed,
+ onLongPress?: ?(event: PressEvent) => mixed,
+ onPress?: ?(event: PressEvent) => mixed,
+ onPressIn?: ?(event: PressEvent) => mixed,
+ onPressOut?: ?(event: PressEvent) => mixed,
+ pressRetentionOffset?: ?EdgeInsetsProp,
+ rejectResponderTermination?: ?boolean,
+ testID?: ?string,
+ touchSoundDisabled?: ?boolean,
+|}>;
+
+type State = $ReadOnly<{|
+ pressability: Pressability,
+|}>;
+
+const PASSTHROUGH_PROPS = [
+ 'accessibilityActions',
+ 'accessibilityElementsHidden',
+ 'accessibilityHint',
+ 'accessibilityIgnoresInvertColors',
+ 'accessibilityLabel',
+ 'accessibilityLiveRegion',
+ 'accessibilityRole',
+ 'accessibilityState',
+ 'accessibilityValue',
+ 'accessibilityViewIsModal',
+ 'hitSlop',
+ 'importantForAccessibility',
+ 'nativeID',
+ 'onAccessibilityAction',
+ 'onBlur',
+ 'onFocus',
+ 'onLayout',
+ 'testID',
+];
+
+class TouchableWithoutFeedback extends React.Component {
+ _tvTouchable: ?TVTouchable;
+
+ state: State = {
+ pressability: new Pressability({
+ getHitSlop: () => this.props.hitSlop,
+ getLongPressDelayMS: () => {
+ if (this.props.delayLongPress != null) {
+ const maybeNumber = this.props.delayLongPress;
+ if (typeof maybeNumber === 'number') {
+ return maybeNumber;
+ }
+ }
+ return 500;
+ },
+ getPressDelayMS: () => this.props.delayPressIn,
+ getPressOutDelayMS: () => this.props.delayPressOut,
+ getPressRectOffset: () => this.props.pressRetentionOffset,
+ getTouchSoundDisabled: () => this.props.touchSoundDisabled,
+ onBlur: event => {
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onLongPress: event => {
+ if (this.props.onLongPress != null) {
+ this.props.onLongPress(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ onPressIn: event => {
+ if (this.props.onPressIn != null) {
+ this.props.onPressIn(event);
+ }
+ },
+ onPressOut: event => {
+ if (this.props.onPressOut != null) {
+ this.props.onPressOut(event);
+ }
+ },
+ onResponderTerminationRequest: () =>
+ !this.props.rejectResponderTermination,
+ onStartShouldSetResponder: () => !this.props.disabled,
+ }),
+ };
+
+ render(): React.Node {
+ const element = React.Children.only(this.props.children);
+ const children = [element.props.children];
+ if (__DEV__) {
+ if (element.type === View) {
+ children.push(
+ ,
+ );
+ }
+ }
+
+ // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
+ // adopting `Pressability`, so preserve that behavior.
+ const {
+ onBlur,
+ onFocus,
+ ...eventHandlersWithoutBlurAndFocus
+ } = this.state.pressability.getEventHandlers();
+
+ const elementProps: {[string]: mixed, ...} = {
+ ...eventHandlersWithoutBlurAndFocus,
+ accessible: this.props.accessible !== false,
+ focusable:
+ this.props.focusable !== false && this.props.onPress !== undefined,
+ };
+ for (const prop of PASSTHROUGH_PROPS) {
+ if (this.props[prop] !== undefined) {
+ elementProps[prop] = this.props[prop];
+ }
+ }
+
+ return React.cloneElement(element, elementProps, ...children);
+ }
+
+ componentDidMount(): void {
+ if (Platform.isTV) {
+ this._tvTouchable = new TVTouchable(this, {
+ getDisabled: () => this.props.disabled === true,
+ onBlur: event => {
+ if (this.props.onBlur != null) {
+ this.props.onBlur(event);
+ }
+ },
+ onFocus: event => {
+ if (this.props.onFocus != null) {
+ this.props.onFocus(event);
+ }
+ },
+ onPress: event => {
+ if (this.props.onPress != null) {
+ this.props.onPress(event);
+ }
+ },
+ });
+ }
+ }
+
+ componentWillUnmount(): void {
+ if (Platform.isTV) {
+ if (this._tvTouchable != null) {
+ this._tvTouchable.destroy();
+ }
+ }
+ this.state.pressability.reset();
+ }
+}
+
+module.exports = TouchableWithoutFeedback;
diff --git a/packages/react-native-win32/src/Libraries/Components/View/PlatformViewPropTypes.win32.ts b/packages/react-native-win32/src/Libraries/Components/View/PlatformViewPropTypes.win32.ts
deleted file mode 100644
index a0a8cc12e63..00000000000
--- a/packages/react-native-win32/src/Libraries/Components/View/PlatformViewPropTypes.win32.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * @providesModule PlatformViewPropTypes
- * @flow
- */
-'use strict';
-
-export = {};
diff --git a/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewAttributes.win32.js b/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewAttributes.win32.js
index 656428f7b06..b580251fb28 100644
--- a/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewAttributes.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewAttributes.win32.js
@@ -19,8 +19,8 @@ const UIView = {
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRole: true,
- accessibilityStates: true,
accessibilityState: true,
+ accessibilityValue: true,
accessibilityHint: true,
importantForAccessibility: true,
nativeID: true,
diff --git a/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewViewConfig.win32.js b/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewViewConfig.win32.js
index 2733360b24a..0d58f3aaefe 100644
--- a/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewViewConfig.win32.js
+++ b/packages/react-native-win32/src/Libraries/Components/View/ReactNativeViewViewConfig.win32.js
@@ -16,8 +16,8 @@ const ReactNativeViewConfig = {
uiViewClassName: 'RCTView',
baseModuleName: null,
Manager: 'ViewManager',
- Commands: ({}: $TEMPORARY$object<{||}>),
- Constants: ({}: $TEMPORARY$object<{||}>),
+ Commands: ({}: {...}),
+ Constants: ({}: {...}),
bubblingEventTypes: {
...ReactNativeViewViewConfigAndroid.bubblingEventTypes,
topBlur: {
@@ -140,8 +140,9 @@ const ReactNativeViewConfig = {
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRole: true,
- accessibilityStates: true,
+ accessibilityStates: true, // TODO: Can be removed after next release
accessibilityState: true,
+ accessibilityValue: true,
accessibilityViewIsModal: true,
accessible: true,
alignContent: true,
diff --git a/packages/react-native-win32/src/Libraries/Image/Image.win32.js b/packages/react-native-win32/src/Libraries/Image/Image.win32.js
index 330e2f62749..f9d678a9259 100644
--- a/packages/react-native-win32/src/Libraries/Image/Image.win32.js
+++ b/packages/react-native-win32/src/Libraries/Image/Image.win32.js
@@ -7,28 +7,23 @@
* @flow
* @format
*/
+
'use strict';
const DeprecatedImagePropType = require('../DeprecatedPropTypes/DeprecatedImagePropType');
-const NativeModules = require('../BatchedBridge/NativeModules');
const React = require('react');
const ReactNative = require('../Renderer/shims/ReactNative'); // eslint-disable-line no-unused-vars
const StyleSheet = require('../StyleSheet/StyleSheet');
const flattenStyle = require('../StyleSheet/flattenStyle');
-const requireNativeComponent = require('../ReactNative/requireNativeComponent');
const resolveAssetSource = require('./resolveAssetSource');
-const ImageViewManager = NativeModules.ImageViewManager;
-
-// [Windows
-const ImageLoader = NativeModules.ImageLoader; // [Win32 uses ImageLoader for getSize]
-const RCTImageView = requireNativeComponent('RCTImage'); // [Win32] Uses RCTImage instead of RCTImageView
-// Windows]
-
import type {ImageProps as ImagePropsType} from './ImageProps';
import type {ImageStyleProp} from '../StyleSheet/StyleSheet';
+import NativeImageLoaderWin32 from './NativeImageLoaderWin32'; // [Win32] Replace iOS
+
+const RCTImageView = require('./ImageViewNativeComponent');
function getSize(
uri: string,
@@ -37,17 +32,17 @@ function getSize(
) {
//[Win32
/*
- ImageViewManager.getSize(
- uri,
- success,
- failure ||
- function() {
- console.warn('Failed to get size for image: ' + uri);
- },
- );
+ NativeNativeImageLoaderWin32IOS.getSize(uri)
+ .then(([width, height]) => success(width, height))
+ .catch(
+ failure ||
+ function() {
+ console.warn('Failed to get size for image ' + uri);
+ },
+ );
*/
- ImageLoader.getSize(uri, (width: number, height: number, err?: string) => {
+ NativeImageLoaderWin32.getSize(uri, (width: number, height: number, err?: string) => {
if (!err) {
success(width, height);
} else {
@@ -63,11 +58,11 @@ function getSize(
function getSizeWithHeaders(
uri: string,
- headers: {[string]: string},
+ headers: {[string]: string, ...},
success: (width: number, height: number) => void,
failure?: (error: any) => void,
): any {
- return ImageViewManager.getSizeWithHeaders({uri, headers})
+ return NativeImageLoaderWin32.getSizeWithHeaders(uri, headers)
.then(function(sizes) {
success(sizes.width, sizes.height);
})
@@ -80,23 +75,23 @@ function getSizeWithHeaders(
}
function prefetch(url: string): any {
- return ImageViewManager.prefetchImage(url);
+ return NativeImageLoaderWin32.prefetchImage(url);
}
async function queryCache(
urls: Array,
-): Promise<{[string]: 'memory' | 'disk' | 'disk/memory'}> {
- return await ImageViewManager.queryCache(urls);
+): Promise<{[string]: 'memory' | 'disk' | 'disk/memory', ...}> {
+ return await NativeImageLoaderWin32.queryCache(urls);
}
-declare class ImageComponentType extends ReactNative.NativeComponent {
- static getSize: typeof getSize;
- static getSizeWithHeaders: typeof getSizeWithHeaders;
- static prefetch: typeof prefetch;
- static queryCache: typeof queryCache;
- static resolveAssetSource: typeof resolveAssetSource;
- static propTypes: typeof DeprecatedImagePropType;
-}
+type ImageComponentStatics = $ReadOnly<{|
+ getSize: typeof getSize,
+ getSizeWithHeaders: typeof getSizeWithHeaders,
+ prefetch: typeof prefetch,
+ queryCache: typeof queryCache,
+ resolveAssetSource: typeof resolveAssetSource,
+ propTypes: typeof DeprecatedImagePropType,
+|}>;
/**
* A React component for displaying different types of images,
@@ -105,10 +100,7 @@ declare class ImageComponentType extends ReactNative.NativeComponent,
-) => {
+let Image = (props: ImagePropsType, forwardedRef) => {
const source = resolveAssetSource(props.source) || {
uri: undefined,
width: undefined,
@@ -159,7 +151,9 @@ let Image = (
);
};
-Image = React.forwardRef(Image);
+Image = React.forwardRef>(
+ Image,
+);
Image.displayName = 'Image';
/**
@@ -225,7 +219,8 @@ const styles = StyleSheet.create({
},
});
-/* $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. */
-module.exports = (Image: Class);
+module.exports = ((Image: any): React.AbstractComponent<
+ ImagePropsType,
+ React.ElementRef,
+> &
+ ImageComponentStatics);
diff --git a/packages/react-native-win32/src/Libraries/Image/ImageViewNativeComponent.win32.js b/packages/react-native-win32/src/Libraries/Image/ImageViewNativeComponent.win32.js
new file mode 100644
index 00000000000..6174c3ec5a0
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Image/ImageViewNativeComponent.win32.js
@@ -0,0 +1,54 @@
+/**
+ * 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
+ */
+
+'use strict';
+
+const requireNativeComponent = require('../ReactNative/requireNativeComponent');
+
+import codegenNativeComponent from '../Utilities/codegenNativeComponent';
+
+import type {DangerouslyImpreciseStyle} from '../StyleSheet/StyleSheet';
+import type {ResolvedAssetSource} from './AssetSourceResolver';
+import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
+import type {ImageProps} from './ImageProps';
+import type {ViewProps} from '../Components/View/ViewPropTypes';
+import type {ImageStyleProp} from '../StyleSheet/StyleSheet';
+import type {ColorValue} from '../StyleSheet/StyleSheetTypes';
+
+type NativeProps = $ReadOnly<{|
+ ...ImageProps,
+ ...ViewProps,
+
+ style?: ImageStyleProp | DangerouslyImpreciseStyle,
+
+ // iOS native props
+ tintColor?: ColorValue,
+
+ // Android native props
+ shouldNotifyLoadEvents?: boolean,
+ src?: ?ResolvedAssetSource | $ReadOnlyArray<{uri: string, ...}>,
+ headers?: ?string,
+ defaultSrc?: ?string,
+ loadingIndicatorSrc?: ?string,
+|}>;
+
+let ImageViewNativeComponent;
+
+if (global.RN$Bridgeless) {
+ ImageViewNativeComponent = codegenNativeComponent(
+ 'RCTImage', // [Win32] Rename RCTImageView to RCTImage
+ );
+} else {
+ ImageViewNativeComponent = requireNativeComponent(
+ 'RCTImage', // [Win32] Rename RCTImageView to RCTImage
+ );
+}
+
+module.exports = (ImageViewNativeComponent: HostComponent);
diff --git a/packages/react-native-win32/src/Libraries/Image/NativeImageLoaderWin32.js b/packages/react-native-win32/src/Libraries/Image/NativeImageLoaderWin32.js
new file mode 100644
index 00000000000..a3cd9baf480
--- /dev/null
+++ b/packages/react-native-win32/src/Libraries/Image/NativeImageLoaderWin32.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ * @format
+ */
+
+'use strict';
+
+import type {TurboModule} from '../TurboModule/RCTExport';
+import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
+
+export interface Spec extends TurboModule {
+ +getConstants: () => {||};
+ // [Win32 uses callback instead of promise
+ +getSize: (uri: string, callback: (width: number, height: number, err?: string) => void) => void;
+ // Win32]
+
+ // [Win32 These aren't actually implemented, and will just blow up if called
+ // currently.
+ +getSizeWithHeaders: (
+ uri: string,
+ headers: Object,
+ ) => Promise<{
+ width: number,
+ height: number,
+ ...
+ }>;
+ +prefetchImage: (uri: string) => Promise;
+ +queryCache: (uris: Array) => Promise