|
85 | 85 | <script> |
86 | 86 | $(document).ready(function() { |
87 | 87 |
|
| 88 | + function setToggleState($toggle, expanded, label) { |
| 89 | + $toggle.toggleClass('open', expanded); |
| 90 | + $toggle.attr('aria-expanded', expanded ? 'true' : 'false'); |
| 91 | + $toggle.attr('aria-label', (expanded ? 'Collapse ' : 'Expand ') + label); |
| 92 | + } |
| 93 | + |
88 | 94 | // ── Collapsible sub-menu toggles (Elliptic curves, Abstract groups, …) ── |
89 | | - $('.collapsible-container').each(function() { |
| 95 | + $('.collapsible-container').each(function(index) { |
90 | 96 | var $container = $(this); |
91 | 97 | var $content = $container.find('.collapsible-content'); |
92 | | - var $chev = $('<span class="collapsible-chevron open"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 6L15 12L9 18" stroke="currentColor" stroke-width="3"/></svg></span>'); |
93 | | - $content.before($chev); |
94 | | - |
95 | 98 | // Key from the container's OWN text only, excluding child elements |
96 | | - var ownText = $container.clone().children().remove().end().text(); |
| 99 | + var ownText = $.trim($container.clone().children().remove().end().text()); |
97 | 100 | var key = 'lmfdb_sidebar_entry_' + |
98 | | - $.trim(ownText).replace(/\s+/g, '_').substring(0, 30); |
| 101 | + ownText.replace(/\s+/g, '_').substring(0, 30); |
| 102 | + var contentId = 'lmfdb-sidebar-entry-' + index; |
| 103 | + $content.attr('id', contentId); |
| 104 | + |
| 105 | + var $chev = $('<button type="button" class="collapsible-chevron open"><svg aria-hidden="true" focusable="false" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 6L15 12L9 18" stroke="currentColor" stroke-width="3"/></svg></button>'); |
| 106 | + $chev.attr('aria-controls', contentId); |
| 107 | + $content.before($chev); |
99 | 108 |
|
100 | 109 | // Open by default; collapse only if explicitly stored closed. |
101 | 110 | if (localStorage.getItem(key) === '0') { |
102 | 111 | $content.hide(); |
103 | | - $chev.removeClass('open'); |
| 112 | + setToggleState($chev, false, ownText); |
| 113 | + } else { |
| 114 | + setToggleState($chev, true, ownText); |
104 | 115 | } |
105 | 116 |
|
106 | 117 | $container.on('click', function(e) { |
|
109 | 120 |
|
110 | 121 | e.stopPropagation(); |
111 | 122 | e.preventDefault(); |
112 | | - $chev.toggleClass('open'); |
| 123 | + var expanded = $chev.attr('aria-expanded') !== 'true'; |
| 124 | + setToggleState($chev, expanded, ownText); |
113 | 125 | $content.slideToggle(150); |
114 | | - localStorage.setItem(key, $chev.hasClass('open') ? '1' : '0'); |
| 126 | + localStorage.setItem(key, expanded ? '1' : '0'); |
115 | 127 | }); |
116 | 128 | }); |
117 | 129 |
|
118 | 130 | // ── Section (h2) collapse/expand ───────────────────────────────────────── |
119 | | - $('#sidebar h2').each(function() { |
| 131 | + $('#sidebar h2').each(function(index) { |
120 | 132 | var $h2 = $(this); |
121 | 133 | var $content = $h2.nextUntil('h2').not('script'); |
122 | 134 | if ($content.length === 0) return; // 'single' type – nothing to collapse |
| 135 | + var headingText = $.trim($h2.text()); |
123 | 136 |
|
124 | 137 | // Wrap all content between this h2 and the next into one div |
125 | 138 | $content.wrapAll('<div class="section-content"></div>'); |
126 | 139 | var $section = $h2.next('.section-content'); |
| 140 | + var sectionId = 'lmfdb-sidebar-section-' + index; |
| 141 | + $section.attr('id', sectionId); |
127 | 142 |
|
128 | 143 | // Append the chevron into the h2 |
129 | | - var $chev = $('<span class="collapsible-chevron open"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 6L15 12L9 18" stroke="currentColor" stroke-width="3"/></svg></span>'); |
| 144 | + var $chev = $('<button type="button" class="collapsible-chevron open"><svg aria-hidden="true" focusable="false" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 6L15 12L9 18" stroke="currentColor" stroke-width="3"/></svg></button>'); |
| 145 | + $chev.attr('aria-controls', sectionId); |
130 | 146 | $h2.append($chev); |
131 | 147 | $h2.css('cursor', 'pointer'); |
132 | 148 |
|
133 | 149 | // Restore persisted state (default: open) |
134 | | - var key = 'lmfdb_sidebar_sec_' + $.trim($h2.text()).replace(/\s+/g, '_').substring(0, 30); |
| 150 | + var key = 'lmfdb_sidebar_sec_' + headingText.replace(/\s+/g, '_').substring(0, 30); |
135 | 151 | if (localStorage.getItem(key) === '0') { |
136 | 152 | $section.hide(); |
137 | | - $chev.removeClass('open'); |
| 153 | + setToggleState($chev, false, headingText); |
| 154 | + } else { |
| 155 | + setToggleState($chev, true, headingText); |
138 | 156 | } |
139 | 157 |
|
140 | 158 | $h2.on('click', function(e) { |
| 159 | + // Preserve any heading link that may be added in the future. |
| 160 | + if ($(e.target).closest('a').length) return; |
141 | 161 | e.stopPropagation(); |
142 | 162 | e.preventDefault(); |
143 | | - $chev.toggleClass('open'); |
| 163 | + var expanded = $chev.attr('aria-expanded') !== 'true'; |
| 164 | + setToggleState($chev, expanded, headingText); |
144 | 165 | $section.slideToggle(150); |
145 | | - localStorage.setItem(key, $chev.hasClass('open') ? '1' : '0'); |
| 166 | + localStorage.setItem(key, expanded ? '1' : '0'); |
146 | 167 | }); |
147 | 168 | }); |
148 | 169 |
|
149 | 170 | }); |
150 | 171 | </script> |
151 | | - |
|
0 commit comments