Skip to content

Commit

Permalink
[0.69] Backport Accessible/Focusable Updates (#11011)
Browse files Browse the repository at this point in the history
* Backport Focusable/Accessible Changes to 0.69

* Change files

* Adjust Control Code

* Format

* Remove Unneeded

* Adjust Logic

* Change files

* Fix Formatting

* Delete index.d.ts

* Alter View

* Update Sample

* Delete index.d.ts

* Fix Lint

---------

Co-authored-by: Danny van Velzen <dannyvv@microsoft.com>
  • Loading branch information
chiaramooney and dannyvv committed Feb 9, 2023
1 parent e5e5124 commit 85c4e6e
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 67 deletions.
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Backport Focusable/Accessible Changes to 0.69",
"packageName": "@office-iss/react-native-win32",
"email": "34109996+chiaramooney@users.noreply.github.com",
"dependentChangeType": "patch"
}
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Backport Focusable/Accessible Changes to 0.69",
"packageName": "react-native-windows",
"email": "34109996+chiaramooney@users.noreply.github.com",
"dependentChangeType": "patch"
}
22 changes: 22 additions & 0 deletions packages/playground/Samples/view.tsx
Expand Up @@ -11,6 +11,8 @@ export default class Bootstrap extends React.Component<
{},
{
focusable: boolean;
accessible: boolean;
pressable: boolean;
hasStyle: boolean;
hasBorder: boolean;
radius: boolean;
Expand All @@ -23,6 +25,8 @@ export default class Bootstrap extends React.Component<
super(props);
this.state = {
focusable: true,
accessible: true,
pressable: true,
hasStyle: true,
hasBorder: true,
radius: true,
Expand Down Expand Up @@ -94,6 +98,22 @@ export default class Bootstrap extends React.Component<
<Text>focusable</Text>
</View>

<View style={{flexDirection: 'row', alignSelf: 'flex-start'}}>
<Switch
onValueChange={value => this.setState({accessible: value})}
value={this.state.accessible}
/>
<Text>accessible</Text>
</View>

<View style={{flexDirection: 'row', alignSelf: 'flex-start'}}>
<Switch
onValueChange={value => this.setState({pressable: value})}
value={this.state.pressable}
/>
<Text>pressable</Text>
</View>

<View style={{flexDirection: 'row', alignSelf: 'flex-start'}}>
<Switch
onValueChange={value => this.setState({hasStyle: value})}
Expand Down Expand Up @@ -155,6 +175,8 @@ export default class Bootstrap extends React.Component<
}}>
<View
focusable={this.state.focusable ? true : false}
accessible={this.state.accessible ? true : false}
{...{onClick: this.state.pressable}}
style={
this.state.hasStyle
? this.state.hasBorder
Expand Down
33 changes: 10 additions & 23 deletions vnext/Microsoft.ReactNative/Views/ControlViewManager.cpp
Expand Up @@ -82,16 +82,14 @@ bool ControlViewManager::UpdateProperty(
}
} else if (propertyName == "focusable") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean) {
IsFocusable(propertyValue.AsBoolean());
control.IsTabStop(propertyValue.AsBoolean());
nodeToUpdate->IsFocusable(propertyValue.AsBoolean());
} else if (propertyValue.IsNull()) {
control.ClearValue(TAB_STOP_PROPERTY());
IsFocusable(false);
nodeToUpdate->IsFocusable(false);
}
} else {
if (propertyName == "accessible") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean) {
IsAccessible(propertyValue.AsBoolean());
nodeToUpdate->IsAccessible(propertyValue.AsBoolean());
}
}
ret = Super::UpdateProperty(nodeToUpdate, propertyName, propertyValue);
Expand All @@ -108,11 +106,13 @@ bool ControlViewManager::UpdateProperty(
void ControlViewManager::OnPropertiesUpdated(ShadowNodeBase *node) {
auto control(node->GetView().as<xaml::Controls::Control>());

if (IsAccessible() != IsFocusable()) {
control.IsTabStop(false);
xaml::Automation::AutomationProperties::SetAccessibilityView(
control, xaml::Automation::Peers::AccessibilityView::Raw);
}
// If developer specifies either the accessible and focusable prop to be false
// remove accessibility and keyboard focus for component.
const auto isTabStop = (node->IsAccessible() && node->IsFocusable());
const auto accessibilityView =
isTabStop ? xaml::Automation::Peers::AccessibilityView::Content : xaml::Automation::Peers::AccessibilityView::Raw;
control.IsTabStop(isTabStop);
xaml::Automation::AutomationProperties::SetAccessibilityView(control, accessibilityView);
}

void ControlViewManager::OnViewCreated(XamlView view) {
Expand All @@ -123,17 +123,4 @@ void ControlViewManager::OnViewCreated(XamlView view) {
}
}

void ControlViewManager::IsAccessible(bool accessible) {
m_isAccessible = accessible;
}
bool ControlViewManager::IsAccessible() {
return m_isAccessible;
}
void ControlViewManager::IsFocusable(bool focusable) {
m_isFocusable = focusable;
}
bool ControlViewManager::IsFocusable() {
return m_isFocusable;
}

} // namespace Microsoft::ReactNative
9 changes: 0 additions & 9 deletions vnext/Microsoft.ReactNative/Views/ControlViewManager.h
Expand Up @@ -26,15 +26,6 @@ class REACTWINDOWS_EXPORT ControlViewManager : public FrameworkElementViewManage
void OnViewCreated(XamlView view) override;

