modal_handler.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. class ModalHandler {
  2. static exists() {
  3. return document.getElementById("modal-container") !== null;
  4. }
  5. static getModalContainer() {
  6. return document.getElementById("modal-container");
  7. }
  8. static getFocusableElements() {
  9. const container = this.getModalContainer();
  10. if (container === null) {
  11. return null;
  12. }
  13. return container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  14. }
  15. static setupFocusTrap() {
  16. const focusableElements = this.getFocusableElements();
  17. if (focusableElements === null) {
  18. return;
  19. }
  20. const firstFocusableElement = focusableElements[0];
  21. const lastFocusableElement = focusableElements[focusableElements.length - 1];
  22. this.getModalContainer().onkeydown = (e) => {
  23. if (e.key !== 'Tab') {
  24. return;
  25. }
  26. // If there is only one focusable element in the dialog we always want to focus that one with the tab key.
  27. // 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.
  28. if (focusableElements.length === 1) {
  29. firstFocusableElement.focus();
  30. e.preventDefault();
  31. return;
  32. }
  33. if (e.shiftKey && document.activeElement === firstFocusableElement) {
  34. lastFocusableElement.focus();
  35. e.preventDefault();
  36. } else if (!e.shiftKey && document.activeElement === lastFocusableElement) {
  37. firstFocusableElement.focus();
  38. e.preventDefault();
  39. }
  40. };
  41. }
  42. static open(fragment, initialFocusElementId) {
  43. if (ModalHandler.exists()) {
  44. return;
  45. }
  46. this.activeElement = document.activeElement;
  47. const container = document.createElement("div");
  48. container.id = "modal-container";
  49. container.setAttribute("role", "dialog");
  50. container.appendChild(document.importNode(fragment, true));
  51. document.body.appendChild(container);
  52. const closeButton = document.querySelector("button.btn-close-modal");
  53. if (closeButton !== null) {
  54. closeButton.onclick = (event) => {
  55. event.preventDefault();
  56. ModalHandler.close();
  57. };
  58. }
  59. let initialFocusElement;
  60. if (initialFocusElementId !== undefined) {
  61. initialFocusElement = document.getElementById(initialFocusElementId);
  62. } else {
  63. let focusableElements = this.getFocusableElements();
  64. if (focusableElements !== null) {
  65. initialFocusElement = focusableElements[0];
  66. }
  67. }
  68. if (initialFocusElement !== undefined) {
  69. initialFocusElement.focus();
  70. }
  71. this.setupFocusTrap();
  72. }
  73. static close() {
  74. const container = this.getModalContainer();
  75. if (container !== null) {
  76. container.parentNode.removeChild(container);
  77. }
  78. if (this.activeElement !== undefined && this.activeElement !== null) {
  79. this.activeElement.focus();
  80. }
  81. }
  82. }