Skip to content

Commit

Permalink
feat: added focus trap to dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
berezinant committed Sep 13, 2024
1 parent c975a5e commit 989098c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

export class FocusTrap {
private trapElement: HTMLElement;

constructor(trapElement: HTMLElement) {
this.trapElement = trapElement;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.trapElement.addEventListener('keydown', this.handleKeyDown);
}

private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Tab') {
const focusableElements = Array.from(this.trapElement.querySelectorAll<HTMLElement>('[role="option"]')).filter(
(element) => element.style.display !== 'none' && element.tabIndex !== -1
);
if (!focusableElements.length) {
return;
}

const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];

if (event.shiftKey) {
if (document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
}
} else {
if (document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
}
}
}
}

public destroy() {
this.trapElement.removeEventListener('keydown', this.handleKeyDown);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import './styles.scss';
import { hasAncestorWithClass } from '../utils';
import { FocusTrap } from './focus-trap';

// page objects selectors
const DROPDOWN = '[data-role="dropdown"]';
Expand All @@ -11,11 +12,12 @@ const DROPDOWN_LIST = '[data-role="dropdown-listbox"]';

function initDropdowns(): void {
const dropdowns = document.querySelectorAll(DROPDOWN);
dropdowns.forEach((dropdown: Element) =>
dropdowns.forEach((dropdown: Element) => {
dropdown.querySelectorAll(DROPDOWN_TOGGLE)?.forEach((button: Element) => {
button.addEventListener('click', (event) => onToggleDropdown(event, dropdown));
})
);
});
addKeyboardNavigation(dropdown as HTMLElement);
});
}

function onToggleDropdown(_: Event, dropdown: Element): void {
Expand Down Expand Up @@ -46,6 +48,16 @@ function handleOutsideClick(event: MouseEvent): void {
}
}

function addKeyboardNavigation(dropdown: HTMLElement): void {
new FocusTrap(dropdown);
dropdown.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
onToggleDropdown(event, dropdown);
(dropdown.querySelector(DROPDOWN_TOGGLE) as HTMLElement)?.focus();
}
});
}

document.addEventListener('DOMContentLoaded', () => {
initDropdowns();
document.addEventListener('click', handleOutsideClick);
Expand Down

Large diffs are not rendered by default.

0 comments on commit 989098c

Please sign in to comment.