void OnPropertiesUpdated(ShadowNodeBase *node) override;

private:
void IsAccessible(bool accessible);
bool IsAccessible();
void IsFocusable(bool focusable);
bool IsFocusable();

bool m_isAccessible = true;
bool m_isFocusable = true;
};

} // namespace Microsoft::ReactNative
13 changes: 13 additions & 0 deletions vnext/Microsoft.ReactNative/Views/ShadowNodeBase.cpp
Expand Up @@ -166,4 +166,17 @@ void ShadowNodeBase::RedBox(const std::string &message) const noexcept {
GetViewManager()->GetReactContext().CallJSFunction("RCTLog", "logToConsole", folly::dynamic::array("error", message));
}

void ShadowNodeBase::IsAccessible(bool accessible) {
m_isAccessible = accessible;
}
bool ShadowNodeBase::IsAccessible() {
return m_isAccessible;
}
void ShadowNodeBase::IsFocusable(bool focusable) {
m_isFocusable = focusable;
}
bool ShadowNodeBase::IsFocusable() {
return m_isFocusable;
}

} // namespace Microsoft::ReactNative
7 changes: 7 additions & 0 deletions vnext/Microsoft.ReactNative/Views/ShadowNodeBase.h
Expand Up @@ -115,10 +115,17 @@ struct REACTWINDOWS_EXPORT ShadowNodeBase : public ShadowNode {
return m_onMouseEnterRegistered || m_onMouseLeaveRegistered;
}

void IsAccessible(bool accessible);
bool IsAccessible();
void IsFocusable(bool focusable);
bool IsFocusable();

protected:
XamlView m_view;
bool m_updating = false;
comp::CompositionPropertySet m_transformPS{nullptr};
bool m_isFocusable = true;
bool m_isAccessible = true;

public:
double m_padding[(int)ShadowEdges::CountEdges] = INIT_UNDEFINED_EDGES;
Expand Down
62 changes: 27 additions & 35 deletions vnext/Microsoft.ReactNative/Views/ViewViewManager.cpp
Expand Up @@ -44,6 +44,8 @@ class ViewShadowNode : public ShadowNodeBase {
Super::createView(props);

auto panel = GetViewPanel();
IsAccessible(false);
IsFocusable(false);

DynamicAutomationProperties::SetAccessibilityInvokeEventHandler(panel, [=]() {
if (OnClick())
Expand Down Expand Up @@ -118,24 +120,6 @@ class ViewShadowNode : public ShadowNodeBase {
m_onClick = isSet;
}

bool IsFocusable() const {
return m_isFocusable;
}
void IsFocusable(bool isFocusable) {
m_isFocusable = isFocusable;

if (IsControl())
GetControl().IsTabStop(m_isFocusable);
}

bool IsAccessible() const {
return m_isAccessible;
}

void IsAccessible(bool isAccessible) {
m_isAccessible = isAccessible;
}

bool IsHitTestBrushRequired() const {
return IsRegisteredForMouseEvents();
}
Expand Down Expand Up @@ -259,8 +243,6 @@ class ViewShadowNode : public ShadowNodeBase {

bool m_enableFocusRing = true;
bool m_onClick = false;
bool m_isFocusable = false;
bool m_isAccessible = false;
int32_t m_tabIndex = std::numeric_limits<std::int32_t>::max();

xaml::Controls::ContentControl::GotFocus_revoker m_contentControlGotFocusRevoker{};
Expand Down Expand Up @@ -408,20 +390,16 @@ bool ViewViewManager::UpdateProperty(
UpdateCornerRadiusOnElement(nodeToUpdate, pPanel);
} else if (TryUpdateMouseEvents(nodeToUpdate, propertyName, propertyValue)) {
} else if (propertyName == "onClick") {
pViewShadowNode->OnClick(!propertyValue.IsNull() && propertyValue.AsBoolean());
pViewShadowNode->OnClick(propertyValue.AsBoolean());
} else if (propertyName == "overflow") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::String) {
bool clipChildren = propertyValue.AsString() == "hidden";
pPanel.ClipChildren(clipChildren);
}
} else if (propertyName == "focusable") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean)
pViewShadowNode->IsFocusable(propertyValue.AsBoolean());
pViewShadowNode->IsFocusable(propertyValue.AsBoolean());
} else if (propertyName == "enableFocusRing") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean)
pViewShadowNode->EnableFocusRing(propertyValue.AsBoolean());
else if (propertyValue.IsNull())
pViewShadowNode->EnableFocusRing(false);
pViewShadowNode->EnableFocusRing(propertyValue.AsBoolean());
} else if (propertyName == "tabIndex") {
auto tabIndex = propertyValue.AsInt64();
if (tabIndex == static_cast<int32_t>(tabIndex)) {
Expand All @@ -431,9 +409,7 @@ bool ViewViewManager::UpdateProperty(
}
} else {
if (propertyName == "accessible") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean) {
pViewShadowNode->IsAccessible(propertyValue.AsBoolean());
}
pViewShadowNode->IsAccessible(propertyValue.AsBoolean());
}
ret = Super::UpdateProperty(nodeToUpdate, propertyName, propertyValue);
}
Expand All @@ -458,7 +434,10 @@ void ViewViewManager::OnPropertiesUpdated(ShadowNodeBase *node) {
// keep it around, so not adding that code (yet).
}

