1/* ========================================================================== 2 Tabs (the Plan / Produce / Deliver service switcher) and the FAQ accordion. 3 4 Both follow the ARIA authoring practices, which the Showit originals do not: 5 there, every tab and every accordion row is an href="#/" anchor driving a 6 canvas state swap, so none of it is operable by keyboard and every panel 7 ships expanded in the HTML. 8 9 Tabs: roving tabindex, arrow keys move between tabs, Home and End jump. 10 Accordion: real <button> per row inside a heading, aria-expanded, and the 11 panel is genuinely hidden when closed. Rows are deep-linkable via the URL 12 hash, which the live FAQ cannot do at all. 13 ========================================================================== */ 14 15(function () { 16 'use strict'; 17 18 /* ---- Tabs ------------------------------------------------------------- */ 19 Array.prototype.forEach.call( 20 document.querySelectorAll('[data-tabs]'), 21 function (root) { 22 var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]')); 23 var panels = Array.prototype.slice.call(root.querySelectorAll('[role="tabpanel"]')); 24 if (!tabs.length) return; 25 26 // Photos that correspond to each tab, cross-faded on switch. The live 27 // site changes the image behind Plan / Produce / Deliver; without this 28 // the section looks static. 29 var images = Array.prototype.slice.call(root.querySelectorAll('[data-tab-image]')); 30 31 function select(i, focus) { 32 images.forEach(function (img, j) { 33 img.classList.toggle('is-active', i === j); 34 });
35 tabs.forEach(function (t, j) { 36 var on = i === j; 37 t.setAttribute('aria-selected', on ? 'true' : 'false'); 38 t.setAttribute('tabindex', on ? '0' : '-1'); 39 t.classList.toggle('is-active', on); 40 }); 41 panels.forEach(function (p, j) { 42 p.hidden = i !== j; 43 p.classList.toggle('is-active', i === j); 44 }); 45 if (focus) tabs[i].focus(); 46 } 47 48 tabs.forEach(function (t, i) { 49 t.addEventListener('click', function () { select(i, false); }); 50 t.addEventListener('keydown', function (e) { 51 var last = tabs.length - 1; 52 if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { 53 e.preventDefault(); select(i === last ? 0 : i + 1, true); 54 } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { 55 e.preventDefault(); select(i === 0 ? last : i - 1, true); 56 } else if (e.key === 'Home') { 57 e.preventDefault(); select(0, true); 58 } else if (e.key === 'End') { 59 e.preventDefault(); select(last, true); 60 } 61 }); 62 }); 63 64 select(0, false); 65 } 66 ); 67 68 /* ---- Accordion -------------------------------------------------------- */ 69 var rows = Array.prototype.slice.call(document.querySelectorAll('[data-accordion-item]')); 70 71 function panelOf(row) { return row.querySelector('[data-accordion-panel]'); } 72 function triggerOf(row) { return row.querySelector('[data-accordion-trigger]'); } 73 74 function setRow(row, open) { 75 var t = triggerOf(row), p = panelOf(row); 76 if (!t || !p) return; 77 t.setAttribute('aria-expanded', open ? 'true' : 'false'); 78 row.classList.toggle('is-open', open); 79 if (open) { 80 p.hidden = false; 81 p.style.maxHeight = p.scrollHeight + 'px'; 82 } else { 83 p.style.maxHeight = '0px'; 84 window.setTimeout(function () { 85 if (!row.classList.contains('is-open')) p.hidden = true; 86 }, 260); 87 } 88 } 89 90 rows.forEach(function (row) { 91 var t = triggerOf(row), p = panelOf(row); 92 if (!t || !p) return; 93 p.hidden = true; 94 p.style.maxHeight = '0px'; 95 96 t.addEventListener('click', function () { 97 var open = t.getAttribute('aria-expanded') === 'true'; 98 // Accordion rows are independent: opening one does not close the others, 99 // which matters on a 33-question FAQ where people compare answers. 100 setRow(row, !open); 101 if (!open && row.id) { 102 history.replaceState(null, '', '#' + row.id); 103 } 104 }); 105 }); 106 107 // Open and scroll to a row named in the URL, so /faq#some-question works. 108 function openFromHash() { 109 var id = window.location.hash.replace('#', ''); 110 if (!id) return; 111 var row = document.getElementById(id); 112 if (row && row.hasAttribute('data-accordion-item')) { 113 setRow(row, true); 114 row.scrollIntoView({ block: 'center' }); 115 } 116 } 117 window.addEventListener('hashchange', openFromHash); 118 openFromHash(); 119 120 // Keep an open panel's height correct if the viewport reflows the text. 121 var resizeTimer; 122 window.addEventListener('resize', function () { 123 window.clearTimeout(resizeTimer); 124 resizeTimer = window.setTimeout(function () { 125 rows.forEach(function (row) { 126 if (row.classList.contains('is-open')) { 127 var p = panelOf(row); 128 if (p) p.style.maxHeight = p.scrollHeight + 'px'; 129 } 130 }); 131 }, 150); 132 }); 133})();
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.