Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions patches/react-native/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,10 @@
- Upstream PR/issue: This should ideally be the default behavior upstream, but no PR has been filed yet.
- E/App issue: [#83000](https://github.com/Expensify/App/issues/83000)
- PR introducing patch: [#83256](https://github.com/Expensify/App/pull/83256)

### [react-native+0.81.4+029+fix-fabric-accessibility-expanded-collapsed.patch](react-native+0.81.4+029+fix-fabric-accessibility-expanded-collapsed.patch)

- Reason: Fixes a bug in Fabric's (`RCTViewComponentView.mm`) accessibility state handling where the "collapsed" state is never announced by VoiceOver on iOS. The old architecture (`RCTView.m`) correctly handles both "expanded" and "collapsed", but Fabric only announced "expanded" (when `expanded=true`) and silently skipped announcing "collapsed" (when `expanded=false`). This patch changes the condition from `value_or(false)` to `has_value()` so that both states are properly announced.
- Upstream PR/issue: https://github.com/facebook/react-native/issues/XXXXX (Fabric regression vs Paper behavior)
- E/App issue: [#76929](https://github.com/Expensify/App/issues/76929)
- PR introducing patch: [#84244](https://github.com/Expensify/App/pull/84244)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
@@ -1314,8 +1314,9 @@
"mixed", "a checkbox, radio button, or other widget which is both checked and unchecked")];
}
- if (accessibilityState.expanded.value_or(false)) {
- [valueComponents
- addObject:RCTLocalizedString("expanded", "a menu, dialog, accordian panel, or other widget which is expanded")];
- }
+ if (accessibilityState.expanded.has_value()) {
+ [valueComponents addObject:accessibilityState.expanded.value()
+ ? RCTLocalizedString("expanded", "a menu, dialog, accordian panel, or other widget which is expanded")
+ : RCTLocalizedString("collapsed", "a menu, dialog, accordian panel, or other widget which is collapsed")];
+ }

if (accessibilityState.busy) {
7 changes: 6 additions & 1 deletion src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useIsFocused} from '@react-navigation/native';
import type {ForwardedRef} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import type {GestureResponderEvent, LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import type {AccessibilityState, GestureResponderEvent, LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {StyleSheet, View} from 'react-native';
import ActivityIndicator from '@components/ActivityIndicator';
import Icon from '@components/Icon';
Expand Down Expand Up @@ -139,6 +139,9 @@ type ButtonProps = Partial<ChildrenProps> &
/** Accessibility label for the component */
accessibilityLabel?: string;

/** Accessibility state to pass to the pressable */
accessibilityState?: AccessibilityState;

/** The icon asset to display to the left of the text */
icon?: IconAsset | null;

Expand Down Expand Up @@ -278,6 +281,7 @@ function Button({
id = '',
testID = undefined,
accessibilityLabel = '',
accessibilityState,
isSplitButton = false,
link = false,
isContentCentered = false,
Expand Down Expand Up @@ -530,6 +534,7 @@ function Button({
testID={testID}
accessibilityLabel={accessibilityLabel}
role={getButtonRole(isNested)}
accessibilityState={accessibilityState}
hoverDimmingValue={1}
onHoverIn={!isDisabled || !shouldStayNormalOnDisable ? () => setIsHovered(true) : undefined}
onHoverOut={!isDisabled || !shouldStayNormalOnDisable ? () => setIsHovered(false) : undefined}
Expand Down
2 changes: 2 additions & 0 deletions src/components/ButtonWithDropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
secondLineText={secondLineText}
icon={icon}
sentryLabel={sentryLabel}
accessibilityState={!isSplitButton ? {expanded: isMenuVisible} : undefined}
/>

{isSplitButton && (
Expand All @@ -207,6 +208,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
innerStyles={[styles.dropDownButtonCartIconContainerPadding, innerStyleDropButton, isButtonSizeSmall && styles.dropDownButtonCartIcon]}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
sentryLabel={sentryLabel}
accessibilityState={{expanded: isMenuVisible}}
>
<View style={[styles.dropDownButtonCartIconView, innerStyleDropButton]}>
<View style={[success ? styles.buttonSuccessDivider : styles.buttonDivider]} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ function GenericPressable({
aria-disabled={isDisabled}
aria-checked={accessibilityState?.checked}
aria-selected={accessibilityState?.selected}
aria-expanded={accessibilityState?.expanded}
aria-keyshortcuts={keyboardShortcut && `${keyboardShortcut.modifiers.join('')}+${keyboardShortcut.shortcutKey}`}
// ios-only form of inputs
onMagicTap={!isDisabled ? voidOnPressHandler : undefined}
Expand Down
Loading