Skip to content

Commit

Permalink
Implement AccessibilityAction (#3475)
Browse files Browse the repository at this point in the history
  • Loading branch information
licanhua authored Oct 29, 2019
1 parent 31d09fa commit 97955e9
Show file tree
Hide file tree
Showing 17 changed files with 629 additions and 18 deletions.
9 changes: 9 additions & 0 deletions change/react-native-windows-2019-10-21-16-17-09-action.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "none",
"comment": "Implement accessibilityAction",
"packageName": "react-native-windows",
"email": "licanhua@live.com",
"commit": "e931062bbaef57538e8e7ea06c6f1fb929f7b024",
"date": "2019-10-21T23:17:08.915Z",
"file": "F:\\repo\\react-native-windows\\change\\react-native-windows-2019-10-21-16-17-09-action.json"
}
56 changes: 50 additions & 6 deletions packages/E2ETest/app/AccessibilityTestPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,59 @@
import { View, Text } from 'react-native'
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { View, Text, ViewProps } from 'react-native'
import React, { useState } from 'react';

// MyView is a workaround. Currently in typescript, accessibilityAction doesn't allow name to be any string.
const MyView = (props: any) => (<View {...props as ViewProps} />);

export function AccessibilityTestPage() {
const [pressedCountNested, setPressedCountNested] = useState(0);
const [pressedCount, setPressedCount] = useState(0);
const [accessibilityAction, setAccessibilityAction] = useState('');

return (
<View>
<Text accessible={true} accessibilityLiveRegion="polite" style={{fontWeight: "bold"}}>
I'm bold
<Text style={{color: 'red'}} onPress={() => setPressedCountNested(pressedCountNested + 1)} accessible={true} accessibilityLiveRegion="polite">Pressed {pressedCountNested} times</Text>
</Text>
<Text style={{color: 'green'}} onPress={() => setPressedCount(pressedCount + 1)} accessible={true} accessibilityLiveRegion="polite">Pressed {pressedCount} times</Text>
<View>
<Text accessible={true} accessibilityLiveRegion="polite" style={{ fontWeight: "bold" }}>
I'm bold
<Text style={{ color: 'red' }} onPress={() => setPressedCountNested(pressedCountNested + 1)} accessible={true} accessibilityLiveRegion="polite">Pressed {pressedCountNested} times</Text>
</Text>
<Text style={{ color: 'green' }} onPress={() => setPressedCount(pressedCount + 1)} accessible={true} accessibilityLiveRegion="polite">Pressed {pressedCount} times</Text>
</View>
<View>
<MyView
accessible={true}
accessibilityLabel='AccessibilityAction'
accessibilityRole='CheckBox'
accessibilityStates={['checked', 'expanded']}
accessibilityActions={[
{ name: 'toggle', label: 'toggle' },
{ name: 'invoke', label: 'invoke' },
{ name: 'expand', label: 'expand' },
{ name: 'collapse', label: 'collapseIt' },
]}
onAccessibilityAction={(event: { nativeEvent: { actionName: any; }; }) => {
switch (event.nativeEvent.actionName) {
case 'toggle':
setAccessibilityAction('toggle action success');
break;
case 'invoke':
setAccessibilityAction('invoke action success');
break;
case 'expand':
setAccessibilityAction('expand action success');
break;
case 'collapseIt':
setAccessibilityAction('collapseIt action success');
break;
}
}}
>
<Text>accessibilityAction:{accessibilityAction}</Text>
</MyView>
</View>
</View>)
}
21 changes: 21 additions & 0 deletions vnext/ReactUWP/ABI/idl/AccessibilityAction.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// Note: This is just a workaround should be removed in the future.
// Currently there are two folders which hold idl files: ABI/idl and Views/cppwinrt
// ABI/idl is compiled by MidlRT target, but Views/cppwinrt is built by buildcppwinrt.
// Views/cppwinrt is not merged into ReactUWP.Winmd, but ABI/idl is merged.
// so it hits without this file: Exception thrown at 0x7646EF12 (KernelBase.dll) in ReactUWPTestApp.exe: WinRT originate error - 0x80131522 : 'System.TypeLoadException: Could not find Windows Runtime type 'react.uwp.AccessibilityAction'
// This file has the same 'struct AccessibilityAction' in Views/cppwinrt/AccessibilityAction.idl.

import "inspectable.idl";

