Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 2 additions & 1 deletion packages/E2ETest/wdio/test/VisitAllPages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ let pages = [
'<TransparentHitTestExample>',
'<View>',
'Keyboard Focus Example',
// 'Accessibility',
'Accessibility',
'AccessibilityInfo',
'Accessibility Windows',
'AsyncStorage Windows',
'Alert',
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
<ClInclude Include="JsiWriter.h">
<DependentUpon>IJSValueWriter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Modules\AccessibilityInfoModule.h" />
<ClInclude Include="Modules\AlertModule.h" />
<ClInclude Include="Modules\Animated\AdditionAnimatedNode.h" />
<ClInclude Include="Modules\Animated\AnimatedNode.h" />
Expand Down Expand Up @@ -407,6 +408,7 @@
<ClCompile Include="JsiWriter.cpp">
<DependentUpon>IJSValueWriter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Modules\AccessibilityInfoModule.cpp" />
<ClCompile Include="Modules\AlertModule.cpp" />
<ClCompile Include="Modules\Animated\AdditionAnimatedNode.cpp" />
<ClCompile Include="Modules\Animated\AnimatedNode.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
<ClCompile Include="CxxReactUWP\JSBigString.cpp">
<Filter>CxxReactUWP</Filter>
</ClCompile>
<ClCompile Include="Modules\AccessibilityInfoModule.cpp">
<Filter>Modules</Filter>
</ClCompile>
<ClCompile Include="Modules\Animated\AdditionAnimatedNode.cpp">
<Filter>Modules\Animated</Filter>
</ClCompile>
Expand Down Expand Up @@ -404,6 +407,9 @@
<ClInclude Include="Base\CoreNativeModules.h">
<Filter>Base</Filter>
</ClInclude>
<ClInclude Include="Modules\AccessibilityInfoModule.h">
<Filter>Modules</Filter>
</ClInclude>
<ClInclude Include="Modules\Animated\AdditionAnimatedNode.h">
<Filter>Modules\Animated</Filter>
</ClInclude>
Expand Down
79 changes: 79 additions & 0 deletions vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"
#include "AccessibilityInfoModule.h"
#include <UI.Xaml.Automation.Peers.h>
#include <UI.Xaml.Controls.h>
#include <XamlUtils.h>
#include <uiautomationcore.h>
#include <uiautomationcoreapi.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.UI.ViewManagement.h>
#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<void(React::JSValue const &)> const &onSuccess) noexcept {
Copy link

@NikoAri NikoAri Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[](start = 88, length = 1)

(nit) can this be && to avoid std::function copy here and while passing into further lambdas?

auto jsDispatcher = m_context.JSDispatcher();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsDispatcher [](start = 7, length = 12)

std::move?

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<void(React::JSValue const &)> const &onSuccess) noexcept {
onSuccess(UiaClientsAreListening());
}

void AccessibilityInfo::setAccessibilityFocus(double /*reactTag*/) noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AccessibilityInfo::setAccessibilityFocus [](start = 5, length = 40)

We can use AutomationPeer::SetFocusCore() to implement this. See:
https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.setfocuscore?view=winrt-19041
Also see XAML code that implements it here:
%SDXROOT%\onecoreuap\windows\dxaml\xcp\dxaml\lib\winrtgeneratedclasses\AutomationPeer.g.cpp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That moves actual focus to that element. This API is just trying to move the narrator focus. -- So, when running narrator, it would move the blue rectangle without moving keyboard focus.

(Otherwise, this API is pointless, since there is a similar API that sets normal focus)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops I didn't realize it's just supposed to move narrator focus. I'm asking around, meanwhile I see some hits in Windows that suggest raising the AutomationFocusChanged event might work (eg %SDXROOT%\xbox\shellapps\Shared\Xbox.Foundation.UI\Narrator\NarratorReader.cpp line 125)

        // Raise the focus changed automation event, to ensure that this element receives narrator focus so that it reads the element and textToRead together.
        auto automationPeer = FrameworkElementAutomationPeer::FromElement(focusedElement);
        if (automationPeer != nullptr)
        {
            automationPeer.RaiseAutomationEvent(AutomationEvents::AutomationFocusChanged);
        }

In reply to: 481482991 [](ancestors = 481482991)

// no-op - This appears to be unused in RN
}

void AccessibilityInfo::announceForAccessibility(std::string announcement) noexcept {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string [](start = 54, length = 6)

Similar comment, can this be either && or const& to avoid accidental copying?

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto peer = xaml::Automation::Peers::FrameworkElementAutomationPeer::FromElement(element); [](start = 4, length = 90)

Is this still returning null in all cases? If so feel free to file an issue and assign to me, I can look into it.


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
30 changes: 30 additions & 0 deletions vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once

#include <NativeModules.h>

namespace Microsoft::ReactNative {

REACT_MODULE(AccessibilityInfo)
struct AccessibilityInfo : public std::enable_shared_from_this<AccessibilityInfo> {
REACT_INIT(Initialize)
void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept;

REACT_METHOD(isReduceMotionEnabled)
void isReduceMotionEnabled(std::function<void(React::JSValue const &)> const &onSuccess) noexcept;

REACT_METHOD(isTouchExplorationEnabled)
void isTouchExplorationEnabled(std::function<void(React::JSValue const &)> 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
8 changes: 8 additions & 0 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 0 additions & 4 deletions vnext/just-task.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
4 changes: 4 additions & 0 deletions vnext/overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<View>
<Text>{`AccessibilityInfo.isReduceMotionEnabled: ${isReduceMotionEnabled}`}</Text>
<Text>{`AccessibilityInfo.isScreenReaderEnabled: ${isScreenReaderEnabled}`}</Text>
<Button onPress={announceSomething} title="Announce something" />
</View>
);
}

exports.title = 'AccessibilityInfo';
exports.description = 'Examples of using AccessibilityInfo APIs.';
exports.examples = [
{
title: 'Basic AccessibilityInfo',
render(): React.Element<typeof AccessibilityInfoExample> {
return <AccessibilityInfoExample />;
},
},
];
4 changes: 4 additions & 0 deletions vnext/src/RNTester/js/utils/RNTesterList.windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ const APIExamples: Array<IRNTesterExample> = [
key: 'AccessibilityExampleWindows',
module: require('./../examples-win/Accessibility/AccessibilityExampleWindows'),
},
{
key: 'AccessibilityInfo',
module: require('react-native/RNTester/js/examples/Accessibility/AccessibilityInfoExample'),
},
{
key: 'AsyncStorageExampleWindows',
module: require('./../examples-win/AsyncStorage/AsyncStorageExampleWindows'),
Expand Down