about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/components/modal_root.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/components/modal_root.js')
-rw-r--r--app/javascript/flavours/glitch/components/modal_root.js39
1 files changed, 35 insertions, 4 deletions
diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js
index 4e8648b49..f9877d5ea 100644
--- a/app/javascript/flavours/glitch/components/modal_root.js
+++ b/app/javascript/flavours/glitch/components/modal_root.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import 'wicg-inert';
 import createHistory from 'history/createBrowserHistory';
 
 export default class ModalRoot extends React.PureComponent {
@@ -26,8 +27,30 @@ export default class ModalRoot extends React.PureComponent {
     }
   }
 
+  handleKeyDown = (e) => {
+    if (e.key === 'Tab') {
+      const focusable = Array.from(this.node.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((x) => window.getComputedStyle(x).display !== 'none');
+      const index = focusable.indexOf(e.target);
+
+      let element;
+
+      if (e.shiftKey) {
+        element = focusable[index - 1] || focusable[focusable.length - 1];
+      } else {
+        element = focusable[index + 1] || focusable[0];
+      }
+
+      if (element) {
+        element.focus();
+        e.stopPropagation();
+        e.preventDefault();
+      }
+    }
+  }
+
   componentDidMount () {
     window.addEventListener('keyup', this.handleKeyUp, false);
+    window.addEventListener('keydown', this.handleKeyDown, false);
     this.history = this.context.router ? this.context.router.history : createHistory();
   }
 
@@ -39,15 +62,22 @@ export default class ModalRoot extends React.PureComponent {
     } else if (!nextProps.children) {
       this.setState({ revealed: false });
     }
-    if (!nextProps.children && !!this.props.children) {
-      this.activeElement.focus({ preventScroll: true });
-      this.activeElement = null;
-    }
   }
 
   componentDidUpdate (prevProps) {
     if (!this.props.children && !!prevProps.children) {
       this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
+
+      // Because of the wicg-inert polyfill, the activeElement may not be
+      // immediately selectable, we have to wait for observers to run, as
+      // described in https://github.com/WICG/inert#performance-and-gotchas
+      Promise.resolve().then(() => {
+        this.activeElement.focus({ preventScroll: true });
+        this.activeElement = null;
+      }).catch((error) => {
+        console.error(error);
+      });
+
       this.handleModalClose();
     }
     if (this.props.children) {
@@ -60,6 +90,7 @@ export default class ModalRoot extends React.PureComponent {
 
   componentWillUnmount () {
     window.removeEventListener('keyup', this.handleKeyUp);
+    window.removeEventListener('keydown', this.handleKeyDown);
   }
 
   handleModalClose () {