namespace react.uwp
{
[version(1)]

struct AccessibilityAction {
String Name;
String Label;
};
}
1 change: 1 addition & 0 deletions vnext/ReactUWP/ReactUWP.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@
<MidlRT Include="ABI\idl\Module.idl" />
<MidlRT Include="ABI\idl\Instance.idl" />
<MidlRT Include="ABI\idl\ReactControl.idl" />
<MidlRT Include="ABI\idl\AccessibilityAction.idl" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Version.rc">
Expand Down
14 changes: 9 additions & 5 deletions vnext/ReactUWP/Views/DynamicAutomationPeer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ winrt::hstring DynamicAutomationPeer::GetItemStatusCore() const {
// IInvokeProvider

void DynamicAutomationPeer::Invoke() const {
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"invoke");

if (auto const &invokeHandler = GetAccessibilityInvokeEventHandler()) {
invokeHandler();
}
Expand Down Expand Up @@ -183,15 +185,15 @@ winrt::IRawElementProviderSimple DynamicAutomationPeer::SelectionContainer() con
}

void DynamicAutomationPeer::AddToSelection() const {
// Right now RN does not have "selection" events, so this is a no-op
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"addToSelection");
}

void DynamicAutomationPeer::RemoveFromSelection() const {
// Right now RN does not have "selection" events, so this is a no-op
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"removeFromSelection");
}

void DynamicAutomationPeer::Select() const {
// Right now RN does not have "selection" events, so this is a no-op
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"select");
}

// IToggleProvider
Expand All @@ -210,6 +212,8 @@ winrt::ToggleState DynamicAutomationPeer::ToggleState() const {
}

void DynamicAutomationPeer::Toggle() const {
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"toggle");

if (auto const &invokeHandler = GetAccessibilityInvokeEventHandler()) {
invokeHandler();
}
Expand All @@ -233,11 +237,11 @@ winrt::ExpandCollapseState DynamicAutomationPeer::ExpandCollapseState() const {
}

void DynamicAutomationPeer::Expand() const {
// Right now RN does not have "expand" events, so this is a no-op
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"expand");
}

void DynamicAutomationPeer::Collapse() const {
// Right now RN does not have "collapse" events, so this is a no-op
DynamicAutomationProperties::DispatchAccessibilityAction(Owner(), L"collapse");
}

// Private Methods
Expand Down
16 changes: 16 additions & 0 deletions vnext/ReactUWP/Views/DynamicAutomationPeer.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ struct DynamicAutomationPeer : DynamicAutomationPeerT<DynamicAutomationPeer> {
bool HasAccessibilityState(winrt::react::uwp::AccessibilityStates state) const;
bool GetAccessibilityState(winrt::react::uwp::AccessibilityStates state) const;
winrt::react::uwp::AccessibilityInvokeEventHandler GetAccessibilityInvokeEventHandler() const;

static winrt::Windows::UI::Xaml::DependencyProperty AccessibilityActionsProperty();
static void SetAccessibilityActions(
Windows::UI::Xaml::UIElement const &element,
Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction> const &value);
static Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction> GetAccessibilityActions(
Windows::UI::Xaml::UIElement const &element);
static void DispatchAccessibilityAction(
Windows::UI::Xaml::UIElement const &element,
std::wstring_view const &actionName);
static winrt::Windows::UI::Xaml::DependencyProperty AccessibilityActionEventHandlerProperty();
static void SetAccessibilityActionEventHandler(
Windows::UI::Xaml::UIElement const &element,
winrt::react::uwp::AccessibilityActionEventHandler const &value);
static winrt::react::uwp::AccessibilityActionEventHandler GetAccessibilityActionEventHandler(
winrt::Windows::UI::Xaml::UIElement const &element);
};
} // namespace winrt::react::uwp::implementation

Expand Down
65 changes: 65 additions & 0 deletions vnext/ReactUWP/Views/DynamicAutomationProperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,69 @@ winrt::react::uwp::AccessibilityInvokeEventHandler DynamicAutomationProperties::
element.GetValue(AccessibilityInvokeEventHandlerProperty()));
}

winrt::Windows::UI::Xaml::DependencyProperty DynamicAutomationProperties::AccessibilityActionsProperty() {
static winrt::DependencyProperty s_AccessibilityActionsProperty = winrt::DependencyProperty::RegisterAttached(
L"AccessibilityActions",
winrt::xaml_typename<Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction>>(),
dynamicAutomationTypeName,
winrt::PropertyMetadata(nullptr));

return s_AccessibilityActionsProperty;
}

