Skip to content

Commit 1aa7b4b

Browse files
authored
fix: ensure menu-bar is focusable after closing and moving focus (#10273)
1 parent 5cae25d commit 1aa7b4b

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

packages/a11y-base/src/keyboard-direction-mixin.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@ export const KeyboardDirectionMixin = (superclass) =>
108108
if (
109109
this._tabNavigation &&
110110
key === 'Tab' &&
111-
((idx > currentIdx && event.shiftKey) || (idx < currentIdx && !event.shiftKey))
111+
((idx > currentIdx && event.shiftKey) || (idx < currentIdx && !event.shiftKey) || idx === currentIdx)
112112
) {
113113
// Prevent "roving tabindex" logic and let the normal Tab behavior if
114114
// - currently on the first focusable item and Shift + Tab is pressed,
115-
// - currently on the last focusable item and Tab is pressed.
115+
// - currently on the last focusable item and Tab is pressed,
116+
// - currently on the only focusable item and Tab is pressed
116117
return;
117118
}
118119

packages/a11y-base/test/keyboard-direction-mixin.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,17 @@ describe('KeyboardDirectionMixin', () => {
238238
tabKeyDown(items[5]);
239239
expect(element.focused).to.not.equal(items[0]);
240240
});
241+
242+
it('should not prevent default on Tab keydown with only one item present', () => {
243+
element.innerHTML = '<div tabindex="0">Foo</div>';
244+
items = element.children;
245+
items[0].focus();
246+
247+
const spy = sinon.spy();
248+
element.addEventListener('keydown', spy);
249+
tabKeyDown(items[0]);
250+
251+
expect(spy.firstCall.args[0].defaultPrevented).to.be.false;
252+
});
241253
});
242254
});

packages/menu-bar/src/vaadin-menu-bar-mixin.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,8 @@ export const MenuBarMixin = (superClass) =>
761761
*/
762762
_setFocused(focused) {
763763
if (focused) {
764-
const target = this.tabNavigation ? this.querySelector('[focused]') : this.querySelector('[tabindex="0"]');
764+
const selector = this.tabNavigation ? '[focused]' : '[tabindex="0"]';
765+
const target = this.querySelector(`vaadin-menu-bar-button${selector}`);
765766
if (target) {
766767
this._buttons.forEach((btn) => {
767768
this._setTabindex(btn, btn === target);

packages/menu-bar/test/keyboard-navigation.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,45 @@ describe('keyboard navigation', () => {
454454
});
455455
});
456456
});
457+
458+
describe('single button', () => {
459+
beforeEach(async () => {
460+
menu.items = [{ text: 'Item 1', children: [{ text: 'Item 1 1' }] }];
461+
await nextUpdate(menu);
462+
buttons = menu._buttons;
463+
firstGlobalFocusable.focus();
464+
});
465+
466+
it('should be focusable on Shift + Tab after closing and moving focus by default', async () => {
467+
await sendKeys({ press: 'Tab' });
468+
469+
await sendKeys({ press: 'ArrowDown' });
470+
await nextRender();
471+
472+
await sendKeys({ press: 'Escape' });
473+
474+
await sendKeys({ press: 'Tab' });
475+
expect(document.activeElement).to.equal(lastGlobalFocusable);
476+
477+
await sendKeys({ press: 'Shift+Tab' });
478+
expect(document.activeElement).to.equal(buttons[0]);
479+
});
480+
481+
it('should be focusable on Shift + Tab after closing and moving focus with Tab navigation', async () => {
482+
menu.tabNavigation = true;
483+
484+
await sendKeys({ press: 'Tab' });
485+
486+
await sendKeys({ press: 'ArrowDown' });
487+
await nextRender();
488+
489+
await sendKeys({ press: 'Escape' });
490+
491+
await sendKeys({ press: 'Tab' });
492+
expect(document.activeElement).to.equal(lastGlobalFocusable);
493+
494+
await sendKeys({ press: 'Shift+Tab' });
495+
expect(document.activeElement).to.equal(buttons[0]);
496+
});
497+
});
457498
});

0 commit comments

Comments
 (0)