Skip to content

Commit 938ebc9

Browse files
authored
refactor: use focusVisible: true in FocusTrapController focus (#10031)
1 parent 7a78ef3 commit 938ebc9

File tree

2 files changed

+27
-4
lines changed

2 files changed

+27
-4
lines changed

packages/a11y-base/src/focus-trap-controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2021 - 2025 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { getFocusableElements, isElementFocused } from './focus-utils.js';
6+
import { getFocusableElements, isElementFocused, isKeyboardActive } from './focus-utils.js';
77

88
const instances = [];
99

@@ -87,7 +87,7 @@ export class FocusTrapController {
8787
instances.push(this);
8888

8989
if (this.__focusedElementIndex === -1) {
90-
this.__focusableElements[0].focus();
90+
this.__focusableElements[0].focus({ focusVisible: isKeyboardActive() });
9191
}
9292
}
9393

@@ -147,7 +147,7 @@ export class FocusTrapController {
147147
const currentIndex = this.__focusedElementIndex;
148148
const nextIndex = (focusableElements.length + currentIndex + step) % focusableElements.length;
149149
const element = focusableElements[nextIndex];
150-
element.focus();
150+
element.focus({ focusVisible: true });
151151
if (element.localName === 'input') {
152152
element.select();
153153
}

packages/a11y-base/test/focus-trap-controller.test.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from '@vaadin/chai-plugins';
22
import { sendKeys } from '@vaadin/test-runner-commands';
3-
import { defineLit, fixtureSync } from '@vaadin/testing-helpers';
3+
import { defineLit, fixtureSync, mousedown } from '@vaadin/testing-helpers';
44
import sinon from 'sinon';
55
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
66
import { FocusTrapController } from '../src/focus-trap-controller.js';
@@ -95,6 +95,23 @@ describe('FocusTrapController', () => {
9595
expect(document.activeElement).to.equal(input);
9696
});
9797

98+
it('should use focusVisible: false when focusing the first focusable element if keyboard not active', () => {
99+
const input = trap.querySelector('#trap-input-1');
100+
const spy = sinon.spy(input, 'focus');
101+
mousedown(document.body);
102+
controller.trapFocus(trap);
103+
expect(spy.firstCall.args[0]).to.deep.equal({ focusVisible: false });
104+
});
105+
106+
it('should use focusVisible: true when focusing the first focusable element if keyboard active', async () => {
107+
// Mimic opening the overlay on key press
108+
await sendKeys({ press: 'Enter' });
109+
const input = trap.querySelector('#trap-input-1');
110+
const spy = sinon.spy(input, 'focus');
111+
controller.trapFocus(trap);
112+
expect(spy.firstCall.args[0]).to.deep.equal({ focusVisible: true });
113+
});
114+
98115
describe('no focusable elements', () => {
99116
beforeEach(() => {
100117
trap.querySelectorAll('input, textarea').forEach((input) => {
@@ -193,6 +210,12 @@ describe('FocusTrapController', () => {
193210
await tab();
194211
expect(spy.calledOnce).to.be.false;
195212
});
213+
214+
it('should focus element with focusVisible on Tab', async () => {
215+
const spy = sinon.spy(trapInput2, 'focus');
216+
await tab();
217+
expect(spy.firstCall.args[0]).to.deep.equal({ focusVisible: true });
218+
});
196219
});
197220

198221
describe('custom tab order', () => {

0 commit comments

Comments
 (0)