bool shouldBeControl = viewShadowNode->IsFocusable();
// If component is focusable, it should be a ViewControl.
// If component is a View with accessible set to true, the component should be focusable, thus we need a ViewControl.
bool shouldBeControl =
(viewShadowNode->IsFocusable() || (viewShadowNode->IsAccessible() && !viewShadowNode->OnClick()));
if (auto view = viewShadowNode->GetView().try_as<xaml::UIElement>()) {
// If we have DynamicAutomationProperties, we need a ViewControl with a
// DynamicAutomationPeer
Expand All @@ -468,6 +447,7 @@ void ViewViewManager::OnPropertiesUpdated(ShadowNodeBase *node) {
panel.FinalizeProperties();

TryUpdateView(viewShadowNode, panel, shouldBeControl);
SyncFocusableAndAccessible(viewShadowNode, shouldBeControl);
}

void ViewViewManager::TryUpdateView(
Expand Down Expand Up @@ -578,11 +558,23 @@ void ViewViewManager::TryUpdateView(

if (useControl)
pViewShadowNode->GetControl().Content(visualRoot);
}

if (useControl && pViewShadowNode->IsAccessible() != pViewShadowNode->IsFocusable()) {
pViewShadowNode->GetControl().IsTabStop(false);
xaml::Automation::AutomationProperties::SetAccessibilityView(
pViewShadowNode->GetControl(), xaml::Automation::Peers::AccessibilityView::Raw);
void ViewViewManager::SyncFocusableAndAccessible(ViewShadowNode *pViewShadowNode, bool useControl) {
// If developer specifies either the accessible and focusable prop to be false
// remove accessibility and keyboard focus for component. Exception is made
// for case where a View with undefined onPress is specified, where
// component gains accessibility focus when either the accessible and focusable prop are true.
if (useControl) {
const auto isFocusable = pViewShadowNode->IsFocusable();
const auto isAccessible = pViewShadowNode->IsAccessible();
const auto isPressable = pViewShadowNode->OnClick();
const auto isTabStop =
(isPressable && isFocusable && isAccessible) || (!isPressable && (isFocusable || isAccessible));
const auto accessibilityView = isTabStop ? xaml::Automation::Peers::AccessibilityView::Content
: xaml::Automation::Peers::AccessibilityView::Raw;
pViewShadowNode->GetControl().IsTabStop(isTabStop);
xaml::Automation::AutomationProperties::SetAccessibilityView(pViewShadowNode->GetControl(), accessibilityView);
}
}

Expand Down
1 change: 1 addition & 0 deletions vnext/Microsoft.ReactNative/Views/ViewViewManager.h
Expand Up @@ -41,6 +41,7 @@ class ViewViewManager : public FrameworkElementViewManager {

XamlView CreateViewCore(int64_t tag, const winrt::Microsoft::ReactNative::JSValueObject &) override;
void TryUpdateView(ViewShadowNode *viewShadowNode, winrt::Microsoft::ReactNative::ViewPanel &pPanel, bool useControl);
void SyncFocusableAndAccessible(ViewShadowNode *viewShadowNode, bool useControl);

xaml::Media::SolidColorBrush EnsureTransparentBrush();

Expand Down

0 comments on commit 85c4e6e

Please sign in to comment.