Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AccessibilityActions #3475

Merged
merged 1 commit into from
Oct 29, 2019
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
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 @@ -421,6 +421,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) {
licanhua marked this conversation as resolved.
Show resolved Hide resolved
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