@synergy-design-system/components
Version:
This package provides the base of the Synergy Design System as native web components. It uses [lit](https://www.lit.dev) and parts of [shoelace](https://shoelace.style/). Synergy officially supports the latest two versions of all major browsers (as define
121 lines (118 loc) • 4.32 kB
JavaScript
import {
activeElements,
getDeepestActiveElement,
getTabbableElements
} from "./chunk.G76BKD7B.js";
// src/internal/modal.ts
var activeModals = [];
var Modal = class {
constructor(element) {
this.tabDirection = "forward";
this.handleFocusIn = () => {
if (!this.isActive()) return;
this.checkFocus();
};
this.handleKeyDown = (event) => {
var _a;
if (event.key !== "Tab" || this.isExternalActivated) return;
if (!this.isActive()) return;
const currentActiveElement = getDeepestActiveElement();
this.previousFocus = currentActiveElement;
if (this.previousFocus && this.possiblyHasTabbableChildren(this.previousFocus)) {
return;
}
if (event.shiftKey) {
this.tabDirection = "backward";
} else {
this.tabDirection = "forward";
}
const tabbableElements = getTabbableElements(this.element);
let currentFocusIndex = tabbableElements.findIndex((el) => el === currentActiveElement);
this.previousFocus = this.currentFocus;
const addition = this.tabDirection === "forward" ? 1 : -1;
while (true) {
if (currentFocusIndex + addition >= tabbableElements.length) {
currentFocusIndex = 0;
} else if (currentFocusIndex + addition < 0) {
currentFocusIndex = tabbableElements.length - 1;
} else {
currentFocusIndex += addition;
}
this.previousFocus = this.currentFocus;
const nextFocus = (
/** @type {HTMLElement} */
tabbableElements[currentFocusIndex]
);
if (this.tabDirection === "backward") {
if (this.previousFocus && this.possiblyHasTabbableChildren(this.previousFocus)) {
return;
}
}
if (nextFocus && this.possiblyHasTabbableChildren(nextFocus)) {
return;
}
event.preventDefault();
this.currentFocus = nextFocus;
(_a = this.currentFocus) == null ? void 0 : _a.focus({ preventScroll: false });
const allActiveElements = [...activeElements()];
if (allActiveElements.includes(this.currentFocus) || !allActiveElements.includes(this.previousFocus)) {
break;
}
}
setTimeout(() => this.checkFocus());
};
this.handleKeyUp = () => {
this.tabDirection = "forward";
};
this.element = element;
this.elementsWithTabbableControls = ["iframe"];
}
/** Activates focus trapping. */
activate() {
activeModals.push(this.element);
document.addEventListener("focusin", this.handleFocusIn);
document.addEventListener("keydown", this.handleKeyDown);
document.addEventListener("keyup", this.handleKeyUp);
}
/** Deactivates focus trapping. */
deactivate() {
activeModals = activeModals.filter((modal) => modal !== this.element);
this.currentFocus = null;
document.removeEventListener("focusin", this.handleFocusIn);
document.removeEventListener("keydown", this.handleKeyDown);
document.removeEventListener("keyup", this.handleKeyUp);
}
/** Determines if this modal element is currently active or not. */
isActive() {
return activeModals[activeModals.length - 1] === this.element;
}
/** Activates external modal behavior and temporarily disables focus trapping. */
activateExternal() {
this.isExternalActivated = true;
}
/** Deactivates external modal behavior and re-enables focus trapping. */
deactivateExternal() {
this.isExternalActivated = false;
}
checkFocus() {
if (this.isActive() && !this.isExternalActivated) {
const tabbableElements = getTabbableElements(this.element);
if (!this.element.matches(":focus-within")) {
const start = tabbableElements[0];
const end = tabbableElements[tabbableElements.length - 1];
const target = this.tabDirection === "forward" ? start : end;
if (typeof (target == null ? void 0 : target.focus) === "function") {
this.currentFocus = target;
target.focus({ preventScroll: false });
}
}
}
}
possiblyHasTabbableChildren(element) {
return this.elementsWithTabbableControls.includes(element.tagName.toLowerCase()) || element.hasAttribute("controls");
}
};
export {
Modal
};
//# sourceMappingURL=chunk.AYAX7BG7.js.map