From 6aa31aa5381329b86eda46e1ee461d827e2f0d27 Mon Sep 17 00:00:00 2001
From: Olivia206 <olivia.simonet206@gmail.com>
Date: Wed, 17 Apr 2024 15:31:15 +0200
Subject: [PATCH] created focus-trap util for modal and search js

---
 assets/js/theme/design-system/modal.js  | 24 ++------------------
 assets/js/theme/design-system/search.js | 30 +++++--------------------
 assets/js/theme/utils/focus-trap.js     | 20 +++++++++++++++++
 3 files changed, 28 insertions(+), 46 deletions(-)
 create mode 100644 assets/js/theme/utils/focus-trap.js

diff --git a/assets/js/theme/design-system/modal.js b/assets/js/theme/design-system/modal.js
index 53d6f67c..c025b890 100644
--- a/assets/js/theme/design-system/modal.js
+++ b/assets/js/theme/design-system/modal.js
@@ -1,3 +1,4 @@
+import { focusTrap } from '../utils/focus-trap';
 
 const CLASSES = {
     modalOpened: 'has-modal-opened'
@@ -37,7 +38,7 @@ class Modal {
             if (event.keyCode === 27 || event.key === 'Escape') {
                 this.toggle(false);
             } else if (event.key === "Tab" && this.state.isOpened) {
-                this.innerFocus(event);
+                focusTrap(event, this.element, this.state.isOpened);
                 event.preventDefault();
             }
         });
@@ -49,27 +50,6 @@ class Modal {
         });
     }
 
-    innerFocus(event) {
-        const focusables = 'a, button, input, textarea, select, details, [tabindex], [contenteditable="true"]';
-        const elements = this.element.querySelectorAll(focusables);
-
-        const focusableInDialog = Array.from(elements).filter(element => element.tabIndex >= 0);
-        const firstFocusable = focusableInDialog[0];
-        const lastFocusable = focusableInDialog.at(-1);
-
-        if (!this.state.isOpened) {
-            return;
-        }
-
-        if (!this.element.contains(event.target) && event.shiftKey) {
-            lastFocusable.focus();
-        }
-        else if (!this.element.contains(event.target)) {
-            firstFocusable.focus();
-        }
-        firstFocusable.focus();
-    }
-
     toggle(open = !this.state.isOpened) {
         this.state.isOpened = open;
         const classAction = this.state.isOpened ? 'add' : 'remove';
diff --git a/assets/js/theme/design-system/search.js b/assets/js/theme/design-system/search.js
index b35b2d61..49d41d85 100644
--- a/assets/js/theme/design-system/search.js
+++ b/assets/js/theme/design-system/search.js
@@ -1,3 +1,5 @@
+import { focusTrap } from '../utils/focus-trap';
+
 class Search {
     constructor(button, pageFind) {
         this.state = {
@@ -39,7 +41,7 @@ class Search {
                     this.button.focus();
                 }
             } else if (event.key === "Tab" && this.state.isOpened) {
-                this.innerFocus(event);
+                focusTrap(event, this.element, this.state.isOpened);
                 
                 this.buttonMore = this.element.querySelector('.pagefind-ui__results + button');
 
@@ -68,35 +70,15 @@ class Search {
             button.parentElement.removeChild(button);
         }
     }
-    innerFocus(event) {
-        const focusables = 'a, button, input, textarea, select, details, [tabindex], [contenteditable="true"]';
-        const elements = this.element.querySelectorAll(focusables);
-        
-        const focusableInDialog = Array.from(elements).filter(element => element.tabIndex >= 0);
-        const firstFocusable = focusableInDialog[0];
-        const lastFocusable = focusableInDialog.at(-1);
-
-        if (!this.state.isOpened) {
-            return;
-        }
-        if (!this.element.contains(event.target) && event.shiftKey) {
-            lastFocusable.focus();
-            event.preventDefault();
-        }
-        else if (!this.element.contains(event.target)) {            
-            firstFocusable.focus();
-            event.preventDefault();
-        }
-    }
 
     buttonMoreFocus() {
         const observer = new MutationObserver(mutations => {
             mutations.forEach(mutation => {
-              mutation.addedNodes.forEach(addedNode => {
+            mutation.addedNodes.forEach(addedNode => {
                 if (addedNode instanceof HTMLAnchorElement) {
-                  addedNode.focus();
+                    addedNode.focus();
                 }
-              });
+                });
             });
         });   
         const observerConfig = { childList: true, subtree: true };
diff --git a/assets/js/theme/utils/focus-trap.js b/assets/js/theme/utils/focus-trap.js
new file mode 100644
index 00000000..ba6c8fe7
--- /dev/null
+++ b/assets/js/theme/utils/focus-trap.js
@@ -0,0 +1,20 @@
+export function focusTrap(event, element, isOpened) {
+    const focusables = 'a, button, input, textarea, select, details, [tabindex], [contenteditable="true"]';
+    const elements = element.querySelectorAll(focusables);
+    
+    const focusableInDialog = Array.from(elements).filter(element => element.tabIndex >= 0);
+    const firstFocusable = focusableInDialog[0];
+    const lastFocusable = focusableInDialog.at(-1);
+
+    if (!isOpened) {
+        return;
+    }
+    if (!element.contains(event.target) && event.shiftKey) {
+        lastFocusable.focus();
+        event.preventDefault();
+    }
+    else if (!element.contains(event.target)) {            
+        firstFocusable.focus();
+        event.preventDefault();
+    }
+}
-- 
GitLab