kopia lustrzana https://github.com/shoelace-style/shoelace
65 wiersze
1.8 KiB
TypeScript
65 wiersze
1.8 KiB
TypeScript
import { getTabbableBoundary } from './tabbable';
|
|
|
|
let activeModals: HTMLElement[] = [];
|
|
|
|
export default class Modal {
|
|
element: HTMLElement;
|
|
tabDirection: 'forward' | 'backward' = 'forward';
|
|
|
|
constructor(element: HTMLElement) {
|
|
this.element = element;
|
|
this.handleFocusIn = this.handleFocusIn.bind(this);
|
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
this.handleKeyUp = this.handleKeyUp.bind(this);
|
|
}
|
|
|
|
activate() {
|
|
activeModals.push(this.element);
|
|
document.addEventListener('focusin', this.handleFocusIn);
|
|
document.addEventListener('keydown', this.handleKeyDown);
|
|
document.addEventListener('keyup', this.handleKeyUp);
|
|
}
|
|
|
|
deactivate() {
|
|
activeModals = activeModals.filter(modal => modal !== this.element);
|
|
document.removeEventListener('focusin', this.handleFocusIn);
|
|
document.removeEventListener('keydown', this.handleKeyDown);
|
|
document.removeEventListener('keyup', this.handleKeyUp);
|
|
}
|
|
|
|
isActive() {
|
|
// The "active" modal is always the most recent one shown
|
|
return activeModals[activeModals.length - 1] === this.element;
|
|
}
|
|
|
|
checkFocus() {
|
|
if (this.isActive()) {
|
|
if (!this.element.matches(':focus-within')) {
|
|
const { start, end } = getTabbableBoundary(this.element);
|
|
const target = this.tabDirection === 'forward' ? start : end;
|
|
|
|
if (typeof target?.focus === 'function') {
|
|
target.focus({ preventScroll: true });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
handleFocusIn() {
|
|
this.checkFocus();
|
|
}
|
|
|
|
handleKeyDown(event: KeyboardEvent) {
|
|
if (event.key === 'Tab' && event.shiftKey) {
|
|
this.tabDirection = 'backward';
|
|
|
|
// Ensure focus remains trapped after the key is pressed
|
|
requestAnimationFrame(() => this.checkFocus());
|
|
}
|
|
}
|
|
|
|
handleKeyUp() {
|
|
this.tabDirection = 'forward';
|
|
}
|
|
}
|