diff --git a/change/react-native-windows-2020-08-26-11-33-49-accessinfo.json b/change/react-native-windows-2020-08-26-11-33-49-accessinfo.json new file mode 100644 index 00000000000..2588886788c --- /dev/null +++ b/change/react-native-windows-2020-08-26-11-33-49-accessinfo.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Initial AccessibilityInfo implementation", + "packageName": "react-native-windows", + "email": "acoates-ms@noreply.github.com", + "dependentChangeType": "patch", + "date": "2020-08-26T18:33:49.434Z" +} diff --git a/packages/E2ETest/wdio/test/VisitAllPages.spec.ts b/packages/E2ETest/wdio/test/VisitAllPages.spec.ts index ded4c790c97..17a03ee92dd 100644 --- a/packages/E2ETest/wdio/test/VisitAllPages.spec.ts +++ b/packages/E2ETest/wdio/test/VisitAllPages.spec.ts @@ -32,7 +32,8 @@ let pages = [ '', '', 'Keyboard Focus Example', - // 'Accessibility', + 'Accessibility', + 'AccessibilityInfo', 'Accessibility Windows', 'AsyncStorage Windows', 'Alert', diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index b0b496a9545..b4c20caeaa0 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -204,6 +204,7 @@ IJSValueWriter.idl + @@ -407,6 +408,7 @@ IJSValueWriter.idl + diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters index 9c11569c47c..4270a024341 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters @@ -65,6 +65,9 @@ CxxReactUWP + + Modules + Modules\Animated @@ -404,6 +407,9 @@ Base + + Modules + Modules\Animated diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp new file mode 100644 index 00000000000..a09dfe49deb --- /dev/null +++ b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "AccessibilityInfoModule.h" +#include +#include +#include +#include +#include +#include +#include +#include "Unicode.h" +#include "Utils/Helpers.h" + +namespace Microsoft::ReactNative { + +void AccessibilityInfo::Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept { + m_context = reactContext; +} + +void AccessibilityInfo::isReduceMotionEnabled(std::function const &onSuccess) noexcept { + auto jsDispatcher = m_context.JSDispatcher(); + m_context.UIDispatcher().Post([weakThis = weak_from_this(), jsDispatcher, onSuccess] { + if (auto strongThis = weakThis.lock()) { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + auto animationsEnabled = uiSettings.AnimationsEnabled(); + jsDispatcher.Post([animationsEnabled, onSuccess] { onSuccess(!animationsEnabled); }); + } + }); +} + +void AccessibilityInfo::isTouchExplorationEnabled( + std::function const &onSuccess) noexcept { + onSuccess(UiaClientsAreListening()); +} + +void AccessibilityInfo::setAccessibilityFocus(double /*reactTag*/) noexcept { + // no-op - This appears to be unused in RN +} + +void AccessibilityInfo::announceForAccessibility(std::string announcement) noexcept { + m_context.UIDispatcher().Post([context = m_context, announcement = std::move(announcement)] { + xaml::UIElement element{nullptr}; + + // Windows requires a specific element to announce from. Unfortunately the react-native API does not provide a tag + // So we need to find something to try to raise the notification event from + + if (auto window = xaml::Window::Current()) { + element = window.Content(); + } + + if (!element && react::uwp::Is19H1OrHigher()) { + // XamlRoot added in 19H1 + if (auto xamlRoot = React::XamlUIService::GetXamlRoot(context.Properties().Handle())) { + element = xamlRoot.Content(); + } + } + + if (!element) { + return; + } + + auto peer = xaml::Automation::Peers::FrameworkElementAutomationPeer::FromElement(element); + + if (!peer) { + return; + } + + winrt::hstring hstr{Microsoft::Common::Unicode::Utf8ToUtf16(announcement)}; + peer.RaiseNotificationEvent( + xaml::Automation::Peers::AutomationNotificationKind::Other, + xaml::Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent, + hstr, + hstr); + }); +} + +} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.h b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.h new file mode 100644 index 00000000000..50f6da7c3ee --- /dev/null +++ b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include + +namespace Microsoft::ReactNative { + +REACT_MODULE(AccessibilityInfo) +struct AccessibilityInfo : public std::enable_shared_from_this { + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; + + REACT_METHOD(isReduceMotionEnabled) + void isReduceMotionEnabled(std::function const &onSuccess) noexcept; + + REACT_METHOD(isTouchExplorationEnabled) + void isTouchExplorationEnabled(std::function const &onSuccess) noexcept; + + REACT_METHOD(setAccessibilityFocus) + void setAccessibilityFocus(double reactTag) noexcept; + + REACT_METHOD(announceForAccessibility) + void announceForAccessibility(std::string announcement) noexcept; + + private: + React::ReactContext m_context; +}; + +} // namespace Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index e1383633892..bde6842bb5d 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -14,6 +14,7 @@ #include "Microsoft.ReactNative/IReactNotificationService.h" #include "Microsoft.ReactNative/Threading/MessageQueueThreadFactory.h" +#include "../../codegen/NativeAccessibilityInfoSpec.g.h" #include "../../codegen/NativeAppStateSpec.g.h" #include "../../codegen/NativeClipboardSpec.g.h" #include "../../codegen/NativeDevSettingsSpec.g.h" @@ -31,6 +32,7 @@ #include "DevMenu.h" #include "IReactContext.h" #include "IReactDispatcher.h" +#include "Modules/AccessibilityInfoModule.h" #include "Modules/AlertModule.h" #include "Modules/AppStateModule.h" #include "Modules/ClipboardModule.h" @@ -151,6 +153,12 @@ void ReactInstanceWin::LoadModules( } }; + registerTurboModule( + L"AccessibilityInfo", + winrt::Microsoft::ReactNative::MakeTurboModuleProvider< + ::Microsoft::ReactNative::AccessibilityInfo, + ::Microsoft::ReactNativeSpecs::AccessibilityInfoSpec>()); + registerTurboModule(L"Alert", winrt::Microsoft::ReactNative::MakeModuleProvider<::Microsoft::ReactNative::Alert>()); registerTurboModule( diff --git a/vnext/just-task.js b/vnext/just-task.js index adedf8995b4..7dd6158738c 100644 --- a/vnext/just-task.js +++ b/vnext/just-task.js @@ -8,21 +8,17 @@ const path = require('path'); const { task, - cleanTask, series, condition, option, argv, tscTask, - tscWatchTask, eslintTask, apiExtractorVerifyTask, apiExtractorUpdateTask, - parallel, } = require('just-scripts'); const {execSync} = require('child_process'); const fs = require('fs'); -const srcPath = path.resolve(process.cwd(), 'src'); const copyRNLibaries = require('./Scripts/copyRNLibraries'); option('production'); diff --git a/vnext/overrides.json b/vnext/overrides.json index dcc0e6c8e9e..0656b12067a 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -453,6 +453,10 @@ "baseHash": "282bb1231e430fe2263a4cae29ac5d8254c0cab8", "issue": 5834 }, + { + "type": "platform", + "file": "src/RNTester/js/examples/Accessibility/AccessibilityInfoExample.js" + }, { "type": "derived", "file": "src/RNTester/js/examples/PlatformColor/PlatformColorExample.windows.js", diff --git a/vnext/src/RNTester/js/examples/Accessibility/AccessibilityInfoExample.js b/vnext/src/RNTester/js/examples/Accessibility/AccessibilityInfoExample.js new file mode 100644 index 00000000000..48e9156ce3b --- /dev/null +++ b/vnext/src/RNTester/js/examples/Accessibility/AccessibilityInfoExample.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const React = require('react'); +const {AccessibilityInfo, Button, Text, View} = require('react-native'); + +function announceSomething() { + AccessibilityInfo.announceForAccessibility('Something!'); +} + +function AccessibilityInfoExample(props): React.Node { + const [isReduceMotionEnabled, setIsReduceMotionEnabled] = React.useState( + 'unknown', + ); + const [isScreenReaderEnabled, setIsScreenReaderEnabled] = React.useState( + 'unknown', + ); + + React.useEffect(() => { + AccessibilityInfo.isReduceMotionEnabled().done(isEnabled => { + setIsReduceMotionEnabled(isEnabled); + }); + AccessibilityInfo.isScreenReaderEnabled().done(isEnabled => { + setIsScreenReaderEnabled(isEnabled); + }); + }, [setIsReduceMotionEnabled, setIsScreenReaderEnabled]); + + return ( + + {`AccessibilityInfo.isReduceMotionEnabled: ${isReduceMotionEnabled}`} + {`AccessibilityInfo.isScreenReaderEnabled: ${isScreenReaderEnabled}`} +