Skip to content
Open
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
15 changes: 15 additions & 0 deletions src/aria/menu/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,21 @@ describe('Menu Trigger Pattern', () => {
expect(document.activeElement).toBe(trigger);
});

it('should only close the submenu on escape from a submenu', async () => {
await keydown(getTrigger(), 'Enter');
await keydown(getItem('Apple')!, 'ArrowDown');
await keydown(getItem('Banana')!, 'ArrowDown');
const berries = getItem('Berries')!;
await keydown(berries, 'ArrowRight');
expect(berries.getAttribute('aria-expanded')).toBe('true');

await keydown(getItem('Blueberry')!, 'Escape');

expect(berries.getAttribute('aria-expanded')).toBe('false');
expect(isExpanded()).toBe(true);
expect(document.activeElement).toBe(berries);
});

it('should close on selecting an item on click', async () => {
await click(getTrigger());
await click(getItem('Apple')!);
Expand Down
27 changes: 27 additions & 0 deletions src/aria/private/menu/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,18 @@ describe('Menu Trigger Pattern', () => {
expect(menu.visible()).toBe(false);
});

it('should only close the submenu on escape from a submenu', () => {
trigger.onKeydown(down());
menu.onKeydown(right());
expect(submenu?.visible()).toBe(true);

submenu?.onKeydown(escape());
expect(submenu?.visible()).toBe(false);
expect(menu.visible()).toBe(true);
expect(trigger.expanded()).toBe(true);
expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[0]);
});

it('should close the triggered menu on selecting an item on click', () => {
trigger.onClick();
expect(trigger.expanded()).toBe(true);
Expand Down Expand Up @@ -836,6 +848,21 @@ describe('Menu Bar Pattern', () => {
expect(menuA.visible()).toBe(false);
});

it('should only close the nested submenu on escape from a nested submenu', () => {
const menubarItems = menubar.inputs.items();
const nested = getMenuPattern(menuA.inputs.items()[0], ['apricot']);
menubar.onClick(clickMenuItem(menubarItems, 0));
menuA.onKeydown(right());
expect(nested.visible()).toBe(true);

nested.onKeydown(escape());

expect(nested.visible()).toBe(false);
expect(menuA.visible()).toBe(true);
expect(menubarItems[0].expanded()).toBe(true);
expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]);
});

it('should close the menubar menu on selecting an item on click', () => {
const menubarItems = menubar.inputs.items();
menubar.onClick(clickMenuItem(menubarItems, 0));
Expand Down
16 changes: 15 additions & 1 deletion src/aria/private/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export class MenuPattern<V> {
.on('Home', () => this.first())
.on('End', () => this.last())
.on('Enter', () => this.trigger())
.on('Escape', () => this.closeAll())
.on('Escape', () => this.closeCurrent())
.on(this._expandKey, () => this.expand())
.on(this._collapseKey, () => this.collapse())
.on(this.dynamicSpaceKey, () => this.trigger())
Expand Down Expand Up @@ -449,6 +449,20 @@ export class MenuPattern<V> {
this.inputs.parent()?.close();
}

/**
* Closes the menu that currently has focus and moves focus back to the item that opened it.
* Top-level menus (opened from a trigger or a menubar) close the whole menu instead.
*/
closeCurrent() {
const parent = this.inputs.parent();

if (parent instanceof MenuItemPattern && !(parent.inputs.parent() instanceof MenuBarPattern)) {
parent.close({refocus: true});
} else {
this.closeAll();
}
}

/** Closes the menu and all parent menus. */
closeAll() {
const root = this.root();
Expand Down
Loading