void DynamicAutomationProperties::SetAccessibilityActions(
Windows::UI::Xaml::UIElement const &element,
Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction> const &value) {
return element.SetValue(AccessibilityActionsProperty(), winrt::box_value(value));
}

Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction>
DynamicAutomationProperties::GetAccessibilityActions(Windows::UI::Xaml::UIElement const &element) {
return winrt::unbox_value<Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction>>(
element.GetValue(AccessibilityActionsProperty()));
}

void DynamicAutomationProperties::DispatchAccessibilityAction(
Windows::UI::Xaml::UIElement const &element,
std::wstring_view const &actionName) {
if (element) {
auto vector = GetAccessibilityActions(element);
if (vector) {
for (uint32_t i = 0; i < vector.Size(); i++) {
auto item = vector.GetAt(i);

if (item.Name.operator std::wstring_view() == actionName) {
if (auto const &handler = GetAccessibilityActionEventHandler(element)) {
handler(item);
}
}
}
}
}
}

winrt::Windows::UI::Xaml::DependencyProperty DynamicAutomationProperties::AccessibilityActionEventHandlerProperty() {
static winrt::DependencyProperty s_AccessibilityActionEventHandlerProperty =
winrt::DependencyProperty::RegisterAttached(
L"AccessibilityActionEventHandler",
winrt::xaml_typename<winrt::react::uwp::AccessibilityActionEventHandler>(),
dynamicAutomationTypeName,
winrt::PropertyMetadata(winrt::box_value<winrt::react::uwp::AccessibilityActionEventHandler>(nullptr)));

return s_AccessibilityActionEventHandlerProperty;
}

void DynamicAutomationProperties::SetAccessibilityActionEventHandler(
winrt::Windows::UI::Xaml::UIElement const &element,
winrt::react::uwp::AccessibilityActionEventHandler const &value) {
element.SetValue(
AccessibilityActionEventHandlerProperty(),
winrt::box_value<winrt::react::uwp::AccessibilityActionEventHandler>(value));
}

winrt::react::uwp::AccessibilityActionEventHandler DynamicAutomationProperties::GetAccessibilityActionEventHandler(
winrt::Windows::UI::Xaml::UIElement const &element) {
return winrt::unbox_value<winrt::react::uwp::AccessibilityActionEventHandler>(
element.GetValue(AccessibilityActionEventHandlerProperty()));
}
} // namespace winrt::react::uwp::implementation
20 changes: 20 additions & 0 deletions vnext/ReactUWP/Views/DynamicAutomationProperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ struct DynamicAutomationProperties : DynamicAutomationPropertiesT<DynamicAutomat
winrt::react::uwp::AccessibilityInvokeEventHandler const &value);
static winrt::react::uwp::AccessibilityInvokeEventHandler GetAccessibilityInvokeEventHandler(
winrt::Windows::UI::Xaml::UIElement const &element);

static winrt::Windows::UI::Xaml::DependencyProperty AccessibilityActionsProperty();

static void SetAccessibilityActions(
Windows::UI::Xaml::UIElement const &element,
Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction> const &value);

static Windows::Foundation::Collections::IVector<react::uwp::AccessibilityAction> GetAccessibilityActions(
Windows::UI::Xaml::UIElement const &element);

static void DispatchAccessibilityAction(
Windows::UI::Xaml::UIElement const &element,
std::wstring_view const &actionName);

static winrt::Windows::UI::Xaml::DependencyProperty AccessibilityActionEventHandlerProperty();
static void SetAccessibilityActionEventHandler(
Windows::UI::Xaml::UIElement const &element,
winrt::react::uwp::AccessibilityActionEventHandler const &value);
static winrt::react::uwp::AccessibilityActionEventHandler GetAccessibilityActionEventHandler(
winrt::Windows::UI::Xaml::UIElement const &element);
};

} // namespace winrt::react::uwp::implementation
Expand Down
48 changes: 45 additions & 3 deletions vnext/ReactUWP/Views/FrameworkElementViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,54 @@
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <winrt/Windows.UI.Xaml.h>

#include "Utils/PropertyHandlerUtils.h"

#include "DynamicAutomationProperties.h"

namespace winrt {
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Automation;
using namespace Windows::UI::Xaml::Automation::Peers;
using namespace Windows::Foundation::Collections;
} // namespace winrt

