PageSourceSearch

https://www.themailworks.com/static/js/tabs.js?v=16a09b2ba6

js themailworks.com collected 2026-10-08 10:02:09 UTC 5,028 bytes, 133 lines download raw bytes

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.