@ment-labs/focus-trap
Version:
A JavaScript utility for effective focus trapping within a specified element.
7 lines (6 loc) • 3.96 kB
JavaScript
/*
@ment-labs/focus-trap 2.0.0
Copyright © 2024 Ment Labs
*/
var r=class{started=!1;constructor(t){this.controller=t}get element(){return this.controller.element}};var i=class extends r{start(){this.started||(document.addEventListener("focus",this.captureFocus,{capture:!0}),this.started=!0)}stop(){this.started&&(document.removeEventListener("focus",this.captureFocus,{capture:!0}),this.started=!1)}captureFocus=()=>this.controller.captureFocus()};function u(e){return!!e&&e.closest("[inert], :disabled, [hidden], details:not([open]), dialog:not([open])")==null&&e.tabIndex>=0&&typeof e.focus=="function"}function c(e){return!(e.hidden||e.closest("[hidden]"))&&(!e.type||e.type!=="hidden")&&(e.offsetWidth>0||e.offsetHeight>0)}var a=class extends r{mutationObserver=new MutationObserver(t=>this.pageChanged(t));start(){this.started||(this.mutationObserver.observe(document.body,{childList:!0,subtree:!0,attributes:!0}),this.started=!0)}stop(){this.started&&(this.mutationObserver.disconnect(),this.started=!1)}pageChanged(t){if(this.shouldReleaseFocus())return this.controller.releaseFocus();this.controller.captureFocus(),this.areChangesOutsideElement(t)&&this.controller.makeOutsideElementsInert()}shouldReleaseFocus(){return!this.element.isConnected||!c(this.element)}areChangesOutsideElement(t){return t.some(({target:s})=>!this.element.contains(s))}};var o=class{started=!1;constructor(t){this.controller=t}startRestricting(){this.started||(this.element.addEventListener("keydown",this.restrict),this.started=!0)}stopRestricting(){this.started&&(this.element.removeEventListener("keydown",this.restrict),this.started=!1)}restrict=t=>{t.key==="Tab"&&this.tabbableElements.length===0?(t.preventDefault(),t.stopPropagation()):t.key==="Tab"&&t.shiftKey&&document.activeElement===this.firstTabbableElement?(t.preventDefault(),t.stopPropagation(),this.focusLastTabbableElement()):t.key==="Tab"&&!t.shiftKey&&document.activeElement===this.lastTabbableElement&&(t.preventDefault(),t.stopPropagation(),this.focusFirstTabbableElement())};focusFirstTabbableElement(){this.tabbableElements.length!==0&&this.firstTabbableElement.focus()}focusLastTabbableElement(){this.tabbableElements.length!==0&&this.lastTabbableElement.focus()}get element(){return this.controller.element}get tabbableElements(){return Array.from(this.element.querySelectorAll("*")).filter(u)}get firstTabbableElement(){return this.tabbableElements[0]}get lastTabbableElement(){return this.tabbableElements[this.tabbableElements.length-1]}};var n=class{id=crypto.randomUUID();focusObserver=new i(this);pageObserver=new a(this);tabNavigation=new o(this);constructor(t){this.element=t}trapFocus(){this.isTrapped||(this.element.dataset.focusTrapId=this.id,this.element.dataset.focusTrapRoot=!0,this.captureFocus(),this.makeOutsideElementsInert(),this.focusObserver.start(),this.pageObserver.start(),this.tabNavigation.startRestricting())}releaseFocus(){this.isRoot&&(this.tabNavigation.stopRestricting(),this.pageObserver.stop(),this.focusObserver.stop(),this.element.removeAttribute("data-focus-trap-root"),this.relatedElements.forEach(t=>{t.removeAttribute("data-focus-trap-id"),t.inert=!1}))}makeOutsideElementsInert(){if(!this.isRoot)return;let t=this.element;for(;t!==document.body;)Array.from(t.parentElement?.children||[]).filter(s=>s!==t&&!s.inert&&!s.hasAttribute("data-focus-trap-id")).forEach(s=>{s.dataset.focusTrapId=this.id,s.inert=!0}),t=t.parentElement}captureFocus(){this.isRoot&&!this.element.contains(document.activeElement)&&this.element.focus()}get isTrapped(){return this.element.hasAttribute("data-focus-trap-id")}get isRoot(){return this.element.dataset.focusTrapRoot&&this.element.dataset.focusTrapId===this.id}get relatedElements(){return document.querySelectorAll(`[data-focus-trap-id="${this.id}"]`)}};var l=class{#t;constructor(t){this.#t=new n(t)}start(){this.#t.trapFocus()}stop(){this.#t.releaseFocus()}};export{l as default};
//# sourceMappingURL=focus-trap.js.map