Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: ContextualMenu should not include headers and dividers in ARIA index calculations",
"packageName": "@fluentui/react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ const DEFAULT_PROPS: Partial<IContextualMenuProps> = {
beakWidth: 16,
};

/* return number of menu items, excluding headers and dividers */
function getItemCount(items: IContextualMenuItem[]): number {
let totalItemCount = 0;
for (const item of items) {
if (item.itemType !== ContextualMenuItemType.Divider && item.itemType !== ContextualMenuItemType.Header) {
const itemCount = item.customOnRenderListLength ? item.customOnRenderListLength : 1;
totalItemCount += itemCount;
}
}
return totalItemCount;
}

export function getSubmenuItems(
item: IContextualMenuItem,
options?: {
Expand Down Expand Up @@ -949,7 +961,7 @@ export const ContextualMenuBase: React.FunctionComponent<IContextualMenuProps> =
key: `section-${sectionProps.title}-title`,
itemType: ContextualMenuItemType.Header,
text: sectionProps.title,
id: id,
id,
};
ariaLabelledby = id;
} else {
Expand All @@ -975,23 +987,34 @@ export const ContextualMenuBase: React.FunctionComponent<IContextualMenuProps> =
}

if (sectionProps.items && sectionProps.items.length > 0) {
let correctedIndex = 0;
return (
<li role="presentation" key={sectionProps.key || sectionItem.key || `section-${index}`}>
<div {...groupProps}>
<ul className={menuClassNames.list} role="presentation">
{sectionProps.topDivider && renderSeparator(index, itemClassNames, true, true)}
{headerItem && renderListItem(headerItem, sectionItem.key || index, itemClassNames, sectionItem.title)}
{sectionProps.items.map((contextualMenuItem, itemsIndex) =>
renderMenuItem(
{sectionProps.items.map((contextualMenuItem, itemsIndex) => {
const menuItem = renderMenuItem(
contextualMenuItem,
itemsIndex,
itemsIndex,
sectionProps.items.length,
correctedIndex,
getItemCount(sectionProps.items),
hasCheckmarks,
hasIcons,
menuClassNames,
),
)}
);
if (
contextualMenuItem.itemType !== ContextualMenuItemType.Divider &&
contextualMenuItem.itemType !== ContextualMenuItemType.Header
) {
const indexIncrease = contextualMenuItem.customOnRenderListLength
? contextualMenuItem.customOnRenderListLength
: 1;
correctedIndex += indexIncrease;
}
return menuItem;
})}
{sectionProps.bottomDivider && renderSeparator(index, itemClassNames, false, true)}
</ul>
</div>
Expand Down Expand Up @@ -1062,9 +1085,9 @@ export const ContextualMenuBase: React.FunctionComponent<IContextualMenuProps> =
onItemMouseEnter: onItemMouseEnterBase,
onItemMouseLeave: onMouseItemLeave,
onItemMouseMove: onItemMouseMoveBase,
onItemMouseDown: onItemMouseDown,
executeItemClick: executeItemClick,
onItemKeyDown: onItemKeyDown,
onItemMouseDown,
executeItemClick,
onItemKeyDown,
expandedMenuItemKey,
openSubMenu,
dismissSubMenu: onSubMenuDismiss,
Expand Down Expand Up @@ -1160,7 +1183,7 @@ export const ContextualMenuBase: React.FunctionComponent<IContextualMenuProps> =
? getMenuClassNames(theme!, className)
: getClassNames(styles, {
theme: theme!,
className: className,
className,
});

const hasIcons = itemsHaveIcons(items);
Expand Down Expand Up @@ -1217,13 +1240,7 @@ export const ContextualMenuBase: React.FunctionComponent<IContextualMenuProps> =

// The menu should only return if items were provided, if no items were provided then it should not appear.
if (items && items.length > 0) {
let totalItemCount = 0;
for (const item of items) {
if (item.itemType !== ContextualMenuItemType.Divider && item.itemType !== ContextualMenuItemType.Header) {
const itemCount = item.customOnRenderListLength ? item.customOnRenderListLength : 1;
totalItemCount += itemCount;
}
}
const totalItemCount = getItemCount(items);

const calloutStyles = classNames.subComponentStyles
? (classNames.subComponentStyles.callout as IStyleFunctionOrObject<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ describe('ContextualMenu', () => {
className: 'SubMenuClass',
},
],
onDismiss: onDismiss,
onDismiss,
},
},
];
Expand Down Expand Up @@ -742,6 +742,61 @@ describe('ContextualMenu', () => {
expect(menuItems.length).toEqual(10);
});

it('calculates index and total of menu items correctly', () => {
const items: IContextualMenuItem[] = [
{ key: 'header', text: 'header', itemType: ContextualMenuItemType.Header },
{ key: '1', text: 'One' },
{ key: 'divider', itemType: ContextualMenuItemType.Divider },
{ key: '2', text: 'Two' },
];

ReactTestUtils.act(() => {
ReactTestUtils.renderIntoDocument<IContextualMenuProps>(<ContextualMenu items={items} />);
});

const menuItems = document.querySelectorAll('li button');
const total = menuItems[0].getAttribute('aria-setsize');
const index1 = menuItems[0].getAttribute('aria-posinset');
const index2 = menuItems[1].getAttribute('aria-posinset');

expect(total).toBe('2');
expect(index1).toBe('1');
expect(index2).toBe('2');
});

it('calculates index and total of menu items in a section correctly', () => {
const items: IContextualMenuItem[] = [
{
key: 'section1',
itemType: ContextualMenuItemType.Section,
sectionProps: {
topDivider: true,
bottomDivider: true,
title: 'Actions',
items: [
{ key: 'header', text: 'header', itemType: ContextualMenuItemType.Header },
{ key: '1', text: 'One' },
{ key: 'divider', itemType: ContextualMenuItemType.Divider },
{ key: '2', text: 'Two' },
],
},
},
];

ReactTestUtils.act(() => {
ReactTestUtils.renderIntoDocument<IContextualMenuProps>(<ContextualMenu items={items} />);
});

const menuItems = document.querySelectorAll('li button');
const total = menuItems[0].getAttribute('aria-setsize');
const index1 = menuItems[0].getAttribute('aria-posinset');
const index2 = menuItems[1].getAttribute('aria-posinset');

expect(total).toBe('2');
expect(index1).toBe('1');
expect(index2).toBe('2');
});

describe('with links', () => {
const testUrl = 'http://test.com';
let items: IContextualMenuItem[];
Expand Down