1/* ------------------------------------------------------------------ 2 modal-navigation.js © 2025â07â29 3 Call initModalNavigation() once after the DOM + Bootstrap are ready. 4 ------------------------------------------------------------------ */ 5 6export function initModalNavigation () { 7 /* 1 collect navâenabled modals, honour dataâorder (default 0) */ 8 const modals = Array.from( 9 document.querySelectorAll('.modal:not([data-nav="false"])') 10 ) 11 .sort((a, b) => (+a.dataset.order || 0) - (+b.dataset.order || 0)); 12 13 const ids = modals.map(m => m.id.replace(/^modal-/, '')); 14 let idx = -1; // current index 15 16 /* 2 ensure each navâenabled modal has an accessible footer with arrows */ 17 modals.forEach(m => { 18 /* footer already present? -> skip */ 19 if (m.querySelector('.modal-footer')) return; 20 21 /* build footer */ 22 const footer = document.createElement('div'); 23 footer.className = 'modal-footer d-flex justify-content-center gap-3'; 24 25 footer.innerHTML = ` 26 <button class="btn btn-secondary" 27 type="button" 28 aria-label="Previous information card" 29 data-nav-prev>â</button> 30 31 <button class="btn btn-primary" 32 type="button" 33 aria-label="Back to map" 34 data-bs-dismiss="modal">Map</button> 35 36 <button class="btn btn-secondary" 37 type="button" 38 aria-label="Next information card" 39 data-nav-next>â</button> 40 `; 41 m.querySelector('.modal-content')?.appendChild(footer); 42 }); 43 44 /* 3 helper to (reâ)open by sequence index */ 45 function showByIndex (newIdx) { 46 if (!ids.length) return; // no navigable modals 47 48 /* close current */ 49 const openEl = document.querySelector('.modal.show'); 50 if (openEl) window.bootstrap.Modal.getInstance(openEl)?.hide(); 51 52 idx = (newIdx + ids.length) % ids.length; // wrap 53 const box = document.getElementById(`modal-${ids[idx]}`); 54 const inst = window.bootstrap.Modal.getOrCreateInstance(box); 55 inst.show(); 56 57 /* focus the modal heading for WCAG "focus moves to dialog" */ 58 const h = box.querySelector('.modal-title, h3, h2, h1'); 59 setTimeout(() => h?.focus(), 17); // after transition 60 } 61 62 /* 4 exposed API â handles both navigable AND navâdisabled modals */ 63 window.openModal = id => { 64 const navIdx = ids.indexOf(id); 65 if (navIdx >= 0) { 66 // Modal is in the navigation sequence â open with â â arrows 67 showByIndex(navIdx); 68 } else { 69 // Modal exists but has navigation disabled â open standalone (no arrows) 70 const el = document.getElementById(`modal-${id}`); 71 if (!el) return; 72 const openEl = document.querySelector('.modal.show'); 73 if (openEl) window.bootstrap.Modal.getInstance(openEl)?.hide(); 74 window.bootstrap.Modal.getOrCreateInstance(el).show(); 75 } 76 }; 77 78 window.nextModal = () => showByIndex(idx + 1); 79 window.prevModal = () => showByIndex(idx - 1); 80 81 /* 5 delegate clicks */ 82 document.addEventListener('click', e => { 83 if (e.target.closest('[data-nav-next]')) { window.nextModal(); } 84 if (e.target.closest('[data-nav-prev]')) { window.prevModal(); } 85 }); 86 87 /* 6 arrowâkey support inside any open modal */ 88 document.addEventListener('keydown', e => { 89 if (!document.querySelector('.modal.show')) return; 90 if (e.key === 'ArrowRight') { e.preventDefault(); window.nextModal(); } 91 if (e.key === 'ArrowLeft') { e.preventDefault(); window.prevModal(); } 92 }); 93}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.