Skip to content

Commit aa28258

Browse files
authored
Narrator announces changes to accessibilityState (#9871)
* announcing * only announce when value is different * refactor * account for mixed state in checked * Revert "account for mixed state in checked", fix formatting This reverts commit d39db4d. * mixed state redo * Change files * disabled negation fix * disabled * address feedback
1 parent 8c37ad2 commit aa28258

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Added support for Narrator announcing accessibilityState changes",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/Views/FrameworkElementViewManager.cpp

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <WindowsNumerics.h>
1818
#include <winrt/Windows.Foundation.h>
1919

20+
#include <UI.Xaml.Automation.Peers.h>
2021
#include <UI.Xaml.Automation.h>
2122
#include <UI.Xaml.Controls.h>
2223
#include "Utils/PropertyHandlerUtils.h"
@@ -416,20 +417,61 @@ bool FrameworkElementViewManager::UpdateProperty(
416417
const std::string &innerName = pair.first;
417418
const auto &innerValue = pair.second;
418419

419-
if (innerName == "selected")
420+
auto peer = xaml::Automation::Peers::FrameworkElementAutomationPeer::FromElement(element);
421+
422+
if (innerName == "selected") {
420423
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Selected)] =
421424
innerValue.AsBoolean();
422-
else if (innerName == "disabled")
425+
const auto prevSelectedState = DynamicAutomationProperties::GetAccessibilityStateSelected(element);
426+
if (peer != nullptr && prevSelectedState != innerValue.AsBoolean()) {
427+
peer.RaisePropertyChangedEvent(
428+
winrt::SelectionItemPatternIdentifiers::IsSelectedProperty(),
429+
winrt::box_value(prevSelectedState),
430+
winrt::box_value(innerValue.AsBoolean()));
431+
}
432+
} else if (innerName == "disabled") {
423433
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Disabled)] =
424434
innerValue.AsBoolean();
425-
else if (innerName == "checked") {
435+
const auto prevDisabledState = DynamicAutomationProperties::GetAccessibilityStateDisabled(element);
436+
437+
if (peer != nullptr && prevDisabledState != innerValue.AsBoolean()) {
438+
peer.RaisePropertyChangedEvent(
439+
winrt::AutomationElementIdentifiers::IsEnabledProperty(),
440+
winrt::box_value(!prevDisabledState),
441+
winrt::box_value(!innerValue.AsBoolean()));
442+
}
443+
} else if (innerName == "checked") {
426444
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Checked)] =
427445
innerValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean && innerValue.AsBoolean();
428446
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Unchecked)] =
429447
innerValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean && !innerValue.AsBoolean();
430448
// If the state is "mixed" we'll just set both Checked and Unchecked to false,
431449
// then later in the IToggleProvider implementation it will return the Intermediate state
432450
// due to both being set to false (see DynamicAutomationPeer::ToggleState()).
451+
const auto prevCheckedState = DynamicAutomationProperties::GetAccessibilityStateChecked(element);
452+
const auto prevUncheckedState = DynamicAutomationProperties::GetAccessibilityStateUnchecked(element);
453+
454+
if (peer != nullptr) {
455+
if (prevCheckedState !=
456+
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Checked)] ||
457+
prevUncheckedState !=
458+
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Unchecked)]) {
459+
// Checking if either state has changed here to catch changes involving "mixed" state.
460+
const auto oldValue = prevCheckedState ? winrt::ToggleState::On : winrt::ToggleState::Off;
461+
if (innerValue.Type() != winrt::Microsoft::ReactNative::JSValueType::Boolean) {
462+
peer.RaisePropertyChangedEvent(
463+
winrt::TogglePatternIdentifiers::ToggleStateProperty(),
464+
winrt::box_value(oldValue),
465+
winrt::box_value(winrt::ToggleState::Indeterminate));
466+
} else {
467+
const auto newValue = innerValue.AsBoolean() ? winrt::ToggleState::On : winrt::ToggleState::Off;
468+
peer.RaisePropertyChangedEvent(
469+
winrt::TogglePatternIdentifiers::ToggleStateProperty(),
470+
winrt::box_value(oldValue),
471+
winrt::box_value(newValue));
472+
}
473+
}
474+
}
433475
} else if (innerName == "busy")
434476
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Busy)] =
435477
!innerValue.IsNull() && innerValue.AsBoolean();
@@ -438,6 +480,19 @@ bool FrameworkElementViewManager::UpdateProperty(
438480
!innerValue.IsNull() && innerValue.AsBoolean();
439481
states[static_cast<int32_t>(winrt::Microsoft::ReactNative::AccessibilityStates::Collapsed)] =
440482
innerValue.IsNull() || !innerValue.AsBoolean();
483+
484+
const auto prevExpandedState = DynamicAutomationProperties::GetAccessibilityStateExpanded(element);
485+
486+
if (peer != nullptr && prevExpandedState != innerValue.AsBoolean()) {
487+
const auto newValue =
488+
innerValue.AsBoolean() ? winrt::ExpandCollapseState::Expanded : winrt::ExpandCollapseState::Collapsed;
489+
const auto oldValue =
490+
prevExpandedState ? winrt::ExpandCollapseState::Expanded : winrt::ExpandCollapseState::Collapsed;
491+
peer.RaisePropertyChangedEvent(
492+
winrt::ExpandCollapsePatternIdentifiers::ExpandCollapseStateProperty(),
493+
winrt::box_value(oldValue),
494+
winrt::box_value(newValue));
495+
}
441496
}
442497
}
443498
}

0 commit comments

Comments
 (0)