Skip to content

Commit fc21bff

Browse files
authored
ContextualMenu A11y Narrator Bug Fix (#3905)
* fix narrator bug * remove comment * remove unused expanded prop * Change files * gate to win32
1 parent 70eb746 commit fc21bff

File tree

6 files changed

+49
-7
lines changed

6 files changed

+49
-7
lines changed

apps/fluent-tester/src/TestComponents/ContextualMenu/ContextualMenuTest.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ const NestedContextualMenu: React.FunctionComponent = () => {
219219
itemKey="4"
220220
onHoverIn={toggleShowSubmenu}
221221
componentRef={stdMenuItemRef}
222+
expanded={showSubmenu}
222223
/>
223224
{showSubmenu && (
224225
<Submenu target={stdMenuItemRef} onDismiss={onDismissSubmenu} onShow={onShowSubmenu} setShowMenu={toggleShowSubmenu}>
@@ -446,6 +447,7 @@ const ScrollViewContextualMenu: React.FunctionComponent = () => {
446447
itemKey="3"
447448
onHoverIn={toggleShowSubmenu}
448449
componentRef={stdMenuItemRef}
450+
expanded={showSubmenu}
449451
/>
450452
{showSubmenu && (
451453
<Submenu
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix narrator bug",
4+
"packageName": "@fluentui-react-native/contextual-menu",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix narrator bug",
4+
"packageName": "@fluentui-react-native/tester",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/components/ContextualMenu/src/ContextualMenuItem.types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import type * as React from 'react';
22

33
import type { IViewProps } from '@fluentui-react-native/adapters';
44
import type { IconProps } from '@fluentui-react-native/icon';
5-
import type { IFocusable, IPressableState } from '@fluentui-react-native/interactive-hooks';
6-
import type { IPressableProps } from '@fluentui-react-native/pressable';
5+
import type { IFocusable, IPressableState, PressablePropsExtended } from '@fluentui-react-native/interactive-hooks';
76
import type { ITextProps } from '@fluentui-react-native/text';
87
import type { FontTokens, IForegroundColorTokens, IBackgroundColorTokens, IBorderTokens } from '@fluentui-react-native/tokens';
98
import type { IRenderData } from '@uifabricshared/foundation-composable';
@@ -48,7 +47,7 @@ export interface ContextualMenuItemTokens extends FontTokens, IForegroundColorTo
4847
iconWeight?: number;
4948
}
5049

51-
export interface ContextualMenuItemProps extends Omit<IPressableProps, 'onPress'> {
50+
export interface ContextualMenuItemProps extends Omit<PressablePropsExtended, 'onPress'> {
5251
/*
5352
** A unique key-identifier for each menu item
5453
*/

packages/components/ContextualMenu/src/SubmenuItem.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const SubmenuItem = compose<SubmenuItemType>({
2727
const defaultComponentRef = React.useRef(null);
2828
const {
2929
disabled,
30+
expanded,
3031
itemKey,
3132
icon,
3233
text,
@@ -147,23 +148,44 @@ export const SubmenuItem = compose<SubmenuItemType>({
147148
const onKeyDownProps = useKeyDownProps(showSubmenuOnKeyDown, ' ', 'Enter', 'ArrowLeft', 'ArrowRight');
148149
const onAccTap = onAccessibilityTap ?? onItemPress;
149150

150-
// grab the styling information, referencing the state as well as the props
151+
// Default accessibility actions to help screen readers announce expanded/collapsed state
152+
// Only provide on win32 to follow platform-specific accessibility patterns
153+
const defaultAccessibilityActions = React.useMemo(() => {
154+
if (Platform.OS === ('win32' as any)) {
155+
return [
156+
{ name: 'Expand', label: 'Expand submenu' },
157+
{ name: 'Collapse', label: 'Collapse submenu' },
158+
];
159+
}
160+
return [];
161+
}, []);
162+
163+
// Merge user accessibility actions with defaults
164+
const finalAccessibilityActions = React.useMemo(() => {
165+
const userActions = userProps.accessibilityActions;
166+
if (userActions && userActions.length > 0) {
167+
return [...defaultAccessibilityActions, ...userActions];
168+
}
169+
return defaultAccessibilityActions;
170+
}, [userProps.accessibilityActions, defaultAccessibilityActions]);
171+
151172
const styleProps = useStyling(userProps, (override: string) => state[override] || userProps[override]);
152173
// create the merged slot props
153174
const slotProps = mergeSettings<SubmenuItemSlotProps>(styleProps, {
154175
root: {
155176
ref: cmRef,
156177
...pressablePropsModified,
157178
...onKeyDownProps,
179+
...rest,
158180
accessible: true,
159181
accessibilityLabel: accessibilityLabel,
160182
accessibilityRole: 'menuitem',
161-
accessibilityState: { disabled: state.disabled ?? false, selected: state.selected },
183+
accessibilityState: { disabled: state.disabled ?? false, expanded: expanded ?? false, selected: state.selected },
162184
accessibilityValue: { text: itemKey },
185+
accessibilityActions: finalAccessibilityActions,
163186
disabled,
164187
focusable: !disabled,
165188
onAccessibilityTap: onAccTap,
166-
...rest,
167189
},
168190
content: {
169191
accessible: false,

packages/components/ContextualMenu/src/SubmenuItem.types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ export const submenuItemName = 'SubmenuItem';
1111
export interface SubmenuItemTokens extends ContextualMenuItemTokens {
1212
chevronColor?: string;
1313
}
14-
export type SubmenuItemProps = ContextualMenuItemProps;
14+
export interface SubmenuItemProps extends ContextualMenuItemProps {
15+
/**
16+
* Whether the submenu is currently expanded/visible
17+
*/
18+
expanded?: boolean;
19+
}
1520

1621
export type SubmenuItemState = ContextualMenuItemState;
1722

0 commit comments

Comments
 (0)