From b08b497da0a3ace246e1c38667e58f7cadf8316c Mon Sep 17 00:00:00 2001 From: kmelmon <33470154+kmelmon@users.noreply.github.com> Date: Thu, 16 Apr 2020 11:36:27 -0700 Subject: [PATCH 1/5] Implement accessibilityState (#4617) * Implement AccessibilityState * formatting * Change files * bring back accessibilityStates to port back to 0.61 * formatting * handle mixed state * update test * prettier fixes --- ...020-04-15-15-25-43-AccessibilityState.json | 8 +++ .../Views/FrameworkElementViewManager.cpp | 60 +++++++++++++++++-- .../AccessibilityExampleWindows.tsx | 57 ++++++++++++------ 3 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json diff --git a/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json b/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json new file mode 100644 index 00000000000..6099cacb2eb --- /dev/null +++ b/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "implement accessibilityState", + "packageName": "react-native-windows", + "email": "kmelmon@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-04-15T22:25:43.418Z" +} \ No newline at end of file diff --git a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp index c64b5f08e9d..455bf114bad 100644 --- a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp +++ b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp @@ -141,12 +141,21 @@ void FrameworkElementViewManager::TransferProperties(XamlView oldView, XamlView } } +static folly::dynamic GetAccessibilityStateProps() { + folly::dynamic props = folly::dynamic::object(); + + props.update(folly::dynamic::object("selected", "boolean")("disabled", "boolean")("checked", "string")( + "busy", "boolean")("expanded", "boolean")); + return props; +} + 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")( - "accessibilityActions", "array")("accessibilityLiveRegion", "string")); + "accessibilityStates", "array")("accessibilityState", GetAccessibilityStateProps())( + "accessibilityHint", "string")("accessibilityLabel", "string")("accessibilityPosInSet", "number")( + "accessibilitySetSize", "number")("testID", "string")("tooltip", "string")("accessibilityActions", "array")( + "accessibilityLiveRegion", "string")); return props; } @@ -156,7 +165,7 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, for (const auto &pair : reactDiffMap.items()) { const std::string &propertyName = pair.first.getString(); const folly::dynamic &propertyValue = pair.second; - + if (propertyName == "opacity") { if (propertyValue.isNumber()) { double opacity = propertyValue.asDouble(); @@ -436,6 +445,49 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, element, states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)]); DynamicAutomationProperties::SetAccessibilityStateCollapsed( element, states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)]); + } else if (propertyName == "accessibilityState") { + bool states[static_cast(winrt::react::uwp::AccessibilityStates::CountStates)] = {}; + + if (propertyValue.isObject()) { + for (const auto &pair : propertyValue.items()) { + const std::string &innerName = pair.first.getString(); + const folly::dynamic &innerValue = pair.second; + + if (innerName == "selected") + states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)] = innerValue.getBool(); + else if (innerName == "disabled") + states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)] = innerValue.getBool(); + else if (innerName == "checked") { + states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)] = + innerValue.isBool() && innerValue.getBool(); + states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)] = + innerValue.isBool() && !innerValue.getBool(); + // If the state is "mixed" we'll just set both Checked and Unchecked to false, + // then later in the IToggleProvider implementation it will return the Intermediate state + // due to both being set to false (see DynamicAutomationPeer::ToggleState()). + } else if (innerName == "busy") + states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)] = innerValue.getBool(); + else if (innerName == "expanded") { + states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)] = innerValue.getBool(); + states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)] = !innerValue.getBool(); + } + } + } + + DynamicAutomationProperties::SetAccessibilityStateSelected( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)]); + DynamicAutomationProperties::SetAccessibilityStateDisabled( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)]); + DynamicAutomationProperties::SetAccessibilityStateChecked( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)]); + DynamicAutomationProperties::SetAccessibilityStateUnchecked( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)]); + DynamicAutomationProperties::SetAccessibilityStateBusy( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)]); + DynamicAutomationProperties::SetAccessibilityStateExpanded( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)]); + DynamicAutomationProperties::SetAccessibilityStateCollapsed( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)]); } else if (propertyName == "testID") { if (propertyValue.isString()) { auto value = react::uwp::asHstring(propertyValue); diff --git a/vnext/src/RNTester/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx b/vnext/src/RNTester/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx index 71e09f4ea38..4f92feeb5a1 100644 --- a/vnext/src/RNTester/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx +++ b/vnext/src/RNTester/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx @@ -269,7 +269,7 @@ class AccessibilityStateExamples extends React.Component { public state = { viewDisabled: false, itemsSelected: [false, false, false], - viewChecked: false, + viewChecked: 0, viewBusy: false, viewCollapsed: false, }; @@ -293,7 +293,7 @@ class AccessibilityStateExamples extends React.Component { backgroundColor: this.state.viewDisabled ? 'gray' : 'lightskyblue', }} accessibilityRole="none" - accessibilityStates={this.state.viewDisabled ? ['disabled'] : []}> + accessibilityState={{disabled: this.state.viewDisabled}}> This View should be{' '} {this.state.viewDisabled ? 'disabled' : 'enabled'} according to UIA @@ -317,9 +317,9 @@ class AccessibilityStateExamples extends React.Component { }} accessibilityRole="button" accessibilityLabel={'Selectable item ' + (item.index + 1)} - accessibilityStates={ - this.state.itemsSelected[item.index] ? ['selected'] : [] - } + accessibilityState={{ + selected: this.state.itemsSelected[item.index], + }} onPress={() => this.selectPress(item.index)}> {this.state.itemsSelected[item.index] @@ -331,8 +331,8 @@ class AccessibilityStateExamples extends React.Component { keyExtractor={(item, index) => index.toString()} /> - The following TouchableHighlight toggles accessibilityState.checked - and accessibilityState.unchecked for the View under it: + The following TouchableHighlight cycles accessibilityState.checked + through unchecked/checked/mixed for the View under it: + accessibilityState={{ + checked: + this.state.viewChecked === 0 + ? false + : this.state.viewChecked === 1 + ? true + : 'mixed', + }}> This View should be{' '} - {this.state.viewChecked ? 'Checked' : 'Unchecked'} according to UIA + {this.state.viewChecked === 0 + ? 'Unchecked' + : this.state.viewChecked === 1 + ? 'Checked' + : 'Mixed'}{' '} + according to UIA - The following TouchableHighlight toggles accessibilityState.busy for - the View under it: + The following TouchableHighlight toggles the acessibilityState.busy + for the View under it: + accessibilityState={{busy: this.state.viewBusy}}> This View should be {this.state.viewBusy ? 'Busy' : 'Not Busy'}{' '} according to UIA @@ -394,9 +409,9 @@ class AccessibilityStateExamples extends React.Component { }} accessibilityRole="none" //@ts-ignore - accessibilityStates={ - this.state.viewCollapsed ? ['collapsed'] : ['expanded'] - }> + accessibilityState={{ + expanded: !this.state.viewCollapsed, + }}> This View should be{' '} {this.state.viewCollapsed ? 'Collapsed' : 'Expanded'} according to @@ -418,7 +433,11 @@ class AccessibilityStateExamples extends React.Component { }; private checkedPress = () => { - this.setState({viewChecked: !this.state.viewChecked}); + let newChecked = this.state.viewChecked + 1; + if (newChecked === 3) { + newChecked = 0; + } + this.setState({viewChecked: newChecked}); }; private busyPress = () => { From ad610aeb05ea9083d5e56da91d82c7ad93801966 Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Thu, 16 Apr 2020 14:30:50 -0700 Subject: [PATCH 2/5] resolve merge conflict --- vnext/ReactUWP/Views/FrameworkElementViewManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp index 455bf114bad..3dd8ffc1944 100644 --- a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp +++ b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp @@ -449,8 +449,8 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, bool states[static_cast(winrt::react::uwp::AccessibilityStates::CountStates)] = {}; if (propertyValue.isObject()) { - for (const auto &pair : propertyValue.items()) { - const std::string &innerName = pair.first.getString(); + for (const auto &innerPair : propertyValue.items()) { + const std::string &innerName = innerPair.first.getString(); const folly::dynamic &innerValue = pair.second; if (innerName == "selected") From 7c4eff335d1291f9d9f38134af1877b5a10ec993 Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Thu, 16 Apr 2020 14:40:40 -0700 Subject: [PATCH 3/5] idunno what the heck I'm doing but apparently need to change this --- ...t-native-windows-2020-04-15-15-25-43-AccessibilityState.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json b/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json index 6099cacb2eb..5e14288de8a 100644 --- a/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json +++ b/change/react-native-windows-2020-04-15-15-25-43-AccessibilityState.json @@ -1,5 +1,5 @@ { - "type": "prerelease", + "type": "patch", "comment": "implement accessibilityState", "packageName": "react-native-windows", "email": "kmelmon@microsoft.com", From 67c88e253229ec0fa6b2869a612a9b13ec80e72f Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Thu, 16 Apr 2020 15:10:48 -0700 Subject: [PATCH 4/5] f***ing clang --- .../Views/FrameworkElementViewManager.cpp | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp index 3dd8ffc1944..5b3d54b2702 100644 --- a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp +++ b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp @@ -165,7 +165,7 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, for (const auto &pair : reactDiffMap.items()) { const std::string &propertyName = pair.first.getString(); const folly::dynamic &propertyValue = pair.second; - + if (propertyName == "opacity") { if (propertyValue.isNumber()) { double opacity = propertyValue.asDouble(); @@ -445,49 +445,49 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, element, states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)]); DynamicAutomationProperties::SetAccessibilityStateCollapsed( element, states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)]); - } else if (propertyName == "accessibilityState") { - bool states[static_cast(winrt::react::uwp::AccessibilityStates::CountStates)] = {}; - - if (propertyValue.isObject()) { - for (const auto &innerPair : propertyValue.items()) { - const std::string &innerName = innerPair.first.getString(); - const folly::dynamic &innerValue = pair.second; - - if (innerName == "selected") - states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)] = innerValue.getBool(); - else if (innerName == "disabled") - states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)] = innerValue.getBool(); - else if (innerName == "checked") { - states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)] = - innerValue.isBool() && innerValue.getBool(); - states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)] = - innerValue.isBool() && !innerValue.getBool(); - // If the state is "mixed" we'll just set both Checked and Unchecked to false, - // then later in the IToggleProvider implementation it will return the Intermediate state - // due to both being set to false (see DynamicAutomationPeer::ToggleState()). - } else if (innerName == "busy") - states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)] = innerValue.getBool(); - else if (innerName == "expanded") { - states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)] = innerValue.getBool(); - states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)] = !innerValue.getBool(); + } else if (propertyName == "accessibilityState") { + bool states[static_cast(winrt::react::uwp::AccessibilityStates::CountStates)] = {}; + + if (propertyValue.isObject()) { + for (const auto &innerPair : propertyValue.items()) { + const std::string &innerName = innerPair.first.getString(); + const folly::dynamic &innerValue = pair.second; + + if (innerName == "selected") + states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)] = innerValue.getBool(); + else if (innerName == "disabled") + states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)] = innerValue.getBool(); + else if (innerName == "checked") { + states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)] = + innerValue.isBool() && innerValue.getBool(); + states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)] = + innerValue.isBool() && !innerValue.getBool(); + // If the state is "mixed" we'll just set both Checked and Unchecked to false, + // then later in the IToggleProvider implementation it will return the Intermediate state + // due to both being set to false (see DynamicAutomationPeer::ToggleState()). + } else if (innerName == "busy") + states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)] = innerValue.getBool(); + else if (innerName == "expanded") { + states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)] = innerValue.getBool(); + states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)] = !innerValue.getBool(); + } } } - } - DynamicAutomationProperties::SetAccessibilityStateSelected( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)]); - DynamicAutomationProperties::SetAccessibilityStateDisabled( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)]); - DynamicAutomationProperties::SetAccessibilityStateChecked( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)]); - DynamicAutomationProperties::SetAccessibilityStateUnchecked( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)]); - DynamicAutomationProperties::SetAccessibilityStateBusy( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)]); - DynamicAutomationProperties::SetAccessibilityStateExpanded( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)]); - DynamicAutomationProperties::SetAccessibilityStateCollapsed( - element, states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)]); + DynamicAutomationProperties::SetAccessibilityStateSelected( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)]); + DynamicAutomationProperties::SetAccessibilityStateDisabled( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Disabled)]); + DynamicAutomationProperties::SetAccessibilityStateChecked( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Checked)]); + DynamicAutomationProperties::SetAccessibilityStateUnchecked( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Unchecked)]); + DynamicAutomationProperties::SetAccessibilityStateBusy( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Busy)]); + DynamicAutomationProperties::SetAccessibilityStateExpanded( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Expanded)]); + DynamicAutomationProperties::SetAccessibilityStateCollapsed( + element, states[static_cast(winrt::react::uwp::AccessibilityStates::Collapsed)]); } else if (propertyName == "testID") { if (propertyValue.isString()) { auto value = react::uwp::asHstring(propertyValue); From a546e4b4f3e634e04070f13159aec79902f213f5 Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Thu, 16 Apr 2020 16:25:49 -0700 Subject: [PATCH 5/5] fix merge conflict --- vnext/ReactUWP/Views/FrameworkElementViewManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp index 5b3d54b2702..04d7745ca63 100644 --- a/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp +++ b/vnext/ReactUWP/Views/FrameworkElementViewManager.cpp @@ -451,7 +451,7 @@ void FrameworkElementViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, if (propertyValue.isObject()) { for (const auto &innerPair : propertyValue.items()) { const std::string &innerName = innerPair.first.getString(); - const folly::dynamic &innerValue = pair.second; + const folly::dynamic &innerValue = innerPair.second; if (innerName == "selected") states[static_cast(winrt::react::uwp::AccessibilityStates::Selected)] = innerValue.getBool();