namespace react {
namespace uwp {

template <>
struct json_type_traits<winrt::react::uwp::AccessibilityAction> {
static winrt::react::uwp::AccessibilityAction parseJson(const folly::dynamic &json) {
auto action = winrt::react::uwp::AccessibilityAction();

for (auto &item : json.items()) {
if (item.first == "name") {
action.Name = react::uwp::asHstring(item.second);
} else if (item.first == "label") {
action.Label = react::uwp::asHstring(item.second);
}
}
return action;
}
};

template <>
struct json_type_traits<winrt::IVector<winrt::react::uwp::AccessibilityAction>> {
static winrt::IVector<winrt::react::uwp::AccessibilityAction> parseJson(const folly::dynamic &json) {
auto vector = winrt::single_threaded_vector<winrt::react::uwp::AccessibilityAction>();

if (json.isArray()) {
for (const auto &action : json) {
if (!action.isObject())
continue;

vector.Append(json_type_traits<winrt::react::uwp::AccessibilityAction>::parseJson(action));
}
}
return vector;
}
};

FrameworkElementViewManager::FrameworkElementViewManager(const std::shared_ptr<IReactInstance> &reactInstance)
: Super(reactInstance) {}

Expand Down Expand Up @@ -87,6 +123,8 @@ void FrameworkElementViewManager::TransferProperties(XamlView oldView, XamlView
TransferProperty(oldView, newView, DynamicAutomationProperties::AccessibilityStateExpandedProperty());
TransferProperty(oldView, newView, DynamicAutomationProperties::AccessibilityStateCollapsedProperty());
TransferProperty(oldView, newView, DynamicAutomationProperties::AccessibilityInvokeEventHandlerProperty());
TransferProperty(oldView, newView, DynamicAutomationProperties::AccessibilityActionEventHandlerProperty());
TransferProperty(oldView, newView, DynamicAutomationProperties::AccessibilityActionsProperty());

auto tooltip = winrt::ToolTipService::GetToolTip(oldView);
oldView.ClearValue(winrt::ToolTipService::ToolTipProperty());
Expand All @@ -103,9 +141,10 @@ void FrameworkElementViewManager::TransferProperties(XamlView oldView, XamlView

folly::dynamic FrameworkElementViewManager::GetNativeProps() const {
folly::dynamic props = Super::GetNativeProps();
props.update(folly::dynamic::object("accessible", "boolean")("accessibilityRole", "string")(
"accessibilityStates", "array")("accessibilityHint", "string")("accessibilityLabel", "string")(
"accessibilityPosInSet", "number")("accessibilitySetSize", "number")("testID", "string")("tooltip", "string"));
props.update(
folly::dynamic::object("accessible", "boolean")("accessibilityRole", "string")("accessibilityStates", "array")(
"accessibilityHint", "string")("accessibilityLabel", "string")("accessibilityPosInSet", "number")(
"accessibilitySetSize", "number")("testID", "string")("tooltip", "string")("accessibilityActions", "array"));
return props;
}

Expand Down Expand Up @@ -421,6 +460,9 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate,
}
} else if (TryUpdateFlowDirection(element, propertyName, propertyValue)) {
continue;
} else if (propertyName == "accessibilityActions") {
auto value = json_type_traits<winrt::IVector<winrt::react::uwp::AccessibilityAction>>::parseJson(propertyValue);
DynamicAutomationProperties::SetAccessibilityActions(element, value);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion vnext/ReactUWP/Views/ViewManagerBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ dynamic ViewManagerBase::GetExportedCustomDirectEventTypeConstants() const {
folly::dynamic eventTypes = folly::dynamic::object();
eventTypes.update(folly::dynamic::object("topLayout", folly::dynamic::object("registrationName", "onLayout"))(
"topMouseEnter", folly::dynamic::object("registrationName", "onMouseEnter"))(
"topMouseLeave", folly::dynamic::object("registrationName", "onMouseLeave"))
"topMouseLeave", folly::dynamic::object("registrationName", "onMouseLeave"))(
"topAccessibilityAction", folly::dynamic::object("registrationName", "onAccessibilityAction"))
// ("topMouseMove",
// folly::dynamic::object("registrationName",
// "onMouseMove"))
Expand Down
10 changes: 10 additions & 0 deletions vnext/ReactUWP/Views/ViewViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ class ViewShadowNode : public ShadowNodeBase {
else
DispatchEvent("topAccessibilityTap", std::move(folly::dynamic::object("target", m_tag)));
});

DynamicAutomationProperties::SetAccessibilityActionEventHandler(
panel, [=](winrt::react::uwp::AccessibilityAction const &action) {
folly::dynamic eventData = folly::dynamic::object("target", m_tag);

eventData.insert(
"actionName", action.Label.empty() ? HstringToDynamic(action.Name) : HstringToDynamic(action.Label));

DispatchEvent("topAccessibilityAction", std::move(eventData));
});
}

bool IsControl() {
Expand Down
Loading

0 comments on commit 97955e9

Please sign in to comment.