From 07444720b97c8225d81a96dbd4322957027aa503 Mon Sep 17 00:00:00 2001
From: Robert Lange <robert.lange@uni-leipzig.de>
Date: Tue, 22 Nov 2022 13:32:48 +0100
Subject: [PATCH] refs #22564 [finc] keep focus within offcanvas

* close canvas on escape key
* set focus on search-filter-toggle after closing facet sidebar
* fix skip link to sidebar facets
* fix skip link to sidebar facets - hide on mobile
* set focus on search-filter-toggle after closing facet sidebar

* TODO: check instances with "offcanvas = false" like de_l152 after VF6-upgrade

refs #22564 [finc] use event.key instead of event.keyCode
---
 themes/finc/js/common-finc.js                 | 75 ++++++++++++++++---
 .../finc/templates/Recommend/SideFacets.phtml |  2 +-
 themes/finc/templates/layout/layout.phtml     |  4 +-
 3 files changed, 67 insertions(+), 14 deletions(-)

diff --git a/themes/finc/js/common-finc.js b/themes/finc/js/common-finc.js
index 3b80a9ac86a..033bc3db4de 100644
--- a/themes/finc/js/common-finc.js
+++ b/themes/finc/js/common-finc.js
@@ -8,27 +8,80 @@ function setupOffcanvas() {
       $('.close-offcanvas').focus(); // Set focus to the back button of the sidebar
       $('.sidebar .active').focus(); // Special case: account sidebar: set focus on the active element
     });
