Jake Vanderwerf
2026-01-01 0e4b986e81f8132a44e61fa8df18860301cc3468
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class Navigation {
    constructor() {
        this.counter = 0;
        this.initElements();
        if (this.navs.size === 0) {
            return;
        }
 
        this.openNav = null;
        this.initListeners();
    }
 
    initElements() {
        this.navs = new Map();
        document.querySelectorAll('nav:has(.submenu), nav:has(.toggle)').forEach(nav => {
            let navID = nav.id;
            if (navID === '') {
                navID = `nav-${this.counter}`;
                nav.id = navID;
                this.counter++;
            }
            if (nav.querySelector('.submenu')) {
                nav.addEventListener('mouseenter', this.hoverOnListener);
                nav.addEventListener('mouseleave', this.hoverOffListener);
            }
 
            let [
                toggles,
                submenus,
                submenuToggles
            ] = [
                nav.querySelectorAll('nav .toggle'),
                nav.querySelectorAll('.has-submenu'),
                nav.querySelectorAll('.toggle:not(.main)')
            ];
            let elements = {
                nav: nav,
                toggles: toggles,
                submenus: submenus,
                submenuToggles: submenuToggles
            }
            this.navs.set(navID, elements);
            this.counter++;
        });
    }
 
    initListeners() {
        this.clickListener = this.handleClick.bind(this);
        this.escapeListener = this.handleEscape.bind(this);
        this.hoverOnListener = this.handleHoverOn.bind(this);
        this.hoverOffListener = this.handleHoverOff.bind(this);
 
        document.addEventListener('click', this.clickListener);
    }
    handleClick(e) {
        if (this.navs.size === 0) {
            return;
        }
        if (this.openNav && e.target.closest(`#${this.openNav}`) === null) {
            this.toggleNav(false, this.openNav);
        }
 
        // if (!e.target.closest(this.openNav)) {
        //  console.log('Not closest nav ids');
        //  console.log(this.navIDs());
        //  return;
        // }
 
        let toggle = e.target.closest('.toggle.main');
        if (toggle) {
            let nav = toggle.closest('nav');
            this.toggleNav(!nav.classList.contains('open'), nav.id);
        }
 
        let submenuToggle = e.target.closest('[data-action="toggle-submenu"], .has-submenu .a')
        if (submenuToggle) {
            let li = submenuToggle.closest('li');
            this.toggleSubmenu(!li.classList.contains('open'), li);
        }
 
    }
 
    handleHoverOn(e) {
        let nav =  e.target.closest('nav');
        if (nav) {
            this.toggleNav(true, nav.id);
        }
        let submenu = e.target.closest('.has-submenu');
        if (submenu) {
            this.toggleSubmenu(true, submenu);
        }
    }
 
    handleHoverOff(e) {
        let nav =  e.target.closest('nav');
        if (nav) {
            this.toggleNav(false, nav.id);
        }
        // let submenu = e.target.closest('.has-submenu');
        // if (submenu) {
        //  this.toggleSubmenu(false, submenu);
        // }
    }
 
    handleEscape(e) {
        if (this.openNav && e.key === 'Escape') {
            this.toggleNav(false, this.openNav);
        }
    }
 
    toggleNav(on, id) {
        let nav = this.navs.get(id);
        if (!nav) {
            return;
        }
 
        if (on && id !== this.openNav) {
            this.toggleNav(false, this.openNav);
        }
        if (on) {
            this.openNav = id;
            document.addEventListener('keydown', this.escapeListener);
        } else {
            if (this.openNav === id) {
                this.openNav = null;
            }
            document.removeEventListener('keydown', this.escapeListener);
            if (!nav.nav.classList.contains('sidebar')) {
                Array.from(nav.submenus).forEach(submenu => {
                    if(submenu.classList.contains('open')) {
                        this.toggleSubmenu(false, submenu);
                    }
                });
            }
        }
 
        nav.nav.ariaExpanded = on;
        nav.nav.classList.toggle('open', on);
        nav.ariaHidden = !on;
        if (on) {
            nav.nav.querySelector('a:not(.skip-to-content)')?.focus();
        }
    }
 
    toggleSubmenu(on, submenu) {
        let [
            toggle,
            firstLink
        ] = [
            submenu.querySelector('.toggle'),
            submenu.querySelector('a')
        ];
 
        submenu.classList.toggle('open', on);
        submenu.ariaHidden = !on;
        toggle.ariaExpanded = on;
        if (on && firstLink) {
            firstLink.focus();
        }
    }
 
}
 
document.addEventListener('DOMContentLoaded', function() {
    window.jvbNav = new Navigation();
});