aboutsummaryrefslogtreecommitdiff
path: root/internal/ui/static/js/modal_handler.js
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <f@miniflux.net> 2023-08-10 19:46:45 -0700
committerGravatar Frédéric Guillot <f@miniflux.net> 2023-08-10 20:29:34 -0700
commit168a870c025bfef6efdeb46e166e79a16093c157 (patch)
tree4d8ab69c7e3ef03a7ade06e7b5e5053429a64c3b /internal/ui/static/js/modal_handler.js
parentc2349032552891745cbbc3d2a9e772845a0239f4 (diff)
downloadv2-168a870c025bfef6efdeb46e166e79a16093c157.tar.gz
v2-168a870c025bfef6efdeb46e166e79a16093c157.tar.zst
v2-168a870c025bfef6efdeb46e166e79a16093c157.zip
Move internal packages to an internal folder
For reference: https://go.dev/doc/go1.4#internalpackages
Diffstat (limited to 'internal/ui/static/js/modal_handler.js')
-rw-r--r--internal/ui/static/js/modal_handler.js101
1 files changed, 101 insertions, 0 deletions
diff --git a/internal/ui/static/js/modal_handler.js b/internal/ui/static/js/modal_handler.js
new file mode 100644
index 00000000..0fa55bfa
--- /dev/null
+++ b/internal/ui/static/js/modal_handler.js
@@ -0,0 +1,101 @@
+class ModalHandler {
+ static exists() {
+ return document.getElementById("modal-container") !== null;
+ }
+
+ static getModalContainer() {
+ return document.getElementById("modal-container");
+ }
+
+ static getFocusableElements() {
+ let container = this.getModalContainer();
+
+ if (container === null) {
+ return null;
+ }
+
+ return container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
+ }
+
+ static setupFocusTrap() {
+ let focusableElements = this.getFocusableElements();
+
+ if (focusableElements === null) {
+ return;
+ }
+
+ let firstFocusableElement = focusableElements[0];
+ let lastFocusableElement = focusableElements[focusableElements.length - 1];
+
+ this.getModalContainer().onkeydown = (e) => {
+ if (e.key !== 'Tab') {
+ return;
+ }
+
+ // If there is only one focusable element in the dialog we always want to focus that one with the tab key.
+ // This handles the special case of having just one focusable element in a dialog where keyboard focus is placed on an element that is not in the tab order.
+ if (focusableElements.length === 1) {
+ firstFocusableElement.focus();
+ e.preventDefault();
+ return;
+ }
+
+ if (e.shiftKey && document.activeElement === firstFocusableElement) {
+ lastFocusableElement.focus();
+ e.preventDefault();
+ } else if (!e.shiftKey && document.activeElement === lastFocusableElement) {
+ firstFocusableElement.focus();
+ e.preventDefault();
+ }
+ };
+ }
+
+ static open(fragment, initialFocusElementId) {
+ if (ModalHandler.exists()) {
+ return;
+ }
+
+ this.activeElement = document.activeElement;
+
+ let container = document.createElement("div");
+ container.id = "modal-container";
+ container.setAttribute("role", "dialog");
+ container.appendChild(document.importNode(fragment, true));
+ document.body.appendChild(container);
+
+ let closeButton = document.querySelector("button.btn-close-modal");
+ if (closeButton !== null) {
+ closeButton.onclick = (event) => {
+ event.preventDefault();
+ ModalHandler.close();
+ };
+ }
+
+ let initialFocusElement;
+ if (initialFocusElementId !== undefined) {
+ initialFocusElement = document.getElementById(initialFocusElementId);
+ } else {
+ let focusableElements = this.getFocusableElements();
+ if (focusableElements !== null) {
+ initialFocusElement = focusableElements[0];
+ }
+ }
+
+ if (initialFocusElement !== undefined) {
+ initialFocusElement.focus();
+ }
+
+ this.setupFocusTrap();
+ }
+
+ static close() {
+ let container = this.getModalContainer();
+ if (container !== null) {
+ container.parentNode.removeChild(container);
+ }
+
+ if (this.activeElement !== undefined && this.activeElement !== null) {
+ this.activeElement.focus();
+ }
+ }
+}