aboutsummaryrefslogtreecommitdiff
path: root/internal/ui/static/js/modal_handler.js
blob: 0fa55bfafdd10bc7bc9c1c6e1dea62ee43d39f93 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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();
        }
    }
}