-  }
-  // Handle sidebar in case of config.ini [Site] offcanvas=false configuration
-  if ($('.sidebar').length > 0 && !($(document.body).hasClass("offcanvas"))) {
-    $('[data-toggle="offcanvas"]').click(function offcanvasClick(e) {
-      e.preventDefault();
-      window.location.href = '#myresearch-sidebar';
-      $('.close-offcanvas').focus(); // Set focus to the back button of the sidebar
-      $('.sidebar .active').focus(); // Special case: account sidebar: set focus on the active element
+
+    // Keep focus within sidebar on mobile
+    $('.sidebar').on('keydown', function (e) {
+      if (document.body.classList.contains('active') && $(document).width() <= 767) {
+        if (e.key === 'Escape') { // esc
+          $('.close-offcanvas').click();
+        }
+        if (e.key === 'Tab') { // tab
+          retainFocus(e, this);
+        }
+      }
     });
+  }
+  if ($('.sidebar').length > 0) {
+    // Handle sidebar in case of config.ini [Site] offcanvas=false configuration
+    if (!($(document.body).hasClass("offcanvas"))) {
+      $('[data-toggle="offcanvas"]').click(function offcanvasClick(e) {
+        e.preventDefault();
+        window.location.href = '#myresearch-sidebar';
+        $('.close-offcanvas').focus(); // Set focus to the back button of the sidebar
+        $('.sidebar .active').focus(); // Special case: account sidebar: set focus on the active element
+      });
+    }
+    // Handle sidebar in case of config.ini [Site] offcanvas=false configuration - END
+
     $('.close-offcanvas').click(function offcanvasClick(e) {
-      e.preventDefault();
-      window.location.href = '#content';
+      // Handle sidebar in case of config.ini [Site] offcanvas=false configuration
+      if (!($(document.body).hasClass("offcanvas"))) {
+        e.preventDefault();
+        window.location.href = '#content';
+      }
       $('.search-filter-toggle').focus(); // Set focus on the toggle button
     });
   }
 }
 
+/**
+ * Keyboard and focus controllers
+ * Copied from lightbox.js
+ */
+var FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
+function getFocusableNodes (element) {
+  let nodes = element.querySelectorAll(FOCUSABLE_ELEMENTS);
+  return Array.apply(null, nodes);
+}
+
+function retainFocus(event, panel) {
+  var focusableNodes = getFocusableNodes(panel);
+
+  // no focusable nodes
+  if (focusableNodes.length === 0)
+    return;
+
+  if (!panel.contains(document.activeElement)) {
+    focusableNodes[0].focus();
+  } else {
+    var focusedItemIndex = focusableNodes.indexOf(document.activeElement);
+
+    if (event.shiftKey && focusedItemIndex === 0) {
+      focusableNodes[focusableNodes.length - 1].focus();
+      event.preventDefault();
+    }
+
+    if (!event.shiftKey && focusableNodes.length > 0 && focusedItemIndex === focusableNodes.length - 1) {
+      focusableNodes[0].focus();
+      event.preventDefault();
+    }
+  }
+}
+
 /* use css class 'skip-to' to jump to landmark - RL */
 $(document).ready(function () {
   $(document).on('click enter',".skip-to", function (e) {
-    if (e.type === 'enter' && e.keyCode !== 13) {
+    if (e.type === 'enter' && e.key !== 'Enter') {
       return;
     }
 
diff --git a/themes/finc/templates/Recommend/SideFacets.phtml b/themes/finc/templates/Recommend/SideFacets.phtml
index 2f054bfd36f..571a5a3d95e 100644
--- a/themes/finc/templates/Recommend/SideFacets.phtml
+++ b/themes/finc/templates/Recommend/SideFacets.phtml
@@ -57,7 +57,7 @@
     <div class="facet-group" id="side-panel-<?=$this->escapeHtmlAttr($title) ?>">
       <?php /* finc adds h3 and aria coding; also uses a instead of button for W3C validation - #19695 */ ?>
       <h3>
-        <a <?php if (in_array($title, $collapsedFacets)): ?>class="title collapsed" aria-expanded="false"<?php else: ?>class="title" aria-expanded="true"<?php endif ?> data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" >
+        <a <?php if (in_array($title, $collapsedFacets)): ?>class="title collapsed" aria-expanded="false"<?php else: ?>class="title" aria-expanded="true"<?php endif ?> id="control-for-side-collapse-<?=$this->escapeHtmlAttr($title)?>" data-toggle="collapse" href="#side-collapse-<?=$this->escapeHtmlAttr($title) ?>" >
           <?php /* finc: add span #19934 */ ?>
           <?=$this->transEsc($cluster['label'])?> <span class="sr-only"><?=$this->transEsc('facet_select_hint') ?></span>
         </a>
diff --git a/themes/finc/templates/layout/layout.phtml b/themes/finc/templates/layout/layout.phtml
index cd069a76a70..88bdc236b6a 100644
--- a/themes/finc/templates/layout/layout.phtml
+++ b/themes/finc/templates/layout/layout.phtml
@@ -215,9 +215,9 @@ if (!isset($this->layout()->searchbox)) {
           <?php endif; ?>
         <?php endforeach; ?>
       <?php elseif (strcmp($this->layout()->userLang, 'de') == 0): ?>
-        <a class="sr-only skip-to" href="#myresearch-sidebar"><?=$this->transEsc($this->overrideSideFacetCaption ?? 'Narrow Search')?></a>
+        <a class="sr-only skip-to hidden-xs" href="#control-for-side-collapse-facet_avail"><?=$this->transEsc($this->overrideSideFacetCaption ?? 'Narrow Search')?></a>
       <?php else: ?>
-        <a class="sr-only skip-to" href="#myresearch-sidebar"><?=$this->transEsc('skip-to')?><?=strtolower($this->transEsc($this->overrideSideFacetCaption ?? 'Narrow Search'))?></a>
+        <a class="sr-only skip-to hidden-xs" href="#control-for-side-collapse-facet_avail"><?=$this->transEsc('skip-to')?><?=strtolower($this->transEsc($this->overrideSideFacetCaption ?? 'Narrow Search'))?></a>
       <?php endif; ?>
     <?php endif; ?>
   </div>
-- 
GitLab