diff --git a/.ado/android-pr.yml b/.ado/android-pr.yml index ff205fdf4ba872..2734e7c0857ff9 100644 --- a/.ado/android-pr.yml +++ b/.ado/android-pr.yml @@ -16,7 +16,7 @@ jobs: - job: AndroidRNPR displayName: Android React Native PR pool: - vmImage: vs2017-win2016 + vmImage: ubuntu-18.04 timeoutInMinutes: 90 # how long to run the job before automatically cancelling cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them steps: @@ -27,18 +27,27 @@ jobs: submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules persistCredentials: false # set to 'true' to leave the OAuth token in the Git config after the initial fetch + - task: UseNode@1 + inputs: + version: '12.x' + - template: templates/apple-droid-node-patching.yml parameters: apply_office_patches: true - - task: UseNode@1 + # Install NuGet + - task: CmdLine@2 inputs: - version: '10.x' + script: mkdir $(System.DefaultWorkingDirectory)/nuget-bin/ && curl -o $(System.DefaultWorkingDirectory)/nuget-bin/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + - # Install NuGet v4.6.4+ - - task: NuGetToolInstaller@1 + # This is currently required as the command task (strangely) always runs elevated .. + # which makes all the files touched by the above patching step unreadable by following non-command tasks without sudo. + # This makes all the files readable. + - task: CmdLine@2 + displayName: chmod inputs: - versionSpec: '>=4.6.4' + script: chmod -R +r . - task: CmdLine@2 displayName: "Rename package to react-native" @@ -50,119 +59,41 @@ jobs: inputs: script: npm install - - task: NuGetCommand@2 - displayName: NuGet restore - inputs: - command: restore - restoreSolution: ReactAndroid/packages.config - feedsToUse: config - #vstsFeed: # Required when feedsToUse == Select - #includeNuGetOrg: true # Required when feedsToUse == Select - nugetConfigPath: ReactAndroid/NuGet.Config - #externalFeedCredentials: # Optional - #noCache: false - #disableParallelProcessing: false - restoreDirectory: packages/ - verbosityRestore: Detailed # Options: quiet, normal, detailed - #packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' # Required when command == Push - #nuGetFeedType: 'internal' # Required when command == Push# Options: internal, external - #publishVstsFeed: # Required when command == Push && NuGetFeedType == Internal - #publishPackageMetadata: true # Optional - #allowPackageConflicts: # Optional - #publishFeedCredentials: # Required when command == Push && NuGetFeedType == External - #verbosityPush: 'Detailed' # Options: quiet, normal, detailed - #packagesToPack: '**/*.csproj' # Required when command == Pack - #configuration: '$(BuildConfiguration)' # Optional - #packDestination: '$(Build.ArtifactStagingDirectory)' # Optional - #versioningScheme: 'off' # Options: off, byPrereleaseNumber, byEnvVar, byBuildNumber - #includeReferencedProjects: false # Optional - #versionEnvVar: # Required when versioningScheme == ByEnvVar - #majorVersion: '1' # Required when versioningScheme == ByPrereleaseNumber - #minorVersion: '0' # Required when versioningScheme == ByPrereleaseNumber - #patchVersion: '0' # Required when versioningScheme == ByPrereleaseNumber - #packTimezone: 'utc' # Required when versioningScheme == ByPrereleaseNumber# Options: utc, local - #includeSymbols: false # Optional - #toolPackage: # Optional - #buildProperties: # Optional - #basePath: # Optional - #verbosityPack: 'Detailed' # Options: quiet, normal, detailed - #arguments: # Required when command == Custom - -# - task: CmdLine@2 -# displayName: Setup Build Dependencies -# inputs: -# script: .ado\setup_droid_deps.bat - - task: CmdLine@2 displayName: Remove RNTesterApp.android.bundle inputs: - script: del RNTesterApp.android.bundle + script: rm -f RNTesterApp.android.bundle - task: CmdLine@2 displayName: Create RNTester bundle inputs: - script: node cli.js bundle --entry-file .\RNTester\js\RNTesterApp.android.js --bundle-output RNTesterApp.android.bundle --platform android + script: node cli.js bundle --entry-file ./RNTester/js/RNTesterApp.android.js --bundle-output RNTesterApp.android.bundle --platform android - - task: Gradle@1 + - task: CmdLine@2 displayName: gradlew installArchives -# env: -# REACT_NATIVE_DEPENDENCIES: $(System.DefaultWorkingDirectory)\build_deps inputs: - gradleWrapperFile: gradlew - # workingDirectory: src\react-native\ - options: -Pparam="excludeLibs" - tasks: installArchives - publishJUnitResults: false - #testResultsFiles: '**/TEST-*.xml' # Required when publishJUnitResults == True - #testRunTitle: # Optional - #codeCoverageToolOption: 'None' # Optional. Options: none, cobertura, jaCoCo - #codeCoverageClassFilesDirectories: 'build/classes/main/' # Required when codeCoverageToolOption == False - #codeCoverageClassFilter: # Optional - #codeCoverageFailIfEmpty: false # Optional - #javaHomeOption: 'JDKVersion' # Options: jDKVersion, path - #jdkVersionOption: 'default' # Optional. Options: default, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6 - #jdkDirectory: # Required when javaHomeOption == Path - #jdkArchitectureOption: 'x64' # Optional. Options: x86, x64 - #gradleOptions: '-Xmx1024m' # Optional - #sonarQubeRunAnalysis: false - #sqGradlePluginVersionChoice: 'specify' # Required when sonarQubeRunAnalysis == True# Options: specify, build - #sonarQubeGradlePluginVersion: '2.6.1' # Required when sonarQubeRunAnalysis == True && SqGradlePluginVersionChoice == Specify - #checkStyleRunAnalysis: false # Optional - #findBugsRunAnalysis: false # Optional - #pmdRunAnalysis: false # Optional + script: ./gradlew installArchives -Pparam="excludeLibs" - template: templates\prep-android-nuget.yml - - task: NuGetCommand@2 + # Very similar to the default pack task .. but appends 'ndk21' to the nuget pack version + - task: CmdLine@2 displayName: 'Verify NuGet can be packed' inputs: - command: pack - packagesToPack: 'ReactAndroid/ReactAndroid.nuspec' - packDestination: '$(System.DefaultWorkingDirectory)' - buildProperties: buildNumber=$(buildNumber);commitId=$(Build.SourceVersion) + script: NDK=ndk`cat ${ANDROID_SDK_ROOT}/ndk-bundle/source.properties 2>&1 | grep Pkg.Revision | awk '{ print $3}' | awk -F. '{ print $1 }'`; mono $(System.DefaultWorkingDirectory)/nuget-bin/nuget.exe pack $(System.DefaultWorkingDirectory)/ReactAndroid/ReactAndroid.nuspec -OutputDirectory $(System.DefaultWorkingDirectory) -Properties buildNumber=$(buildNumber)-$NDK;commitId=$(Build.SourceVersion) - - task: Gradle@1 + - task: CmdLine@2 + displayName: 'Npm pack' + inputs: + script: node .ado/npmOfficePack.js --fake + env: + BUILD_STAGINGDIRECTORY: $(Build.StagingDirectory) + BUILD_SOURCESDIRECTORY: $(Build.SourcesDirectory) + BUILD_SOURCEBRANCH: $(Build.SourceBranch) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + githubApiToken: $(githubApiToken) + + - task: CmdLine@2 displayName: gradlew clean inputs: - gradleWrapperFile: gradlew - # workingDirectory: src\ - #options: # Optional - tasks: clean - publishJUnitResults: false - #testResultsFiles: '**/build/test-results/TEST-*.xml' - #testRunTitle: # Optional - #codeCoverageToolOption: 'None' # Optional. Options: none, cobertura, jaCoCo - #codeCoverageClassFilesDirectories: 'build/classes/main/' # Required when codeCoverageToolOption == False - #codeCoverageClassFilter: # Optional - #codeCoverageFailIfEmpty: false # Optional - #javaHomeOption: 'JDKVersion' # Options: jDKVersion, path - #jdkVersionOption: 'default' # Optional. Options: default, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6 - #jdkDirectory: # Required when javaHomeOption == Path - #jdkArchitectureOption: 'x64' # Optional. Options: x86, x64 - #gradleOptions: '-Xmx1024m' # Optional - #sonarQubeRunAnalysis: false - #sqGradlePluginVersionChoice: 'specify' # Required when sonarQubeRunAnalysis == True# Options: specify, build - #sonarQubeGradlePluginVersion: '2.6.1' # Required when sonarQubeRunAnalysis == True && SqGradlePluginVersionChoice == Specify - #checkStyleRunAnalysis: false # Optional - #findBugsRunAnalysis: false # Optional - #pmdRunAnalysis: false # Optional \ No newline at end of file + script: ./gradlew clean \ No newline at end of file diff --git a/.ado/android_symlink_ndk.bat b/.ado/android_symlink_ndk.bat new file mode 100644 index 00000000000000..157a6ffa8fc294 --- /dev/null +++ b/.ado/android_symlink_ndk.bat @@ -0,0 +1,28 @@ +REM @if "%DEBUG%" == "" @echo off + +REM Android tools doesn't support SDK/NDK paths containing space in it. +REM This scrip creates a symlink to Android NDK to work around the limitation. +REM We use the + +set ANDROID_NDK_SYMLINK_PATH=c:\android_ndk_symlink__ + +REM 1. Try ANDROID_NDK environment variable +set ANDROID_NDK_PATH=%ANDROID_NDK% + +REM 2. May be SDK has ndk-bundle in it. +IF "%ANDROID_NDK_PATH%"=="" set ANDROID_NDK_PATH=%ANDROID_home%\ndk-bundle + +echo %ANDROID_NDK_PATH% + +if exist %ANDROID_NDK_PATH% ( + mklink /J %ANDROID_NDK_SYMLINK_PATH% "%ANDROID_NDK_PATH%" + goto :success +) else ( + goto :error +) + +:success +exit /b 0 + +:error +exit /b 1 \ No newline at end of file diff --git a/.ado/npmOfficePack.js b/.ado/npmOfficePack.js index 8398987bbae0a7..70107c1842d0cb 100644 --- a/.ado/npmOfficePack.js +++ b/.ado/npmOfficePack.js @@ -19,7 +19,7 @@ function exec(command) { } } -function doPublish() { +function doPublish(fakeMode) { console.log(`Target branch to publish to: ${publishBranchName}`); const {releaseVersion, branchVersionSuffix} = gatherVersionInfo() @@ -27,7 +27,7 @@ function doPublish() { const onlyTagSource = !!branchVersionSuffix; if (!onlyTagSource) { // -------- Generating Android Artifacts with JavaDoc - exec("gradlew installArchives"); + exec(path.join(process.env.BUILD_SOURCESDIRECTORY, "gradlew") + " installArchives"); // undo uncommenting javadoc setting exec("git checkout ReactAndroid/gradle.properties"); @@ -40,7 +40,19 @@ function doPublish() { const npmTarPath = path.resolve(__dirname, '..', npmTarFileName); const finalTarPath = path.join(process.env.BUILD_STAGINGDIRECTORY, 'final', npmTarFileName); console.log(`Copying tar file ${npmTarPath} to: ${finalTarPath}`) - fs.copyFileSync(npmTarPath, finalTarPath); + + if(fakeMode) { + if (!fs.existsSync(npmTarPath)) + throw "The final artefact to be published is missing."; + } else { + fs.copyFileSync(npmTarPath, finalTarPath); + } } -doPublish(); \ No newline at end of file +var args = process.argv.slice(2); + +let fakeMode = false; +if (args.length > 0 && args[0] === '--fake') + fakeMode = true; + +doPublish(fakeMode); \ No newline at end of file diff --git a/.ado/publish.yml b/.ado/publish.yml index 65be2bc2d9422d..b78a2acd575063 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -103,7 +103,7 @@ jobs: - job: RNGithubOfficePublish displayName: React-Native GitHub Publish to Office pool: - vmImage: vs2017-win2016 + vmImage: ubuntu-18.04 timeoutInMinutes: 90 # how long to run the job before automatically cancelling cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them steps: @@ -114,14 +114,27 @@ jobs: submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules persistCredentials: true # set to 'true' to leave the OAuth token in the Git config after the initial fetch + - task: UseNode@1 + inputs: + version: '12.x' + - template: templates/apple-droid-node-patching.yml parameters: apply_office_patches: true - # Install NuGet v4.6.4+ - - task: NuGetToolInstaller@1 + # This is currently required as the command task (strangely) always runs elevated .. + # which makes all the files touched by the above patching step unreadable by following non-command tasks without sudo. + # This makes all the files readable. + - task: CmdLine@2 + displayName: chmod + inputs: + script: chmod -R +r . + + # Install NuGet + - task: CmdLine@2 inputs: - versionSpec: '>=4.6.4' + script: mkdir $(System.DefaultWorkingDirectory)/nuget-bin/ && curl -o $(System.DefaultWorkingDirectory)/nuget-bin/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + continueOnError: true - task: CmdLine@2 displayName: "Rename package to react-native" @@ -138,86 +151,18 @@ jobs: inputs: script: node .ado/bumpOfficeFileVersions.js - - task: NuGetCommand@2 - displayName: NuGet restore - inputs: - command: restore - restoreSolution: ReactAndroid/packages.config - feedsToUse: config - #vstsFeed: # Required when feedsToUse == Select - #includeNuGetOrg: true # Required when feedsToUse == Select - nugetConfigPath: ReactAndroid/NuGet.Config - #externalFeedCredentials: # Optional - #noCache: false - #disableParallelProcessing: false - restoreDirectory: packages/ - verbosityRestore: Detailed # Options: quiet, normal, detailed - #packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' # Required when command == Push - #nuGetFeedType: 'internal' # Required when command == Push# Options: internal, external - #publishVstsFeed: # Required when command == Push && NuGetFeedType == Internal - #publishPackageMetadata: true # Optional - #allowPackageConflicts: # Optional - #publishFeedCredentials: # Required when command == Push && NuGetFeedType == External - #verbosityPush: 'Detailed' # Options: quiet, normal, detailed - #packagesToPack: '**/*.csproj' # Required when command == Pack - #configuration: '$(BuildConfiguration)' # Optional - #packDestination: '$(Build.ArtifactStagingDirectory)' # Optional - #versioningScheme: 'off' # Options: off, byPrereleaseNumber, byEnvVar, byBuildNumber - #includeReferencedProjects: false # Optional - #versionEnvVar: # Required when versioningScheme == ByEnvVar - #majorVersion: '1' # Required when versioningScheme == ByPrereleaseNumber - #minorVersion: '0' # Required when versioningScheme == ByPrereleaseNumber - #patchVersion: '0' # Required when versioningScheme == ByPrereleaseNumber - #packTimezone: 'utc' # Required when versioningScheme == ByPrereleaseNumber# Options: utc, local - #includeSymbols: false # Optional - #toolPackage: # Optional - #buildProperties: # Optional - #basePath: # Optional - #verbosityPack: 'Detailed' # Options: quiet, normal, detailed - #arguments: # Required when command == Custom - -# - task: CmdLine@2 -# displayName: Setup Build Dependencies -# inputs: -# script: .ado\setup_droid_deps.bat - - - task: Gradle@1 + - task: CmdLine@2 displayName: gradlew installArchives -# env: -# REACT_NATIVE_DEPENDENCIES: $(System.DefaultWorkingDirectory)\build_deps - inputs: - gradleWrapperFile: gradlew - # workingDirectory: src\react-native\ - options: -Pparam="excludeLibs" - tasks: installArchives - publishJUnitResults: false - #testResultsFiles: '**/TEST-*.xml' # Required when publishJUnitResults == True - #testRunTitle: # Optional - #codeCoverageToolOption: 'None' # Optional. Options: none, cobertura, jaCoCo - #codeCoverageClassFilesDirectories: 'build/classes/main/' # Required when codeCoverageToolOption == False - #codeCoverageClassFilter: # Optional - #codeCoverageFailIfEmpty: false # Optional - #javaHomeOption: 'JDKVersion' # Options: jDKVersion, path - #jdkVersionOption: 'default' # Optional. Options: default, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6 - #jdkDirectory: # Required when javaHomeOption == Path - #jdkArchitectureOption: 'x64' # Optional. Options: x86, x64 - #gradleOptions: '-Xmx1024m' # Optional - #sonarQubeRunAnalysis: false - #sqGradlePluginVersionChoice: 'specify' # Required when sonarQubeRunAnalysis == True# Options: specify, build - #sonarQubeGradlePluginVersion: '2.6.1' # Required when sonarQubeRunAnalysis == True && SqGradlePluginVersionChoice == Specify - #checkStyleRunAnalysis: false # Optional - #findBugsRunAnalysis: false # Optional - #pmdRunAnalysis: false # Optional + inputs: + script: ./gradlew installArchives -Pparam="excludeLibs" - template: templates\prep-android-nuget.yml - - task: NuGetCommand@2 + # Very similar to the default pack task .. but appends 'ndk21b' to the nuget pack version + - task: CmdLine@2 displayName: 'NuGet pack' inputs: - command: pack - packagesToPack: 'ReactAndroid/ReactAndroid.nuspec' - packDestination: '$(Build.StagingDirectory)\final' - buildProperties: buildNumber=$(buildNumber);commitId=$(Build.SourceVersion) + script: OUTPUT_DIR=$(Build.StagingDirectory)/final;NDK=ndk`cat ${ANDROID_SDK_ROOT}/ndk-bundle/source.properties 2>&1 | grep Pkg.Revision | awk '{ print $3}' | awk -F. '{ print $1 }'`; mono $(System.DefaultWorkingDirectory)/nuget-bin/nuget.exe pack $(System.DefaultWorkingDirectory)/ReactAndroid/ReactAndroid.nuspec -OutputDirectory $OUTPUT_DIR -Properties buildNumber=$(buildNumber)-$NDK;commitId=$(Build.SourceVersion) - task: CmdLine@2 displayName: 'Npm pack' @@ -225,6 +170,7 @@ jobs: script: node .ado/npmOfficePack.js env: BUILD_STAGINGDIRECTORY: $(Build.StagingDirectory) + BUILD_SOURCESDIRECTORY: $(Build.SourcesDirectory) BUILD_SOURCEBRANCH: $(Build.SourceBranch) SYSTEM_ACCESSTOKEN: $(System.AccessToken) githubApiToken: $(githubApiToken) @@ -232,5 +178,5 @@ jobs: - task: PublishBuildArtifacts@1 displayName: 'Publish final artifacts' inputs: - PathtoPublish: '$(Build.StagingDirectory)\final' - ArtifactName: 'ReactNative-Final' + PathtoPublish: '$(Build.StagingDirectory)/final' + ArtifactName: 'ReactNative-Final' diff --git a/.ado/setup_droid_deps.bat b/.ado/setup_droid_deps.bat deleted file mode 100644 index 984c87016322fd..00000000000000 --- a/.ado/setup_droid_deps.bat +++ /dev/null @@ -1,20 +0,0 @@ -@if "%DEBUG%" == "" @echo off - -REM Assuming the script is run from the root directory of a local clone of Microsoft fork of react-native. i.e. http:\\github.com\Microsoft\react-native - -set BUILD_DEPS_DIR=build_deps - -IF EXIST %BUILD_DEPS_DIR% ( - rmdir /s /q %BUILD_DEPS_DIR% - if errorlevel 1 echo "Cleaning up the build dependency directory failed !" 1>&2 -) - -mkdir %BUILD_DEPS_DIR% - -mklink /D /J %BUILD_DEPS_DIR%\boost ReactAndroid\packages\boost.1.68.0.0\lib\native\include\boost -mklink /D /J %BUILD_DEPS_DIR%\double-conversion double-conversion\double-conversion -mklink /D /J %BUILD_DEPS_DIR%\Folly Folly\ -mklink /D /J %BUILD_DEPS_DIR%\glog glog - -REM When setting up locally, set the environement variable as follows. -REM set REACT_NATIVE_DEPENDENCIES=%CD%\%BUILD_DEPS_DIR% \ No newline at end of file diff --git a/.ado/setup_droid_deps.sh b/.ado/setup_droid_deps.sh new file mode 100755 index 00000000000000..0949966cffa5f0 --- /dev/null +++ b/.ado/setup_droid_deps.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +BUILD_DEPS_DIR=build_deps + +rm -rf $BUILD_DEPS_DIR +mkdir $BUILD_DEPS_DIR + +ln -s "$PWD/ReactAndroid/packages/boost.1.68.0.0/lib/native/include/boost" "$BUILD_DEPS_DIR/boost" +ln -s "$PWD/double-conversion/double-conversion" "$BUILD_DEPS_DIR/double-conversion" +ln -s "$PWD/Folly/" "$BUILD_DEPS_DIR/Folly" +ln -s "$PWD/glog/" "$BUILD_DEPS_DIR/glog" \ No newline at end of file diff --git a/.ado/templates/apple-droid-node-patching.yml b/.ado/templates/apple-droid-node-patching.yml index 3b56bbbb9a99fb..6548bd8bff5214 100644 --- a/.ado/templates/apple-droid-node-patching.yml +++ b/.ado/templates/apple-droid-node-patching.yml @@ -5,4 +5,4 @@ steps: - task: CmdLine@2 displayName: Apply Android specific patches for Office consumption inputs: - script: node $(System.DefaultWorkingDirectory)/android-patches/bundle/bundle.js patch $(System.DefaultWorkingDirectory) BasicBuild V8Integration --patch-store $(System.DefaultWorkingDirectory)/android-patches/patches-0.61.5 --log-folder $(System.DefaultWorkingDirectory)/android-patches/logs --confirm ${{ parameters.apply_office_patches }} + script: node $(System.DefaultWorkingDirectory)/android-patches/bundle/bundle.js patch $(System.DefaultWorkingDirectory) BasicBuild --patch-store $(System.DefaultWorkingDirectory)/android-patches/patches-0.61.5 --log-folder $(System.DefaultWorkingDirectory)/android-patches/logs --confirm ${{ parameters.apply_office_patches }} diff --git a/.ado/templates/apple-job-javascript.yml b/.ado/templates/apple-job-javascript.yml index db58da8dffbcaa..fb4a65b92e6dab 100644 --- a/.ado/templates/apple-job-javascript.yml +++ b/.ado/templates/apple-job-javascript.yml @@ -2,11 +2,14 @@ parameters: apply_office_patches: '' steps: + - script: 'brew update' + displayName: 'brew update' + - script: 'brew bundle' displayName: 'brew bundle' - - script: brew link node@10 --overwrite --force - displayName: 'ensure node 10' + - script: brew link node@12 --overwrite --force + displayName: 'ensure node 12' - template: apple-xcode-select.yml diff --git a/.ado/templates/apple-job-react-native.yml b/.ado/templates/apple-job-react-native.yml index 32192ab8e36f71..3b1818b396fa61 100644 --- a/.ado/templates/apple-job-react-native.yml +++ b/.ado/templates/apple-job-react-native.yml @@ -15,10 +15,14 @@ steps: displayName: 'Clean DerivedData' # Install the required components specified in the Brewfile file. + - script: 'brew update' + displayName: 'brew update' + - script: 'brew bundle' displayName: 'brew bundle' - - script: brew link node@10 --overwrite --force + - script: brew link node@12 --overwrite --force + displayName: 'ensure node 12' # Task Group: XCode select proper version - template: apple-xcode-select.yml diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000000..f9d0bf0b218d26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,34 @@ +--- +name: React Native for macOS feature request +about: Suggest a new feature or idea +title: Your feature request +labels: Proposal +assignees: '' + +--- + + + +# Proposal: [your title here] + + +## Summary + + +## Motivation + + + +## Basic example + + + +## Open Questions + + diff --git a/.github/ISSUE_TEMPLATE/module_request.md b/.github/ISSUE_TEMPLATE/module_request.md new file mode 100644 index 00000000000000..f7cd96c071ee78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/module_request.md @@ -0,0 +1,28 @@ +--- +name: React Native for macOS Community Module support request +about: Suggest that React Native for macOS support be added to a community module +title: Support react-native-foo +labels: Extensions +assignees: '' + +--- + + + +# Support `react-native-foo` on React Native for macOS + + +## Basic Info +- Module Repo URL: [link to repo]() +- Target version: +- [ ] Issue exists on module repo (if so, [link]()) + + +## Which app/customer is requesting this module? + + +## In what scenarios will this module be used? + + +## Essential Features + diff --git a/Brewfile b/Brewfile index 02dfa1bb2163e5..21eb8b1a032f48 100644 --- a/Brewfile +++ b/Brewfile @@ -1,2 +1,2 @@ -brew "node@10" +brew "node@12" brew "watchman" diff --git a/Brewfile.lock.json b/Brewfile.lock.json new file mode 100644 index 00000000000000..5ae86877f508d6 --- /dev/null +++ b/Brewfile.lock.json @@ -0,0 +1,60 @@ +{ + "entries": { + "brew": { + "node@12": { + "version": "12.18.3", + "bottle": { + "cellar": ":any", + "prefix": "/usr/local", + "files": { + "catalina": { + "url": "https://homebrew.bintray.com/bottles/node%4012-12.18.3.catalina.bottle.tar.gz", + "sha256": "8371625cae6cd2efa83e52a49d3c2e389e8d6be8261a0c80a710750e89ddf7d8" + }, + "mojave": { + "url": "https://homebrew.bintray.com/bottles/node%4012-12.18.3.mojave.bottle.tar.gz", + "sha256": "f410b99756e3247145b43f2775d4ad00b99dcdf4366fac74c05daef7c771cb60" + }, + "high_sierra": { + "url": "https://homebrew.bintray.com/bottles/node%4012-12.18.3.high_sierra.bottle.tar.gz", + "sha256": "5de456604c237daac3f9d3dd8b03cb13ce8d7c3efacf644093a4a3da4f8f1a53" + } + } + } + }, + "watchman": { + "version": "4.9.0_4", + "bottle": { + "cellar": "/usr/local/Cellar", + "prefix": "/usr/local", + "files": { + "catalina": { + "url": "https://homebrew.bintray.com/bottles/watchman-4.9.0_4.catalina.bottle.tar.gz", + "sha256": "7840f564c11d33425c9eb8985f9156e782e66ef2a3578329dba83ee15a9bf0be" + }, + "mojave": { + "url": "https://homebrew.bintray.com/bottles/watchman-4.9.0_4.mojave.bottle.tar.gz", + "sha256": "ba2338b0f23c8b8817fd7bfa92466b7a97ab416e93ec6c3a400041aef013de86" + }, + "high_sierra": { + "url": "https://homebrew.bintray.com/bottles/watchman-4.9.0_4.high_sierra.bottle.tar.gz", + "sha256": "150468653b5c5a8e4eb92a3ecf420f157ed0e4772513ee93425bb3f635964dad" + } + } + } + } + } + }, + "system": { + "macos": { + "catalina": { + "HOMEBREW_VERSION": "2.4.9", + "HOMEBREW_PREFIX": "/usr/local", + "Homebrew/homebrew-core": "8a19447722a5bd15a5ce806d5d35178f30990e4d", + "CLT": "", + "Xcode": "11.5", + "macOS": "10.15.6" + } + } + } +} diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js index 3d3ee07711ea31..f80a18669cb883 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.macos.js @@ -12,10 +12,29 @@ 'use strict'; +const Promise = require('../../Promise'); +const RCTDeviceEventEmitter = require('../../EventEmitter/RCTDeviceEventEmitter'); + +import NativeAccessibilityManager from './NativeAccessibilityManager'; + const warning = require('fbjs/lib/warning'); -type ChangeEventName = $Keys<{}>; +const CHANGE_EVENT_NAME = { + invertColorsChanged: 'invertColorsChanged', + reduceMotionChanged: 'reduceMotionChanged', + reduceTransparencyChanged: 'reduceTransparencyChanged', + screenReaderChanged: 'screenReaderChanged', +}; +type ChangeEventName = $Keys<{ + change: string, + invertColorsChanged: string, + reduceMotionChanged: string, + reduceTransparencyChanged: string, + screenReaderChanged: string, +}>; + +const _subscriptions = new Map(); const AccessibilityInfo = { /** * iOS only @@ -32,31 +51,78 @@ const AccessibilityInfo = { }, /** - * iOS only + * Query whether inverted colors are currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when invert color is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isInvertColorsEnabled */ isInvertColorsEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentInvertColorsState(resolve, reject); + } else { + reject(reject); + } + }); }, /** - * Android and iOS only + * Query whether reduced motion is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce motion is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isReduceMotionEnabled */ isReduceMotionEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentReduceMotionState(resolve, reject); + } else { + reject(reject); + } + }); }, /** - * iOS only + * Query whether reduced transparency is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a reduce transparency is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isReduceTransparencyEnabled */ isReduceTransparencyEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentReduceTransparencyState( + resolve, + reject, + ); + } else { + reject(reject); + } + }); }, /** - * Android and iOS only + * Query whether a screen reader is currently enabled. + * + * Returns a promise which resolves to a boolean. + * The result is `true` when a screen reader is enabled and `false` otherwise. + * + * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#isScreenReaderEnabled */ isScreenReaderEnabled: function(): Promise { - return Promise.resolve(false); + return new Promise((resolve, reject) => { + if (NativeAccessibilityManager) { + NativeAccessibilityManager.getCurrentVoiceOverState(resolve, reject); + } else { + reject(reject); + } + }); }, /** @@ -71,15 +137,38 @@ const AccessibilityInfo = { addEventListener: function( eventName: ChangeEventName, handler: Function, - ): void { - warning(false, 'AccessibilityInfo is not supported on this platform.'); + ): Object { + let listener; + + if (eventName === 'change') { + listener = RCTDeviceEventEmitter.addListener( + CHANGE_EVENT_NAME.screenReaderChanged, + handler, + ); + } else if (CHANGE_EVENT_NAME[eventName]) { + listener = RCTDeviceEventEmitter.addListener(eventName, handler); + } + + _subscriptions.set(handler, listener); + return { + remove: AccessibilityInfo.removeEventListener.bind( + null, + eventName, + handler, + ), + }; }, removeEventListener: function( eventName: ChangeEventName, handler: Function, ): void { - warning(false, 'AccessibilityInfo is not supported on this platform.'); + const listener = _subscriptions.get(handler); + if (!listener) { + return; + } + listener.remove(); + _subscriptions.delete(handler); }, /** @@ -88,7 +177,9 @@ const AccessibilityInfo = { * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#setaccessibilityfocus */ setAccessibilityFocus: function(reactTag: number): void { - warning(false, 'AccessibilityInfo is not supported on this platform.'); + if (NativeAccessibilityManager) { + NativeAccessibilityManager.setAccessibilityFocus(reactTag); + } }, /** @@ -97,7 +188,9 @@ const AccessibilityInfo = { * See http://facebook.github.io/react-native/docs/accessibilityinfo.html#announceforaccessibility */ announceForAccessibility: function(announcement: string): void { - warning(false, 'AccessibilityInfo is not supported on this platform.'); + if (NativeAccessibilityManager) { + NativeAccessibilityManager.announceForAccessibility(announcement); + } }, }; diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js index 8e5fae6eafa4c1..f1811c25192d93 100644 --- a/Libraries/Components/Button.js +++ b/Libraries/Components/Button.js @@ -21,6 +21,7 @@ const View = require('./View/View'); const invariant = require('invariant'); import type {PressEvent} from '../Types/CoreEventTypes'; +import type {FocusEvent, BlurEvent} from './TextInput/TextInput'; // TODO(OSS Candidate ISS#2710739) type ButtonProps = $ReadOnly<{| /** @@ -100,6 +101,18 @@ type ButtonProps = $ReadOnly<{| * Used to locate this view in end-to-end tests. */ testID?: ?string, + + // [TODO(OSS Candidate ISS#2710739) + /** + * Handler to be called when the button receives key focus + */ + onBlur?: ?(e: BlurEvent) => void, + + /** + * Handler to be called when the button loses key focus + */ + onFocus?: ?(e: FocusEvent) => void, + // ]TODO(OSS Candidate ISS#2710739) |}>; /** @@ -147,6 +160,8 @@ class Button extends React.Component { nextFocusUp, disabled, testID, + onFocus, // TODO(OSS Candidate ISS#2710739) + onBlur, // TODO(OSS Candidate ISS#2710739) } = this.props; const buttonStyles = [styles.button]; const textStyles = [styles.text]; @@ -189,6 +204,8 @@ class Button extends React.Component { testID={testID} disabled={disabled} onPress={onPress} + onFocus={onFocus} // TODO(OSS Candidate ISS#2710739) + onBlur={onBlur} // TODO(OSS Candidate ISS#2710739) touchSoundDisabled={touchSoundDisabled}> diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index e0301cbcf26e64..388f1c007b2665 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -1025,6 +1025,8 @@ const TextInput = createReactClass({ {this.props.children} diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index b7c8bf1158737f..5b19eb18692995 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -454,6 +454,8 @@ const TouchableHighlight = ((createReactClass({ onDragEnter={this.props.onDragEnter} onDragLeave={this.props.onDragLeave} onDrop={this.props.onDrop} + onFocus={this.props.onFocus} + onBlur={this.props.onBlur} draggedTypes={this.props.draggedTypes} // ]TODO(macOS/win ISS#2323203) nativeID={this.props.nativeID} testID={this.props.testID}> diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 5a8ae9d3d19c45..ae5fb4efbad055 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -355,6 +355,8 @@ const TouchableOpacity = ((createReactClass({ onDragEnter={this.props.onDragEnter} onDragLeave={this.props.onDragLeave} onDrop={this.props.onDrop} + onFocus={this.props.onFocus} + onBlur={this.props.onBlur} draggedTypes={this.props.draggedTypes} // ]TODO(macOS ISS#2323203) onResponderTerminate={this.touchableHandleResponderTerminate}> {this.props.children} diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 9cf5530d57065d..5584507e50dd4b 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -351,6 +351,8 @@ const TouchableWithoutFeedback = ((createReactClass({ onDragEnter: this.props.onDragEnter, onDragLeave: this.props.onDragLeave, onDrop: this.props.onDrop, + onFocus: this.props.onFocus, + onBlur: this.props.onBlur, draggedTypes: this.props.draggedTypes, // ]TODO(macOS ISS#2323203) children, }); diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index c3e2df9d0dc86e..ea09a7a980d46b 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -619,6 +619,11 @@ - (void)windowDidChangeBackingProperties:(NSNotification *)notification [self reloadImage]; } +- (RCTPlatformView *)reactAccessibilityElement +{ + return (RCTPlatformView *)_imageView.cell; +} + - (NSColor *)tintColor { NSColor *tintColor = nil; diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index e7dfb1dcd4d616..9ec09b0f801803 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -2021,6 +2021,7 @@ class CellRenderer extends React.Component< return renderItem({ item, index, + isSelected, // TODO(macOS ISS#2323203) separators: this._separators, }); } diff --git a/Libraries/Text/Text/RCTTextView.h b/Libraries/Text/Text/RCTTextView.h index 7edc12ffe8f9b9..26b74c11743307 100644 --- a/Libraries/Text/Text/RCTTextView.h +++ b/Libraries/Text/Text/RCTTextView.h @@ -6,6 +6,7 @@ */ #import +#import // TODO(OSS Candidate ISS#2710739) #import // TODO(macOS ISS#2323203) @@ -13,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTTextView : RCTUIView // TODO(macOS ISS#3536887) +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher; // TODO(OSS Candidate ISS#2710739) + @property (nonatomic, assign) BOOL selectable; - (void)setTextStorage:(NSTextStorage *)textStorage diff --git a/Libraries/Text/Text/RCTTextView.m b/Libraries/Text/Text/RCTTextView.m index 23e3601deec3fd..484f892f6ce3d0 100644 --- a/Libraries/Text/Text/RCTTextView.m +++ b/Libraries/Text/Text/RCTTextView.m @@ -16,6 +16,7 @@ #import // TODO(macOS ISS#2323203) #import #import +#import // TODO(OSS Candidate ISS#2710739) #import @@ -26,13 +27,26 @@ @implementation RCTTextView CAShapeLayer *_highlightLayer; #if !TARGET_OS_OSX // TODO(macOS ISS#2323203) UILongPressGestureRecognizer *_longPressGestureRecognizer; -#endif // TODO(macOS ISS#2323203) +#else // [TODO(macOS ISS#2323203) + NSString * _accessibilityLabel; +#endif // ]TODO(macOS ISS#2323203) + RCTEventDispatcher *_eventDispatcher; // TODO(OSS Candidate ISS#2710739) NSArray *_Nullable _descendantViews; // TODO(macOS ISS#3536887) NSTextStorage *_Nullable _textStorage; CGRect _contentFrame; } +// [TODO(OSS Candidate ISS#2710739) +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [self initWithFrame:CGRectZero])) { + _eventDispatcher = eventDispatcher; + } + return self; +} +// ]TODO(OSS Candidate ISS#2710739) + - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { @@ -49,6 +63,25 @@ - (instancetype)initWithFrame:(CGRect)frame } #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) +- (void)dealloc +{ + [self removeAllTextStorageLayoutManagers]; +} + +- (void)removeAllTextStorageLayoutManagers +{ + // On macOS AppKit can throw an uncaught exception + // (-[NSConcretePointerArray pointerAtIndex:]: attempt to access pointer at index ...) + // during the dealloc of NSLayoutManager. The _textStorage and its + // associated NSLayoutManager dealloc later in an autorelease pool. + // Manually removing the layout managers from _textStorage prior to release + // works around this issue in AppKit. + NSArray *managers = [[_textStorage layoutManagers] copy]; + for (NSLayoutManager *manager in managers) { + [_textStorage removeLayoutManager:manager]; + } +} + - (BOOL)canBecomeKeyView { // RCTText should not get any keyboard focus unless its `selectable` prop is true @@ -118,17 +151,7 @@ - (void)setTextStorage:(NSTextStorage *)textStorage descendantViews:(NSArray *)descendantViews // TODO(macOS ISS#3536887) { #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) - // On macOS when a large number of flex layouts are being performed, such - // as when a window is being resized, AppKit can throw an uncaught exception - // (-[NSConcretePointerArray pointerAtIndex:]: attempt to access pointer at index ...) - // during the dealloc of NSLayoutManager. The _textStorage and its - // associated NSLayoutManager dealloc later in an autorelease pool. - // Manually removing the layout manager from _textStorage prior to release - // works around this issue in AppKit. - NSArray *managers = [_textStorage layoutManagers]; - for (NSLayoutManager *manager in managers) { - [_textStorage removeLayoutManager:manager]; - } + [self removeAllTextStorageLayoutManagers]; #endif // ]TODO(macOS ISS#2323203) _textStorage = textStorage; @@ -273,6 +296,28 @@ - (void)didMoveToWindow #pragma mark - Accessibility +#if TARGET_OS_OSX // [TODO(macOS ISS#2323203) + +// This code is here to cover for a mismatch in the what accessibilityLabels and accessibilityValues mean in iOS versus macOS. +// In macOS a text element will always read its accessibilityValue, but will only read it's accessibilityLabel if it's value is set. +// In iOS a text element will only read it's accessibilityValue if it has no accessibilityLabel, and will always read its accessibilityLabel. +// This code replicates the expected behavior in macOS by: +// 1) Setting the accessibilityValue = the react-native accessibilityLabel prop if one exists and setting it equal to the text's contents otherwise. +// 2) Making sure that its accessibilityLabel is always nil, so that it doesn't read out the label twice. + +- (void)setAccessibilityLabel:(NSString *)label +{ + _accessibilityLabel = [label copy]; +} + +- (NSString *)accessibilityValue +{ + if (_accessibilityLabel) { + return _accessibilityLabel; + } + return _textStorage.string; +} +#else // ]TODO(macOS ISS#2323203) - (NSString *)accessibilityLabel { NSString *superAccessibilityLabel = [super accessibilityLabel]; @@ -281,6 +326,7 @@ - (NSString *)accessibilityLabel } return _textStorage.string; } +#endif // TODO(macOS ISS#2323203) #pragma mark - Context Menu @@ -349,6 +395,31 @@ - (void)rightMouseDown:(NSEvent *)event } } } + +- (BOOL)becomeFirstResponder +{ + if (![super becomeFirstResponder]) { + return NO; + } + + // If we've gained focus, notify listeners + [_eventDispatcher sendEvent:[RCTFocusChangeEvent focusEventWithReactTag:self.reactTag]]; + + return YES; +} + +- (BOOL)resignFirstResponder +{ + if (![super resignFirstResponder]) { + return NO; + } + + // If we've lost focus, notify listeners + [_eventDispatcher sendEvent:[RCTFocusChangeEvent blurEventWithReactTag:self.reactTag]]; + + return YES; +} + #endif // ]TODO(macOS ISS#2323203) - (BOOL)canBecomeFirstResponder diff --git a/Libraries/Text/Text/RCTTextViewManager.m b/Libraries/Text/Text/RCTTextViewManager.m index 8903ec74f7d0be..5826c754668ad4 100644 --- a/Libraries/Text/Text/RCTTextViewManager.m +++ b/Libraries/Text/Text/RCTTextViewManager.m @@ -59,7 +59,7 @@ - (void)dealloc - (RCTUIView *)view // TODO(macOS ISS#3536887) { - return [RCTTextView new]; + return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; // TODO(OSS Candidate ISS#2710739) } - (RCTShadowView *)shadowView diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m index a8fe7f0a39445b..c043366e1de3f4 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m @@ -168,6 +168,9 @@ - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doComman // enter/return if (commandSelector == @selector(insertNewline:) || commandSelector == @selector(insertNewlineIgnoringFieldEditor:)) { [self textFieldDidEndEditingOnExit]; + if ([[_backedTextInputView textInputDelegate] textInputShouldReturn]) { + [[_backedTextInputView window] makeFirstResponder:nil]; + } commandHandled = YES; //backspace } else if (commandSelector == @selector(deleteBackward:)) { @@ -275,18 +278,16 @@ - (void)textViewDidEndEditing:(__unused UITextView *)textView - (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { +#if !TARGET_OS_OSX // TODO(macOS ISS#2323203) // Custom implementation of `textInputShouldReturn` and `textInputDidReturn` pair for `UITextView`. if (!_backedTextInputView.textWasPasted && [text isEqualToString:@"\n"]) { if ([_backedTextInputView.textInputDelegate textInputShouldReturn]) { [_backedTextInputView.textInputDelegate textInputDidReturn]; -#if !TARGET_OS_OSX // TODO(macOS ISS#2323203) [_backedTextInputView endEditing:NO]; -#else // [TODO(macOS ISS#2323203) - [[_backedTextInputView window] endEditingFor:nil]; -#endif // ]TODO(macOS ISS#2323203) return NO; } } +#endif // ]TODO(macOS ISS#2323203) BOOL result = [_backedTextInputView.textInputDelegate textInputShouldChangeTextInRange:range replacementText:text]; if (result) { @@ -353,9 +354,11 @@ - (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector BOOL commandHandled = NO; id textInputDelegate = [_backedTextInputView textInputDelegate]; // enter/return - if (textInputDelegate.textInputShouldReturn && (commandSelector == @selector(insertNewline:) || commandSelector == @selector(insertNewlineIgnoringFieldEditor:))) { - [_backedTextInputView.window makeFirstResponder:nil]; - commandHandled = YES; + if ((commandSelector == @selector(insertNewline:) || commandSelector == @selector(insertNewlineIgnoringFieldEditor:))) { + if (textInputDelegate.textInputShouldReturn) { + [_backedTextInputView.window makeFirstResponder:nil]; + commandHandled = YES; + } //backspace } else if (commandSelector == @selector(deleteBackward:)) { commandHandled = textInputDelegate != nil && ![textInputDelegate textInputShouldHandleDeleteBackward:_backedTextInputView]; diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index 436f799f664bc0..8b7ef9dc644096 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -375,7 +375,7 @@ - (void)textInputDidBeginEditing [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus reactTag:self.reactTag - text:self.backedTextInputView.attributedText.string + text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) key:nil eventCount:_nativeEventCount]; } @@ -389,13 +389,13 @@ - (void)textInputDidEndEditing { [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd reactTag:self.reactTag - text:self.backedTextInputView.attributedText.string + text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) key:nil eventCount:_nativeEventCount]; [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur reactTag:self.reactTag - text:self.backedTextInputView.attributedText.string + text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) key:nil eventCount:_nativeEventCount]; } @@ -407,11 +407,17 @@ - (BOOL)textInputShouldReturn // `onSubmitEditing` is called when "Submit" button // (the blue key on onscreen keyboard) did pressed // (no connection to any specific "submitting" process). - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit - reactTag:self.reactTag - text:self.backedTextInputView.attributedText.string - key:nil - eventCount:_nativeEventCount]; +#if TARGET_OS_OSX // [TODO(macOS Candidate ISS#2710739) + if (_blurOnSubmit) { +#endif // ]TODO(macOS Candidate ISS#2710739) + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit + reactTag:self.reactTag + text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) + key:nil + eventCount:_nativeEventCount]; +#if TARGET_OS_OSX // [TODO(macOS Candidate ISS#2710739) + } +#endif // ]TODO(macOS Candidate ISS#2710739) return _blurOnSubmit; } @@ -469,7 +475,7 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin } } - NSString *previousText = backedTextInputView.attributedText.string ?: @""; + NSString *previousText = [backedTextInputView.attributedText.string copy] ?: @""; // TODO(OSS Candidate ISS#2710739) if (range.location + range.length > backedTextInputView.attributedText.string.length) { _predictedText = backedTextInputView.attributedText.string; @@ -516,7 +522,7 @@ - (void)textInputDidChange if (_onChange) { _onChange(@{ - @"text": self.attributedText.string, + @"text": [self.attributedText.string copy], // [TODO(macOS Candidate ISS#2710739) @"target": self.reactTag, @"eventCount": @(_nativeEventCount), }); diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/Libraries/Text/TextInput/Singleline/RCTUITextField.h index 50028411d3e36f..d4d41609a0df94 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -37,9 +37,10 @@ NS_ASSUME_NONNULL_BEGIN @property (assign, getter=isEditable) BOOL editable; #endif #if TARGET_OS_OSX -@property (nonatomic, assign) NSTextAlignment textAlignment; -@property (nonatomic, copy, nullable) NSAttributedString *attributedText; @property (nonatomic, copy, nullable) NSString *text; +@property (nonatomic, copy, nullable) NSAttributedString *attributedText; +@property (nonatomic, copy) NSDictionary *defaultTextAttributes; +@property (nonatomic, assign) NSTextAlignment textAlignment; @property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled; @property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled; @property (nonatomic, strong, nullable) RCTUIColor *selectionColor; diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.m b/Libraries/Text/TextInput/Singleline/RCTUITextField.m index 1a198ef45baf3a..99e4ae07582685 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.m +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.m @@ -82,11 +82,6 @@ @implementation RCTUITextField { #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) @dynamic delegate; -static UIFont *defaultPlaceholderFont() -{ - return [UIFont systemFontOfSize:17]; -} - static RCTUIColor *defaultPlaceholderTextColor() { return [NSColor placeholderTextColor]; @@ -106,8 +101,10 @@ - (instancetype)initWithFrame:(CGRect)frame object:self]; #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) - self.bordered = NO; - self.accessibilityRole = NSAccessibilityTextFieldRole; + [self setBordered:NO]; + [self setAllowsEditingTextAttributes:YES]; + [self setAccessibilityRole:NSAccessibilityTextFieldRole]; + [self setBackgroundColor:[NSColor clearColor]]; #endif // ]TODO(macOS ISS#2323203) _textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self]; @@ -124,6 +121,10 @@ - (void)dealloc - (void)_textDidChange { _textWasPasted = NO; +#if TARGET_OS_OSX // [TODO(macOS ISS#2323203) + [self setAttributedText:[[NSAttributedString alloc] initWithString:[self text] + attributes:[self defaultTextAttributes]]]; +#endif // ]TODO(macOS ISS#2323203) } #pragma mark - Accessibility @@ -246,11 +247,14 @@ - (void)setReactTextAttributes:(RCTTextAttributes *)reactTextAttributes if ([reactTextAttributes isEqual:_reactTextAttributes]) { return; } -#if !TARGET_OS_OSX // TODO(macOS ISS#2323203) self.defaultTextAttributes = reactTextAttributes.effectiveTextAttributes; -#endif // TODO(macOS ISS#2323203) _reactTextAttributes = reactTextAttributes; [self _updatePlaceholder]; + +#if TARGET_OS_OSX // [TODO(macOS ISS#2323203) + [self setAttributedText:[[NSAttributedString alloc] initWithString:[self text] + attributes:[self defaultTextAttributes]]]; +#endif // ]TODO(macOS ISS#2323203) } - (RCTTextAttributes *)reactTextAttributes @@ -264,8 +268,8 @@ - (void)_updatePlaceholder return; } - NSMutableDictionary *attributes = [NSMutableDictionary new]; #if !TARGET_OS_OSX // TODO(macOS ISS#2323203) + NSMutableDictionary *attributes = [NSMutableDictionary new]; if (_placeholderColor) { [attributes setObject:_placeholderColor forKey:NSForegroundColorAttributeName]; } @@ -273,9 +277,8 @@ - (void)_updatePlaceholder self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attributes]; #else // [TODO(macOS ISS#2323203) + NSMutableDictionary *attributes = [[self defaultTextAttributes] mutableCopy]; attributes[NSForegroundColorAttributeName] = _placeholderColor ?: defaultPlaceholderTextColor(); - attributes[NSFontAttributeName] = self.font ?: defaultPlaceholderFont(); - self.placeholderAttributedString = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attributes]; #endif // ]TODO(macOS ISS#2323203) diff --git a/RNTester/Podfile b/RNTester/Podfile index f3dc5fd4f22c36..22bb43f55079b3 100644 --- a/RNTester/Podfile +++ b/RNTester/Podfile @@ -125,6 +125,13 @@ post_install do |installer| puts ' adding arm64e to ' + config.name end end + # TODO(macOS ISS#2323203): the internal Microsoft build pipeline needs macOS arm64 slices + if target.platform_name == :osx + target.build_configurations.each do |config| + (config.build_settings['ARCHS'] ||= ['$(ARCHS_STANDARD)']) << 'arm64' + puts ' adding arm64 to ' + config.name + end + end # ]TODO(macOS ISS#2323203) end end diff --git a/RNTester/Podfile.lock b/RNTester/Podfile.lock index 7c493890405adb..1d97b34ccef2bb 100644 --- a/RNTester/Podfile.lock +++ b/RNTester/Podfile.lock @@ -348,34 +348,34 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost-for-react-native: a110407d9db2642fd2e1bcd7c5a51c81f2521dc9 DoubleConversion: a1bc12a74baa397a2609e0f10e19b8062d864053 - FBLazyVector: 27745a4d0491b18462b8a829cafe5b391c1a1cea - FBReactNativeSpec: f8aea29d6885c0bc3b70d896a0bca87ce0c538d9 + FBLazyVector: 681897af71189193fab35027ce8e7275c4549006 + FBReactNativeSpec: cfbced7241c878d9c864b363d069a6e2468285c3 Folly: feff29ba9d0b7c2e4f793a94942831d6cc5bbad7 glog: b3f6d74f3e2d33396addc0ee724d2b2b79fc3e00 - RCTRequired: 2221f8503c82f16dd17b80f1f24364ab52c535b3 - RCTTypeSafety: 4c2066331e33e07ee75cb1e0f8b0d0130e1b4a70 - React: 48d5b2455809de47c49eaa2a76481d749a270b22 - React-ART: e101035d34f6f1c91efa2dda36170d52ea1805dd - React-Core: d97fa087d3f2298d4cd1adb036d6c92786016083 - React-CoreModules: 07fe1b814a1d9c904461893b479788af679c71ce - React-cxxreact: 7ac59e70e07e8fe43321c18f9c5ceaa56df5b2d9 - React-jsi: f546632f168847c40d6e9da59fa0a6a9f4bba5b4 - React-jsiexecutor: 8fa2e826a04ef0ac40f4107d360e0ef7e8bdcded - React-jsinspector: 74fa650554649ad9510c3fd2241f6b6005e03afe - React-RCTActionSheet: e5d218448597369d3da876dce972400c77ddfa6d - React-RCTAnimation: ef71a69bff156342a8ff7e1c6f89ff22f7ff51e4 - React-RCTBlob: eb2724e849b04b1b9ec789a089d5f1f74e7ae1fc - React-RCTImage: 05b4998d324144e14bbfeef349980951db44a08b - React-RCTLinking: d61df6e5500628a0adabdc2bbefe302d43b4e895 - React-RCTNetwork: 77c59c78a7a50c16f46f0c7056c136b5ed490458 - React-RCTPushNotification: 9a92fc9a0c191b2569176df8f982b7ba69fda745 - React-RCTSettings: 8d2e058ab45baba4dfd7e5ef96c3e75d116adb88 - React-RCTTest: 8b59c7dfecf7abbfa36dce0606e8dbfa1b7dbad9 - React-RCTText: 37753e7f02cf5e3fb49d403308d763c9dc32c482 - React-RCTVibration: 3c9ea5e5f55af86b1aef5999dd760fb0ac7949a9 - ReactCommon: 6b4217de36eb3a10c271c909b28e129d368c0057 - Yoga: 9b8e9b530e77aeb7f38dd54c0895bcd3a35ea29c + RCTRequired: fa143a98ccd5f5c952f07364a331c75b8dc4e663 + RCTTypeSafety: 1d976d01072f9968ab3c2cafb2eb7f6ca7c48fdd + React: 73d4569aed422237bfb5ec1f471aa4d8c045b331 + React-ART: 07fa82267a87e67e0c3523a5f595e86504658d0b + React-Core: 277537483a5b0c41beae832d1c7a9794561b6529 + React-CoreModules: 4e9e1c821a7a5fafb3ffd9f9ba085b40f477da83 + React-cxxreact: 22e64325458325149e51111832d1005fcf27c0b5 + React-jsi: 9d087842253c8d0775b318caeccbdee547c89316 + React-jsiexecutor: f0043f9030ec0c79b937fd6ab8f7c4f06a7be241 + React-jsinspector: 503fb0b052442aed724f735789174754b2b72687 + React-RCTActionSheet: 82b3af83ed5043659bc39316966571e4b1b7ef4e + React-RCTAnimation: a48f680c9c5e55c27bf945d5864eb5d638da1576 + React-RCTBlob: 9c266ac30bfbfb10bb62b3f74c049febf80fc2e5 + React-RCTImage: 5889e573cea7e140a447d4cd4658c87885669d81 + React-RCTLinking: 4699cfc4eba902b7f7f1652178af4d8cc18ee971 + React-RCTNetwork: 5fea7fcf7bbe49a37dfa9fddc499dff6506297b7 + React-RCTPushNotification: aba4d4cb1f0f851b2df81b11cb3c402dd3123cf0 + React-RCTSettings: ec7ee3a25d2f16a69936335bf7ef8f9d61c70fb0 + React-RCTTest: ed828653bca00aa9f5071f554b54b4d36a254648 + React-RCTText: d22b5f61ed6db5670e6aaedc183337b6f7c73337 + React-RCTVibration: 80c7b79af833447615624f16cf1ec6bed8babccc + ReactCommon: 10f21f50611c651a89b8323520ca71eac9f7df2b + Yoga: f18826821d9110f5d57c15dadabe63ba8b75c2e2 -PODFILE CHECKSUM: 2bf56de39dc7e153b4f1637e0f4874f2591fc55a +PODFILE CHECKSUM: 1a5700c13b2bc8ffc5ed4282d795e8909c883f63 -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.3 diff --git a/RNTester/RNTesterIntegrationTests/RNTesterSnapshotTests.m b/RNTester/RNTesterIntegrationTests/RNTesterSnapshotTests.m index dd8c882c0d2711..21b28f90f153ef 100644 --- a/RNTester/RNTesterIntegrationTests/RNTesterSnapshotTests.m +++ b/RNTester/RNTesterIntegrationTests/RNTesterSnapshotTests.m @@ -43,9 +43,7 @@ - (void)test##name \ RCT_TEST(LayoutExample) RCT_TEST(ARTExample) RCT_TEST(ScrollViewExample) -#if !TARGET_OS_OSX // Reason: Intermittent failure: crash deallocating NSTextStorage of a TextView: tracked by https://github.com/microsoft/react-native-macos/issues/357 RCT_TEST(TextExample) -#endif #if !TARGET_OS_TV // No switch or slider available on tvOS RCT_TEST(SwitchExample) diff --git a/RNTester/js/examples/Accessibility/AccessibilityExample.js b/RNTester/js/examples/Accessibility/AccessibilityExample.js index 39ba0ab1bc3ae4..c98870e38eab9c 100644 --- a/RNTester/js/examples/Accessibility/AccessibilityExample.js +++ b/RNTester/js/examples/Accessibility/AccessibilityExample.js @@ -553,7 +553,101 @@ class ScreenReaderStatusExample extends React.Component<{}> { ); } } +// [TODO(OSS Candidate ISS#2710739) +class DisplayOptionsStatusExample extends React.Component<{}> { + state = {}; + componentDidMount() { + AccessibilityInfo.addEventListener( + 'invertColorsChanged', + this._handleInvertColorsToggled, + ); + AccessibilityInfo.isInvertColorsEnabled().done(isEnabled => { + this.setState({ + invertColorsEnabled: isEnabled, + }); + }); + + AccessibilityInfo.addEventListener( + 'reduceMotionChanged', + this._handleReduceMotionToggled, + ); + AccessibilityInfo.isReduceMotionEnabled().done(isEnabled => { + this.setState({ + reduceMotionEnabled: isEnabled, + }); + }); + + AccessibilityInfo.addEventListener( + 'reduceTransparencyChanged', + this._handleReduceTransparencyToggled, + ); + AccessibilityInfo.isReduceTransparencyEnabled().done(isEnabled => { + this.setState({ + reduceTransparencyEnabled: isEnabled, + }); + }); + } + + componentWillUnmount() { + AccessibilityInfo.removeEventListener( + 'invertColorsChanged', + this._handleInvertColorsToggled, + ); + AccessibilityInfo.removeEventListener( + 'reduceMotionChanged', + this._handleReduceMotionToggled, + ); + AccessibilityInfo.removeEventListener( + 'reduceTransparencyChanged', + this._handleReduceTransparencyToggled, + ); + } + + _handleInvertColorsToggled = isEnabled => { + this.setState({ + invertColorsEnabled: isEnabled, + }); + }; + + _handleReduceMotionToggled = isEnabled => { + this.setState({ + reduceMotionEnabled: isEnabled, + }); + }; + + _handleReduceTransparencyToggled = isEnabled => { + this.setState({ + reduceTransparencyEnabled: isEnabled, + }); + }; + + render() { + return ( + + + + Invert colors is{' '} + {this.state.invertColorsEnabled ? 'enabled' : 'disabled'}. + + + + + Reduce motion is{' '} + {this.state.reduceMotionEnabled ? 'enabled' : 'disabled'}. + + + + + Reduce transparency is{' '} + {this.state.reduceTransparencyEnabled ? 'enabled' : 'disabled'}. + + + + ); + } +} +// ]TODO(OSS Candidate ISS#2710739) class AnnounceForAccessibility extends React.Component<{}> { _handleOnPress = () => AccessibilityInfo.announceForAccessibility('Announcement Test'); @@ -570,6 +664,26 @@ class AnnounceForAccessibility extends React.Component<{}> { } } +class SetAccessibilityFocus extends React.Component<{}> { + _handleOnPress = () => { + if (findNodeHandle(this.focusRef.current)) { + const reactTag = findNodeHandle(this.focusRef.current); + AccessibilityInfo.setAccessibilityFocus(reactTag); + } + }; + render() { + this.focusRef = React.createRef(); + return ( + +